class:: EZRanger
summary:: A wrapper class for a label, a rangeslider, and numberboxes
categories:: GUI>EZ-GUI
related:: Classes/EZGui, Classes/StaticText, Classes/Slider, Classes/NumberBox

description::
EZRanger 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 EZRanger will create its own window. See link::Classes/EZGui:: more options.

subsection:: Scrolling and Arrow Keys
EZRanger's number boxs scroll 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::s.  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::

method:: new

argument:: parent
The parent view or window. If the parent is nil, then EZRanger 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 EZRanger instance as its argument.

argument:: initVal
An instance of link::Classes/Array:: code::[lo, hi]::. 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 code::false::.

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

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

argument:: unitWidth
Number of pixels width for the unit label. The default is 0. If 0, then no code::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 = EZRanger(w, 400@16," test  ", \freq, { |v| v.value.postln }, [50,2000], unitWidth:30)
)

// Simplest version, no parent view, so a window is created
(
EZRanger(nil, 400@16," test  ", \freq, { |v| v.value.postln }, [50,2000])
)
::
The contained views can be accessed via the EZRanger instance variables: code::rangeSlider::, code::hiBox::, code::loBox::, code::unitView::, code::labelView::.

instancemethods::

subsection:: Accessing Instance and Class Variables

method:: unitView
The units label. Only appears if code::unitWidth:: was set to > 0.

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

method:: loBox
The code::lo:: value link::Classes/NumberBox::.

method:: action
Set/get a link::Classes/Function:: or link::Classes/FunctionList:: to be evaluated when the value changes. The first argument will be the EZRanger.

method:: rangeSlider
The link::Classes/RangeSlider:: link::Classes/View::

method:: lo
Set/get the low value.

method::hi
Set/get the high value

method:: hiBox
The hi value link::Classes/NumberBox::.

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

subsection:: Doing Some Task (optional)

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

method:: value
Gets/sets the code::lo:: and code::hi:: values.
argument:: vals
An instance of link::Classes/Array:: code:: [lo, hi] ::.

method:: valueAction
Sets the value and performs the action at the index value and the global action.
argument:: vals
An instance of link::Classes/Array:: code:: [lo, hi] ::.

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:: sliderColor
An instance of link::Classes/Color::. The slider code::background::.
argument:: numBackground
An instance of link::Classes/Color::. The code::background:: 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 slider 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=EZRanger(w, 400@16," test  ", \freq,{|v| v.value.postln},[50,2000],unitWidth:30);
	g.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey, Color.white, Color.yellow);
);

// lots of range sliders on a view
(
w=Window.new.front;
w.view.decorator=FlowLayout(w.view.bounds);
w.view.decorator.gap=1@1;

20.do{
	g=EZRanger(w, 400@16," test  ", \freq,{|v| v.value.postln},[50.rand,50+20000.rand],unitWidth:30)
	.setColors(Color.grey,Color.white, Color.grey(0.7),Color.grey,Color.white, Color.white, Color.yellow)
	.font_(Font("Helvetica",11));

};
);

Window.closeAll

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

m=nil;
m=2@2; // comment for no margin


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

(		// all features, small font
		g=EZRanger(nil, 400@16," freq  ", \freq,
			initVal:[100.rand,200+2000.rand],unitWidth:30, numberWidth:60,layout:\horz, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white));
		g.window.bounds = g.window.bounds.moveBy(-180,50);
		g.font_(Font("Helvetica",10));
);

(		// no unitView
		g=EZRanger(nil, 400@16," freq  ", \freq,initVal:[100.rand,200+2000.rand],
			unitWidth:0, numberWidth:60,layout:\horz, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white));
		g.window.bounds = g.window.bounds.moveBy(-180, -20);
		g.font_(Font("Helvetica",10));
);
(		// no label, so use window name as label
		g=EZRanger(nil, 400@16, nil, \freq,initVal:[100.rand,200+2000.rand],
			unitWidth:0, numberWidth:60,layout:\horz, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white));
		g.window.bounds = g.window.bounds.moveBy(-180, -90);
		g.window.name="Freq";
		g.font_(Font("Helvetica",10));
);

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

(		// all features
		g=EZRanger(nil, 300@42," freq  ", \freq,initVal:[100.rand,200+2000.rand],
			unitWidth:30, numberWidth:60,layout:\line2, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white));
		g.window.bounds = g.window.bounds.moveBy(-180,-160);
);

(		// no unitView, with label
		g=EZRanger(nil, 300@42," freq  ", \freq,initVal:[100.rand,200+2000.rand],
			unitWidth:0, numberWidth:60,layout:\line2, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white));
		g.window.bounds = g.window.bounds.moveBy(-180,-260);
);

(		// no label
		g=EZRanger(nil, 300@42,nil, \freq, initVal:[100.rand,200+2000.rand],
			unitWidth:30, numberWidth:60,layout:\line2, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white));
		g.window.bounds = g.window.bounds.moveBy(-180,-360);
		g.window.name="Freq";
);

(		// no label, so use window name as label
		g=EZRanger(nil, 150@42,nil, \freq,initVal:[100.rand,200+2000.rand],
			unitWidth:0, numberWidth:60,layout:\line2, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white));
		g.window.bounds = g.window.bounds.moveBy(-180,-460);
		g.window.name="Freq";
);

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

(		// all features, small font
		g=EZRanger(nil, 45@300," Vol  ", \db.asSpec.step_(0.01),initVal:[-3-15.rand,-2.rand],
			unitWidth:30, numberWidth:60,layout:\vert, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white,\h));
		g.window.bounds = g.window.bounds.moveBy(250,50);
		g.font_(Font("Helvetica",9));
);
(		// no label, small font
		g=EZRanger(nil, 45@300, nil, \db.asSpec.step_(0.01),initVal:[-3-15.rand,-2.rand],
			unitWidth:30, numberWidth:60,layout:\vert, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white,\h));
		g.window.bounds = g.window.bounds.moveBy(310,50);
		g.font_(Font("Helvetica",9));
);
(		// no Units small font
		g=EZRanger(nil, 45@300, " Vol", \db.asSpec.step_(0.01),initVal:[-3-15.rand,-2.rand],
			unitWidth:0, numberWidth:60,layout:\vert, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white,\h));
		g.window.bounds = g.window.bounds.moveBy(370,50);
		g.font_(Font("Helvetica",9));
);
(		// no unitView, no units small font
		g=EZRanger(nil, 45@300, nil, \db.asSpec.step_(0.01),initVal:[-3-15.rand,-2.rand],
			unitWidth:0, numberWidth:60,layout:\vert, margin:2@2);
		g.setColors(Color.grey,Color.white, Color.grey(0.7),
			Color.grey, Color.white, Color.yellow, background:Color.grey(0.7),
				knobColor: HiliteGradient(Color.grey, Color.white,\h));
		g.window.bounds = g.window.bounds.moveBy(430,50);
		g.font_(Font("Helvetica",9));
);

)

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

////Sound Example


(	// example to explore a synthesis idea:
p = ProxySpace.push(s.boot);

q = q ? ();
q.freqRange = [200, 2000];
q.ampRange = [0.1, 1];
q.ringRange = [0.1, 10];
q.numRange = [3, 30];

q.soundfunc = { |dens=5|
	Splay.ar(
		Array.fill(exprand(q.numRange[0], q.numRange[1]).asInteger, {
			Ringz.ar(
				Dust.ar(dens),
				exprand(q.freqRange[0], q.freqRange[1]),
				exprand(q.ringRange[0], q.ringRange[1]),
				exprand(q.ampRange[0], q.ampRange[1])
			)
		})
	).distort
};
)
~plong.play;

~plong.fadeTime = 3;
~plong = q[\soundfunc];

(
w = Window("cow herd").front;
w.view.decorator_(FlowLayout(w.bounds.copy.moveTo(0, 0)));

Spec.add(\ring, [0.03, 30, \exp]);
Spec.add(\num, [3, 30, \exp, 1]);

EZRanger(w, 390@20, "numRange", \num, { |sl| q.numRange = sl.value; }, labelWidth: 65)
	.round_(1);

EZRanger(w, 390@20, "freqRange", \freq, { |sl| q.freqRange = sl.value; }, q.freqRange, labelWidth: 65)
	.round_(0.1);
EZRanger(w, 390@20, "ringRange", \ring, { |sl| q.ringRange = sl.value; }, q.ringRange, labelWidth: 65)
	.round_(0.0001);
EZRanger(w, 390@20, "ampRange", \amp, { |sl| q.ampRange = sl.value; }, q.ampRange, labelWidth: 65)
	.round_(0.0001);
Button(w, 190@20).states_([[\newSound]]).action_({~plong = q[\soundfunc] });
)
::
