GetDunne Wiki

Notes from the desk of Shane Dunne, software development consultant

User Tools

Site Tools


juce_gui_basics

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
juce_gui_basics [2021/02/08 16:30]
shane [Look and Feel]
juce_gui_basics [2021/02/08 23:52] (current)
shane [Subclassing JUCE GUI classes]
Line 12: Line 12:
 You might choose to subclass //juce::Button// if you want to create your own type of button widget with unique appearance and behavior. To respond to the button being clicked, you would overload the //clicked()// member function. To change its appearance, you would overload //paint()//, and so on. You might choose to subclass //juce::Button// if you want to create your own type of button widget with unique appearance and behavior. To respond to the button being clicked, you would overload the //clicked()// member function. To change its appearance, you would overload //paint()//, and so on.
  
-Creating a derived class like this allows you to override as much of the standard class's functionality as you like, and inherit the rest.+Creating a derived class like this allows you to override as much of the standard class's functionality as you like, and inherit the rest. It's usually more work than the other two approaches.
  
 ==== Listeners ==== ==== Listeners ====
Line 50: Line 50:
  
 Whenever possible, you should try to derive your custom GUI component classes from a logically-equivalent JUCE class. For example, you could subclass //juce::Slider// to create your own custom "knob" class. This not only allows you to inherit a great deal of useful functionality; it will also save you from ripping your hair out by the roots when you need to connect your custom widgets to your plug-in's parameters. Whenever possible, you should try to derive your custom GUI component classes from a logically-equivalent JUCE class. For example, you could subclass //juce::Slider// to create your own custom "knob" class. This not only allows you to inherit a great deal of useful functionality; it will also save you from ripping your hair out by the roots when you need to connect your custom widgets to your plug-in's parameters.
 +
 +It's almost beyond belief that JUCE---a framework used almost exclusively for audio plug-ins---does not already include a knob class. When working through the [[https://juce.com/learn/tutorials|JUCE tutorials]], you have to get all the way to the [[https://docs.juce.com/master/tutorial_look_and_feel_customisation.html|42nd example]] before they show you how to make a real knob, and it's painful, because they have you do it using another complex aspect of JUCE called //Look-and-Feel// classes.
  
 ==== Look and Feel ==== ==== Look and Feel ====
-It is almost beyond belief that JUCE---a framework used almost exclusively for audio plug-ins---does not already include knob class. Wellit sort-of does//juce::Slider// can be told to display horizontallyverticallyor in a "rotaryform, which is so far from being useful it'ridiculousWhen working through the [[https://juce.com/learn/tutorials|JUCE tutorials]], you have to get all the way to the [[https://docs.juce.com/master/tutorial_look_and_feel_customisation.html|42nd example]] before they show you how to make real knob, and it's painful, because you have to learn about yet another complex aspect of JUCE called //Look-and-Feel// classes.+JUCE provides a "Look and Feel" (LAF) system, which is ostensibly there to simplify the process of "skinning", i.e., altering multiple aspects of a program's GUI in a systematic way. I find it overly complicated, and the idea of actually using a custom LAF class to re-skin a whole GUI is daunting. In practice, it tends to be used in much more limited ways. 
 + 
 +The basic idea is that the JUCE GUI-widget classes don't actually contain code for "look and feel" aspects like drawing the actual graphics; instead they call appropriate routines from an attached [[https://docs.juce.com/master/classLookAndFeel.html|juce::LookAndFeel]]-derived class. To change the look and feel, you simply call the widget object's //setLookAndFeel()// method, so it points to a different //juce::LookAndFeel// object. 
 + 
 +==== Graphics ==== 
 +The JUCE framework is //huge//, and although some aspects of it may seem less clean, well-designed, or complete than others, one of its great strengths is its graphics support. 
 + 
 +JUCE's graphics API, which is rich and fully cross-platform, is accessed through large class called [[https://docs.juce.com/master/classGraphics.html|juce::Graphics]]. You never need to instantiate this class; the framework always does it for you. All of your "drawing" your code will go into various //juce::Component//-derived classes that you write, specifically their //paint()// method, whose one argument is a reference to a //juce::Graphics// object. 
 + 
 +==== Layout is basically top-down ==== 
 +Every //juce::Component//-derived class also implements a //resized()// function, which gets called automatically whenever the Component's //bounds// (top-left X/Y coordinates, width and height) are changed. Any Component may have "child" Components, which are normally drawn entirely inside its bounds. One of the main jobs of each Component's //resized()// method is to compute where each child component should go and call its //setBounds()// member. 
 + 
 +Thus, JUCE implements a //top-down layout approach//: 
 +  * The top-level (outermost) Component receives a //setBounds()// call (usually from the JUCE framework itself). 
 +  * You would not normally override //setBounds()// yourself. The default implementation does bit of internal housekeeping, and calls //resized()// to do the real work. 
 +  * You DO override //setBounds()//, and the first thing you do there is call //getLocalBounds()// to get your component's assigned bounds (in the form of a [[https://docs.juce.com/master/classRectangle.html|juce::Rectangle]]). You decide how to divide up this rectangular space, and compute smaller //juce::Rectangle//s, which you pass to your child components' //setBounds()// methods. 
 + 
 +//A few important aspects// of layout in JUCE don't quite work this way: 
 +  * Any time you create a //juce::Component// subclass for a top-level window (e.g., the main GUI window for a plug-in), you put a call to //setSize()// right at the bottom of your class's constructorThis fixes the //width// and //height// of the componentso the framework need only set its //position// on the screen. 
 + 
 +However, as far as I know, there is no generic way to do //bottom-up layout// in a JUCE program. If a component needs to know the "natural" or "default" dimensions of its child components (in order to compute their positions based on some kind of layout algorithm), you have to implement that yourself; //juce::Component// does not provide specific methods you can override for this purpose. 
 + 
 +==== JUCE has many layout-related classes ==== 
 +JUCE does provide quite a substantial set of layout-related classes. See https://docs.juce.com/master/group__juce__gui__basics-layout.html, but be prepared to do a lot of readinghead-scratchingand experimentation, because none of these components is very well documented. 
 + 
 +(The truth is, //no part of JUCE is truly well-documented//. There's quite a bit of documentation, but a hell of a lot more code. I would estimate that it is less than 5% adequately documented, perhaps closer to 1%.) 
 + 
 +==== Linking GUI components to variables in your code ==== 
 +JUCE does not provide simple mechanisms to link, say, a //juce::Slider// control to a C++ //float// in your code. Instead, the Slider has a "current valuewhich you can get/set using its //getValue()// and //setValue()// methods. You can also inherit from //juce::Slider::Listener//, or set your Slider's //onChange// std::function pointer to a lambda in your code, to receive callbacks when the user changes the value. 
 + 
 +==== Linking GUI components to plug-in parameters ==== 
 +JUCE does provide a very high-level system to link GUI controls to //plug-in parameters//, which goes by the ungainly class name //juce::AudioProcessorValueTreeState// (abbreviated AVTS). 
 +  * [[https://docs.juce.com/master/classAudioProcessor.html|//juce::AudioProcessor]] is the root class for your plug-in'DSP code 
 +  * [[https://docs.juce.com/master/classValueTree.html|juce::ValueTree]] implements a hierarchical structure of "values" which are [[https://docs.juce.com/master/classvar.html|juce::var]] objects. 
 +  * [[https://docs.juce.com/master/classAudioProcessorValueTreeState.html|juce::AudioProcessorValueTreeState]] (AVTS) is a special kind of //juce::ValueTree// intended to represent the "state" of all a plug-in's parameters (the same ones it exposes to a host for automation). 
 +  * AVTS knows all about //parameters//, which are all expressed as subclasses of [[https://docs.juce.com/master/classAudioProcessorParameter.html|juce::AudioProcessorParameter]] 
 +  * AVTS also defines several "attachment" classes e.g. //ButtonAttachment////SliderAttachment//, etc. which set up automated, two-way links between a GUI control and its associated parameter. This is by far the best way to do this in a plug-in. 
 +  * //juce::AudioProcessorParameter// has an associated //Listener// class, which you can override to receive a callback whenever the parameter value gets changed, either by the user manipulating the GUI, or through host automation, or in a few other cases (like loading presets, adjusting related parameters, etc.) 
 + 
 +===== JUCE is NOT a Model-View-Controller based framework ===== 
 +Nearly all GUI frameworks in widespread use are based on the classic [[wp>Model–view–controller|Model-View-Controller]] design pattern, but JUCE is not. 
 + 
 +As you see from the above sections, GUIs for audio plug-ins are not built according to anything like an MVC pattern. You will, however, find a few spots where MVC-like ideas are applied, the main one being the [[https://docs.juce.com/master/classListBox.html|ListBox]]/[[https://docs.juce.com/master/classListBoxModel.html|ListBoxModel]] pair of classes: to create scrolling list, you use a //juce::ListBox// component, which is completely generic, and link it to a //juce::ListBoxModel//-derived object which contains the specific data to be listed. 
 + 
 +==== Plugin Processor and Editor ==== 
 +JUCE implements a plug-in using a pair of classes: 
 +  * The DSP code is called the plug-in //processor// and is derived from [[https://docs.juce.com/master/classAudioProcessor.html|juce::AudioProcessor]] 
 +  * The GUI is called the //editor// and is derived from [[https://docs.juce.com/master/classAudioProcessorEditor.html|juce::AudioProcessorEditor]] 
 + 
 +The //processor// is what is instantiated by a host (e.g. DAW). It has a //createEditor()// method, whose job is to create an instance of the //editor// and return a pointer to it. The editor has a member variable which is a reference to the corresponding processor instance; through this reference, the editor can communicate with the processor, call its methods, get at public member variables, etc. (This is a //convention//, not something which is enforced by the class definitions.) 
 + 
 +Because the processor can exist alone, i.e., there is no requirement that the host //ever// instantiate the editor, and most hosts actually delete the editor instance each time the user closes the plug-in GUI window, there is no reciprocal arrangement where the processor can get at "the" editor. (There are ways, but they are indirect.) The more common pattern is that the editor (or any of its child components) are set up as //Listeners// of the processor, so they get callbacks when its state changes in a way that requires the GUI to update. The JUCE classes [[https://docs.juce.com/master/classChangeBroadcaster.html|ChangeBroadcaster]] and [[https://docs.juce.com/master/classChangeListener.html|ChangeListener]] are most often used for this. 
 + 
 +As mentioned above, JUCE's "attachments" mechanism allows GUI controls to be connected directly to the corresponding plug-in parameters (which themselves are embedded in a //juce::AudioProcessorValueTreeState// member variable of the processor). These connections are two-way and work basically automatically. 
juce_gui_basics.1612801803.txt.gz · Last modified: 2021/02/08 16:30 by shane