Rotating Knob Image

Hey everyone,

I’m trying to replace the RotarySlider look with an image that I can then rotate. I’ve created a new Look and Feel class and switched out the default class for this one. I’ve also overridden the drawRotarySlider method.

The code below doesn’t draw any knobs, unless I add:

g.drawImageWithin(mKnobImage, 0, 0, width, height, RectanglePlacement::centred);"

The rotation doesn’t work either.

CustomLookAndFeel::CustomLookAndFeel()
{
    mKnobImage = juce::ImageCache::getFromMemory(BinaryData::RotaryKnob_png, BinaryData::RotaryKnob_pngSize);
}

void CustomLookAndFeel::drawRotarySlider(Graphics& g, int x, int y, int width, int height, float sliderPos,
    const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider)
{
    AffineTransform transform;
    transform.rotation(sliderPos * rotaryEndAngle, 0, 0);
    g.drawImageTransformed(mKnobImage, transform, false);
}

Any help is much appreciated! (Also I’ll eventually switch out the pngs with svg’s. I first wanted to get something “simple” working).

The rotation needs a centre to rotate about. You have chosen 0, 0, which might not even be within the slider rectangle (note x and y).

I would recommend:

auto image = juce::ImageCache::getFromMemory (BinaryData::RotaryKnob_png, BinaryData::RotaryKnob_pngSize);
juce::Rectangle<float> rect (x, y, width, height);
auto transform = juce::AffineTransform::rotation (juce::jmap (sliderPos, rotaryStartAngle, rotaryEndAngle),
                                                  rect.getCentreX(), rect.getCentreY());
g.drawImageTransformed (image, transform, false);

a) the static function is called without an object and returns a rotation
b) the rotation needs the centre of the rectangle
c) thanks to the ImageCache, there is no need to store the image in a member variable

1 Like

I plan to implement images in my controls but I don’t intend to make the image rotate, it would be static and the dial and lights would be superimposed, either as an image or as a drawing. The justification is that the rotation is not really appreciated, the shadows and highlights always remain static.

Don’t you think this could be more practical, although it only works for circular rotary controls and sliders.

1 Like

Thanks for the clear explanation!

That makes a lot of sense. Unfortunately enough it didn’t fix my issue, so I may be doing something else wrong as well.

Here’s the function where I setup my slider:

void TVRATremoloAudioProcessorEditor::sliderSetup(SliderWithMenu& slider, AudioParameterFloat* param, int x, int y, int width, int height, Slider::SliderStyle style) {
    slider.setBounds(x, y, width, height);
    slider.setRange(param->range.start, param->range.end);
    slider.setValue(param->get());
    slider.setTextBoxStyle(Slider::NoTextBox, false, 0, 0);
    slider.setSliderStyle(style);
    addAndMakeVisible(slider);
    slider.onValueChange = [&slider, param] { *param = (float)slider.getValue(); };
    slider.onDragStart = [&slider, param] { param->beginChangeGesture(); };
    slider.onDragEnd = [&slider, param] { param->endChangeGesture(); };
    slider.setupMouseEvent(*this, audioProcessor, param->getParameterIndex());
}

I noticed x and y always return 0 in the drawRotarySlider function, even though they have the correct value in the slidersetup function above.

When I copy paste the original drawRotarySlider code in it seems that x and y at 0 doesn’t really matter. The default slider still gets drawn at the correct place. I’m a bit confused how that works.

Yea, that looks like a way smarter approach.

I’ll try that after I get the basic rotating image working.

Thanks for the tip! :slight_smile:

Ah, with some more testing I got it working!

The image was way too big so I had to rescale it. Now it appears and it rotates nicely :slight_smile:

1 Like

Slightly OT, but looking at your slider.onValueChanged etc:

Have a look at juce::ParameterAttachment, specifically juce::SliderParameterAttachment.

This will correctly handle setting up the slider from the parameter configuration. This includes:

  • correct range with skew and other specialities
  • setting up the same textToValue and valueToText, so the value appears the same in the slider textbox (if any) and the host automation
  • managing beginChangeGesture and endChangeGesture
  • makes sure the value change doesn’t bounce

in fact I think it is not even a choice, it is mandatory depending on the type of knob. For a knob with text, geometric shapes, drawings or reliefs, a rotating image is necessary, but for a circular knob with reflections, highlights, shadows, a rotated image does not work.

Oh that looks pretty handy, thanks!

Learning so much new stuff … :stuck_out_tongue:

I mean, not that mandatory. Maybe I like the looks of rotating shadows :stuck_out_tongue:

Good tips though! I’ve since switched to vector art so I’ll make sure to not rotate the shadows and highlights. I like your idea of superimposed dials and lights, so I’ll probably use that.