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