/usr/share/SuperCollider/HelpSource/Guides/GUI-Introduction.schelp is in supercollider-common 1:3.6.3~repack-5.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 | title:: Introduction to GUI
summary:: An introduction to writing graphical user interface code
categories:: GUI
SECTION:: First problem: Platform independence
strong::Why do you need to know about this?::
numberedlist::
## You'll create a Window.new and wonder why you got back a SCWindow or QWindow.
## You might use Mac-only GUI objects and wonder why your friend on Windows can't run your code.
## On Mac, you might run some examples that are specific to Q- objects and wonder why they don't work right away.
::
strong::Short answer::
Make sure you select the right strong::GUI kit::.
When SuperCollider starts, one of the available kits becomes the default.
table::
## strong::Platform:: || strong::Default kit::
## Mac OSX || Cocoa -- code::GUI.cocoa::
## Linux/FreeBSD || Qt -- code::GUI.qt::
## Windows || Qt -- code::GUI.qt::
::
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.)
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:
code::GUI.qt;::
::
See the link::Classes/GUI:: help file for more background on the GUI kits.
subsection:: Use "GUI redirect" classes whenever possible
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.
code::
// DO write
w = Window.new.front;
// DO NOT write
w = SCWindow.new.front;
::
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.
For a list of all the generic GUI classes and their kit-specific equivalents see link::Overviews/GUI-Classes::.
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.
SECTION:: Basic elements: Windows, views and containers
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.
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.
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.
note::
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.
::
note::
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.
::
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.
code::
w = Window.new("GUI Introduction", Rect(200,200,255,100));
b = Button.new(w,Rect(10,0,80,30)).states_([["Hide"],["Show"]]);
s = Slider.new(w,Rect(95,0,150,30));
c = CompositeView.new(w,Rect(20,35,100,60));
StaticText.new(c,Rect(0,0,80,30)).string_("Hello");
StaticText.new(c,Rect(20,30,80,30)).string_("World!");
b.action = { c.visible = b.value.asBoolean.not };
s.action = { c.bounds = Rect( s.value * 150 + 20, 35, 100, 100 ) };
w.front;
::
SECTION:: Automatic positioning and resizing of views
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.
subsection:: View's resize options
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.
code::
w = Window.new("GUI Introduction", Rect(200,200,200,200));
TextField.new(w,Rect(0,0,200,30)).resize_(2);
Slider.new(w,Rect(0,30,30,170)).resize_(4);
TextView.new(w,Rect(30,30,170,170)).resize_(5);
w.front;
::
subsection:: Decorators
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.
code::
w = Window.new("GUI Introduction", Rect(200,200,320,320)).front;
// notice that FlowLayout refers to w.view, which is the container view
// automatically created with the window and occupying its entire space
w.view.decorator = FlowLayout(w.view.bounds);
14.do{ Slider(w, 150@20) };
::
subsection:: Layouts
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.
See the link::Guides/GUI-Layout-Management:: guide for detailed explanation.
note::
Layouts are currently implemented strong::only in Qt GUI::. The following example will not work in other GUI kits.
code::
w = Window.new("GUI Introduction").layout_(
VLayout(
HLayout( Button(), TextField(), Button() ),
TextView()
)
).front;
::
::
note::
Layouts are not compatible with decorators and will ignore view resize options. The effect of combining layouts and decorators is undefined.
::
SECTION:: Customizing appearance
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.
subsection:: Colors
Colors are represented in GUI code by the link::Classes/Color:: class.
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.
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.
Whenever you execute the following example, random colors will be applied to different aspects of the views:
code::
(
w = Window("GUI Introduction").background_(Color.rand).front;
b = Button(w, Rect(10,10,100,30)).states_([
["One",Color.rand,Color.rand],
["Two",Color.rand,Color.rand],
["Three",Color.rand,Color.rand]
]);
l = ListView.new(w, Rect(10,50,200,100))
.items_(["One","Two","Three"])
.colors_([Color.rand,Color.rand,Color.rand])
.hiliteColor_(Color.blue)
.selectedStringColor_(Color.white);
s = Slider(w, Rect(10, 160, 200, 20))
.knobColor_(Color.rand)
.background_(Color.rand);
)
::
subsection:: Palette
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.
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.
code::
(
x = QPalette.auto(Color.red(0.8), Color.red(0.5));
y = QPalette.auto(Color.cyan(1.4), Color.cyan(1.8));
p = QtGUI.palette;
QtGUI.palette = x;
w = Window.new("GUI Introduction").front;
w.onClose = {QtGUI.palette = p};
Button.new(w, Rect(10,10,100,30)).states_([
["Red", Color.black, Color.grey(0.7)],
["Cyan"]
]).action_({ |b| QtGUI.palette = if(b.value == 0){x}{y} });
ListView.new(w, Rect(10,50,200,100))
.items_(["One","Two","Three"])
.colors_([Color.grey(0.4),Color.grey(0.5),Color.grey(0.6)]);
Slider(w, Rect(10, 160, 200, 20));
RangeSlider(w, Rect(10, 190, 200, 20));
)
::
subsection:: Fonts
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.
code::
(
w = Window.new("GUI Introduction",Rect(200,200,200,70)).front;
a = [Font.defaultMonoFace, Font.defaultSansFace, Font.defaultSerifFace];
b = Button.new(w,Rect(10,10,180,50))
.states_([["Monospace"],["Sans serif"],["Serif"]])
.font_(a[0])
.action_({|b| b.font = a[b.value]});
)
::
subsection:: Other visual properties
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.
SECTION:: Actions and hooks: Make that button do something!
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.
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::.
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.
subsection:: Default actions
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.
In the following example, pressing the button will open an exact same window but at different position.
code::
~makeWindow = { var w;
w = Window.new("Evader",Rect(500.rand + 100, 500.rand + 100, 200,50)).front;
Button.new(w,Rect(10,10,180,30)).states_([["Evade"]]).action_(~makeWindow);
};
~makeWindow.value;
::
subsection:: Keyboard and mouse actions
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.
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.
See link::Classes/View#Mouse actions:: for details.
In the following example the StaticText will report whether the Button is pressed or released.
code::
w = Window.new(bounds:Rect(200,200,200,50)).front;
b = Button.new(w,Rect(10,10,80,30)).states_([["Off"],["On"]]);
t = StaticText(w,Rect(100,10,90,30)).string_("Button released");
b.mouseDownAction = { t.string = "Button pressed" };
b.mouseUpAction = { t.string = "Button released" };
::
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.
See link::Classes/View#Key actions:: for details.
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.
code::
w = Window.new(bounds:Rect(200,200,200,100)).front;
x = TextField(w,Rect(10,10,80,30));
y = TextField(w,Rect(110,10,80,30));
t = StaticText(w,Rect(10,40,180,50));
~reset = {t.background = Color.red};
x.keyDownAction = {t.background = Color.green};
x.keyUpAction = ~reset;
y.keyDownAction = {t.background = Color.blue};
y.keyUpAction = ~reset;
~reset.value;
::
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 processing:: for detailed explanation.
subsection:: Drag and drop actions
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.
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.
See link::Classes/View#Drag and drop:: for details.
code::
(
w = Window.new.front;
a = Button(w, Rect(10, 10, 200, 20)).states_([["Hi There!"]]);
a.beginDragAction = { a.dragLabel ="I'm dragging: \""++ a.states[0][0]++"\""; a.states[0][0] };
DragSink(w,Rect(10,40,200,20)).align_(\center).string="Cmd-drag from Button to here";
)
::
subsection:: Other specialized actions
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.
subsection:: Hooks
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).
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.
SECTION:: Custom views
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.
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.
subsection:: The simple way: using the UserView class
The simple way comprises the following steps:
numberedList::
## create a User View
## define a draw function
## define the default action
## define mouse actions
## define key actions
## define drag and drop actions
::
You can omit steps which you don't need.
code::
(
var value = 0.5;
w = Window.new.front;
// (1) create a UserView
v = UserView(w,Rect(50,50,200,20));
// (2) define a drawing function using Pen
v.drawFunc = {
// Draw the fill
Pen.fillColor = Color.grey;
Pen.addRect(Rect(0,0, v.bounds.width*value,v.bounds.height));
Pen.fill;
// Draw the triangle
Pen.fillColor = Color.red;
Pen.moveTo(((v.bounds.width*value)-5) @ v.bounds.height);
Pen.lineTo(((v.bounds.width*value)+5) @ v.bounds.height);
Pen.lineTo(((v.bounds.width*value)) @ (v.bounds.height/2));
Pen.lineTo(((v.bounds.width*value)-5) @ v.bounds.height);
Pen.fill;
// Draw the frame
Pen.strokeColor = Color.black;
Pen.addRect(Rect(0,0, v.bounds.width,v.bounds.height));
Pen.stroke;
};
// (3) set the default action
v.action = {value.postln; v.refresh};
// (4) define mouse actions
v.mouseDownAction = { arg view, x = 0.5,y, m;
//m.postln;
([256, 0].includes(m)).if{ // restrict to no modifier
value = (x).linlin(0,v.bounds.width,0,1); v.doAction};
};
v.mouseMoveAction = v.mouseDownAction;
// (5) (optional) define key actions
v.keyDownAction = { arg view, char, modifiers, unicode,keycode;
if (unicode == 16rF700, { value = (value+0.1).clip(0,1) });
if (unicode == 16rF703, { value = (value+0.1).clip(0,1) });
if (unicode == 16rF701, { value = (value-0.1).clip(0,1) });
if (unicode == 16rF702, { value = (value-0.1).clip(0,1) });
v.doAction;
};
// (6) (optional) define drag and drop behavior
v.beginDragAction = {value}; // what to drag
v.canReceiveDragHandler = {View.currentDrag.isNumber}; // what to receive
v.receiveDragHandler = {value = View.currentDrag; v.doAction }; // what to do on receiving
// just for testing drag and drop
Slider(w,Rect(50,100,200,20));
StaticText(w,Rect(50,150,350,50)).string_("To Test Drag and Drop,\nHold down Cmd (Ctl) Key");
)
::
subsection:: The advanced way: subclassing the UserView
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.
SECTION:: Caution: GUI and timing
warning::
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:
list::
## Code scheduled on the SystemClock and the TempoClock
## Code executed in response to OSC messages
::
::
If you attempt to interact with a GUI object in the contexts listed above, an error will be thrown.
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.
An example of scheduling GUI code on the AppClock:
code::
w=Window.new.front;
Routine{
20.do{
w.bounds=Rect(200.rand, 200+200.rand, 300,300);
0.1.wait;
};
w.close;
}.play(AppClock)
::
The same thing using the SystemClock in combination with the defer mechanism:
code::
w=Window.new.front;
Routine{
20.do{
{w.bounds=Rect(200.rand, 200+200.rand, 300,300) }.defer; // you must defer this
0.1.wait;
};
{w.close}.defer; // you must defer this
}.play(SystemClock)
::
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:
|