I need to call paint?
repaint() should already be called when the colour changes. There must have been something else happening in lookAndFeelChanged() thatās not happening any more. Iāll take a look. You should be able to call sendLookAndFeelChange() after setting the colour to reproduce the previous behaviour if you need to (i.e. it will interrupt the slider value).
So I really do need to use LookAndFeel regardless?
Sorry I probably wasnāt clear enough. Iām just suggesting thatās what you can do as a temporary work around. Iāll take a look at this as soon as possible.
I need to write all the drawing routine for the slider?
OK I think I know whatās going wrong, a fix will have to wait until next week though.
Thanks again for reporting.
@loveslap sorry itās taken so long to get back to this.
- The changes Iāve made in develop will need to be reverted.
- Although I agree the behaviour youāve reported is certainly unexpected I will need to put this work on hold for a bit as the changes required to fix this are likely to have ramifications for many other users so a lot of thought and testing will need to go into the final solution.
All that being said @PaulDriessen already made a hint towards a good work around. Unfortunately itās slightly more complicated than it may first appear as the colour youāre setting needs to be applied to a Label inside the Slider rather than the Slider itself.
Unfortunately the Label is a private and unexposed member of the Slider class however, we can be a little sneaky and get access to it via a custom LookAndFeel class. Iāve produced a working example below that demonstrates the technique.
class SequencerSlider : public juce::Slider
{
public:
SequencerSlider()
{
// do all the usual slider setup here
setSliderStyle (juce::Slider::SliderStyle::LinearBarVertical);
setRange (0.0, 10.0, 0.1);
// calling this should trigger a call to our custom
// createSliderTextBox() implementation
setLookAndFeel (&lookAndFeel);
// only call this after setting the look-and-feel
setHighlighted (false);
}
~SequencerSlider()
{
setLookAndFeel (nullptr);
}
// by overriding this we prevent the base class getting the call
// if it did it would end up calling createSliderTextBox which we
// don't want as that's the cause of the interruptions to the
// sliders mouse events!
void colourChanged() override
{
// this allows the look-and-feel to update the labels colours
lookAndFeel.sliderColourChanged (*this);
// we'll also need to tell this component to repaint now a
// colour has changed
repaint();
}
void setHighlighted (bool shouldHighlight)
{
setColour (juce::Slider::textBoxOutlineColourId, shouldHighlight ? juce::Colours::white
: juce::Colours::black);
}
private:
// make sure to inherit from the actual look-and-feel class
// we want to use for the rest of the slider drawing
class LookAndFeel : public juce::LookAndFeel_V4
{
public:
juce::Label* createSliderTextBox (juce::Slider& slider)
{
// we pass this call on to the actual look-and-feel we want
// to use, but before returning it to the underlying slider
// we store the value in a member variable for later use (sneaky!)
auto* l = juce::LookAndFeel_V4::createSliderTextBox (slider);
label = l;
// we call this to make sure we've setup all the label
// colours correctly
sliderColourChanged (slider);
return l;
}
void sliderColourChanged (juce::Slider& slider)
{
if (label == nullptr)
return;
// This is code effectively taken from juce::LookAndFeel_V2::createSliderTextBox()
// it is the colours that will be used for a LinearBar or LinearBarVertical slider
// in almost all cases, feel free to change them if needed, but generally using
// setColour on the slider should be sufficient
label->setColour (Label::textColourId, slider.findColour (Slider::textBoxTextColourId));
label->setColour (Label::backgroundColourId, Colours::transparentBlack);
label->setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId));
label->setColour (TextEditor::textColourId, slider.findColour (Slider::textBoxTextColourId));
label->setColour (TextEditor::backgroundColourId, slider.findColour (Slider::textBoxBackgroundColourId).withAlpha (0.7f));
label->setColour (TextEditor::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId));
label->setColour (TextEditor::highlightColourId, slider.findColour (Slider::textBoxHighlightColourId));
}
private:
// we use a weak reference here so if the label is deleted
// from under our feet we wont accidentally dereference it!
juce::WeakReference<Component> label;
};
// This gives us a unique instance of our look-and-feel per slider
// meaning each instance can store its own reference to its label
LookAndFeel lookAndFeel;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SequencerSlider)
};
// Code below here is just an example of how we might use the above slider in practice
//==============================================================================
class MyComponent : public juce::Component
, private juce::Timer
{
public:
//==============================================================================
MyComponent()
{
createAndAddSliders (8);
highlightSlider (0);
setSize (600, 400);
startTimerHz (1);
}
~MyComponent() override
{
stopTimer();
}
//==============================================================================
void paint (juce::Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
}
void resized() override
{
auto bounds = getLocalBounds();
auto sliderSize = bounds.getWidth() / sliders.size();
for (auto* slider : sliders)
slider->setBounds (bounds.removeFromLeft (sliderSize));
}
private:
void createAndAddSlider()
{
auto slider = std::make_unique<SequencerSlider>();
addAndMakeVisible (slider.get());
sliders.add (std::move (slider));
}
void createAndAddSliders (int numSliders)
{
for (int i = 0; i < numSliders; ++i)
createAndAddSlider();
}
void highlightSlider (int index)
{
sliders[highlightedSliderIndex]->setHighlighted (false);
highlightedSliderIndex = index % sliders.size();
sliders[highlightedSliderIndex]->setHighlighted (true);
}
void timerCallback() override
{
highlightSlider (highlightedSliderIndex + 1);
}
//==============================================================================
juce::OwnedArray<SequencerSlider> sliders;
int highlightedSliderIndex {};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyComponent)
};
I hope that helps.
All the best, and thanks again for reporting.
It dawned on me this morning that there is an easier way to get the underlying label, just loop through the children to find it instead.
class SequencerSlider : public juce::Slider
{
public:
SequencerSlider()
{
// do all the usual slider setup here
setSliderStyle (juce::Slider::SliderStyle::LinearBarVertical);
setRange (0.0, 10.0, 0.1);
setHighlighted (false);
}
// by overriding this we prevent the base class getting the call
// if it did it would end up calling createSliderTextBox which we
// don't want as that's the cause of the interruptions to the
// sliders mouse events!
void colourChanged() override
{
// this allows us to update the labels colours using the
// colours from the slider
updateLabelColours();
// we should also tell this component to repaint as one of its
// colours may have changed
repaint();
}
void setHighlighted (bool shouldHighlight)
{
setColour (juce::Slider::textBoxOutlineColourId, shouldHighlight ? juce::Colours::white
: juce::Colours::black);
}
private:
Label* findLabel()
{
// by searching through all the children and returning the
// first child of type `Label` it should give us the label
// component we need in order to update it's colours without
// completely recreating it
for (auto* child : getChildren())
{
if (auto* l = dynamic_cast<Label*> (child))
return l;
}
return nullptr;
}
// this will be called whenever a child component is added or
// removed, giving us an opportunity to get a pointer to the label
// we want to set the colours on once it's been added
void childrenChanged() override
{
label = findLabel();
updateLabelColours();
}
void updateLabelColours()
{
if (label == nullptr)
return;
// This is code effectively taken from juce::LookAndFeel_V2::createSliderTextBox()
// it is the colours that will be used for a LinearBar or LinearBarVertical slider
// in almost all cases, feel free to change them if needed, but generally using
// setColour on the slider should be sufficient
label->setColour (Label::textColourId, findColour (Slider::textBoxTextColourId));
label->setColour (Label::backgroundColourId, Colours::transparentBlack);
label->setColour (Label::outlineColourId, findColour (Slider::textBoxOutlineColourId));
label->setColour (TextEditor::textColourId, findColour (Slider::textBoxTextColourId));
label->setColour (TextEditor::backgroundColourId, findColour (Slider::textBoxBackgroundColourId).withAlpha (0.7f));
label->setColour (TextEditor::outlineColourId, findColour (Slider::textBoxOutlineColourId));
label->setColour (TextEditor::highlightColourId, findColour (Slider::textBoxHighlightColourId));
}
Label* label {nullptr};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SequencerSlider)
};
Nice idea.
I think it can be improved further: there is no need to keep the label pointer, removing any chance to get a dangling pointer.
This is all you need:
// this will be called whenever a child component is added or
// removed
void childrenChanged() override
{
updateLabelColours();
}
void updateLabelColours()
{
if (auto* label =findLabel())
{
// This is code effectively taken from juce::LookAndFeel_V2::createSliderTextBox()
// it is the colours that will be used for a LinearBar or LinearBarVertical slider
// in almost all cases, feel free to change them if needed, but generally using
// setColour on the slider should be sufficient
label->setColour (Label::textColourId, findColour (Slider::textBoxTextColourId));
label->setColour (Label::backgroundColourId, Colours::transparentBlack);
label->setColour (Label::outlineColourId, findColour (Slider::textBoxOutlineColourId));
label->setColour (TextEditor::textColourId, findColour (Slider::textBoxTextColourId));
label->setColour (TextEditor::backgroundColourId, findColour (Slider::textBoxBackgroundColourId).withAlpha (0.7f));
label->setColour (TextEditor::outlineColourId, findColour (Slider::textBoxOutlineColourId));
label->setColour (TextEditor::highlightColourId, findColour (Slider::textBoxHighlightColourId));
}
}
But if you want to keep it, I would recommend using a
juce::Component::SafePointer<juce::Label> label;
Nice touch!
This works quite well for my but one problem is that click on the slider makes a cursor appear.
How can I surpress the cursor?
