SwingOSC – Java-based cross-platform replacements for Cocoa GUI classes
This class is meant as an emulation of SCEnvelopeView. last mod: 04-apr-09 sciss
Also refer to [JSCView] for different behaviour affecting all widgets
no-op / not working
different behaviour
size_, editable, readOnly_, elasticMode_, allConnections, items were removed
because they have no defined behaviour or are broken in cocoa GUI
superclass superclass is JSCAbstractMultiSliderView, not JSCMultiSliderView
mouse control slightly different. special cocoa "shift+click mode" not implemented, instead multiple selections
index should not be confused with current selection. call 'selection' to see which nodes are selected
extended functionality
font_ sets the text-label font
clipThumbs_ alternative coordinate mode
strings returns the text labels (or nil if none had been set)
connections returns all the custom connections (or nil if none had been made)
selection returns an array of booleans specifying which nodes are selected
deselectIndex complements selectIndex
curve_, setCurve to change the shape of the node connections
lockBounds_ to lock the time value of the bounding nodes
known issues / todo
focus border not adjusted when resizing the component programmatically (why?)
drag+drop NOT YET IMPLEMENTED
background todo: custom colours should not be darkened?
metaAction does not get evaluated on ctrl+mouseclick/drag
cursor keys movements should apply to all selected nodes
JSCEnvelopeView
Note: please use the abstraction layer GUI.envelopeView if possible! (see [GUI])
An envelope view is a collections of two dimensional (x, y) points, possibly connected through lines. A common application is the presentation of a breakpoint-function or envelope (hence the name), where the x-axis corresponds to time and the y-axis to some parameter that is controlled by the envelope (e.g. amplitude, frequency, etc.). It is similar to [JSCMultiSliderView] but instead of vertical "sliders" associated with integer indices, we instead have knobs that can be also moved horizontally.
value_([times,values])
value_([times,values,curves])
sets all the nodes, where times and values are all 0..1.
curves is optional (defaults to \linear) and can be either a single element
or an array of elements. see [Env] for the possible values. Note: \exponential and floats
are not yet supported!
value
[times,values]
reads all the nodes, where times and values are all 0..1
action_
registers a function that is called when the user selects or moves the nodes. the function is passed the view
index
the current or last moved node. initially -1
drawRect_(boolean)
sets whether to show the nodes' knob-boxes or not
drawLines_(boolean)
sets whether to draw lines between the nodes
setThumbSize(index, size)
sets the size of a point for the specified index, if the index is -1 set the size for all points
thumbSize_(size)
sets the size of all nodes' knob-boxes (width and height)
setThumbWidth(index, width)
sets the width of a knob-box for the node specified by the index, if the index is -1 set the width for all nodes
thumbWidth_( width)
sets the width of all nodes' knob-boxes
setThumbHeight(index, height)
sets the height of a point for the specified index, if the index is -1 set the height for all points
thumbHeight_(height)
sets the height of all nodes' knob-boxes
setEditable(index, boolean)
makes a specified node unmoveable (if boolean is false) or moveable (if boolean is true)
editable_(boolean)
makes all nodes unmoveable (if boolean is false) or moveable (if boolean is true)
selectionColor_(color)
sets the color of knob-boxes which are selected
setFillColor(index, color)
sets the color of a nodes' knob-box
fillColor_(color)
sets the color for all nodes
setString(index, string)
assigns a text label to a node
curve_(curve)
sets the curve shape for all node's connections
setCurve(index, curve)
sets the curve shape for a particular node's connection(s)
connect_(index, arrayofpoints)
connects a node to others. the first time you call this, the standard connection scheme is switched off
// to select + move several points, press the mouse
// somewhere on the panel and drag a rubberband.
// to extend the rubberband-selection, keep shift pressed.
//
// alternatively, shift+click on successive nodes.
//
// to deselect all, click somewhere on the panel.
// to deselect a single node, shift+click on it.
(
a = JSCWindow( "envelope", Rect( 200, 450, 250, 100 ));
b = JSCEnvelopeView( a, Rect( 4, 4, 230, 80 ))
.selectionColor_( Color.red )
.resize_( 5 )
.action_({ arg b; [ b.index, b.value ].postln }) // print last clicked index along with all node values
.value_([[ 0.0, 0.1, 0.5, 1.0 ], [ 0.1, 1.0, 0.8, 0.0 ]]); // set the initial node values (x and y)
a.front;
)
(
// make the first node unmoveable
// (you can still move it using x_ though!)
b.setEditable( 0, false );
)
(
// alternatively (SwingOSC only) you can
// lock the x coordinate of the bounding nodes:
b.setEditable( 0, true );
b.lockBounds = true;
)
// to enforce causal order, you can set the
// horizontal editing mode to one of the
// values \clamp (nodes cannot be moved beyond their neighbours)
// or \relay (when trying to move a node beyond a neighbour,
// the neighbour gets selected and moved instead). to return
// to normal mode, use \free:
// lock the x coordinate of the bounding nodes:
// (SwingOSC only)
b.horizontalEditMode = \clamp;
b.horizontalEditMode = \relay;
b.horizontalEditMode = \free;
// snap movements to a grid (0 = no snapping)
b.step = 0.125;
b.step = 0.0;
// check out the current selection (SwingOSC only)
b.selection; // an array of booleans, one for each node, true if that node is selected
// add a node to the selection
b.selectIndex( 0 );
b.selectIndex( 2 );
// remove a node from the selection (SwingOSC only)
b.deselectIndex( 2 );
// programmatically move a node
b.select( 1 ); // note: no visible effect
b.x = (b.x + 0.1.bilinrand).clip( 0, 1 );
b.y = 1.0.rand;
(
a = JSCWindow("test", Rect(200 , 450, 370, 120));
a.view.decorator = FlowLayout(a.view.bounds);
b = JSCEnvelopeView( a, Rect( 10, 10, 350, 100 ))
.resize_( 5 )
.fillColor_( Color.green )
.selectionColor_( Color.red )
.drawRects_( true )
.value_([(0.0, 0.1 .. 1.0), (0.0, 0.1 .. 1.0)])
.horizontalEditMode_( \relay )
.lockBounds_( true );
a.front;
)
(
r = Routine({
var j = 0;
20.do({ arg i;
b.select((b.size -1).rand.abs);
0.1.wait;
b.x_(1.0.rand.abs);
b.y_(1.0.rand.abs);
});
b.select(-1);
});
AppClock.play(r);
)
c = b.value[0]; // the x-values
c = b.value[1]; // the y-values
b.selection; // see which nodes are selected! (SwingOSC only)
// show boxes with a label in it
(
a = JSCWindow( "text-boxes", Rect( 200, 450, 450, 450 ));
b = JSCEnvelopeView( a, Rect( 4, 4, 440, 440 ))
.resize_( 5 )
.thumbWidth_( 60.0 )
.thumbHeight_( 15.0 )
.selectionColor_( Color.red )
.value_([[ 0.1, 0.4, 0.5, 0.3 ], [ 0.1, 0.2, 0.9, 0.7 ]]);
4.do({ arg i;
b.setString( i, [ "this", "is", "so much", "fun" ].at( i ));
b.setFillColor( i, [ Color.yellow, Color.white, Color.green ].choose ); // random colour
});
a.front;
)
// switch the label font (SwingOSC only!)
b.font = JSCFont( "SansSerif", 16 );
b.thumbHeight = 22; // no automatic thumb size adjustment, so we do it manually
b.thumbWidth = 80;
// let's see what the labels are
b.strings;
// control the way the view is displayed
b.drawLines = false;
b.drawRects = false;
b.drawLines = true;
b.drawRects = true;
b.strokeColor = Color.blue; // colour for outlines, labels, and connections
// the nodes can be connected.
// once you call connect, the standard neighbouring connections disappear!
// note that each connect overwrites all previous connections of the particular node.
(
b.connect( 3, [ 0, 1, 2 ]); // connect node 3 to nodes 0, 1, and 2
b.connect( 0, [ 1, 2, 3 ]); // connect node 0 to nodes 1, 2, and 3
b.drawLines_( true ); // (in case it was switched off)
)
// check out what the connections are
b.connections; // nil if no connections had been made, otherwise an array of arrays (one for each source node)
At the moment, the graph is considered "undirected", therefore as you can see, setting a connection from for example node 0 to node 1, implies that a connection is added from node 1 to 0. in a future version, there might be an option to use a directed graph instead. note, that the returned connection arrays are not garantueed to be sorted in any way.
Using Curve-Shapes
In SwingOSC, it is possible to use other shapes for the connections than straight lines. The possible shapes are described in the [Env] helpfile. Note: \exponential and Float values are not yet implemented. You can specify the curves of the envelope by calling curve_( <curve> ), or to change just a single node's shape, call setCurve( <index>, <curve> ). You can also supply a third array to the value_ method, so that it becomes value_([ <times>, <levels>, <curves> ]). Here is an example that applies new curves from a popup menu to the currently selected nodes:
(
a = JSCWindow( "envelope", Rect( 200, 450, 250, 114 ));
b = JSCEnvelopeView( a, Rect( 4, 4, 242, 80 ))
.selectionColor_( Color.red )
.resize_( 5 )
.action_({ arg b; [ b.index, b.value ].postln }) // print last clicked index along with all node values
.lockBounds_( true )
.horizontalEditMode_( \relay )
.value_([(0.0, 0.1 .. 1.0), (0.0, 0.1 .. 1.0)]);
c = JSCPopUpMenu( a, Rect( 4, 88, 90, 24 ))
.resize_( 7 )
.allowsReselection_( true )
.items_([ \step, \linear, /* exponential, */ \sine, \welch, /* 2.0, */ \squared, \cubed ])
.value_( 1 )
.action_({ arg pop; var curve = pop.items[ pop.value ];
b.selection.do({ arg selected, idx; if( selected, { b.setCurve( idx, curve )})});
b.doAction;
});
a.front;
)
Note that for backwards compatibility, value by default returns only a two element array. As soon as you modify the curves, value will return a third element that is an array of curve parameters.
Envelope Support
There are methods to directly feed in Env objects and to retrieve Env objects from the current values. To set the envelope view according to an Env object, use the setEnv method. To retrieve the values of the view as an Env object use asEnv( <minLevel>, <maxLevel> ), or call editEnv( <env>, <minLevel>, <maxLevel> ) to update an existing Env object. Example:
(
v = Env.adsr;
a = JSCWindow( "envelope", Rect( 200, 450, 250, 114 )).front;
b = JSCEnvelopeView( a, Rect( 4, 4, 242, 80 ))
.resize_( 5 )
.action_({ arg b; b.editEnv( v, 0.0, 1.0 )})
.lockBounds_( true )
.horizontalEditMode_( \relay )
.setEnv( v, 0.0, 1.0 );
JSCButton( a, Rect( 4, 88, 90, 24 ))
.resize_( 7 )
.states_([[ "Plot" ]])
.action_({
GUI.useID( \swing, { v.plot });
});
a.front;
)
Alternative Coordinates Mode
normally, as in the cocoa GUI, the coordinates of each node are "tweaked" to make its knob-box fit entirely into the view. That is, if you have say, the first node located at x = 0, y = 1, and thumbWidth is 80 pixels, thumbHeight is 40 pixels ...
b.select( 0 ); b.x = 0; b.y = 1; b.thumbWidth = 40; b.thumbHeight = 40; b.setFillColor( 0, Color.clear ); b.setString( 0, nil );
... you will notice that the connection to the other nodes does not begin in the top left corner of the view. that is because the knob-box is always centered around the connection location, and the knob-box was moved a little to fit completely onto the view. This can be confusing when you wish to see the precise coordinates of your envelope. Therefore, in SwingOSC you can switch to an alternative coordinates mode, where all the value coordinates appear as expect and hence the knob-boxes are allowed to be clipped at the view's boundaries:
b.clipThumbs = true;
b.clipThumbs = false;
b.clipThumbs = true;
// default keyboard mappings
csr left/right decrease/increase x
csr down/up decrease/increase y
alt + csr left/left select previous/next node