Overview
Here you can find some notes on creating GUIs for SuperCollider.
Content
Creating Windows
GUIs do exist in windows. You have to save a Window Object in a variable. This variable is needed for other GUI objects, which need to reference a Window object. After creating a Window Object you cannot see it by default, you need to bring it to .front. With the method .alwaysOnTop_(true) the window always stays in front.
w = Window.new(); // create a Window object
w.front; // show the object
w.close; // close the window
w = Window().alwaysOnTop_(true).front; // create a Window object that always stays in frontEvery GUI Object has bounds, which describe the position of the object on the screen and the size. The screen bounds can be returned with these lines:
Window.screenBounds; // returns the actual size of the screen
Window.availableBounds; // returns the bounds of the available screen
// in this way you can do math with these values and calculated the bounds of your window
Window.screenBounds.width / 2;The size/bounds of the window is declared via a Rect Object.
For the Window Object the orientation starts at the lower left corner of the screen. This is different to the View Objects.
(
w = Window.new("my window", Rect(0.0, 25.0, 500.0, 500.0))
.alwaysOnTop_(true) // brings the window allways to top1
.front;
)You can also bring Windows like the Server Meter always to front.
s.meter; // creates the server meter
s.serverMeter.window.alwaysOnTop_(true); // keeps it always on topView Objects
The View Object represents a GUI object, a rectangle with transparent background, but it has many child objects like Button, Slider and Knob. So commonly these GUI Objects are subsumized as View Objects.
In difference to the Window object, the orientation for View objects starts at the upper left corner
(
Window.closeAll; // handy to start with
w = Window().front;
~sl = Slider(w, Rect(50, 50, 20, 250)); // orientation starts at the upper left corner
)Here is a list of important View objects:
Slider : horizontal or vertical slider
StaticText : represents test, can be used for labeles
Button : state button
LevelIndicator : level meter
Layout Management
To not have to deal with pixel counting for your View objects, there are some Layout Management Objects like
FlowLayout, StackLayout, HLayout, VLayout and GridLayout.
(
Window.closeAll;
w = Window().front;
~sl = {Slider()} ! 8;
w.layout_(
HLayout(*~sl) // this unpacks an array as an comma seperated list
)
)This also makes the slider resizable with the window and puts them automatically on the window.
You can also set the orientation of your View Objects.
(
Window.closeAll;
w = Window().front;
~sl = {Slider().orientation_(\horizontal)} ! 8;
w.layout_(
VLayout(*~sl)
)
)A GridLayout example:
(
Window.closeAll;
w = Window().front;
~kn = {Knob()} ! 8;
~sl = {Slider()} ! 8;
w.layout_(
GridLayout.rows(~kn, ~sl) // this takes arrays as inputs
)
)
w.view.children; // list all View Objects that are children to the Window Object
A StackLayout example:
(
Window.closeAll;
w = Window().front;
~sl0 = Slider().background_(Color.magenta);
~sl1 = Slider().background_(Color.green);
~layout = StackLayout(~sl0, ~sl1);
w.layout_(~layout);
w.alwaysOnTop_(true);
)
~layout.index_(1); // change the shown layer of the StackLayoutValues & Action
You can get and set values of a View object like this:
(
Window.closeAll;
w = Window().alwaysOnTop_(true).front;
~sl = Slider(w, Rect(50, 50, 50, 250));
)
~sl.value;
~sl.value_(0.2); // sets the value silently, the action function is not executed
~sl.valueAction_(0.5); // sets the slider and executed the action functionEvery View Object can hold a Action Function which is executed when you interact with the object:
(
Window.closeAll;
w = Window().alwaysOnTop_(true).front;
~sl = Slider(w, Rect(50, 50, 50, 250))
.action_({ |v| v.value.postln });
)
~sl.value_(0.2); // sets the value silently, the action function is not executed
~sl.valueAction_(0.5); // sets the slider and executed the action functionControlSpec is a helpful Utility Object, which allows mapping normalized values to a specific with range with a specific behaviour.
(
Window.closeAll;
w = Window().alwaysOnTop_(true).front;
~spec = ControlSpec(20, 20000, 'exp', default: 220);
~sl = Slider(w, Rect(50, 50, 50, 250))
.action_({ |v| ~spec.map(v.value).postln });
)ControlSpec has also some default specs, for different scaling application like midi and db.
ControlSpec.specs.keys;
ControlSpec.specs[\db];
(
Window.closeAll;
w = Window().alwaysOnTop_(true).front;
~spec = ControlSpec(20, 20000, 'exp', default: 220);
~dbSpec = ControlSpec.specs[\db];
~sl = {Slider()} ! 2;
~sl[0].action_({ |v| ~spec.map(v.value).postln });
~sl[1].action_({ |v| ~dbSpec.map(v.value).postln });
w.layout_(
HLayout(*~sl));
)Keyboard & Mouse Interaction
You can also have interactions with Mouse and Keyboard Input on your Window objects.
Example for Keyboard Input:
(
Window.closeAll;
w = Window().alwaysOnTop_(true).front;
w.view.keyDownAction_({ // apply a keyDownAction to your window
arg view, char, modifiers, unicode, keycode, key; // expects these arguments as input
[view, char, modifiers, unicode, keycode, key].postln;
if( char == $l ) { "do something".postln; }}); // to express a literal character you need to use $
)Example for Mouse Input:
(
Window.closeAll;
w = Window().alwaysOnTop_(true).front;
w.view.mouseDownAction_({ // apply a mouseDownAction to your window
arg view, x, y, modifiers, buttonNumber, clickCount; // expects these arguments as input
[view, x, y, modifiers, buttonNumber, clickCount].postln;
});
)MIDI Interaction
Here are some examples on changing the values of a View Object via a MIDI device:
MIDIIn.connectAll;
(
Window.closeAll; // handy to start with
w = Window().front;
~spec = ControlSpec(0, 127);
~sl = Slider(w, Rect(50, 50, 50, 250)); // orientation starts at the upper left corner
MIDIdef.cc(\cc, {
arg val;
val.postln;
AppClock.sched(0, { ~sl.value_(~spec.unmap(val)) }) // this action needs to be schedule on the AppClock
}, 0);
)
// same as above, but in a shortform for scheduling on AppClock
(
Window.closeAll; // handy to start with
w = Window().front;
~spec = ControlSpec(0, 127);
~sl = Slider(w, Rect(50, 50, 50, 250)); // orientation starts at the upper left corner
MIDIdef.cc(\cc, {
arg val;
val.postln;
{ ~sl.value_(~spec.unmap(val)) }.defer; // this action needs to be schedule on the AppClock, same as above but shortform
}, 0);
)