How To Make Click&Drag Number Box Slider (Without the Slider)?

ah yes! LinearBarHorizontal with Alpha = 0 does it!!
but how can I get rid of the outline? where is it located?

many thanks!

No idea offhand, you can probably find it in the lookandfeel methods somewhere. Or set its colour to transparent.

ok thank you very much! found it.
if someone is interested:

you have to override
drawLabel (Graphics& g, Label& label)
this is how mine looks:

void OtherLookAndFeel::drawLabel (Graphics& g, Label& label)
{
    g.fillAll (label.findColour (Label::backgroundColourId));
    
    if (! label.isBeingEdited())
    {
        const float alpha = label.isEnabled() ? 1.0f : 0.5f;
        Font font (getLabelFont (label));
        font.setHeight (fontHeight);
        g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha));
        g.setFont (font);
        
        Rectangle<int> textArea (label.getBorderSize().subtractedFrom (label.getLocalBounds()));
        
        g.drawFittedText (label.getText(), textArea, label.getJustificationType(),
                          jmax (1, (int) (textArea.getHeight() / font.getHeight())),
                          label.getMinimumHorizontalScale());
        
        g.setColour (Colours::transparentBlack);
    }
    else if (label.isEnabled())
    {
        g.setColour (Colours::transparentBlack);
    }
    
    g.drawRect (label.getLocalBounds());
}

and in the custom l+f:

setColour (Slider::thumbColourId, Colours::transparentBlack); //
setColour (Slider::trackColourId, Colours::transparentBlack); //
setColour (Slider::backgroundColourId, Colours::transparentBlack); //

3 Likes

I’ll give you the header of my class… which should give you some ideas… my class has some additional functionality you won’t require…

class CNumericLabelLF : public LookAndFeel_V3
{
public:
    CNumericLabelLF() {};

    void drawLinearSliderBackground (Graphics& , int , int , int , int , float , float , float , const Slider::SliderStyle , Slider& ) override
    {
    }

    void drawLinearSliderThumb (Graphics& , int , int , int , int , float , float , float , const Slider::SliderStyle , Slider& ) override
    {
    }

    Font getLabelFont (Label& label) override
    {
        return label.getFont().withHeight (11.0);
    }

private:

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CNumericLabelLF)
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

class CNumericSlider;

class CNumericLabel : public Label,
                      public Slider::Listener
{
public:

    CNumericLabel (const String& componentName = String(), const String& labelText = String());
    ~CNumericLabel();

    void resized() override;

    void sliderValueChanged (Slider* slider) override;
    void sliderDragEnded (Slider* slider) override;

    void setRange (int iMin, int iMax);

    void setDoubleClickReturnValue (int iDefault);

    void setValue (int iNewValue);

    void setIsTopValue (bool bIsMaxLabel)       { m_bIsMaxValue = bIsMaxLabel;          }

    int  getValue() noexcept                    { return m_iValue;                      }

    void setOppositeValue (int iOppositeValue)  { m_iOppositeValue = iOppositeValue;    }

    ////////////////////////////////////////////////////////////////////////////

    class CNumericSlider : public Slider
    {
    public:

        CNumericSlider (CNumericLabel* pOwner) : m_pOwner (pOwner)
        {
            setTextBoxStyle (Slider::NoTextBox, false, 70, 20);
            setSliderSnapsToMousePosition (false);
        }

        ~CNumericSlider() {}

        void mouseDown (const MouseEvent& event) override
        {
            unfocusAllComponents();

            Slider::mouseDown (event);
        }

        void mouseEnter (const MouseEvent& event) override
        {
            m_pOwner->setColour (Label::outlineColourId, Colours::grey);

            Slider::mouseEnter (event);
        }

        void mouseExit (const MouseEvent& event) override
        {
            m_pOwner->setColour (Label::outlineColourId, Colour (0x1000282));

            Slider::mouseExit (event);
        }

    private:

        CNumericLabel*  m_pOwner;


        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CNumericSlider)
    };

    ////////////////////////////////////////////////////////////////////////////

    //==============================================================================
    /** Receives callbacks when a CNumericLabel object changes.
	 */
    class  Listener
    {
    public:

        virtual ~Listener() {}

        /** Called when a CNumericLabel object is changed.
         */
        virtual void labelChanged (CNumericLabel* label) = 0;

        virtual void labelDragEnded (CNumericLabel* ) {}
    };

    /** Adds a listener to receive callbacks when the value changes.
     */
    void addListener (Listener* const listener);

    /** Removes a listener that was previously added with addListener(). */
    void removeListener (Listener* const listener);

    /** Updates the CNumericLabel's listeners.
     Call this to explicitly tell any registerd listeners that the value has changed.
	 */
	void updateListeners();

private:

    int     m_iValue;
    int     m_iMinValue;
    int     m_iMaxValue;
    int     m_iDefaultValue;

    bool    m_bIsMaxValue;
    int     m_iOppositeValue;

    ScopedPointer<CNumericSlider>   m_pHiddenSlider;

    CNumericLabelLF m_Look;

    ListenerList<Listener> m_Listeners;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CNumericLabel)
};

typedef CNumericLabel::Listener CNumericLabelListener;

Cheers,

Rail

1 Like

thank you very much for sharing this!!

In case anyone arrives here from Google like I did, there’s an easy way to make a draggable number box without overriding LookAndFeelMethods or writing any custom classes, simply:

slider.setSliderStyle(Slider::SliderStyle::LinearBarVertical);
slider.setSliderSnapsToMousePosition(false);
slider.setColour(Slider::trackColourId, Colours::transparentBlack);
slider.setSize(60, 20);
4 Likes

Thanks, but the speed of the change wile dragging, isn’t it very fast, because of the small vertical distance? I think there is still the demand for a real label only mode.

Here’s a similar topic (I think) of what you may be shooting for. Check out my example at the bottom Ableton Live-Style Text Slider

@chkn Slider::setMouseDragSensitivity() allows you to change the vertical distance

You can also hold down the option key for finer control

I’m using this, but when I edit the number it jumps to the left.
Is there a way to eliminate this behavior?
image

When you edit the number, then it becomes a TextEditor, which does not have the same justification.

I corrected this by editing the JUCE source code like this (added the line with the comment):

void Label::showEditor()
{
    if (editor == nullptr)
    {
        editor.reset (createEditorComponent());
        editor->setSize (10, 10);
        addAndMakeVisible (editor.get());
        editor->setText (getText(), false);
        editor->setKeyboardType (keyboardType);
        editor->addListener (this);
        editor->grabKeyboardFocus();

        // makes the editor have the same justification as the label
        editor->setJustification(getJustificationType());

Perhaps you can do this in a LookAndFeel; I don’t remember. I seem to recall that it was impossible to override this behavior any other way but I could be wrong.

He can use Label::getCurrentTextEditor() in a lambda or Listener callback when the editor is shown

std::function<void()> onEditorShow;

then set the Justification of the editor.

Rail

True, but being in a Slider, you would have to get a pointer to the label first which is not trivial (I seem to recall a recent discussion about that). I know I had to do some tricky stuff to get a pointer to a Slider’s Label.

But in general, to me, this is a “bug” in JUCE: why would you NOT want the TextEditor to have the same justification as the label to which it belongs? Who would want the text jumping around to a different location when you edit the label? This one line fixes the issue in EVERY instance of label usage, inside Labels, Sliders, ComboBoxes, etc.

If he adds this to the CNumericSlider class it will probably work…

void mouseDoubleClick (const MouseEvent& event) override
{
    m_pOwner->showEditor();
        
    TextEditor* pEditor = m_pOwner->getCurrentTextEditor();
        
    if (pEditor != nullptr)
    {
        pEditor->setJustification (m_pOwner->getJustificationType());
            
        pEditor->setInputRestrictions (2, "0123456789");
            
        pEditor->setColour (TextEditor::textColourId, Colours::white);
        pEditor->setColour (TextEditor::highlightedTextColourId, Colours::white);
        pEditor->setColour (TextEditor::backgroundColourId, Colours::black);
                
        pEditor->setBounds (getLocalBounds());
    }
        
    Slider::mouseDoubleClick (event);
}

Rail

I suspect just overriding createSliderTextBox() in the LookAndFeel should do it, something like (untested)…

class MyLookAndFeel : public juce::LookAndFeel_V4
{
    juce::Label* createSliderTextBox (juce::Slider& slider) override
    {
        if (auto* label = juce::LookAndFeel_V4::createSliderTextBox (slider))
        {
            label->setJustificationType (juce::Justification::centred);
            return label;
        }

        return nullptr;
    }
};

Maybe, but unfortunately this would potentially be a subtle breaking change for a lot of users.

Everyone that don’t want it to move around all the time when entering the new text/number.

Entering and displaying text/numbers are two different things. Both conceptually and visually. When you just want to displays a text you mostly wan’t to do it in a clear, distinct and visually pleasing way, sometimes even with an artistic touch…

When entering text you have other concerns; it should be easy and it should be accurate.

If you then have the text right (or center) aligned makes the already entered text shift to the left for every new character entered, which makes it harder to read and a probably a bit more error prone. It’s just more cognitively straining to watch moving text. Especially when you’re writing at the same time.

Compare it to writing on a paper. The text is left-align (at least for us in the western civilization), it doesn’t move and the text is entered to the right. And you clearly see when you come to the end of the paper and have to stop…

I think that’s the way people are accustomed to also when using computers. And if that’s not enough of reasons the control/widget should prefer left alignment for entering mode, you have the additional one of a clear indication that something happened when you clicked the control and that it’s now in entering mode.

Can’t agree with you there, but OK, fair enough, “someone” likes it that way. :slight_smile:

There are plenty of other methods to indicate “something happened” such as changing the background colour or the text colour, rather than having the text suddenly jump to a different position.

But I wouldn’t expect this to be changed in JUCE, so for me, my one line fix does what I need.

That’s not going to do anything to the justification of the textEditor that is not even created until Label::showEditor().

You would need to do something like this; I just tested it; it works:

    class MyLookAndFeel : public juce::LookAndFeel_V4
    {
        juce::Label* createSliderTextBox (juce::Slider& slider) override
        {
            if (auto* label = juce::LookAndFeel_V4::createSliderTextBox (slider))
            {
                label->onEditorShow = [this, label] {
                    label->showEditor();
                    if (auto* ed = label->getCurrentTextEditor())
                        ed->setJustification(label->getJustificationType());
                };

                return label;
            }
            
            return nullptr;
        }
    };

Agreed. :slight_smile:

Thanks for the correction @stephenk. Quick check are you sure you need to call label->showEditor() in label->onEditorShow ?

You’re right - that’s redundant. Revised:

    class MyLookAndFeel : public juce::LookAndFeel_V4
    {
        juce::Label* createSliderTextBox (juce::Slider& slider) override
        {
            if (auto* label = juce::LookAndFeel_V4::createSliderTextBox (slider))
            {
                label->onEditorShow = [this, label] {
                    if (auto* ed = label->getCurrentTextEditor())
                        ed->setJustification(label->getJustificationType());
                };
                return label;
            }          
            return nullptr;
        }
    };
1 Like