Skinning the Slider / Replacing knob and slider track with PNGs


#1

I haven't found anything that covers how to replace the slider knob and slider track with your own PNG images, in a way clear to the beginner, after searching on the forum with keywords like "slider image", "slider png" and others.

I'm new to both JUCE and C++, so I'd need a bit of care taken to understand.

From what I gather so far, it looks like I'd need to load the PNG file for the slider knob and slider track in the PluginEditor.cpp -> Resources Tab.  Then, the image names are shown there and in the PluginEditor.h file.  Then, I'm guessing that whatever code needs to happen would be placed in the PluginEditor.cpp between the //[MiscUserDefs] comments.

I've seen posts about overwriting the lookandfeel() class, but I don't know how that is done, and would love to see an example of how the slider could be skinned that way.  I'm not sure if that approach with lookandfeel() would change all sliders, and I may not want every slider in a plugin design to necessarily look the same, although it is common in GUI designs for all sliders to be idenitical.

Skinning the knobs and track of a slider is such a routine thing... I'd imagine someone has worked out a quick and easy way to do this.

I'm also interested in skinning rotating knobs both with and without the knobman filmstrips, but I don't want to get into too much at once.  I'll be a happier person once I'm sucessfully skinning these sliders.


#2

Did you see this one? http://www.juce.com/forum/topic/how-do-you-use-filmstrip-images-knobs

Or this one? http://www.juce.com/forum/topic/how-easily-use-png-knobman-my-code

Personally I prefer the vector approach to drawing knobs, that way I can resize to whatever I want. Though there's a few things I still can't get quite as nice I had them in KnobMan :)


#3

Yeah, please don't be one of those "PNG skin" people - it's soooo 1990s. Vectors + clean, simple graphics are the way to go.

(I've been repeating this same message for a decade, which is why I've always resisted requests to support image-strips and any of that crap. The world of web-design finally woke up to vector graphics a couple of years ago, but plugin writers just seem to love their horrible 3D skeuomorphic interfaces!)


#4

I agree, I love the interfaces of plugins like those from Fabfilter and iZotope.  I think great vector interface work is amazing.  I also love the interfaces of plugins from Universal Audio which seem to be less vector-based, and I think it doesn't have to prevent a plugin from being decent.

I've been in mastering for over a decade, written a book about it, prototyped some plugins and I'm good with Photoshop and for now I have to work with my talents as they are.  Taking on learning JUCE and C++ is a challenge.  Once I get on my feet well enough, I won't let anything stop me from getting good with vector graphics because I appreciate your advice and agree that's the way to go -- but right now for me it couldn't compare with what I could do with some old-fashioned PNG skinning in the meantime while I'm getting started.

Andrew J, I did read those posts and they both seem to be about PNG filmstrips.  At the moment, I just want to replace the slider knob and track with a non-filmstrip PNG.  I know this is simple, but I'm just getting started.  If I have a little help over a few bumps, I'll keep going and get better.


#5

I think you should have a look to the second link he posted, to get how to import PNG files into your JUCE project. Then, you should see the documentation about the LookAndFeel classes. They allow you to replace elements in JUCE components to anything you want.


#6

I don't think that the technical capabilities of Juce should reflect a fixed set of aesthetics. If people feel more comfortable designing in Photoshop, why not support those people?

 

Personally, I love my minimalist vector graphics. However, plenty of potential customers DON'T love these graphics, and want realistic GUIs. Making it more difficult for a developer to design realistic GUIs seems like an intentional crippling of the API, based on a very personal aesthetic decision.

 

Flat design is in right now. I can tell this from all the graphics designers in my Twitter feed bitching about how flat design is too trendy. This suggests that the "cutting edge" designers of tomorrow will be going for SUPER realistic GUIs, or retro-90s GUIs. Supporting traditional skins would allow Juce to stay abreast of the latest trends, and keep turning and turning in the widening gyre.

 

Sean Costello


#7

I did see how to import binaries and that LookAndFeel is the way to go and mentioned them in my original post.  I'm still not having any success with it though. 

I have discovered the slider knob is called the "slider thumb" and that the slider track is called "slider background" in the documentation.

I see these references about the classes:

LookAndFeel_V3:  http://www.juce.com/api/classLookAndFeel__V3.html

Slider look and feel:  http://www.juce.com/api/structSlider_1_1LookAndFeelMethods.html

So far, my best guess to replace the slider thumb/knob somehow with this class:

virtual void Slider::LookAndFeelMethods::drawLinearSliderThumb ( Graphics &  ,
int  x,
int  y,
int  width,
int  height,
float  sliderPos,
float  minSliderPos,
float  maxSliderPos,
const Slider::SliderStyle  ,
Slider &   
)

My best guess to replace the slider background/track is to use this class:

virtual void Slider::LookAndFeelMethods::drawLinearSliderBackground ( Graphics &  ,
int  x,
int  y,
int  width,
int  height,
float  sliderPos,
float  minSliderPos,
float  maxSliderPos,
const Slider::SliderStyle  style,
Slider &   
)

I don't know how to use these classes, together with the image name assigned in the introjucer (it happens to be "faderknob_png"), in a way that connects them with sliders I've created in the Introjucer.

Seems like the code would go between the //[MiscUserDefs]  comments in the PluginEditor.cpp.


#8

Quick and dirty solution:

 

Create a sublass of the original LookAndFeel class. Then, overwrite the following function:



virtual void drawRotarySlider    (   Graphics &     g,
                                     int     x,
                                     int     y,
                                     int     width,
                                     int     height,
                                     float     sliderPosProportional,
                                     float     rotaryStartAngle,
                                     float     rotaryEndAngle,
                                     Slider &     slider );

with something like:

void LookAndFeelCustom::drawRotarySlider    (    Graphics &     g,
                                                 int     x,
                                                 int     y,
                                                 int     width,
                                                 int     height,
                                                 float     sliderPosProportional,
                                                 float     rotaryStartAngle,
                                                 float     rotaryEndAngle,
                                                 Slider &     slider )
{    

    Image myStrip;    

    myStrip = ImageCache::getFromMemory (BinaryData::knobmid_png, BinaryData::knobmid_pngSize);

    
    const double fractRotation = (slider.getValue() - slider.getMinimum()) / (slider.getMaximum() -      slider.getMinimum()); //value between 0 and 1 for current amount of rotation
    const int nFrames = myStrip.getHeight()/myStrip.getWidth(); // number of frames for vertical film strip
    const int frameIdx = (int)ceil(fractRotation * ((double)nFrames-1.0) ); // current index from 0 --> nFrames-1

    const float radius = jmin (width / 2.0f, height / 2.0f) ;
    const float centreX = x + width * 0.5f;
    const float centreY = y + height * 0.5f;
    const float rx = centreX - radius - 1.0f;
    const float ry = centreY - radius /* - 1.0f*/;

    g.drawImage( myStrip, // image    
                 (int)rx, (int)ry, myStrip.getWidth(), myStrip.getWidth(),   // dest
                 0, frameIdx*myStrip.getWidth(), myStrip.getWidth(), myStrip.getWidth()); // source

}

Voila! (this only supports a specific size, you'll have to play around with values, but you probably get the idea)


Custom knobs and buttons?
GUI: 3D & Sprite Sheet
#9

Alright, eventually I do want to do some filmstrip rotary knobs and when that time comes I'll give this a try. 

At the moment, I'm trying to stay focused on replacing the slider "thumb" and slider "background" with my own PNG images.  Not a filmstrip -- just a little static PNG image.  I really want to find out how to do this the right way while I'm trying to build a foundation with JUCE.

I haven't done subclassing before.  What I seem to understand is that if I find a class in the JUCE documentation like this slider thumb look and feel class:


virtual void Slider::LookAndFeelMethods::drawLinearSliderThumb ( Graphics &  , int  x, int  y, int  width, int  height, float  sliderPos, float  minSliderPos, float  maxSliderPos, const Slider::SliderStyle  , Slider &    )

...then I can "overwrite" it by putting this, im assuming between //[MiscUserDefs]  comments in the PluginEditor.cpp:


void LookAndFeelCustom::drawRotarySlider ( Graphics & g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, Slider & slider )

..then it looks like I'm supposed to do an open bracket afterwards and I can start specifying each parameter in those parenthesis that we see in the code above.  That's where things start getting murky for me.  It'd be one thing if it was something like this:

{ImageCache::getFromMemory (BinaryData::knobmid_png, 125, 50, 200, 50,  15, 45, 210, 150, "and I dont know what Slider& slider is")}

...which I list it this way to say it would make sense to me if each parameter was sort of separated by commas or something.  Some separator or something between each value or variable being passed in as a parameter.

I'm confused about this and after more than a week, I'm still working towards replacing a slider "thumb" and "background" with a PNG.  


#10
#ifndef __JUCE_LookAndFeelCustom_JUCEHEADER__
#define __JUCE_LookAndFeelCustom_JUCEHEADER__

//==============================================================================
class LookAndFeelCustom : public LookAndFeel_V3 // inherit from LookAndFeel_V3
{
public:

    LookAndFeelCustom();
    virtual ~LookAndFeelCustom();

    /* Now we created an exact copy of the base class LookAndFeel_V3. It "inherited" all the functionality of LookAndFeel_V3, now we extend it with our own functionality by overwriting the original functionality. E.g: */

    // overwrite the original drawRotarySlider with out own

    virtual void drawRotarySlider    (    Graphics &     g,
                                     int     x,
                                     int     y,
                                     int     width,
                                     int     height,
                                     float     sliderPosProportional,
                                     float     rotaryStartAngle,
                                     float     rotaryEndAngle,
                                     Slider &     slider );

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeelCustom);
};

#endif   // __JUCE_LookAndFeelCustom_JUCEHEADER__

Inheritance is a powerfull tool, it's like having 1000 programmers at your hand, and they never do a mistake twice. ;) Seriously, give it a read/try, it's not that difficult.


#11

There's really no point in struggling along if you don't even know how the basic C++ language works. Go and read some books about it, or you're just going to keep feeling frustrated!


#12

I bought the recommended books in the Getting Started area plus some others, I'm reading them and working with JUCE every day.  I've had some progress so far, but still a long road ahead I think.

I can't give up on replacing the slider thumb with a PNG file.  Struggling through it is probably the best pedagogy for me and it's already giving me focus on the things I need to learn more about to work with JUCE.

Also, I'm learning about the forum and getting to know how things are here.


#13

I do get the part about inheritance and that basically I can create a new lookandfeel class that inherits from the existing one.  I understand I can overwrite what's in the original class, without having to recreate the class.  I see how to declare the new LookAndFeelCustom class and have it inherit from LookAndFeel_V3.

What I don't get is how to specify the PNG file I have already imported as a binary to be used as the slider thumb.  It's specifying that graphic, not a filmstrip, but a static graphic that I don't see how to do.

I think I could just about do this now using the rotary knob and a filmstrip created in Knobman, and have seen plenty of posts about that, but I'm more interested in the slider.


#14

You might want to have a look at the juced extension classes for Juce.

https://code.google.com/p/juced/

Albeit I don't know if they still work with Juce v3, there is an implementation for skinned sliders, knobs etc in there.


#15

I went over to the downloads page on that site but it seems like the files there are for Linux only.  I'm on Windows but tried anyway, and wasn't able to take a look.

Still reading C++ books and learning every day, and still trying to chip away at getting the sliders skinned.  Right now I have code I'd think would work, but doesn't.  

I battled compile errors after reading everything here and one thing that stopped some errors was to put this in the constructor area in the PluginEditor.cpp (my slider thumb image is called faderknob_png): 

    cachedImage_faderknob_png = ImageCache::getFromMemory (faderknob_png, faderknob_pngSize);

Now I have this code in the Misc User Code area in the PluginEditor.cpp:


#ifndef __JUCE_LookAndFeelCustom_JUCEHEADER__
#define __JUCE_LookAndFeelCustom_JUCEHEADER__
Image faderknob;
//==============================================================================
class LookAndFeelCustom : public LookAndFeel_V3 // inherit from LookAndFeel_V3
{
public:
    LookAndFeelCustom();
    virtual ~LookAndFeelCustom();
    /* Now we created an exact copy of the base class LookAndFeel_V3. It "inherited" all the functionality of LookAndFeel_V3, now we extend it with our own functionality by overwriting the original functionality. E.g: */
    // overwrite the original drawRotarySlider with out own
    virtual void drawLinearSliderThumb    (        Graphics &     g,
                                                int     x,
                                                int     y,
                                                int     width,
                                                int     height,
                                                float     sliderPos,
                                                float     minSliderPos,
                                                float     maxSliderPos,
                                                const Slider::SliderStyle,
                                                Slider &     slider )
        {
                g.drawImageWithin(cachedImage_faderknob_png, OriginOffset-tmpF/2.0f, OriginOffset-1.25f*tmpF/2, tmpF, tmpF, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false);
        }
    
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeelCustom);
};
#endif   // __JUCE_LookAndFeelCustom_JUCEHEADER__

No errors shown in VS2010 -- but I go into the introjucer WYSIWYG subcomponents tab and those sliders are still looking the same.


#16

If you're only using cachedImage_faderknob_png in LookAndFeelCustom, then you should have it as a member of that class and initialise it in its constructor rather than in the PluginEditor. By the look of what you pasted, you shouldn't need to declare "Image faderknob;" outside the class either.

To get your plugin to use your custom look and feel, you'll need to declare it in PluginEditor.h...

private:
    LookAndFeelCustom customLnF;

... and then in the constructor for your AudioProcessorEditor

    LookAndFeel::setDefaultLookAndFeel(&customLnF);

This means that the AudioProcessorEditor and all of it's children will automatically use your custom LookAndFeel.

If you only want to use the custom look and feel for a particular slider then do the following instead:

slider->setLookAndFeel(&customLnF);

#17

I moved this line initializing cachedImage_faderknob_png to the constructor of the class:

cachedImage_faderknob_png = ImageCache::getFromMemory (faderknob_png, faderknob_pngSize);

Once I moved it there, I got a compile error saying these were undefined:  cachedImage_faderknob_png, faderknob_png, faderknob_pngSize.

Also, I moved the Image faderknob to inside the class.  I tried it both inside the constructor and after the destructor in an attempt to clear the compile errors mentioned above.

I did everything else suggested in the post, exactly as they are.  I tried applying it to all of the children instead of a particular slider, since that seems a little more straight-forward.

This is my entire [MiscUserDefs] section now:


//[MiscUserDefs] You can add your own user definitions and misc code here...
#ifndef __JUCE_LookAndFeelCustom_JUCEHEADER__
#define __JUCE_LookAndFeelCustom_JUCEHEADER__
//==============================================================================
class LookAndFeelCustom : public LookAndFeel_V3 // inherit from LookAndFeel_V3
{
public:
    LookAndFeelCustom()
    {
        Image faderknob;
        cachedImage_faderknob_png = ImageCache::getFromMemory (faderknob_png, faderknob_pngSize);
    };
    virtual ~LookAndFeelCustom();
    Image faderknob;
    /* Now we created an exact copy of the base class LookAndFeel_V3. It "inherited" all the functionality of LookAndFeel_V3, now we extend it with our own functionality by overwriting the original functionality. E.g: */
    // overwrite the original drawRotarySlider with out own
    virtual void drawLinearSliderThumb    (        Graphics &     g,
                                                int     x,
                                                int     y,
                                                int     width,
                                                int     height,
                                                float     sliderPos,
                                                float     minSliderPos,
                                                float     maxSliderPos,
                                                const Slider::SliderStyle,
                                                Slider & slider )
    {
        g.drawImageWithin(cachedImage_faderknob_png, OriginOffset-tmpF/2.0f, OriginOffset-1.25f*tmpF/2, tmpF, tmpF, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false);
    }
private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeelCustom);
};
#endif   // __JUCE_LookAndFeelCustom_JUCEHEADER__
//
[/MiscUserDefs]

Checked in introjucer, even though there were the compile errors going on.  Still those sliders are looking the same.

All help has been and will be very appreciated, whether it is specific ideas or on how to approach this.  Right now I am reading books and watching videos on C++, trying out things for many hours, then when it seems hopeless, I do a forum post.  I've been working on trying to skin the sliders for about three weeks now.


#18

Check your private messages :)


#19

yeah, I'm going to have to agree with Sean here, even though I'm not as much a minamalist as he is :)

I have a lot of experience working on UIs for audio products (one of the companies mentioned in the thread--which doesn't use Juce), and even though we've started to move away from skeumorphism, the fact of the matter is that the tools for working with raster graphics are just better and more flexible.

Jules, please don't assume that anyone who wants filmstrips wants hardware-looking knobs. I just want to design a nice looking (flat actually) set of widgets and I'm tearing my hair out trying to implement it with Juce because it's not built around the idea of using PNGs, which sometimes is just the right tool for the job from a designer's perspective.

 

-nick


#20

I want to agree strongly with Sean’s point that JUCE “should not reflect a fixed set of aesthetics”. Skeuomorphic is a particular style of raster graphic manipulation and I’ve seen both excellent and Hideous examples of skeuomorphic design. I personally am designing a product intended to run on Mac and PC platforms that is targeted at singers, many of who from my experience are computer-phobic.
Giving them an interface which feels more like an “appliance” than a “computer program” goes a long way at overcoming their phobia. Going strictly for a “flat” aesthetic seems to totally overlook a huge dimension of the user experience, particularly for music related products where users are likely to have aesthetic sensibilities, perhaps more than the normal computer user.
I have some guitars that are made of rosewood. It is beautiful. When I pick it up and play it that rosewood is a significant part of my experience. A flat aesthetic to me would be analogous to spray painting that beautiful guitar silver because it was a “cleaner interface”.
I argue for getting away from the nasty taste the overuse and examples bad skeuomorphic has left in some peoples mouth and calling it “raster graphic manipulation” or something similar.