RoundButton

part of wslib



a Pen-based replacement for SCButton, with extra styling options. 



RoundButton works the same way as SCButton. 

But, as the name states, it is rounded.


(

w = Window( "RoundButton" ).front;

w.addFlowLayout;

b = RoundButton( w, 80@20 ).states_([[ "hit me" ], [ "again" ]] ).action_({ "was hit".postln });

)


Action can be either a function or an array of functions. In cas of an array each function is called at the swich to a new state.


b.action = [ { "was hit".postln }, { "was hit again".postln } ];


If a RoundButton's bounds are a square, the button shape becomes a circle:


c = RoundButton( w, 20@20 ).states_([[ "x", Color.red(0.5), Color.gray(0.6) ]] );


RoundButton also supports icons from DrawIcon. If the button state name is a Symbol

it is replaced by an icon with that name.


k = RoundButton( w, 80@20 ).states_([[ \play ]] );


l = RoundButton( w, 20@20 ).states_([[ \stop, Color.white, Color.black ]] );


States can also contain instances of SCImage. They will be displayed at original size, centered in the button and not clipped.


// osx only until JSCImage answers to .width and .height

o = Image.open(String.scDir ++ "/SuperCollider.app/Contents/Resources/SCcube.icns");

p = RoundButton(w, 250@150).states_( [ [o] ] );


Functions can also be used as a state. They will be used as drawing functions, which receive a the button, a rect and the radius as arguments. Drawing is always relative to the origin of the button's bounds.


(

u = RoundButton(w, 80@20).states_( [ 

[{ |button, rect, radius| 

Pen.line( rect.leftBottom, rect.rightTop );

Pen.line( rect.rightBottom, rect.leftTop );

Pen.stroke;

}] ] );

)


The fillcolor of a state can also be a Gradient, or a SCImage.


(

q = RoundButton(w, 80@20).states_( [ 

["horizontal", Color.white, Gradient( Color.blue(0.5), Color.red )],

["vertical", Color.white, Gradient( Color.black, Color.white.alpha_(0.25), \v)]] );

)


(

r = SCImage("/Library/Desktop Pictures/Ripples Blue.jpg");

t = RoundButton(w, 80@20).states_( [["image", Color.black, r  ]] );

)


There are various convenience methods for setting states and functions. The label setting method automatically creates one or more states:


u = RoundButton(w, 80@20).label_( "hello" ); // creates a single state

v = RoundButton(w, 80@20).label_( [ "hello", "bye" ] ); // creates two states


The hiliteColor sets the color of the second state (if available and not specified in the state itself). This corresponds with the hiliteColor setting from SmoothSlider.


x = RoundButton(w, 80@20).label_( "hello".dup ).hiliteColor_( Color.blue );


RoundButton has some extra features for graphic finetuning:


radius

the radius of the round corners in px


d = RoundButton( w, 80@20 ).states_([[ "radius=0", Color.black, Color.gray(0.7) ]] ).radius_( 0 ); 


e = RoundButton( w, 80@20 ).states_([[ "radius=5",Color.gray(0.8),Color.blue(0.6) ]] ).radius_( 5 );


f = RoundButton( w, 80@20 ).states_([[ "radius=Array" ]]).radius_( [0,10,4,7] );


border

border width in px (default 2)


h = RoundButton( w, 80@20 ).states_([[ "border=0" ]] ).border_( 0 );


i = RoundButton( w, 80@20 ).states_([[ "border=5" ]] ).border_( 5 );


extrude

bevel extrusion boolean (default = true). A.k.a. bevel


j = RoundButton( w, 80@20 ).states_([[ "no extrusion" ]] ).extrude_( false );


you can use bevel as well, which does the same thing.



moveWhenPressed

amount of px to move the button label by when pressed


j.moveWhenPressed_( 0 );



inverse

inverse border colors


m = RoundButton( w, 20@20 ).states_([[ \speaker ]] ).inverse_( true );


m.inverse_( false );


n = RoundButton( w, 20@20 ).states_([[ \arrow ]] ).extrude_(false).border_(1).inverse_( true );


stringColor

sets the default string color (if not specified in the states)


o = RoundButton( w, 80@20 ).label_("white").background_(Color.black).stringColor_(Color.white);


p = RoundButton( w, 80@20 ).states_([["red", Color.red], ["blue"]]).stringColor_(Color.blue);




These (and in fact all) methods can be set manually or via the skin system (inherited from superclass RoundView). Skinning works in varous ways. A skin is a Dictionary or Event containing keys and values for each property to be set. One is to set a skin via the skin method:


(

w = Window( "RoundButton skins" ).front;

w.addFlowLayout;


RoundButton.skin = ( 

radius: 3, 

border: 1,

font: Font( Font.defaultSansFace, 11 ),

background: Color.gray(0.9), 

stringColor: Color.gray(0.2),

extrude: false 

);


a = RoundButton( w, 80@18 ).label_( "hi" );

b = RoundButton( w, 40@18 ).label_( "bye" );

)


Once the skin is set it remains there for all future RoundButtons. It can be removed by setting it to nil. Another way is to "push" a skin, via pushSkin. This works the same way as pushing an Environment, i.e. the current skin is stored and a new skin is added. When calling popSkin after this the previous skin will surface again.


(

w = Window( "RoundButton skins" ).front;

w.addFlowLayout;


RoundButton.skin = nil; // remove old one


RoundButton.pushSkin( 

( radius: 5,

inverse: true,

font: Font( Font.defaultSerifFace, 11 )

)

);


a = RoundButton( w, 60@18 ).label_( "skin 1" );

b = RoundButton( w, 40@18 ).label_( "bye" );


RoundButton.pushSkin( 

( extrude: false,

border: 1,

font: Font( Font.defaultMonoFace, 9 )

)

);


c = RoundButton( w, 60@18 ).label_( "skin 2" );

d = RoundButton( w, 40@18 ).label_( "bye" );


RoundButton.popSkin;


e = RoundButton( w, 150@18 ).label_( "back to skin 1" );


)


This can also be done in the form of useWithSkin( skin, function ):


(

w = Window( "RoundButton skins" ).front;

w.addFlowLayout;


RoundButton.skin = nil; // remove old one(s)


RoundButton.useWithSkin( 

( radius: 2,

extrude: false,

background: Color.gray(0.3),

font: Font( Font.defaultSansFace, 11 ).boldVariant,

stringColor: Color.yellow

), {

a = RoundButton( w, 60@18 ).label_( "skinned" );

b = RoundButton( w, 40@18 ).label_( "bye" );

});


c = RoundButton( w, 60@18 ).label_( "default" );

)


And the final way to skin is by doing it directly to the RoundButton itself, via applySkin. This is the best way if the skin is only meant for a few objects in between others.


(

w = Window( "RoundButton skins" ).front;

w.addFlowLayout;


// the skin

z = ( radius: 3,

border: 1,

extrude: false,

background: Gradient( Color.white, Color.gray(0.7), \v ),

font: Font( Font.defaultSansFace, 11 ).boldVariant,

stringColor: Color.red(0.25)

);

a = RoundButton( w, 60@18 ).label_( "normal" );

b = RoundButton( w, 60@18 ).label_( "skinned" ).applySkin( z );

c = RoundButton( w, 120@18 ).label_( "normal again" );

)


The skinning system works for all members of the RoundView family (RoundButton, RoundSlider, SmoothRangeSlider, RoundNumberBox and their 'Smooth' variants, and EZSmoothSlider etc.)


(

RoundButton.pushSkin( ( 

radius: 4,

border: 1,

background: Gradient( Color.white, Color.gray(0.7), \v ),

hiliteColor: Color.blue(0.75).alpha_(0.5),

font: Font( Font.defaultMonoFace, 9 ),

stringColor: Color.blue(0.5),

align: \center,

focusColor: Color.clear,

scroll_step: 0.01

) );


w = Window( "round skin system" ).front;

w.addFlowLayout;


10.do({ |i|

RoundButton( w, 20@20 ).label_( i.asString.dup );

RoundNumberBox( w, 40@20 ).value_((1/(i+1)).round(0.01));

RoundSlider( w, 320@20 ).value_(1/(i+1));

});


RoundButton.popSkin;

)


In this case the properties in the skin are applied to all views. There is an option to single out on a specific type of view. In this example some properties apply to all, and some are overridden for specific view types:


(

RoundButton.pushSkin( ( 


// global properties

radius: 4,

border: 1,

background: Gradient( Color.white, Color.gray(0.7), \v ),

hiliteColor: Color.web16.purple.alpha_(0.5),

font: Font( Font.defaultMonoFace, 9 ),

stringColor: Color.web16.purple.brightness( -0.33 ),

align: \center,

focusColor: Color.clear,

// view specific

'RoundNumberBox': (scroll_step: 0.01, //additional

normalColor: Color.web16.purple.brightness( -0.33 ),

clipLo: 0,

clipHi: 1 ), 

'RoundButton': ( font: Font( Font.defaultSansFace, 9 ).boldVariant ), // override

'RoundRangeSlider': ( background: Gradient( Color.white.alpha_(0), Color.gray(0.7), \h ),

    knobColor: Gradient( Color.white, Color.gray(0.7), \h ),

    stringAlignToKnob: true,

    minRange: 0.1

)

) 

);


w = Window( "round skin system" ).front;

w.addFlowLayout;


10.do({ |i|

RoundButton( w, 20@20 ).label_( i.asString.dup );

RoundNumberBox( w, 40@20 ).value_((1/(i+1)).round(0.01));

RoundRangeSlider( w, 320@20 )

.value_(0.5 + ([-0.5,0.5] /(i+1)))

.string_( i.asString );

});


RoundButton.popSkin;

)