BoxMatrix


a 2D grid of boxes/buttons


boxes can be styled, titled, clicked, double clicked, dragged, copied, moved, swapped


cascading style system allows display of different states for whatever is in the box

default style

individual box styling

named styles (eg playing)

mouse events / dragging styles

Each box is an environment and stores variables.

These are used to set display style 

and to hold sounds, patterns, players, functions etc.


Supports easy dragging, copying, moving, swapping of boxes with or without modifiers

This was designed initially with the goal of playing with patterns or parts, though it can also be used for sequencing, patching things together or pixel painting.




BoxMatrix(parent,bounds,numCols,numRows)


// 6 by 10 with no bounds set

x = BoxMatrix(nil,nil,6,10);


// scales boxes to fit in the bounds

x = BoxMatrix(nil,1000@600,6,10);


// user views are not that fast

// you won't want to make it this big

// especially if its mostly empty space

x = BoxMatrix(nil,1000@600,60,100);




Handlers are similar to view handlers but instead of x,y they are passed a 'box' which is an environment object.


(

// 6 by 10 with no bounds set

x = BoxMatrix(nil,nil,6,10);


x.mouseDownAction = { arg box,modifiers,buttonNumber,clickCount;

["down",box,modifiers,buttonNumber,clickCount].postln;

box.title = clickCount.asString;

};


x.mouseUpAction = { arg box,modifiers;

["up",box,modifiers].postln;

};


x.mouseMoveAction = { arg box,modifiers;

["move",box,modifiers].postln;

};


// SCUserView doesnt implement mouse over yet

x.mouseOverAction = { arg box,modifiers;

["over",box,modifiers].postln;

};


)


x.defaultStyle holds the default display variables for every box:


        defaultStyle = (

            font: GUI.font.new(*skin.fontSpecs),

            fontColor: skin.fontColor,

            boxColor: skin.offColor,

            borderColor: skin.foreground,

            center: false // text align

            );        


// set the default color

x.defaultStyle.boxColor = Color.white;

x.refresh;


Each box has standard display variables that can override the matrix's defaultStyle:


title

font

fontColor

boxColor

borderColor

center = true/false text align


(

x.at(1@1).title = "dom7";

x.at(1@1).boxColor = Color.rand;

x.refresh

)



// setting a default box style and an individual box's style

(

x = BoxMatrix(nil,100@500,2,10);


// what a default box looks like

x.defaultStyle.center = true;

x.defaultStyle.boxColor = Color.white;


x.mouseMoveAction = { arg box,modifiers;

if(box.title != "x",{

box.title = "x";

box.boxColor = Color.rand;

})

};


// control click to clear

x.mouseDownAction = { arg box,modifiers,buttonNumber,clickCount;

if(modifiers.isCtrl,{

box.title = "";

box.boxColor = x.defaultStyle.boxColor;

})

};

)



You can store musical settings in the box environment


(

x = BoxMatrix(nil,400@400,4,4);


4.do { |i|

b = x.at(i@i);

b.title = i.asString;

b.center = true;

b.pattern = i;

};


// play the pattern on click

x.mouseDownAction = { arg box,modifiers,buttonNumber,clickCount;

if(box.pattern.notNil) {

if((box.playing ? false).not,{

["play this pattern:",box.pattern].postln; // launch a sound

box.playing = true

},{

["stop this pattern:",box.pattern].postln; // launch a sound

box.playing = false

})

};

/* could even do this usage:

box.use {

~playing = true

} */

};


x.refresh

)


To show the box's playing/stopped state you will want to use a named style rather then set each box's individual colors.



Named Styles


A style is a dictionary that is used to transform the default box style.

A style is referred to by its name (a symbol)



There are existing style transforms for:


focused (the last clicked or activated box)

// this simply sets the borderColor

        styles['focused'] = (

            borderColor: Color(0.2499443083092, 0.55516802266236, 0.76119402985075)

            );


down (when button is pushed. aka. active in html speak)

dragging (while dragging the box)

// an example of a more complex style using a function

// this blends the box's color with a translucent blue

// and set the borderColor to blue

        styles['dragging'] = (

            boxColor: { arg c; c.blend(Color.blue(alpha:0.2),0.8) },

            borderColor: Color.blue(alpha:0.5)

            );

over (mouse over, but user view doesn't implement that yet)




There are also styles predefined for these common semantic usages:


playing

selected

deactivated

makes everything translucent



You can set the default style transforms as you like:


x.styles['down'] = (

boxColor: { arg c; c.darken(Color.red,0.9) }

);



So now we can make use of these styles:

(

x = BoxMatrix(nil,400@400,4,4);


x.defaultStyle.center = true;


4.do { |i|

b = x.at(i@i);

b.title = i.asString;

b.pattern = i;

b.boxColor = Color.rand;

b.playing = false;

};


// play the pattern on click

x.mouseDownAction = { arg box,modifiers,buttonNumber,clickCount;

if(box.pattern.notNil) {

if((box.playing).not,{

["play:",box.pattern].postln; // launch a sound

box.playing = true;

// add the playing style

x.addStyle(box,'playing');

},{

["stop:",box.pattern].postln; // kill a sound

box.playing = false;

// remove style

x.removeStyle(box,'playing');

})

}

};


x.refresh

)


Notice that style transforms are additive.


eg.

default + playing + focused

default + selected + dragging


so borders may be changed, colors may be made more saturated or more translucent


cascade order:


defaultStyle

+

box's custom style settings: eg box.boxColor = Color.red

+

styles applied by name to the box: eg x.addStyle(box,'playing')

+

dynamic styles added by the matrix : focused, down, over, dragging




You can also declare your own custom styles to represent any states you wish


like 'pending', 'terminal','verrückt'


you can layer multiple style transforms on a box:


x.addStyle(box,'frenzied');

x.addStyle(box,'playing');


and use any of the methods in Color to generate useful visual transforms.

you can change font face, size, color




Dragging


You can drag boxes to other boxes and implement that as moving, copying, layering, connecting to or whatever you like.


You cannot drag views from outside of a user view onto a specific box on the user view.  This is an SCUserView limitation and will be fixed at some point.  The last focused box will receive any drags.


Dragging a box (using command drag) off the matrix will call the begingDragHandler with the last focused box, or if none is set will return the focused box's environment as the dragged object.


(


x = BoxMatrix(nil,500@500,5,5);


x.onBoxDrag = { arg fromBox,toBox,modifiers;


[fromBox,toBox].postln;

// in this case move the contents of the box

x.move(fromBox.point,toBox.point);

};

x.mouseDownAction = { arg box,modifiers,buttonNumber,clicks;

[box,modifiers,buttonNumber,clicks].debug;

if(clicks==2,{

box.title = 100.rand.asString

})

};

)


// other methods for moving things

x.move(f,t)

x.copy(f,t)

x.swap(f,t)

x.clear(f)



// set how far you have to drag before dragging kicks in

x.dragOn = 20


// or drag only on a modifier

x.dragOn = 'isAlt'

x.dragOn = 'isCntrl'

x.dragOn = 'isCmd'

x.dragOn = 'isShift'


// back to non-modifier drag

x.dragOn = 20


// disable dragging completely

x.dragOn = inf



// the keyboard modifier on mouse up (drop time) is passed in

// so you can differentiate between alt drag and shift-alt drag


// drag got move, option-drag to copy

(


x = BoxMatrix(nil,nil,15,32);


x.at(3@3).title = "purr";

x.at(3@3).boxColor = Color.rand;



x.onBoxDrag = { arg fromBox,toBox,modifier;

var newTitle;

[fromBox,toBox].debug;

if(modifier.isAlt,{

// option drag copies

x.copy(fromBox.point,toBox.point);

newTitle = (fromBox.title ? "") + "copy";

// won't work: we just copied into that location

// so the toBox we have here has been replaced

// toBox.title = newTitle;


toBox = x.at(toBox.point);

toBox.title = newTitle;

x.refresh

},{

// else move it

x.move(fromBox.point,toBox.point);

});


};


)

Yes, something has to be done about the text overlaps



keyDownAction


the handler is passed the focused box


delete key deletes

(

x = BoxMatrix(nil,400@400,16,16);


x.defaultStyle.center = true;


16.do { |i|

b = x.at(16.rand@16.rand);

b.title = i.asString;

b.pattern = i;

b.boxColor = Color.rand;

};


x.keyDownAction = { arg box, char,modifiers,unicode,keycode;

[ box, char,modifiers,unicode,keycode].debug;

if(unicode == 127) {

box.title = "";

box.pattern = nil;

box.boxColor = x.defaultStyle.boxColor;

box.refresh;

}

};


x.refresh;


)



Public methods


mouseDownAction

mouseUpAction

mouseOverAction

mouseMoveAction

onBoxDrag

copy(fromPoint,toPoint)

move(fromPoint,toPoint)

swap(fromPoint,toPoint)

clear(point)

keyUpAction

keyDownAction

keyModifiersChangedAction

at(point)

set(point,attribute,value)

setAll(point,dict)

withBox(point,function)

get(point,key)

from the box envir at point, fetch the value for key

getBox(point)

focusedBox

returns the last clicked box

transferFocus(toPoint)

set focus to box at this point

addStyle(box,styleName)

append the named style to the box's list of styles

removeStyle(box,styleName)

remove the named style if it is in the box's list of styles

refresh

draggingPoint

the point that the currently active drag started from

dragOn

a big mythical fire breathing creature.

number: distance to drag before dragging kicks in

inf: drag disabled

symbol: selector to match the modifier: isCtrl isShift isAlt etc

styles

a dictionary of style transformers

defaultStyle

a dictionary of the colors, font etc for a default box with no named style applied

numRows

numCols