BoxGrid a simple GUI grid made of rects


This GUI widget can be useful in various situations such as representing scales,

step sequencers, etc. It is a modification of an earlier class called Grid. Both 

work fine, but for representing some things, one might be better than the other.


The grid is basically a graphical representation of 2 dimensional array of

columns and rows. 


For example, here we have 2 columns with 3 rows: [[0, 1, 0], [1, 1 0]]

(1 and 0 indicating the state of the node in that location)


The getState and setState_ methods function according to the convention of 

passing (x, y), i.e. the horizontal first and then the vertical.


*new(window, bounds, columns, rows);

window - A GUI.window in which the Grid will appear

bounds - The Rect of the Grid 

columns - The number of columns in the grid (vertical lines)

rows - The number of rows in the grid (horizontal lines)

nodeDownAction_ - The function that mouse down on a box will trigger.

nodeTrackAction_ - The function that dragging the cursor will trigger.

nodeUpAction_ - The function that mouse up on a box will trigger.

keyDownAction_ - The function that key down on the view will trigger (makes view focusable)

rightDownAction_ - The function that a right mouse click on a box will trigger.

clearGrid - sets all nodes on the grid to inactive.

setFillMode_(boolean) - Whether the nodes are filled with color or not.

setFillColor_(color) - The color of the filled nodes. (see setBoxColor)

setTrailDrag_(bool, bool) - Mouse tracking on the grid leaves a trail of on states (or switches).

- The first argument is the trails, the second is if it switches on/off.


setNodeString_(index, string) - Set the label/string of a node.

getNodeString_(index) - Get string of a node.

setFont_(font) - Set the font..

setFontColor_(Color) - Set the color of the font.


setNodeStates_(array) - Updates the grid from a 2 dimensional array of columns and rows.

getNodeStates - Returns 2 dimensional array representing the columns and rows.

setNodeBorder_(int) - The border around the selection box.

setState_(row, column, state) - Sets the state of an individual box.

getState(row, column) - Returns the state of an individual box.

setBoxColor_(row, col, Color) - Sets the color of an individual box.

getBoxColor(row, column) - Returns the color of an individual box.

setBackgrColor_(Color) - The background color of the grid.

setBackgrDrawFunc_(func) - Allows Pen to draw behind the grid.




Note: the only methods that differ between BoxGrid and Grid are : setNodeBorder_/setNodeSize_

respectively, and the BoxGrid doesn't take the border argument. There is no "nodeshape" in BoxGrid.

Apart from these differences, the code should run the same, so it's easy to change between the two.




// choose a gui (see GUI helpfile)

GUI.swing;

GUI.cocoa;



// create a grid

BoxGrid.new


BoxGrid.new(bounds: Rect(0, 0, 600, 400), columns: 4, rows: 2)

BoxGrid.new(bounds: Rect(20, 20, 300, 200), columns: 4, rows: 8)



(


w = Window.new("Testing BoxGrid", Rect(10, 500, 500, 212)).front;

BoxGrid.new(w, bounds: Rect(20, 20, 460, 20), columns: 4, rows: 1);

BoxGrid.new(w, bounds: Rect(20, 60, 460, 20), columns: 14, rows: 1);

BoxGrid.new(w, bounds: Rect(20, 100, 460, 20), columns: 12, rows: 1);

BoxGrid.new(w, bounds: Rect(20, 140, 460, 20), columns: 10, rows: 1);


)



(


w = Window.new("Testing BoxGrid", Rect(10, 500, 840, 642)).front;

a = BoxGrid.new(w, bounds: Rect(20, 20, 760, 520), columns: 18, rows: 18);


a.setNodeBorder_(8);


)


a.setNodeBorder_(3); // due to problems with Pen, it is often good to add 0.5


a.setBackgrColor_(Color.white);



// you can use either boolean

a.setState_(1,1,true)

// or integer (1 = true, 0 = false)

a.setState_(1,3,1)


// get state will return only integers (0 or 1)

a.getState(1,3)

a.getState(2,3)


// get the states of all nodes

b = a.getNodeStates

// or just the first row:

c = a.getNodeStates[0]


// clear the grid

a.clearGrid


// set it back to previous state

a.setNodeStates_(b)


// fillmode and color

a.setFillMode_(true);

a.setFillColor_(Color.white);

a.setFillColor_(Color.blue);


a.setBoxColor_(1,1, Color.red);


a.setBackgrColor_(Color.green);

a.setFillMode_(false);

a.setFillMode_(true);


a.setTrailDrag_(true); // leaves a trail of active nodes when moved around the grid

a.setTrailDrag_(true, true); // bool true (so it switches the state of boxes that you move over

a.setTrailDrag_(true, false); // bool false

a.setTrailDrag_(false, false); // back to the default



// or

a.gridNodes[0][3].setState_(true)

a.refresh



(

a.keyDownAction_({arg key, modifiers, unicode; 

[\key, key, \modifiers, modifiers, \unicode, unicode].postln;

}); // this makes the view focusable (with a grey border)

)


a.rightDownAction_({arg nodeloc; "cntrl click on : ".post; nodeloc.postln;}); // right click



(


w = Window.new("Testing BoxGrid", Rect(100, 500, 500, 212)).front;

a = BoxGrid.new(w, bounds: Rect(20, 20, 460, 140), columns: 24, rows: 8);

a.reconstruct({

a.setBackgrColor_(Color.white)

.setNodeBorder_(3)

.setFillMode_(true)

.setFillColor_(Color.new255(103, 148, 103))

.nodeDownAction_({arg nodeloc; nodeloc.postln;})

.nodeTrackAction_({arg nodeloc; nodeloc.postln;})

.nodeUpAction_({arg nodeloc; nodeloc.postln}); 

30.do({

a.setState_(24.rand, 8.rand, 1); // column, row, state

});

});

)



b = a.getNodeStates

// clear the grid

a.clearGrid

// set it back to previous state

a.setNodeStates_(b)



// draw on the same GUI as the grid (labelling the nodes for example)

// see also the example with setBackgrDrawFunc_ here below

(

w = Window.new("Testing BoxGrid", Rect(10, 500, 800, 212)).front;

a = BoxGrid.new(w, bounds: Rect(20, 20, 760, 140), columns: 14, rows: 4);

a.reconstruct({

a.setNodeBorder_(3);

a.setBackgrColor_(Color.white);

14.do({arg i;

4.do({arg j;

a.setNodeString_(i, j, " ["+i.asString+","+j.asString+"]");

});

});

});

)


(

w = Window.new("Testing BoxGrid", Rect(10, 500, 800, 212)).front;

a = BoxGrid.new(w, bounds: Rect(20, 20, 760, 140), columns: 14, rows: 2);

a.reconstruct({

a.setNodeBorder_(3);

a.setBackgrColor_(Color.white);

a.setFont_(GUI.font.new("Helvetica", 12));

14.do({arg i;

2.do({arg j;

a.setNodeString_(i, j, "ZXIEU".scramble);

});

});

});

)



////////// 



(

var w, a;

w = Window.new("a plus", Rect(10, 500, 420, 320)).front;

a = BoxGrid.new(w, bounds: Rect(20, 20, 360, 260), columns: 18, rows: 18);


a.setNodeBorder_(2);

a.nodeDownAction_({arg nodeloc; nodeloc.postln;

a.reconstruct({

a.clearGrid; 

a.gridNodes[nodeloc[1]].do({arg item; item.setState_(true)});

a.gridNodes.do({arg item; item[nodeloc[0]].setState_(true)});

});

});

a.nodeTrackAction_({arg nodeloc; nodeloc.postln;

a.reconstruct({

a.clearGrid; 

a.gridNodes[nodeloc[1]].do({arg item; item.setState_(true)});

a.gridNodes.do({arg item; item[nodeloc[0]].setState_(true)});

});

});

)





/////////////////// some examples from the development of Grid


// the synthdef used in the examples

(

SynthDef(\pure, {arg freq=440, pan=0.0, vol=0.3, envdur=0.8;

var signal, envArray, env;

env = EnvGen.kr(Env.perc(0.01, envdur), doneAction:2); signal = Pan2.ar(SinOsc.ar(freq, 0, vol), pan) * env;

Out.ar(0, signal);

}).add;

)


///////////// scales

// here we have one octave of 19 equal tempered scale (19-TET)


(

var clock, scale;

var nTET = 19; // change this variable to something else (5, 12, 24, etc.)


w = Window.new("Testing BoxGrid", Rect(10, 500, 800, 100+ (20*nTET))).front;


k = Array.fill(nTET, {arg i; 2.pow(i/nTET) * 440;}).reverse; // 19-TET scale in A


a = BoxGrid.new(w, bounds: Rect(20, 20, 760, 20), columns: 32, rows: 1, border: false)

.setFillMode_(true)

.setNodeBorder_(4)

.setFillColor_(Color.white);

b = BoxGrid.new(w, bounds: Rect(20, 50, 760, 20*nTET), columns: 32, rows: nTET, border:true);

b.reconstruct({

b.setBackgrColor_(Color.white)

.setNodeBorder_(8)

.setFillMode_(true)

.setFillColor_(Color.new255(103, 148, 103))

.nodeDownAction_({arg nodeloc; nodeloc.postln; Synth(\pure, [\freq, k[nodeloc[1]], \envType, 1])})

.nodeUpAction_({arg nodeloc; nodeloc.postln; b.setState_(nodeloc[0], nodeloc[1], 0)}); 


// fill it with random nodes

(nTET*3).do({

b.setState_(32.rand, nTET.rand, 1); // column, row, state

});

});

)



// and we can play the grid from above with a clock

// note: if you close the windows without stopping the clock (using apple+dot)

// SuperCollider will crash. That is not a problem with BoxGrid, but with SC.


(

var nTET = 19;

t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;


{a.setState_( beat%32, 0, 1)}.defer;

{a.setState_( beat%32, 0, 0)}.defer(t.beatDur);


nTET.do({arg i;

if(b.getState( beat%32, i) == 1, {

Synth(\pure, [\freq, k[i], \envType, 1]);

})

});

1;

});

b.nodeUpAction_({nil;}); // remove the mouseUp function (so the nodes stay)


)


b.clearGrid



// ----------------------------------------------------------------------


//  the same as above but with some differences:

//  horizontal location is the pitch in one octave

//  vertical location is octave higher/lower



(

var clock, scale;

var nTET = 19;

var octaves = 8;

w = Window.new("Keyboard", Rect(10, 500, 800, 100+ (20*nTET))).front;


k = Array.fill(nTET, {arg i; 2.pow(i/nTET);}); // 19-TET scale in A

n = Array.fill(octaves, {arg i; k * (2.pow(i)*110)}).reverse;


a = BoxGrid.new(w, bounds: Rect(20, 20, 760, 20), columns: nTET, rows: 1)

.setFillMode_(true)

.setFillColor_(Color.white)

.nodeTrackAction_({arg nodeloc; 

octaves.do({arg i;

if(b.getState(i, nodeloc[1]) == 1, {

Synth(\pure, [\freq, n[i][nodeloc[1]]]);

})

});

});


b = BoxGrid.new(w, bounds: Rect(20, 50, 760, 20*nTET), columns: nTET, rows: 8)

//.setBackgrColor_(Color.white)

.setNodeBorder_(8)

.setFillMode_(true)

.setFillColor_(Color.white)

.nodeDownAction_({arg nodeloc; 

nodeloc.postln; 

[\midinote, n[nodeloc[1]][nodeloc[0]].cpsmidi].postln;

Synth(\pure, [\freq, n[nodeloc[1]][nodeloc[0]]])})

.nodeTrackAction_({arg nodeloc; 

nodeloc.postln; 

[\midinote, n[nodeloc[1]][nodeloc[0]].cpsmidi].postln;

Synth(\pure, [\freq, n[nodeloc[1]][nodeloc[0]]])})

.setBackgrDrawFunc_({

19.do({arg i;

if((i==1)||(i==2)||(i==4)||(i==5)||(i==7)||(i==9)||

(i==10)||(i==12)||(i==13)||(i==15)||(i==16)||(i==18),{

Pen.fillColor = Color.new255(0, 0, 0, 80);

},{

Pen.fillColor = Color.new255(255, 255, 255, 80);

});

Pen.fillRect(Rect((i*(760/nTET)), 0, 760/nTET, 380));

})

});

)


// and we can play the grid from above with a clock

(

var nTET = 19;

var octaves = 8;


t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;


{a.setState_(beat%nTET, 0, 1)}.defer;

{a.setState_(beat%nTET, 0, 0)}.defer(t.beatDur);


octaves.do({arg i;

if(b.getState(beat%nTET, i) == 1, {

Synth(\pure, [\freq, n[i][beat%nTET], \envType, 1]);

})

});

1;

});

b.nodeUpAction_({nil;}); // remove the mouseUp function (so the nodes stay)


)


t.tempo_(6)


// -------------------------------------------



// I'm not sure what this is... but it's fun!

// note the pitch algorithm in the array n is different from the above (where we have octaves)

(

var clock, scale;

var nTET = 19; // change this variable to something else (5, 12, 24, etc.)

var octaves = 8;


z = [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 ], [ 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0 ], [ 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1 ], [ 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1 ], [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ];


w = Window.new("Testing Grid", Rect(10, 500, 800, 100+ (20*nTET))).front;


k = Array.fill(nTET, {arg i; 2.pow(i/nTET);}); // 19-TET scale in A

n = Array.fill(octaves, {arg i; k * (1.5.pow(i)*110)}).reverse;


a = BoxGrid.new(w, bounds: Rect(20, 20, 760, 20), columns: nTET, rows: 1, border: false)

.setFillMode_(true)

.setFillColor_(Color.white);

b = BoxGrid.new(w, bounds: Rect(20, 50, 760, 20*nTET), columns: nTET, rows: octaves, border:true)

.setBackgrColor_(Color.new255(203, 248, 203))

.setNodeBorder_(12)

.setFillMode_(true)

.setNodeStates_(z)

.setFillColor_(Color.new255(103, 148, 103))

.nodeDownAction_({arg nodeloc; nodeloc.postln; Synth(\pure, [\freq, n[nodeloc[1]][nodeloc[0]]])});


t = TempoClock(6);

t.schedAbs(t.beats.ceil, { arg beat, sec;


{a.setState_(beat%nTET, 0, 1)}.defer;

{a.setState_(beat%nTET, 0, 0)}.defer(t.beatDur);


octaves.do({arg i;

if(b.getState(beat%nTET, i) == 1, {

Synth(\pure, [\freq, n[i][beat%nTET], \envType, 1]);

})

});

1;

});

)



//////



// same as above but without a TempoClock and here you can use the

// top grid to scroll through the chords, like strumming strings or.... err...

(

var clock, scale;

var nTET = 19; // change this variable to something else (5, 12, 24, etc.)

var octaves = 8;


z = [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 ], [ 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0 ], [ 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1 ], [ 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1 ], [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ];


w = Window.new("Testing BoxGrid", Rect(10, 500, 420, 100+ (10*nTET))).front;


k = Array.fill(nTET, {arg i; 2.pow(i/nTET);}); // 19-TET scale in A

n = Array.fill(octaves, {arg i; k * (1.5.pow(i)*110)}).reverse;


a = BoxGrid.new(w, bounds: Rect(20, 20, 380, 20), columns: nTET, rows: 1)

.setFillMode_(true)

.setFillColor_(Color.white)

.nodeDownAction_({arg nodeloc; 

octaves.do({arg i;

if(b.getState(nodeloc[0], i) == 1, {

Synth(\pure, [\freq, n[i][nodeloc[0]], \envType, 1]);

})

});

})

.nodeUpAction_({arg nodeloc; 

a.setState_(nodeloc[0], nodeloc[1], 0)

})

.nodeTrackAction_({arg nodeloc; 

octaves.do({arg i;

if(b.getState(nodeloc[0], i) == 1, {

Synth(\pure, [\freq, n[i][nodeloc[0]], \envType, 1]);

})

});

});


b = BoxGrid.new(w, bounds: Rect(20, 50, 380, 10*nTET), columns: nTET, rows: octaves)

.setBackgrColor_(Color.new255(203, 248, 203))

.setNodeBorder_(5)

.setFillMode_(true)

.setNodeStates_(z)

.setFillColor_(Color.new255(103, 148, 103))

.nodeDownAction_({arg nodeloc; nodeloc.postln; Synth(\pure, [\freq, n[nodeloc[1]][nodeloc[0]]])});

)




//----------------------------------------------------



///////// a step sequencer

(

var gridArray, clockArray, meter, freq;


w = Window.new("Step sequencer", Rect(10, 500, 400, 250)).front;


a = BoxGrid.new(w, bounds: Rect(20, 20, 360, 20), columns: 16, rows: 1)

.setFillMode_(true)

.setFillColor_(Color.gray);


gridArray = Array.fill(4, {arg i;

BoxGrid.new(w, bounds: Rect(20, 80+(i*30), 360, 20), columns: 16, rows: 1)

.setFillMode_(true)

.setFillColor_(Color.white);

});


t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;


{a.setState_( beat%16, 0, 1)}.defer;

{a.setState_( beat%16, 0, 0)}.defer(t.beatDur);

4.do({arg i;

if(gridArray[i].getState(beat%16, 0) == 1, {

Synth(\pure, [\freq, 440*(i+1), \envType, 1]);

});

});

1;

});

)


t.tempo_(12)






//////////////// polyrhythm


(

var gridArray, clockArray, meter, freq;

meter = [4,3,5];


w = Window.new("polyrhythm", Rect(10, 500, 400, 150)).front;


gridArray = Array.fill(3, {arg i;

BoxGrid.new(w, bounds: Rect(20, 20+(i*30), 360, 20), columns: meter[i], rows: 1)

.setFillMode_(true)

.setFillColor_(Color.white);

});


clockArray = Array.fill(3, {arg i;

t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;

if(gridArray[i].getState(beat%meter[i], 0) == 1, {

Synth(\pure, [\freq, 440*(i+1), \envType, 1]);

});

1;

});

});

)


// if you want to draw the above with equal sized boxes, then:

// (this is one of the problems with the Grid class as it has borders)

(

var gridArray, clockArray, meter, freq;

meter = [4,3,5];


w = Window.new("polyrhythm", Rect(10, 500, 400, 150)).front;


gridArray = Array.fill(3, {arg i;

BoxGrid.new(w, bounds: Rect(20, 20+(i*30), meter[i]*50, 20), columns: meter[i], rows: 1)

.setFillMode_(true)

.setFillColor_(Color.white);

});


clockArray = Array.fill(3, {arg i;

t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;

if(gridArray[i].getState(beat%meter[i], 0) == 1, {

Synth(\pure, [\freq, 440*(i+1), \envType, 1]);

});

1;

});

});

)



// or perhaps:

(

var gridArray, anArray, clockArray, meter, freq;

meter = [4,3,5];


w = Window.new("polyrhythm", Rect(10, 500, 400, 450)).front;


anArray = Array.fill(3, {arg i;

BoxGrid.new(w, bounds: Rect(20, 20+(i*60), meter[i]*50, 10), columns: meter[i], rows: 1)

.setFillMode_(true)

.setNodeBorder_(4)

.setFillColor_(Color.red);

});


gridArray = Array.fill(3, {arg i;

BoxGrid.new(w, bounds: Rect(20, 35+(i*60), meter[i]*50, 20), columns: meter[i], rows: 1)

.setFillMode_(true)

.setFillColor_(Color.white);

});


clockArray = Array.fill(3, {arg i;

t = TempoClock(2);

t.schedAbs(t.beats.ceil, { arg beat, sec;

if(gridArray[i].getState(beat%meter[i], 0) == 1, {

Synth(\pure, [\freq, 440*(i+1), \envType, 1]);

});

// update gui (the top arrays)

{anArray[i].setState_( beat%meter[i], 0, 1)}.defer;

{anArray[i].setState_( beat%meter[i], 0, 0)}.defer(t.beatDur);

1;

});

});


w.onClose_({ // this doesn't work and is a bug in SC-cocoa (it does work in Swing)

clockArray.do({arg clock; clock.clear; clock.stop;});

});

)




// in case you want to draw behind the Grid then use the setBackgrDrawFunc


(

var clock, scale;

var nTET = 12;

var octaves = 8;

w = Window.new("Keyboard", Rect(10, 500, 800, 100+ (20*nTET))).front;


k = Array.fill(nTET, {arg i; 2.pow(i/nTET);});

n = Array.fill(octaves, {arg i; k * (2.pow(i)*110)}).reverse;


b = BoxGrid.new(w, bounds: Rect(20, 50, 760, 20*nTET), columns: nTET, rows: 8)

.setNodeBorder_(8)

.setFillMode_(true)

.setFillColor_(Color.white)

.nodeDownAction_({arg nodeloc; nodeloc.postln; Synth(\pure, [\freq, n[nodeloc[1]][nodeloc[0]]])})

.setBackgrDrawFunc_({

12.do({arg i;

if((i==1)||(i==3)||(i==6)||(i==8)||(i==10),{

GUI.pen.fillColor = Color.new255(60, 60, 60);

},{

GUI.pen.fillColor = Color.new255(255, 255, 255);

});

GUI.pen.fillRect(Rect(1+(i*(760/12)), 0, 760/12, 240));

})

});

)