SmoothRangeSlider

part of wslib


a Pen-based replacement for RangeSlider with extra styling options.


SmoothRangeSlider works the mostly same way as RangeSlider. 

But, as the name states, it is smooth. I created this because, frankly, I don't like the looks of RangeSlider.

Next to that it has a lot of extra options. SmoothRangeSlider is inspired and partly based upon blackrains Knob.

SmoothRangeSlider inherits allmost all functionality from SmoothSlider. It has two knobs instead of one, and the hilite area shows the range.


(

w = Window( "SmoothRangeSlider", Rect( 300,150,220,220) ).front;  

b = SmoothRangeSlider( w,  Rect( 90,30,40,160) )

.lo_(0.25)

.hi_(0.75)

.action_({ |sl| [ sl.lo, sl.hi, thisThread.seconds].postln; });

)


One of the advantages of SmoothRangeSlider above SCRangeSlider is that it doesn't need a deferred function to get or set its value when called from a SystemClock thread or OSC/MIDI responder. And when the action is called (via valueAction or doAction) it is done immediately instead of with the AppClock delay


(

t = Task({

var pt;

pt = PintC( Pseq( [b.value, Pwhite(0.0.dup,1.0.dup,inf)], 1 ), 

1/Pseries(2, 0.25,inf) ).asStream;

b.doAction;

  100.do({ |i| 

  b.value = pt.next; 

  b.background = Color.red.alpha_(0.5).blend( Color.black.alpha_(0.25), i/99 );

  0.05.wait }); 

b.doAction; // thisThread.seconds should differ exactly 5s from the first post

}).start;

)


SmoothRangeSlider listens to all messages as SCRangeSlider does, but has a few extra of its own:



mode

Mode indicates the way a SmoothRangeSlider reacts to the mouse. The mode can be 'jump' or 'drag'

'jump' :  (default) when the mouse starts dragging near one of the two ends of the range it will move that side only. When the mouse starts dragging in the center of the range it will affect the both sides, keeping the range in tact.

'drag' :  behaves like SCRangeSlider; the range is altered by clicking and dragging


b.mode_( \drag ); // try moving the slider before and after running this line


b.mode_( \jump ); // back to default


value

The value of the slider is an array containing the lo and hi values. They are sorted internally, so normally the first value is always the lo, and the second the hi. If a single value is entered it will go to both lo and hi.


b.value = [ 0.1, 0.8 ];


b.value.postln;



minRange

maxRange

These getters/setters allow for limitation to the range. The defaults are 0 (minRange) and 1 (maxRange).


b.minRange = 0.25;

b.value = [ 0.1, 0.2 ]; // would result in a range < 0.25

b.value; // -> [ 0.1, 0.35 ] // corrects hi value to match range, keeps lo




You can also drag the slider to see the range limit at work.



rangeMode

When correcting for the minRange / maxRange the slider uses the lo value as reference by default. I.e. when the range is corrected the hi value is changed. This behaviour can be changed with the rangeMode. There are three modes: \lo (default), \hi and \center. The examples below demonstrate the difference:


b.minRange = 0.25;

b.rangeMode = \hi;

b.value = [ 0.2, 0.4 ]; // would result in a range < 0.25

b.value.postln; // -> [ 0.15, 0.4 ] // corrects lo value to match range, keeps hi


b.rangeMode = \center;

b.value = [ 0.2, 0.4 ]; // would result in a range < 0.25

b.value.postln; // -> [ 0.175, 0.425 ] // adjusts both lo and hi value (center as reference)


The rangeMode is also used by the range_ method.


b.rangeMode = \lo;

b.lo = 0.25;

b.range = 0.5;

b.value.postln; // -> [ 0.25, 0.75 ] // corrects hi value to match range, keeps lo



knobColor

background

hilightColor

Colors, Gradients and SCImages can be used with these getter/setters.


( // random colors

w = Window( "colorful", Rect(200,200, 250, 120 ) ).front.decorate;

7.do({ |i| SmoothRangeSlider( w, 30@100 )

.knobColor_( Color.rand(0,0.8).alpha_( [1,0.5,1,1,0,1,0][i] ) )

.background_( Color.rand(0,0.8).alpha_( [1,0.5,0,1,1,0,0][i] ) )

.hilightColor_( Color.rand(0,0.8).alpha_( [1,0.5,1,0,1,0,1][i] ) )

.hi_( i.linlin( 0,6,0.6,0.9) )

.lo_( i.linlin( 0,6,0.4,0.1) )

});

)


( // random gradients

x = { Color.rand(0,0.8).alpha_( [1.0.rand ** 2, 1].choose ) };

w = Window( "gradients", Rect(200,200, 250, 120 ) ).front.decorate;

7.do({ |i| SmoothRangeSlider( w, 30@100 )

.knobColor_( Gradient( x.(), x.(), [\h, \v].choose ) )

.background_( Gradient( x.(), x.(), [\h, \v].choose ) )

.hilightColor_( Gradient( x.(), x.(), [\h, \v].choose ) )

.hi_( i.linlin( 0,6,0.6,0.9) )

.lo_( i.linlin( 0,6,0.4,0.1) )

});

)


( // random pictures :: OSX only

// downloads a selection of (free) patterns from the web

// ... might take a while ...

p = [ 1, 16, 18, 30, 35, 67, 72, 77, 118, 119, 120, 124, 125, 126, 128, 134, 135, 136, 141, 

150, 155, 156, 157, 160, 161, 172, 173, 184, 185, 190, 195, 196 ].scramble[..20];

p = p.collect({ |nr| "http://static1.grsites.com/archive/textures/blue/blue%.jpg"

.format( nr.asStringToBase(10,3) ); });

w = Window( "pictures", Rect(200,200, 316, 210 ) ).front.decorate;

7.do({ |i|

i = i*3;

SmoothRangeSlider( w, 40@200 )

.knobColor_( SCImage( p[i] ) )

.background_( SCImage( p[i+1] ) )

.hilightColor_( SCImage( p[i+2] ) )

.value_([i.linlin( 0,18,0.4,0.1),i.linlin( 0,18,0.6,0.9)] )

.borderColor_(Color.clear)

});

)


(

w = Window( "smoother SmoothRangeSlider", Rect(200,200, 250, 120 ) ).front.decorate;

z = SmoothRangeSlider( w, 160@15 ).value_( [0.25,0.75] )

.knobColor_(  Gradient( Color.gray(0.9), Color.gray(0.1), \h ) )

.background_( Gradient( Color.black.alpha_(0.8), Color.white.alpha_(0.8), \h ) )

.hiliteColor_( Gradient( Color.blue.alpha_(0.5), Color.web.purple.alpha_(0.25), \v ) )

.knobSize_(1).canFocus_(true);

)


knobSize

knobSize is relative to the width of the slider. It defaults to 0.25. 

The rounded edges of SmoothRangeSlider are influenced by the knobSize.


(

w = Window( "knobSize", Rect(100,100, 120, 250 ) ).front.decorate;

c = SmoothRangeSlider( w, 50@200 ).value_(0.5).knobSize_( 1 );

d = SmoothRangeSlider( w, 50@200 ).value_(0.5).knobSize_( 1 );

c.action_({ |sl| d.knobSize = sl.range; }); 

// moving the slider changes the knobSize of the other

d.action_({ |sl| c.knobSize = sl.range; });

)


baseWidth

baseWidth is relative to the width of the sliders base. It defaults to 1. 


(

w = Window( "basewidths", Rect(200,200, 250, 120 ) ).front.decorate;

7.do({ |i| SmoothRangeSlider( w, 30@100 ).baseWidth_( 1/(i+1) ).value_( [i,i/3]/7 ); });

)


(

w = Window( "cocoa-like slider", Rect(200,200, 200, 120 ) ).front.decorate;

z = SmoothRangeSlider( w, 160@15 ).baseWidth_( 0.3 ).value_( [0.25,0.75] )

.knobColor_(  Gradient( Color.gray(0.8), Color.gray(0.2), \h ) )

.background_( Gradient( Color.black.alpha_(0.8), Color.white.alpha_(0.8), \h ) )

.knobSize_(1).hilightColor_(Color.blue(0.5).alpha_(0.5)).canFocus_(false);

)


border

borderColor

border is the size of the outline border around the slider. It defaults to 0 (no border).

borderColor defines the color of the border


(

w = Window( "borders", Rect(200,200, 250, 120 ) ).front.decorate;

7.do({ |i| SmoothRangeSlider( w, 30@100 ).border_( i )

.borderColor_( Color.rand )

.value_( [i,i/3]/7 ); });

)


(

w = Window( "levelmeters", Rect(200,200, 250, 120 ) ).front.decorate;

7.do({ |i|

SmoothRangeSlider( w, 30@100 ).border_( 1 ).borderColor_( Color.gray(0.2) )

.value_( [i,(i+1)]/7 ).knobSize_(0).canFocus_(false)

.background_( Color.white.alpha_(0.25) )

.hiliteColor_( Gradient( Color.red.alpha_(0.9), Color.green.alpha_(0.8), \v ) );

});

)


extrude

extrude can make a SmoothRangeSlider look more like regular SCSliders by adding a bevel around the base and the knob. It needs to be used in combination with border; if border == it will not show. 


(

w = Window( "SmoothRangeSlider", Rect( 300,150,220,220) ).front;  

b = SmoothRangeSlider( w,  Rect( 90,30,40,160) ).value_([0.25,0.75])

.border_(1).extrude_(true);

)



relThumbSize

the relThumbSize is a little different from the knobSize. It changes the thumbSize variable (inherited from SCSlider) but relative to the slider's length. The default thumbSize is 0. You will only see a change if the thumbSize becomes greater then the absolute knob size (absKnobSize - getter only). This functionality 


(

w = Window( "relThumbSize", Rect(100,100, 220, 120 ) ).front.decorate;

c = SmoothRangeSlider( w, 200@50 );

d = SmoothRangeSlider( w, 200@50 );

c.action_({ |sl| d.relThumbSize = sl.value.mean; });

d.action_({ |sl| c.relThumbSize = sl.value.mean; });

)



(

w = Window( "squared ranger", Rect(100,100, 220, 120 ) ).front.decorate;

c = SmoothRangeSlider( w, 200@20 ).knobSize_(0).relThumbSize_(0.05).setSpan(0.25,0.75);

)



string

font

align

stringColor

stringOrientation

SmoothRangeSliders can also have a label displayed on top. The label can be set using the string_ method. By default the label is always shown horizontally, regardless of the orientation of the slider. The font, align, stringColor and stringOrientation methods can be used to customize the label further.


////

//// example 1: orientation and styling

(

w = Window( "string", Rect(100,100, 216, 180 ) ).front;

w.addFlowLayout;

c = SmoothRangeSlider( w, 50@150 );

d = SmoothRangeSlider( w, 150@50 );

c.string = "String 1";

d.string = "String 2";

)


( // change color

c.stringColor = Color.white;

d.stringColor = Color.white;

)


( // change font

c.font = Font( "Times-Bold", 12 );

d.font = Font( "Times-Bold", 12 );

)


// change orientation (\v, \h, \up, \down or an angle)

c.stringOrientation = \v;

c.stringOrientation = \h;

c.stringOrientation = \up;

c.stringOrientation = 0.3pi;

c.stringOrientation = pi; // upside down


// change alignment

d.align = \left;

d.align = \right;

d.align = \center;


////

//// example 2: dynamic string

(

w = Window( "SmoothRangeSlider", Rect( 300,150,220,220) ).front;  

b = SmoothRangeSlider( w,  Rect( 30,30,160,40) ).value_(0.75)

.action_({ |sl| sl.string = "lo: %, hi: %".format(

sl.value[0].round(0.001),

sl.value[1].round(0.001) 

); });

b.stringColor = Color.white;

b.doAction;

)



stringAlignToKnob

Makes the text align to the center position between the knobs



(

w = Window( "SmoothRangeSlider", Rect( 300,150,220,220) ).front;  

b = SmoothRangeSlider( w,  Rect( 90,30,40,160) ).value_(0.75)

.action_({ |sl| sl.string = sl.value.mean.round(0.01 ).asString; });

b.stringColor = Color.white;

b.thumbSize = 16;

b.stringAlignToKnob = true;

b.doAction;

)