1 title:: Introduction to GUI
2 summary:: An introduction to writing graphical user interface code
7 SECTION:: First problem: Platform independence
9 strong::Why do you need to know about this?::
12 ## You'll create a Window.new and wonder why you got back a SCWindow or QWindow.
13 ## You might use Mac-only GUI objects and wonder why your friend on Windows can't run your code.
14 ## On Mac, you might run some examples that are specific to Q- objects and wonder why they don't work right away.
17 strong::Short answer::
19 Make sure you select the right strong::GUI kit::.
21 When SuperCollider starts, one of the available kits becomes the default.
24 ## strong::Platform:: || strong::Default kit::
25 ## Mac OSX || Cocoa -- code::GUI.cocoa::
26 ## Linux/FreeBSD || Qt -- code::GUI.qt::
27 ## Windows || Qt -- code::GUI.qt::
30 For most cases, the Qt GUI kit is sufficient. Cocoa is retained as the default in Mac OSX for historical reasons. (This may change in a future release.)
32 warning:: Some GUI examples are Qt-specific. They are labeled as such in the documentation. Strong::These examples will not work in OSX unless you manually switch to the Qt kit:: by running the following code:
37 See the link::Classes/GUI:: help file for more background on the GUI kits.
40 subsection:: Use "GUI redirect" classes whenever possible
42 It is strongly recommended to use generic view class names: Window, Button, Slider, etc. Code written using the generic names can run in other GUI kits.
49 w = SCWindow.new.front;
52 In the second example, the use of the Cocoa-specific window class ensures that the code will not run in Linux or Windows without modification. This is usually not a good idea.
54 For a list of all the generic GUI classes and their kit-specific equivalents see link::Overviews/GUI-Classes::.
56 In the rest of this document we will refer to GUI classes by their generic name, adding notes where important differences between GUI kits are of concern.
61 SECTION:: Basic elements: Windows, views and containers
63 The most fundamental element of the GUI is the strong::Window::. It occupies a rectangular space on screen within which other GUI elements are displayed. It usually has a bar that displays the window's title and allows for moving it, resizing it and closing it with the controls it displays or through mouse and keyboard interaction. Some of these aspects may be controlled within SuperCollider GUI code, though it is largely platform-dependent how precisely interaction with a window happens and is visually indicated.
65 The GUI elements contained within a Window are called strong::views::. They all inherit from the basic View class. The view occupies a rectangular space of the window within which it draws itself to display some data or to indicate a mode of interaction between the user and the program. Views receive keyboard and mouse events generated by the user and respond to them by controlling the behavior of the program. They also display information about the state of the program and the data on which it operates.
67 There are also special types of views that can contain other views and are thus called strong::containers::, for example the CompositeView. They allow for structuring GUI in a hierarchical way. A container view is called a strong::parent:: of the views it contains, and they are called its strong::children::. Hierarchical organization allows to easily change aspects of all the views within a container: if the parent view is hidden, so are all the children; if the parent view is moved, so are they. Children are positioned with coordinates relative to their parent.
70 In many aspects, a Window is also considered to be a parent of the views it contains, and can functionally take the same place in code as container views, although that is not true in all cases. When a Window is created it implicitely creates a container view occupying its entire space. When a view is created with a Window as its parent it will actually become a child of that container. See Window's link::Classes/Window#-view#view:: method and View's link::Classes/View#*new#constructor:: for details.
74 In strong::Qt GUI:: there is no distinction between windows, views, and containers. An instance of the View class itself can be displayed directly on screen, and can contain other views, so the same applies to all its subclasses. Most of the methods that are specific to Window and containers in other GUI kits are shared by all views in Qt.
77 The following example shows a window containing a Button, a Slider and a group of StaticText views contained in a CompositeView. When the button is clicked the visibility of the CompositeView is toggled, while interacting with the Slider will move the CompositeView (and consequently all its contents) in horizontal direction.
80 w = Window.new("GUI Introduction", Rect(200,200,255,100));
81 b = Button.new(w,Rect(10,0,80,30)).states_([["Hide"],["Show"]]);
82 s = Slider.new(w,Rect(95,0,150,30));
83 c = CompositeView.new(w,Rect(20,35,100,60));
84 StaticText.new(c,Rect(0,0,80,30)).string_("Hello");
85 StaticText.new(c,Rect(20,30,80,30)).string_("World!");
86 b.action = { c.visible = b.value.asBoolean.not };
87 s.action = { c.bounds = Rect( s.value * 150 + 20, 35, 100, 100 ) };
93 SECTION:: Automatic positioning and resizing of views
95 As a handy alternative to specifying all the dimensions and positions of views explicitely in code, SuperCollider allows for automatic positioning and resizing of views in relation to each other and in relation to window size - at the view creation and dynamically, when window is resized. There is several mechanisms for this purpose.
97 subsection:: View's resize options
99 Views can automatically resize or move when their parent is resized, in one of the nine different ways that define how each of the view's edges will move along with the parent's edges. For documentation see the view's link::Classes/View#-resize#resize:: method and link::Reference/Resize:: document.
102 w = Window.new("GUI Introduction", Rect(200,200,200,200));
103 TextField.new(w,Rect(0,0,200,30)).resize_(2);
104 Slider.new(w,Rect(0,30,30,170)).resize_(4);
105 TextView.new(w,Rect(30,30,170,170)).resize_(5);
109 subsection:: Decorators
111 Decorators are objects that can be assigned to container views to carry the task of positioning the container's child views (currently there exists only one: FlowLayout). After a decorator is assigned to a container, the views created as its children will automatically be positioned in a specific pattern. See documentation of link::Classes/FlowLayout:: for details.
114 w = Window.new("GUI Introduction", Rect(200,200,320,320)).front;
115 // notice that FlowLayout refers to w.view, which is the container view
116 // automatically created with the window and occupying its entire space
117 w.view.decorator = FlowLayout(w.view.bounds);
118 14.do{ Slider(w, 150@20) };
123 Layout classes make part of a complex system to manage both position and size of views. Using layouts, only relations of views within a pattern of organization need to be specified and their exact positions as well as sizes will automatically be deduced based on their type (the content they display and the type of interaction they offer) and in accord with principles of good GUI usability. Layouts also position and resize views dynamically, whenever their parent is resized or their contents change.
125 See the link::Guides/GUI-Layout-Management:: guide for detailed explanation.
128 Layouts are currently implemented strong::only in Qt GUI::. The following example will not work in other GUI kits.
130 w = Window.new("GUI Introduction").layout_(
132 HLayout( Button(), TextField(), Button() ),
140 Layouts are not compatible with decorators and will ignore view resize options. The effect of combining layouts and decorators is undefined.
143 SECTION:: Customizing appearance
145 Views offer various ways to customize their appearance. This ranges from decorative changing of colors they use to draw themselves to controlling how they display various kinds of data.
149 Colors are represented in GUI code by the link::Classes/Color:: class.
151 A typical color that can be customized is background color - a color of choice can be applied to whatever is considered to be the background of a particular view. Views that display some text will typically also allow customizing its color as well.
153 Custom colors may be associated with different changing states of views or data they display, for example: Button allows to associate background and text colors with each one of its states, and will thus switch colors together with state when clicked; ListView allows to set a different background color for each of its items, as well as special background and text colors applied only to the item currently selected.
155 Whenever you execute the following example, random colors will be applied to different aspects of the views:
159 w = Window("GUI Introduction").background_(Color.rand).front;
160 b = Button(w, Rect(10,10,100,30)).states_([
161 ["One",Color.rand,Color.rand],
162 ["Two",Color.rand,Color.rand],
163 ["Three",Color.rand,Color.rand]
165 l = ListView.new(w, Rect(10,50,200,100))
166 .items_(["One","Two","Three"])
167 .colors_([Color.rand,Color.rand,Color.rand])
168 .hiliteColor_(Color.blue)
169 .selectedStringColor_(Color.white);
170 s = Slider(w, Rect(10, 160, 200, 20))
171 .knobColor_(Color.rand)
172 .background_(Color.rand);
178 In Qt GUI, the complete set of colors used to draw the views is represented by a palette (see the link::Classes/QPalette:: class). Using a palette, you can define (most of) the appearance of the whole GUI in one go.
180 In the following example, clicking on the button will switch between two palettes. Note however, that the color assigned to the first Button state will beat the red color defined in the palette, and that colors of individual ListView items are not controlled by the palette.
184 x = QPalette.auto(Color.red(0.8), Color.red(0.5));
185 y = QPalette.auto(Color.cyan(1.4), Color.cyan(1.8));
188 w = Window.new("GUI Introduction").front;
189 w.onClose = {QtGUI.palette = p};
190 Button.new(w, Rect(10,10,100,30)).states_([
191 ["Red", Color.black, Color.grey(0.7)],
193 ]).action_({ |b| QtGUI.palette = if(b.value == 0){x}{y} });
194 ListView.new(w, Rect(10,50,200,100))
195 .items_(["One","Two","Three"])
196 .colors_([Color.grey(0.4),Color.grey(0.5),Color.grey(0.6)]);
197 Slider(w, Rect(10, 160, 200, 20));
198 RangeSlider(w, Rect(10, 190, 200, 20));
204 Views that display some text will typically allow you to specify a custom font for it. Fonts are represented by the link::Classes/Font:: class, which can also be queried for the default font used in general, as well as the default font specifically for the "serif", "sans-serif" and "monospace" font types. It can also be queried for all available fonts on the system.
208 w = Window.new("GUI Introduction",Rect(200,200,200,70)).front;
209 a = [Font.defaultMonoFace, Font.defaultSansFace, Font.defaultSerifFace];
210 b = Button.new(w,Rect(10,10,180,50))
211 .states_([["Monospace"],["Sans serif"],["Serif"]])
213 .action_({|b| b.font = a[b.value]});
217 subsection:: Other visual properties
219 Complex views may have many other ways to customize how they display the same data. link::Classes/MultiSliderView:: and link::Classes/EnvelopeView:: are good examples.
226 SECTION:: Actions and hooks: Make that button do something!
228 Views and windows can be assigned strong::actions:: that they will perform whenever a specific event occurs as a result of user's interaction. Technically, an action can be any Object, and when the relevant event occurs, it's link::Classes/Object#-value#value#:: method will be called. For example, it is useful to assign a Function as an action, which allows one to define an arbitrary chunk of code to be performed in response to a GUI event.
230 Objects can also be given to views and windows to evalute on events that are not a direct result of user's interaction, but convey useful information about the view's operation and the state it moved in. In this case they are often differentiated from actions and called strong::hooks::.
232 Here, we will give an overview of different kinds of actions and hooks. See link::Classes/View#actions_in_general:: and following sections for precise explanation of how to assign and make use of them.
235 subsection:: Default actions
237 Views can typically be assigned a default action with their link::Classes/View#-action#action:: setter method, which will be performed when the view's primary mode of interaction is invoked. The default action for a Button for example occurs when it is clicked, for a Slider when its handle is moved.
239 In the following example, pressing the button will open an exact same window but at different position.
242 ~makeWindow = { var w;
243 w = Window.new("Evader",Rect(500.rand + 100, 500.rand + 100, 200,50)).front;
244 Button.new(w,Rect(10,10,180,30)).states_([["Evade"]]).action_(~makeWindow);
249 subsection:: Keyboard and mouse actions
251 All the views can be assigned actions to specific mouse and keyboard events, no matter what other effects those events might have on the view or what other specialized actions or hooks the view might trigger on these events.
253 You can assign actions to strong::mouse events:: generated when the mouse pointer enters the space of a view, when it moves over them, and when a mouse button is pressed or released.
255 See link::Classes/View#mouse_actions#View: Mouse Actions:: for details.
257 In the following example the StaticText will report whether the Button is pressed or released.
260 w = Window.new(bounds:Rect(200,200,200,50)).front;
261 b = Button.new(w,Rect(10,10,80,30)).states_([["Off"],["On"]]);
262 t = StaticText(w,Rect(100,10,90,30)).string_("Button released");
263 b.mouseDownAction = { t.string = "Button pressed" };
264 b.mouseUpAction = { t.string = "Button released" };
267 You can assign actions to strong::keyboard events:: generated whenever a key is pressed or released while the view has keyboard focus. Keyboard focus is a state of a view in which it has exclusive priority to respond to keyboard events. A view that has keyboard focus typically in a way visually indicates so. On most platforms, pressing the Tab key will switch the keyboard focus between views in the active window and clicking on a view will give it focus.
269 See link::Classes/View#key_actions#View: Key Actions:: for details.
271 Typing text into any of the TextFields in the following example will change the color of the rectangle bellow, for each TextField a different color.
274 w = Window.new(bounds:Rect(200,200,200,100)).front;
275 x = TextField(w,Rect(10,10,80,30));
276 y = TextField(w,Rect(110,10,80,30));
277 t = StaticText(w,Rect(10,40,180,50));
278 ~reset = {t.background = Color.red};
279 x.keyDownAction = {t.background = Color.green};
280 x.keyUpAction = ~reset;
281 y.keyDownAction = {t.background = Color.blue};
282 y.keyUpAction = ~reset;
286 If a key or mouse event is not handled by the view on which it occurs, it may strong::propagate:: to the parent view, and trigger the parent's action. See link::Classes/View#key_and_mouse_event_propagation#View: Key and mouse event propagation:: for detailed explanation.
288 subsection:: Drag and drop actions
290 When a mouse button is pressed on a view together with Cmd(Mac OS) or Ctrl(Other OS) key and the mouse pointer is moved while holding the button, a strong::drag-and-drop:: operation is initiated - in case the view supports it. Most views have a default object that they export when a drag is attempted. For a Slider it is its value, for a List it is the numeric index of the currently selected item, etc. It is said that the exported object is being strong::dragged::. When the dragging gesture ends on another view by releasing the mouse button on top of it, it is said that the dragged object was strong::dropped:: on another view. A view may respond to various objects dropped on it in different ways.
292 It is possible to customize what object a view exports when dragged from and how a view reacts to objects dropped by assigning custom drag and drop actions.
294 See link::Classes/View#drag_and_drop:: for details.
298 w = Window.new.front;
299 a = Button(w, Rect(10, 10, 200, 20)).states_([["Hi There!"]]);
300 a.beginDragAction = { a.dragLabel ="I'm dragging: \""++ a.states[0][0]++"\""; a.states[0][0] };
301 DragSink(w,Rect(10,40,200,20)).align_(\center).string="Cmd-drag from Button to here";
305 subsection:: Other specialized actions
307 Some views can be assigned actions on other events specific to their mode of interaction with the user which you are invited to discover by consulting their documentation.
311 Hooks are various events that signify important changes of state of GUI elements. Technically they are used the same way as actions, but are distinguished from them to denote events that are not a direct result of the user's interaction. Methods of GUI classes used to assign hooks are usually prefixed with "on". (You will also find this naming pattern in methods of other SuperCollider classes, that have hooks in the same sense).
313 For example, one hook that every view as well as Window has is onClose, which is triggered when the window is closed or the view is removed. Other hooks for example exist for the case when a Window becomes or ceases to be the active one.
318 SECTION:: Custom views
320 The UserView is a view that displays and does nothing on itself, but allows emphasis::you:: to define how it will be drawn, and for which you can define the entire behavior using mouse, key, and drag and drop actions. For documentation on all of these aspects, see link::Classes/UserView::, link::Classes/View::, and link::Classes/Pen::. The explanation below, however, will demonstrate the basic techniques for designing a custom view.
322 There is two ways of using the UserView class: either creating it and using its methods to define its behavior, or subclassing it, which gives you more freedom but is more complex. Either way, you will be using the link::Classes/Pen:: class to draw the view. Pen is a powerful class that allows you to algorithmically draw using simple visual primitives like lines, arcs, curves, rectangles, ellipses, etc. and fill the shapes with colors and gradients.
324 subsection:: The simple way: using the UserView class
326 The simple way comprises the following steps:
329 ## create a User View
330 ## define a draw function
331 ## define the default action
332 ## define mouse actions
333 ## define key actions
334 ## define drag and drop actions
337 You can omit steps which you don't need.
342 w = Window.new.front;
344 // (1) create a UserView
345 v = UserView(w,Rect(50,50,200,20));
347 // (2) define a drawing function using Pen
350 Pen.fillColor = Color.grey;
351 Pen.addRect(Rect(0,0, v.bounds.width*value,v.bounds.height));
354 Pen.fillColor = Color.red;
355 Pen.moveTo(((v.bounds.width*value)-5) @ v.bounds.height);
356 Pen.lineTo(((v.bounds.width*value)+5) @ v.bounds.height);
357 Pen.lineTo(((v.bounds.width*value)) @ (v.bounds.height/2));
358 Pen.lineTo(((v.bounds.width*value)-5) @ v.bounds.height);
361 Pen.strokeColor = Color.black;
362 Pen.addRect(Rect(0,0, v.bounds.width,v.bounds.height));
366 // (3) set the default action
367 v.action = {value.postln; v.refresh};
369 // (4) define mouse actions
370 v.mouseDownAction = { arg view, x = 0.5,y, m;
372 ([256, 0].includes(m)).if{ // restrict to no modifier
373 value = (x).linlin(0,v.bounds.width,0,1); v.doAction};
376 v.mouseMoveAction = v.mouseDownAction;
378 // (5) (optional) define key actions
379 v.keyDownAction = { arg view, char, modifiers, unicode,keycode;
380 if (unicode == 16rF700, { value = (value+0.1).clip(0,1) });
381 if (unicode == 16rF703, { value = (value+0.1).clip(0,1) });
382 if (unicode == 16rF701, { value = (value-0.1).clip(0,1) });
383 if (unicode == 16rF702, { value = (value-0.1).clip(0,1) });
387 // (6) (optional) define drag and drop behavior
388 v.beginDragAction = {value}; // what to drag
389 v.canReceiveDragHandler = {View.currentDrag.isNumber}; // what to receive
390 v.receiveDragHandler = {value = View.currentDrag; v.doAction }; // what to do on receiving
393 // just for testing drag and drop
394 Slider(w,Rect(50,100,200,20));
396 StaticText(w,Rect(50,150,350,50)).string_("To Test Drag and Drop,\nHold down Cmd (Ctl) Key");
401 subsection:: The advanced way: subclassing the UserView
403 Subclassing the UserView differs in many aspects from the example above. For a subclassing template and a quick tutorial on how to write a custom widget as a UserView subclass, see the link::Guides/UserView-Subclassing:: guide.
410 SECTION:: Caution: GUI and timing
413 Executing code that uses the GUI system is restricted to main application context. There are many ways in SuperCollider for code to be executed in other contexts that run in parallel with the main one, and interacting with GUI objects is not allowed there. This includes:
416 ## Code scheduled on the SystemClock and the TempoClock
417 ## Code executed in response to OSC messages
422 If you attempt to interact with a GUI object in the contexts listed above, an error will be thrown.
424 Therefore, if you want to use Functions, Routines, Tasks and other similar objects to schedule code that interacts with GUI elements, you must do so using the AppClock, since code scheduled on the AppClock is performed in the main application context. You can of course also reschedule GUI code to the AppClock from within code performed in other contexts, and the link::Classes/Function#-defer#'defer':: mechanism is a convenient shorthand for this.
426 An example of scheduling GUI code on the AppClock:
432 w.bounds=Rect(200.rand, 200+200.rand, 300,300);
439 The same thing using the SystemClock in combination with the defer mechanism:
445 {w.bounds=Rect(200.rand, 200+200.rand, 300,300) }.defer; // you must defer this
448 {w.close}.defer; // you must defer this
452 As mentioned above, using the GUI system is also not allowed in code performed directly in response to OSC messages (this includes functions given to all kinds of OSC reponder classes). The same solutions as above apply: