#lang scribble/manual @(require (for-label racket)) @title{EZSlider} Wrapper class for label, slider, number box@section{categories} GUI>EZ-GUI @section{related} Classes/Slider, Classes/NumberBox, Classes/StaticText, Classes/CompositeView, Classes/EZGui @section{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 @racketblock[nil::, then EZSlider will create its own window. See link::Classes/EZGui:: more options. ] @section{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 the stepping and scrolling will be guessed according to the @racketblock[minval:: and ] @racketblock[maxval:: values of the spec on creation of the view. Unlike the step variable of a regular link::Classes/NumberBox::, ] @racketblock[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 ] @racketblock[controlSpec.step::, while the ctrl-key will give you 10x ] @racketblock[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 ] @racketblock[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. ] @section{classmethods} @section{subsection} Creation / Class Methods @section{method} new @section{argument} parent The parent view or window. If the parent is @racketblock[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. ] @section{argument} bounds An instance of link::Classes/Rect:: or link::Classes/Point::. Default value is @racketblock[160@20::. ] @section{argument} label The label. Default value is @racketblock[nil::. If ] @racketblock[nil::, then no link::Classes/StaticText:: is created. ] @section{argument} controlSpec The link::Classes/ControlSpec:: for scaling the value. @section{argument} action A link::Classes/Function:: called when the value changes. The function is passed the EZSlider instance as its argument. @section{argument} initVal The value to initialize the slider and number box with. If @racketblock[nil::, then it uses the link::Classes/ControlSpec::'s default value. ] @section{argument} initAction A link::Classes/Boolean:: indicating whether the action function should be called when setting the initial value. The default is false. @section{argument} labelWidth Number of pixels width for the label. The default is 60. @section{argument} numberWidth Number of pixels width for the number box. The default is 45. @section{argument} unitWidth Number of pixels width for the unit label. The default is 0. If 0, then no unitLabel is created. @section{argument} labelHeight The default is 20; @section{argument} layout @racketblock[\vert::, ] @racketblock[\line2::, or ] @racketblock[\horz::. The default is ] @racketblock[\horz::. ] @section{argument} gap A link::Classes/Point::. By default, the view tries to get its parent's gap, otherwise it defaults to @racketblock[2@2::. Setting it overrides these. ] @section{argument} margin A link::Classes/Point::. This will inset the bounds occupied by the subviews of view. @section{discussion} @racketblock[ ( 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: ] @racketblock[labelView::, ] @racketblock[sliderView::, ] @racketblock[numberView::. ] @section{instancemethods} @section{subsection} Accessing Instance and Class Variables @section{method} numberView Returns the numberView. @section{method} action A link::Classes/Function:: or link::Classes/Function@section{List} to be evaluated when the value changes. The first argument will be the EZSlider. @section{method} value The value of the slider. @section{method} round Rounds the values in the number box. @section{method} controlSpec An instance of ControlSpec for scaling the values. @section{method} value Gets/sets the list/menu to the index at value. Does not perform the action. @section{argument} val An link::Classes/Integer::. @section{method} valueAction Sets the value and performs the action at the index value and the global action. @section{argument} val An link::Classes/Integer::. @section{method} doAction Performs the action at the current index and the global action. @section{method} set Set the args after creation. You can only set the label if it was not nil from the beginning. @section{method} visible Sets/gets it the component views are visible. @section{argument} bool An instance of link::Classes/Boolean::. Default is @racketblock[true::. ] @section{subsection} Changing Appearance @section{method} setColors @section{argument} stringBackground An instance of link::Classes/Color::. The @racketblock[background:: of the label and unit views. ] @section{argument} stringColor An instance of link::Classes/Color::. The @racketblock[stringColor:: of the label and unit views. ] @section{argument} sliderBackground An instance of link::Classes/Color::. The slider @racketblock[background::. ] @section{argument} numBackground An instance of link::Classes/Color::. The @racketblock[numColor:: of the number view. ] @section{argument} numStringColor An instance of link::Classes/Color::. The @racketblock[stringColor:: of the number view. ] @section{argument} numNormalColor An instance of link::Classes/Color::. The @racketblock[normalColor:: of the number view. ] @section{argument} numTypingColor An instance of link::Classes/Color::. The @racketblock[typingColor:: of the number view. ] @section{argument} knobColor An instance of link::Classes/Color::. The @racketblock[knobColor:: of the knob view. ] @section{argument} background An instance of link::Classes/Color::. The @racketblock[background:: of the enclosing view. ] @section{method} font Set the Font used by all the views. @section{argument} font An instance of link::Classes/Font::. @section{examples} @racketblock[ ( // 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", { arg 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(0, 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", { arg 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(0, 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); }; }) ) :: ]