/usr/share/SuperCollider/HelpSource/Guides/UserView-Subclassing.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 | title:: Custom Views
summary:: A guide to creating custom views by subclassing UserView
categories:: GUI
related:: Classes/UserView, Guides/WritingClasses, Guides/GUI-Introduction
note:: This document only applies to strong:: Cocoa GUI ::. Subclassing a view is specific to a GUI kit, at least because you have to subclass from the kit-specific class, i.e. SCUserView, QUserView or JSCUserView, instead of the generic UserView class.
There are also subtle differences between kits when it comes to subclassing. In particular, in Qt GUI, step 2 of this tutorial is redundant, and step 3 consists of overriding the code::*new:: class method instead of the code::init:: instance method.
Nonetheless, you may find this tutorial helpful for other GUI kits than Cocoa, as most information in it is correct for all the kits.
::
This is a short tutorial on how to make a custom view class by subclassing SCUserView. It is assumed that you know how to write classes in SuperCollider. See link::Guides/WritingClasses::.
At the bottom of this document there is a template class code that you can refer to as an example of the steps taken in the tutorial. To test it simply copy it into a file ending with '.sc' and place the file into your SC Extensions folder. If you are already running SuperCollider, recompile the class library in order for the new class to become available.
You may also find the template useful when creating your own custom view classes. Rename the class MyWidget to whatever you want to call your class, and adjust the variables and methods to fit your design.
SECTION:: Tutorial
The following narrates the code in the template:
SUBSECTION:: 1. Setup instance vars appropriate to your widget
You inherit all of the instance variables from SCUserView and SCView, of course, but many gui widgets need their own variables.
In particular, you need to overide value, to return whatever you want your view to return. The instance variable, step, is also often used to allow value to be quantized. thumbSize is used for both width and height of a slider handle, while thumbWidth or thumbHeight are typically used for only one dimension. x and y are used for mouse clicks. Take a look at a similar widget to see what the standard instance variables are.
SUBSECTION:: 2. Define the *viewClass method
It is mandatory to define the *viewClass class method to return the SCUserView class. You must do this so that your class calls the primitive of SCUserView on init.
SUBSECTION:: 3. Set up your view
You will want to override init to customize your subclass. Here you will set the defaults of some of your instant variables and anything else you want to do on creating the view.
Finally, you should set code::this.drawFunc (SCUserView's drawing function):: to the method this.draw, which you will define below.
SUBSECTION:: 4. Define a drawing function for SCPen
This is where you will define how the view appears. How you draw will typically be dependent on instance variables which you defined, such as value, states (for buttons), x and y (for mouse clicks), or anything else you might need for your design. See link::Classes/Pen:: for drawing methods.
SUBSECTION:: 5. Define typical widget methods
Here you define various methods according to your own design. You should look at similar gui objects to see what they have. The setter, valueAction_ is defined by practically any gui widget, for example. It sets the value and performs the action (already defined in SCView).
SUBSECTION:: 6. Override mouseActions
SCView defines mouseDown, mouseMove and mouseUp, as methods, and the corresponding user definable methods, mouseDownAction, mouseMoveAction and mouseUpAction. You should override mouseDown, mouseMove or mouseUp as needed, and make sure your method calls the corresponding action as well, so your user can later add user actions, just like in the template. See also the link::Classes/View#subclassing#View: Subclassing::.
SUBSECTION:: 7. Define default key actions
Here you define your default key responses in defaultKeyDownAction or defaultKeyUpAction. Differently than with mouse actions, you do not call the keyAction in your method, since this mechanism is handled by SCView. If the user defines a keyDownAction, or keyUpAction, SCView makes it override your default action. See the template, and link::Classes/View#subclassing#View: Subclassing::.
SUBSECTION:: 8. Define default drag and drop actions
Here you define your drag responses in defaultGetDrag, defaultCanReceiveDrag, and defaultReceiveDrag. Differently than with mouse actions, you do not call the dragAction in your method, since this mechanism is handled by SCView. If your user defines a beginDragAction, canReceiveDragHandler, or receiveDragHandler, SCView makes it override your default action. See the template, and link::Classes/View#subclassing#View: Subclassing::.
That's it. Now recompile, only to find your first syntax errors. ;-)
Happy subclassing.
SECTION:: Custom view template
code::
// How to subclass SCUserView to make custom GUI interfaces. Jost Muxfeldt, 2008.
// For many purposes you can use this as a template, and simply adjust the methods
MyWidget : SCUserView {
// (1) Setup instance vars appropriate to your widget. Make sure to define value.
var <>step, <value=0, <>leftColor, <>rightColor, <>thumbWidth=7;
// (2) Set the viewClass to SCUserView
*viewClass { ^SCUserView } // this ensures that SCUserView's primitive is called
// (3) Set up your view
init { |argParent, argBounds|
super.init(argParent, argBounds);
// set defaults of your instance variables
rightColor=Color.grey(0.8);
leftColor=Color.grey(0.2);
step=this.pixelStep;
// set the draw function of the SCUserView
this.drawFunc={ this.draw};
}
// (4) define a drawing function for SCPen
draw{
// Draw the fill
SCPen.fillColor = Color.grey;
Pen.addRect(Rect(0,0, this.bounds.width*value,this.bounds.height));
Pen.fill;
// Draw the triangle
SCPen.fillColor = Color.red;
Pen.moveTo(((this.bounds.width*value)-5) @ this.bounds.height);
Pen.lineTo(((this.bounds.width*value)+5) @ this.bounds.height);
Pen.lineTo(((this.bounds.width*value)) @ (this.bounds.height/2));
Pen.lineTo(((this.bounds.width*value)-5) @ this.bounds.height);
Pen.fill;
// Draw the frame
SCPen.strokeColor = Color.black;
Pen.addRect(Rect(0,0, this.bounds.width,this.bounds.height));
Pen.stroke;
}
// (5) define typical widget methods (only those you need or adjust as needed)
valueAction_{ arg val; // most widgets have this
this.value=val;
this.doAction;
}
value_{ |val| // in many widgets, you can change the
// value and refresh the view , but not do the action.
value=val;
this.refresh;
}
// these are like in SCSlider
increment { |zoom=1| ^this.valueAction = this.value + (max(this.step, this.pixelStep) * zoom) }
decrement { |zoom=1| ^this.valueAction = this.value - (max(this.step, this.pixelStep) * zoom) }
pixelStep { // like in SCSlider
var bounds = this.bounds;
^(bounds.width-1).reciprocal
}
// (6) override mouseActions
mouseDown{ arg x, y, modifiers, buttonNumber, clickCount;
var newVal;
// this allows for user defined mouseDownAction
mouseDownAction.value(this, x, y, modifiers, buttonNumber, clickCount);
// set the value and do the action
([256, 0].includes(modifiers)).if{ // restrict to no modifier
newVal= x.linlin(0,this.bounds.width,0,1);
// translates the relative mouse position in pixels to a value between 0 and 1
if (newVal != value) {this.valueAction_(newVal)}; // only do something if the value changed
};
}
mouseMove{ arg x, y, modifiers, buttonNumber, clickCount;
var newVal;
// this allows for user defined mouseMoveAction
mouseMoveAction.value(this, x, y, modifiers, buttonNumber, clickCount);
// set the value and do the action
([256, 0].includes(modifiers)).if{ // restrict to no modifier
newVal= x.linlin(0,this.bounds.width,0,1);
// translates the relative mouse position in pixels to a value between 0 and 1
if (newVal != value) {this.valueAction_(newVal)}; // only do something if the value changed
};
}
// (7) define default key actions
// make sure to return "this", if successful, and nil if not successful
defaultKeyDownAction { arg char, modifiers, unicode,keycode;
if (unicode == 16rF700, { this.increment; ^this });
if (unicode == 16rF703, { this.increment; ^this });
if (unicode == 16rF701, { this.decrement; ^this });
if (unicode == 16rF702, { this.decrement; ^this });
^nil // bubble if it's an invalid key
}
// (8) define drag and drop
defaultGetDrag {^value} // what to drag
defaultCanReceiveDrag {^currentDrag.isNumber} // when to receive
defaultReceiveDrag { this.valueAction = currentDrag;} // what to do on receiving
}
::
Try this after you have added the class to the class library:
code::
(
GUI.cocoa;
w=Window.new.front;
v=MyWidget(w, Rect(10,20,200,20)).valueAction_(0.5);
q=MyWidget(w, Rect(10,60,200,20)).valueAction_(0.3);
)
::
|