class:: EZSlider
summary:: Wrapper class for label, slider, number box
categories:: GUI>EZ-GUI
related:: Classes/Slider, Classes/NumberBox, Classes/StaticText, Classes/CompositeView, Classes/EZGui

description::
EZSlider is wrapper class which creates an (optional) link::Classes/StaticText::, and a link::Classes/Slider:: plus a link::Classes/NumberBox::. If the parent is code::nil::, then EZSlider will create its own window. See link::Classes/EZGui:: more options.

subsection:: Scrolling and Arrow Keys
EZSlider's number box scrolls by default, using the step size of the link::Classes/ControlSpec::. If the controlSpec's step is set to 0, or is not set, then the stepping and scrolling will be guessed according to the code::minval:: and code::maxval:: values of the spec on creation of the view.  Unlike the step variable of a regular link::Classes/NumberBox::, code::controlSpec.step:: is also the smallest possible increment for the link::Classes/NumberBox::. By default, the shift-key modifier will allow you to step by 100x code::controlSpec.step::, while the ctrl-key will give you 10x code::controlSpec.step::.  Since the alt-key would give you 0.1 of the minimum step, it is disabled by default, but you can change that by setting code::numberView.alt_step:: to any value you like. Accordingly you can customize the other modifiers to fit your needs. See link::Classes/NumberBox:: and link::Classes/Slider::. This also effects the arrow keys for the slider.

classmethods::

subsection:: Creation / Class Methods

method:: new
argument:: parent
The parent view or window. If the parent is code::nil::, then EZSlider will create its own link::Classes/Window::, and place it conveniently on the screen if the bounds are a link::Classes/Point::. If the bounds are a link::Classes/Rect::, then the link::Classes/Rect:: determines the window bounds.

argument:: bounds
An instance of link::Classes/Rect:: or link::Classes/Point::. Default value is code::160@20::.

argument:: label
The label. Default value is code::nil::. If code::nil::, then no link::Classes/StaticText:: is created.

argument:: controlSpec
The link::Classes/ControlSpec:: for scaling the value.

argument:: action
A link::Classes/Function:: called when the value changes. The function is passed the EZSlider instance as its argument.

argument:: initVal
The value to initialize the slider and number box with. If code::nil::, then it uses the link::Classes/ControlSpec::'s default value.

argument:: initAction
A link::Classes/Boolean:: indicating whether the action function should be called when setting the initial value. The default is false.

argument:: labelWidth
Number of pixels width for the label. The default is 60.

argument:: numberWidth
Number of pixels width for the number box. The default is 45.

argument:: unitWidth
Number of pixels width for the unit label. The default is 0. If 0, then no unitLabel is created.

argument:: labelHeight
The default is 20;

argument:: layout
code::\vert::, code::\line2::, or code::\horz::. The default is code::\horz::.

argument:: gap
A link::Classes/Point::. By default, the view tries to get its parent's gap, otherwise it defaults to code::2@2::. Setting it overrides these.

argument:: margin
A link::Classes/Point::. This will inset the bounds occupied  by the subviews of view.

discussion::
code::
(
w = Window.new.front;
g = EZSlider( w,         // parent
              390@20,    // bounds
              " test ",  // label
              \freq,     // controlSpec
              { |ez| (ez.value.asString ++" is the value of " ++ ez).postln } // action
);
g.setColors(Color.grey,Color.white)
);

// Simplest version, no parent view, so a window is created
(
	g = EZSlider(label:" test ");
	g.action_({ |ez| (ez.value.asString ++" is the value of " ++ ez).postln });
);
::
The contained views can be accessed via the EZSlider instance variables: code::labelView::, code::sliderView::, code::numberView::.

instancemethods::

subsection:: Accessing Instance and Class Variables

method:: numberView
Returns the numberView.

method:: action
A link::Classes/Function:: or link::Classes/FunctionList:: to be evaluated when the value changes. The first argument will be the EZSlider.

method:: value
The value of the slider.

method:: round
Rounds the values in the number box.

method:: controlSpec
An instance of ControlSpec for scaling the values.

method:: value
Gets/sets the list/menu to the index at value. Does not perform the action.
argument:: val
An link::Classes/Integer::.

method:: valueAction
Sets the value and performs the action at the index value and the global action.
argument:: val
An link::Classes/Integer::.

method:: doAction
Performs the action at the current index and the global action.

method:: set
Set the args after creation. You can only set the label if it was not nil from the beginning.

method:: visible
Sets/gets it the component views are visible.
argument:: bool
An instance of link::Classes/Boolean::. Default is code::true::.

subsection:: Changing Appearance

method:: setColors
argument:: stringBackground
An instance of link::Classes/Color::. The code::background:: of the label and unit views.
argument:: stringColor
An instance of link::Classes/Color::. The code::stringColor:: of the label and unit views.
argument:: sliderBackground
An instance of link::Classes/Color::. The slider code::background::.
argument:: numBackground
An instance of link::Classes/Color::. The code::numColor:: of the number view.
argument:: numStringColor
An instance of link::Classes/Color::. The code::stringColor:: of the number view.
argument:: numNormalColor
An instance of link::Classes/Color::. The code::normalColor:: of the number view.
argument:: numTypingColor
An instance of link::Classes/Color::. The code::typingColor:: of the number view.
argument:: knobColor
An instance of link::Classes/Color::. The code::knobColor:: of the knob view.
argument:: background
An instance of link::Classes/Color::. The code::background:: of the enclosing view.

method:: font
Set the Font used by all the views.
argument:: font
An instance of link::Classes/Font::.

examples::
code::
(	// basic use
	w=Window.new.front;
	g=EZSlider(w, 400@16," test  ", \freq,unitWidth:30, numberWidth:60,layout:\horz);
	g.setColors(Color.grey,Color.white);
);
g.view.enabled=false
// lots of sliders on on view
(
w=Window.new.front;
w.view.decorator=FlowLayout(w.view.bounds);
w.view.decorator.gap=2@2;

20.do{
	EZSlider(w, 392@16," Freq ", \freq,unitWidth:30,initVal:6000.rand, numberWidth:60,layout:\horz)
	.setColors(Color.grey,Color.white)
	.font_(Font("Helvetica",11));

};
);

Window.closeAll  // use this to close all the windows

/////////////////////////////////////////////////////////////////
////////// click these parentheses to see all features and layouts

(

m=nil;
//m=2@2;		// uncomment this for margin

/////////////////
/// Layout \horz

(		// all features, small font
		g=EZSlider(nil, 400@14," freq  ", \freq,unitWidth:30, numberWidth:60,layout:\horz, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(-180,50);
		g.font_(Font("Helvetica",10));
);

(		// no unitView
		g=EZSlider(nil, 400@16," freq  ", \freq,unitWidth:0, numberWidth:60,layout:\horz, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(-180, -20);
);
(		// no label, so use window name as label
		g=EZSlider(nil, 400@16, nil, \freq,unitWidth:0, numberWidth:60,layout:\horz, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(-180, -90);
		g.window.name="Freq";
);

/////////////////
/// Layout \line2

(		// all features
		g=EZSlider(nil, 300@42," freq  ", \freq,unitWidth:30, numberWidth:60,layout:\line2, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(-180,-160);
);

(		// no unitView, with label
		g=EZSlider(nil, 300@42," freq  ", \freq,unitWidth:0, numberWidth:60,layout:\line2, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(-180,-260);
);

(		// no label
		g=EZSlider(nil, 300@42,nil, \freq, unitWidth:30, numberWidth:60,layout:\line2, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(-180,-360);
		g.window.name="Freq";
);

(		// no lablel, so use window name as label
		g=EZSlider(nil, 150@42,nil, \freq,unitWidth:0, numberWidth:60,layout:\line2, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(-180,-460);
		g.window.name="Freq";
);

/////////////////
/// Layout \vert

(		// all features, small font
		g=EZSlider(nil, 45@300," Vol  ", \db.asSpec.step_(0.01),unitWidth:30, numberWidth:60,layout:\vert, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(250,50);
		g.font_(Font("Helvetica",10));
);
(		// no label, small font
		g=EZSlider(nil, 45@300, nil, \db.asSpec.step_(0.01),unitWidth:30, numberWidth:60,layout:\vert, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(310,50);
		g.font_(Font("Helvetica",10));
);
(		// no Units small font
		g=EZSlider(nil, 45@300, " Vol", \db.asSpec.step_(0.01),unitWidth:0, numberWidth:60,layout:\vert, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(370,50);
		g.font_(Font("Helvetica",10));
);
(		// no unitView, no Units small font
		g=EZSlider(nil, 45@300, nil, \db.asSpec.step_(0.01),unitWidth:0, numberWidth:60,layout:\vert, margin: m);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,
			Color.white, Color.yellow,nil,nil, Color.grey(0.7));
		g.window.bounds = g.window.bounds.moveBy(430,50);
		g.font_(Font("Helvetica",10));
);

)




///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


// Sound example
(
// start server
s.waitForBoot({

var w, startButton, noteControl, cutoffControl, resonControl;
var balanceControl, ampControl;
var node, cmdPeriodFunc;

// define a synth
SynthDef("window-test", { |out, note = 36, fc = 1000, rq = 0.25, bal=0, amp=0.4, gate = 1|
		var x;
		x = Mix.fill(4, {
			LFSaw.ar((note + {0.1.rand2}.dup).midicps, 0, 0.02)
		});
		x = RLPF.ar(x, fc, rq).softclip;
		x = RLPF.ar(x, fc, rq, amp).softclip;
		x = Balance2.ar(x[0], x[1], bal);
		x = x * EnvGen.kr(Env.cutoff, gate, doneAction: Done.freeSelf);
		Out.ar(out, x);
	}, [0.1, 0.1, 0.1, 0.1, 0.1, 0]
).add;




// make the window
w = Window("another control panel", Rect(20, 400, 440, 180));
w.front; // make window visible and front window.
w.view.decorator = FlowLayout(w.view.bounds);
w.view.decorator.gap=2@2;

// add a button to start and stop the sound.
startButton = Button(w, 75 @ 20);
startButton.states = [
	["Start", Color.black, Color.green(0.7)],
	["Stop", Color.white, Color.red(0.7)]
];
startButton.action = {|view|
		if (view.value == 1) {
			// start sound
			node = Synth( "window-test", [
				"note", noteControl.value,
				"fc", cutoffControl.value,
				"rq", resonControl.value,
				"bal", balanceControl.value,
				"amp", ampControl.value.dbamp ]);
		} {
			// set gate to zero to cause envelope to release
			node.release; node = nil;
		};
};

// create controls for all parameters
w.view.decorator.nextLine;
noteControl = EZSlider(w, 430 @ 20, "Note ", ControlSpec(24, 60, \lin, 1, 36, \note),
	{|ez| node.set( "note", ez.value )}, unitWidth:30)
		.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey, Color.white, Color.yellow);

w.view.decorator.nextLine;
cutoffControl = EZSlider(w, 430 @ 20, "Cutoff ", ControlSpec(200, 5000, \exp,0.01,1000,\Hz),
	{|ez| node.set( "fc", ez.value )}, unitWidth:30)
		.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey, Color.white, Color.yellow);

w.view.decorator.nextLine;
resonControl = EZSlider(w, 430 @ 20, "Reson ", ControlSpec(0.1, 0.7,\lin,0.001,0.2,\rq),
	{|ez| node.set( "rq", ez.value )}, unitWidth:30)
		.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey, Color.white, Color.yellow);

w.view.decorator.nextLine;
balanceControl = EZSlider(w, 430 @ 20, "Balance ", \bipolar,
	{|ez| node.set( "bal", ez.value )},  unitWidth:30)
		.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey, Color.white, Color.yellow);

w.view.decorator.nextLine;
ampControl = EZSlider(w, 430 @ 20, "Amp ", \db,
	{|ez| node.set( "amp", ez.value.dbamp )}, -6, unitWidth:30)
		.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey, Color.white, Color.yellow);


// set start button to zero upon a cmd-period
cmdPeriodFunc = { startButton.value = 0; };
CmdPeriod.add(cmdPeriodFunc);

// stop the sound when window closes and remove cmdPeriodFunc.
w.onClose = {
	node.free; node = nil;
	CmdPeriod.remove(cmdPeriodFunc);
};
});
)




// a variant of the above example so one can
// add new parameters and more views are created automatically

(
// start server
s.waitForBoot({

var w, startButton, sliders;
var node, cmdPeriodFunc;
var params, specs;

// define a synth
SynthDef("window-test", { |out, note = 36, fc = 1000, rq = 0.25, bal = 0, amp=0.4, width=0, gate = 1|
		var x;
		x = Mix.fill(4, {
			VarSaw.ar((note + {0.1.rand2}.dup).midicps, 0, width, 0.02)
		});
		x = RLPF.ar(x, fc, rq).softclip;
		x = RLPF.ar(x, fc, rq, amp).softclip;
		x = Balance2.ar(x[0], x[1], bal);
		x = x * EnvGen.kr(Env.cutoff, gate, 5, doneAction: Done.freeSelf);
		Out.ar(out, x);
	}, [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0]
).add;


params = ["note", "fc", "rq", "bal", "amp", "width"];
specs = [
	ControlSpec(24, 60, \lin, 1, 36, \note),
	ControlSpec(200, 5000, \exp,0.01,1000,\Hz),
	ControlSpec(0.1, 0.7,\lin,0.001,0.2,\rq),
	ControlSpec(-1, 1, \lin, 0, 0, \pan),
	ControlSpec(0.0001, 2, \exp, 0, 0.3, \vol), // db spec acts weird, so use self made one
	ControlSpec(0, 1, \lin, 0, 0.3, \width),
];

// make the window
w = Window("another control panel", Rect(20, 400, 440, 180));
w.front; // make window visible and front window.
w.view.decorator = FlowLayout(w.view.bounds);
w.view.decorator.gap=2@2;


// add a button to start and stop the sound.
startButton = Button(w, 75 @ 20);
startButton.states = [
	["Start", Color.black, Color.green(0.7)],
	["Stop", Color.white, Color.red(0.7)]
];
startButton.action = {|view|
		var args;
		if (view.value == 1) {
			// start sound
			params.do { |param, i|
				args = args.add(param);
				args = args.add(sliders[i].value)
			};
			node = Synth("window-test", args.postcs);
		} {
			// set gate to zero to cause envelope to release
			node.release; node = nil;
		};
};

// create controls for all parameters
w.view.decorator.nextLine;
sliders = params.collect { |param, i|
	EZSlider(w, 430 @ 20, param, specs[i], {|ez| node.set( param, ez.value )})
		.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey, Color.white, Color.yellow);
};
// set start button to zero upon a cmd-period
cmdPeriodFunc = { startButton.value = 0; };
CmdPeriod.add(cmdPeriodFunc);

// stop the sound when window closes and remove cmdPeriodFunc.
w.onClose = {
	node.free; node = nil;
	CmdPeriod.remove(cmdPeriodFunc);
};

})
)
::
