Custom graphics (sliders and knobs)


#1

Hi,

I’d like to customize the Slider by loading the graphics for its background and the “hold” (the foreground, which the user clicks and drags). Which is the recommended way to do this? Should I inherit the Slider class and then implement the Paint method. However, as far as I can see I only get a single Graphics object reference to the Paint method. Does that reference point at the background or at the Slider “hold”?

Is there a class for a Knob? I have animated stacked frames for knobs, which I used with the VSTGUI and now I’m looking for an easy way to create an animated knob in Juce using these graphics.

Thanks.

/Mike


#2

no - you need to override the lookandfeel methods for drawing a slider, not the slider class itself.


#3

like so http://ctrlr.googlecode.com/svn/trunk/EdoController/UI/Gekon/
and it can look like this, this is a subclass of a LookAndFeel


#4

Ok. So I use the LookAndFeel class and use drawLinearSliderBackground and drawLinearSliderThumb to draw the background + thumb of the slider.

To load from disk, I use the ImageFileFormat class. Then I create an Image object using ImageFileFormat. Then I create a Graphics object using the Image object. Then I set the Graphics object which is passed to drawLinearSliderThumb. Is that correct?

Is there any support for stacked image frames? Or do I have to split them into individual images and then assign each one depending on which value the slider has?

Cheers
Mike


#5

I dont understand why people do this film strips of images (this reminds me of my very short adventure with VST GUI), you can easily rotate an image, this is more accurate i think:

void NDSLookAndFeel::drawRotarySlider (Graphics &g, int x, int y, int width, int height, float sliderPosProportional, const float rotaryStartAngle, const float rotaryEndAngle, Slider &slider)
{
        AffineTransform rotator;
        if (!slider.isMouseOverOrDragging())
        {
                g.drawImage (knobNormal, x,y,width,height,0,0,width,height,false);
        }
        else
        {
                g.drawImage (knobOver, x,y,width,height,0,0,width,height,false);
        }
        g.drawImageTransformed (knobMask, x, y, width, height, rotator.rotated ((float)sliderPosProportional*rotaryEndAngle, (float)(knobMask->getWidth()/2), (float)(knobMask->getHeight()/2)), false);
}

i do this here
the knobs are actually one PNG that’s rotated and has a mouseOver state


#6

There is a reason. If you have high requirements on the quality of your GUI controls, then you want to render them in a 3D animation tool, so they include shadows and perhaps a light source which shines from a certain angle. As the knob is turned, these properties should of course remain intact and they should not be rotated. That gives a much more professional look and feel to the user interface.


#7

well if you hhave to do this then i see no problem with that. a simple class would handle this.


#8

Probably not the best way. Look at the BinaryBuilder as an option for embedding the images into the actual code. The JuceDemo shows how to use the ImageCache class to access the binary object.

That sounds convoluted. The LookAndFeel methods will typically pass a Graphics reference onto which your image will be drawn. As such, typically, your LookAndFeel method would simply look something like

void genericLAFMethod(Graphics& g)
{
  // Assuming image is an Image pointer that has been extracted from an ImageCache, or anywhere else.
  g.drawImageAt(image, 0, 0)  
}

You’ll need to handle that manually in the painter method.


#9

[quote=“valley”]The LookAndFeel methods will typically pass a Graphics reference onto which your image will be drawn. As such, typically, your LookAndFeel method would simply look something like

void genericLAFMethod(Graphics& g)
{
  // Assuming image is an Image pointer that has been extracted from an ImageCache, or anywhere else.
  g.drawImageAt(image, 0, 0)  
}

[/quote]

Yeah, it was something like that I looked for. Well, I guess I have to split my animations then. And use the drawImageAt method. Not too many, around 100+ frames stacked in each image :shock:


#10

Split them? Surely the reason people use film strips is so you can have one image, and draw it at different y co-ordinates to reveal the frame that you want?


#11

I would definitely recommend taking a few minutes to get to know the binary builder. Since it’ll process all images in a directory in one pass, and produce a single .cpp file for them, it’s going to be a huge time saver for stuff like this.


#12

Customizing “drawLinearSlider” only works if you don’t need some extra information from the slider !

I need to draw 2 slider thumbs: a “ghost” under the users mouse (normal), and a real/thick one for the actual value.

The GUI is meant to work asynchronously with network latency and the “thick” thumb acts like a validation clue for the user.

Any way to do this without rewriting a complete Slider class ?


#13

every component (a slider too) has a set of properties, add your own custom property to the slider and update it, this way you’ll have to values in the Slider class you can use to draw two values. Also there are two value and three value sliders, you can use those too i guess.


#14

Cool !

setComponentProperty and getComponentPropertyDouble are my new friends.

I don’t think I can use these: the other two values are used for range marking (min/max).


#15

Split them? Surely the reason people use film strips is so you can have one image, and draw it at different y co-ordinates to reveal the frame that you want?[/quote]

Yeah, I think this one will do what I need:

void Graphics::drawImage ( const Image *const imageToDraw,
int destX,
int destY,
int destWidth,
int destHeight,
int sourceX,
int sourceY,
int sourceWidth,
int sourceHeight,
const bool fillAlphaChannelWithCurrentBrush = false
)

That would allow drawing portions of a source image into a destination image.


#16

Apparently there is a nice tool called http://www.g200kg.com/en/software/knobman.html on kvr there is a lot of examples of knobs, and i understood why a film strip slider could work. wrote a little class to handle this situation in a LookAndFeel call for drawRotarySlider

class FilmStripSlider
{
	public:
		FilmStripSlider (Image *_knobStrip);
		void drawFrame (Graphics &g, int x, int y, int width, int height, Slider &slider);

	private:
		Image *knobStrip;
		int frameCount;
		int frameSize;
		bool isVerticalStrip;
};
FilmStripSlider::FilmStripSlider (Image *_knobStrip) : knobStrip(_knobStrip)
{
	if (knobStrip->getWidth() > knobStrip->getHeight())
	{
		frameCount		= knobStrip->getWidth() / knobStrip->getHeight();
		frameSize		= knobStrip->getHeight();
		isVerticalStrip	= false;
	}
	else
	{
		frameCount		= knobStrip->getHeight() / knobStrip->getWidth();
		frameSize		= knobStrip->getWidth();
		isVerticalStrip	= true;
	}
}

/* thanks to anita for the math */

void FilmStripSlider::drawFrame (Graphics &g, int x, int y, int width, int height, Slider &slider)
{
	const double div	= slider.getMaximum() / frameCount;
	double pos			= (int)(slider.getValue() / div);

	if (pos > 0)
		pos = pos - 1;

	if (width != height) /* scale */
	{
		x		= (width / 2) - (height / 2);
		width	= height;
	}

	if (isVerticalStrip)
	{
		g.drawImage (knobStrip, x, y, width, height, 0, (int)(pos*frameSize), frameSize, frameSize, false);
	}
	else
	{
		g.drawImage (knobStrip, x, y, width, height, (int)(pos*frameSize), 0, frameSize, frameSize, false);
	}
}

just give it a film strip of knobs and it will draw the right frame depending on the slider value.


#17

thanks! looking this code over now :slight_smile:


#18