/* Widgets.js - Depends on Helpers.js
This isn't intended to be a library to just load and use: it's examples of what is possible, done in different ways in different places.
It's also not intended to be used as javascript, so conventions like having a propety called 'disabled' are deliberately ignored (no property should ever have pre-negated logic)!
Returns the greyscale level [0,1] of a pixel on the background covered by the progress bar as it makes progress
The shading should make the background look like a rounded groove.
let z = GetGrooveLevel(y/height); // The corners behind the sphere need shading the same as the background groove */
GetGrooveLevel=(t)=>Upper(0.2, 1-Saturate(GetSphereZ(1, 0.4-t))); // Get the third coordinate using a spherical model, then lighten the shade
/*__Progress Bar_______________________________________________________________
Properties:
Position on canvas by setting x,y,width,height in pixels.
Odd numbers look best in height (width when vertical) and gap.
t[ 0,1] the current progress position
hue[ 0,1] is the colour from the spectrum: 0=red; 1/3=green; 2/3=blue
stopHue[ 0,1] Colour of the stop button from the spectrum: 0=red(default); 1/3=green; 2/3=blue
bar[-1,1] lighting brightness. Negative indicates lit interally (glowing)
mode: relative (default) or absolute when moving t from 0 to 1.
enabled [false,true] (Optional) default is true
Call StepProgress(canvas,progressBar) each ProgressTick() to move relative Progress bars.
The code convention is upper-case ProgressBar is an object holding a canvas and a progressBar (lower-case p):
ProgressBar = {canvas:canvas, progressBar:progressBar}
var ProgressCanvas = document.getElementById('Progress');
var CyanProgress = {id:'Cyan', t:0.0, onStop:OnStop, hue:1/2 , bar: 0.5, x:0, y: 0, width:150, height:12, mode:'relative'};
var GoldProgress = {id:'Gold', t:0.8, onStop:OnStop, hue:4/25, bar:-0.8, x:0, y:12, width:150, height:25, mode:'relative'};
var OffProgress = {id:'Off' , t:0.5, onStop:OnStop, hue:3/8 , bar: 1.0, x:0, y:37, width:150, height:20, enabled:false};
const ProgressProgress = [CyanProgress, GoldProgress, OffProgress];
RegisterProgressBars(ProgressCanvas, ProgressProgress);
requestAnimationFrame(DrawRegisteredProgressBars);
*/
var ProgressBars=[]; // Append to this global array of Progress Bars using RegisterProgressBars:
var CapturedProgressBar = null; // {canvas,progressBar} Capture the mouse for this progressBar
var HotProgressBar = null; // {canvas,progressBar} Dim the grip until the mouse is over it. Gets set to the hot ProgressBar.
DrawRegisteredProgressBars=()=>{for(const bar of ProgressBars) DrawProgressBar(bar);} // Draw all registered sliders on all canvases
DrawProgressBars=(bars)=>{for(const bar of bars) DrawProgressBar(ProgressBars[bar.index]);} // Draw a particular canvas' sliders
function RegisterProgressBars(canvas,bars) {
const context = canvas.getContext('2d',{alpha:false});
for(const bar of bars) {
const width = ~~(bar.width ); // The 'for' loops would be infinite with fractional width or height!
const height= ~~(bar.height);
bar.index = ProgressBars.length; // The bar can be located in the global array using, for example: DrawProgressBar(ProgressBars[bar.index]);
ProgressBars.push({
canvas: canvas,
progressBar: bar,
width: width ,
height: height,
context: context,
imageData: context.getImageData(bar.x, bar.y, width,height, {willReadFrequently: true})
});
}
canvas.addEventListener('pointerdown', (e)=>{e.preventDefault(); OnProgressMouseDown(e); return false;}, {passive:false}); // return false; prevents a selection being dragged out.
canvas.addEventListener('click' , (e)=>{e.preventDefault(); OnProgressClick (e); return false;}, {passive:false}); // return false; prevents a selection being dragged out.
}
document.addEventListener('pointermove', OnProgressMouseMove, {passive:true});
document.addEventListener('pointerup' , OnProgressMouseUp , {passive:true}); // May not get called if released outside the browser window!
function StepProgress(ProgressBar) {
const progressBar = ProgressBar.progressBar;
const enabled = (progressBar.enabled !== false);
const relative = (progressBar.mode==='relative');
if(enabled && relative) { // Move with each redraw:
if(progressBar.dt===undefined) progressBar.dt = 1/30;
progressBar.t += progressBar.dt;
if(!Between(progressBar.t, 0,1)) { // At an end:
progressBar.t = Saturate(progressBar.t); // Fix the overshoot
progressBar.dt = -progressBar.dt; // Reverse the direction
}
DrawProgressBar(ProgressBar);
}
}
function OnProgressClick(e) {
let ProgressBar = GetMouseProgress(e);
if(ProgressBar
&& ProgressBar.progressBar
&& ProgressBar.progressBar.onStop
&& ProgressBar.progressBar.enabled!==false) ProgressBar.progressBar.onStop();
}
function OnProgressMouseDown(e) {
let ProgressBar = GetMouseProgress(e);
if((null===ProgressBar) || (false===ProgressBar.progressBar.enabled)) return;
if(null != CapturedProgressBar) OnProgressMouseUp();
CapturedProgressBar = ProgressBar; // Capture the mouse for this slider
ProgressBar.progressBar.pressed = true;
DrawProgressBar(ProgressBar); // Redraw the progressBar that is no longer hot
}
function OnProgressMouseMove(e) {
let ProgressBar = GetMouseProgress(e); // Check if the mouse is over a progressBar. ALL if/else options use the mouse coords even if they don't use progressBar!
if((null===ProgressBar) || (false===ProgressBar.progressBar.enabled)) ProgressBar = null;
if(null === CapturedProgressBar) { // Check if any progressBar has captured the mouse
CapturedProgressBar = ProgressBar; // Capture the mouse for this progressBar
if(null === ProgressBar) { // Not currently over a progressBar:
if(null != HotProgressBar) { // There is a hot progressBar!...
ProgressBar = HotProgressBar;
HotProgressBar = null; // ...it's no longer hot! draw it cold:
DrawProgressBar(ProgressBar); // Redraw the progressBar that is no longer hot
}
return;
}
// Operate the CAPTURED progressBar:
if(HotProgressBar != CapturedProgressBar) {
ProgressBar = HotProgressBar; // temporarily reuse progressBar to swap HotProgressBar and CapturedProgressBar:
HotProgressBar = CapturedProgressBar; // swap this with any HotProgressBar progressBar and make the HotProgressBar one cold:
if(null != ProgressBar) DrawProgressBar(ProgressBar); // Redraw the progressBar that is no longer hot
}
DrawProgressBar(CapturedProgressBar);
}else{ // A ProgressBar has previously captured the mouse:
if(!mouseDown) { // Only react to the Primary mouse button
OnProgressMouseUp(); // The mouse may be released undetected outside the browser window!
if((null === ProgressBar) // Not currently over a progressBar:
&& (null !== HotProgressBar)) { // There is a hot progressBar!...
ProgressBar = HotProgressBar;
HotProgressBar = null; // ...it's no longer hot! draw it cold:
DrawProgressBar(ProgressBar); // Redraw the progressBar that is no longer hot
} } }
}
function OnProgressMouseUp() {
if(CapturedProgressBar
&& CapturedProgressBar.progressBar.pressed) {
let ProgressBar = CapturedProgressBar;
CapturedProgressBar = null;
ProgressBar.progressBar.pressed = false;
DrawProgressBar(ProgressBar); // Redraw the progressBar that is no longer hot
}
CapturedProgressBar=null;
}
function GetMouseProgress(e) { // See which progressBar the pointer is over (if any):
for(let bar=ProgressBars.length; bar--;) {
GetMouse(e, ProgressBars[bar].canvas); // Finds the mouse coordinates
if(HitBar(xMouse,yMouse, ProgressBars[bar].progressBar)) return ProgressBars[bar];
}
return null;
}
function DrawProgressBar(ProgressBar) {
const canvas = ProgressBar.canvas;
const progressBar = ProgressBar.progressBar;
const width = ProgressBar.width ;
const height = ProgressBar.height;
const context = ProgressBar.context;
const imageData = ProgressBar.imageData;
const goo = eProgressGoo .checked;
const noise = eProgressNoise.checked;
const radius=height/2; // gets used as a double in graphic processing filter routines
const enabled = (progressBar.enabled !== false);
const relative = (progressBar.mode==='relative');
const pressed = (progressBar.pressed ? -1 : +1); // Drawn down a pixel when pressed
const hue = (enabled ? FromHue(progressBar.hue) : Silver);
const stopHue = (enabled ? FromHue((typeof progressBar.stopHue==='number') ? progressBar.stopHue : 0) : Silver);
const hot = (((null != HotProgressBar) && (HotProgressBar.progressBar === progressBar)) ? 1 : HotDim); // Dim grip until the mouse is over it
/* _______ Calculate the various x-positions across the progress-bar:
|(|_______|)==| X The stretching middle of the graphic is what varies [0,1]
bls s e b r s
aet<--p-->n a i t <-Variable names written vertically top down
rfa a d c g o
tr c k h p
t e t */
const t = HermiteCurve(progressBar.t);
const bar = 1; // Left border
const stop = width-height; // Stop button
const right = stop-1; // Right border
const space = (right-1)-(bar+1)-(height+1); // The maximum stretched space (at progressBar.t == 1)
const left = bar+1+ ~~(space*(relative ? (0.8*progressBar.t) : 0)); // Left semi-circle
const start = left +radius+1; // Start of stretched section (moved away so the blur doesn't blend with the edge)
const end = bar+1+radius+1+ ~~(space*(relative ? Base(0.2,progressBar.t) : progressBar.t)); // Right semi-circle
const back = end + radius; // Background section
const buttonGlow = 0.3 + 0.5*hot + 0.2*pressed; // Levels for the button when normal, hot and pressed
for(let y=height; y--;) {
const dy=y-radius; // vector from control's middle to current point being drawn
const shade = GetGrooveLevel( dy/radius); // Greyscale level [0,1]
const shade1 = GetGrooveLevel(1-y/radius); // Greyscale level [0,1]
const background = {red:shade , green:shade , blue:shade };
const shadow = {red:shade1, green:shade1, blue:shade1};
for(let x=width; x--;) {
const i = xyToIndex(imageData, x,y); // locate the point (x,y) in the ImageData array
const foreground = CloneColor(hue);
let color = CloneColor(background);
let t=(x-left)/(back-left);
t=goo ? 1-(t*(1-t)) : 1;
const glow=noise ? (0.6*(1+Math.abs(InterpolatedNoise(progressBar.t*12+x/8,12+y/2)))) : 0.8; // Bright Button Top
if(x===bar) color = shadow; // Draw left edge
else if(x < left) color = background; // Draw background if in relative mode and left is exposed (a long curved groove)
else if(x < start) GlassSphere(x-start, dy, t*radius, glow, foreground,color); // Draw left semi-circle
else if(x < end) GlassSphere( 0, dy, t*radius, glow, foreground,color); // Draw stretched section. Zero is the middle of the sphere
else if(x < back) GlassSphere(x-end , dy, t*radius, glow, foreground,color); // Draw right semi-circle. Like dx but relative to end
else if(x < right) color = background; // Draw background section (a long curved groove)
else if(x===right) color = shadow; // Draw right edge
if(x > right) {
const xLocal = x-(stop+1)+pressed; // Button centre
const buttonRadius = radius-1+pressed; // Shrink when pressed
const dx = xLocal-buttonRadius; // vector from control's middle to current point being drawn
// Draw the button:
GlassSphere(dx,dy, buttonRadius, buttonGlow, stopHue,color);
// Draw the Cross:
const rx = xLocal-buttonRadius;
const ry = radius-y; // Moves down when pressed
const d = (Math.min(1.0, Math.abs(Math.abs(rx) - Math.abs(ry)) / 2.5)); // This sum generates the cross (tends to 1 at x==y and x==-y)
const opacity = 1.5*hot*(1-Math.hypot(dx,dy)/buttonRadius);
if(opacity>0.5) color = MixColors(opacity*(1-d), White, color); // Draw the cross over the button
}
SetPixel(imageData,i, color);
} }
context.putImageData(imageData, progressBar.x,progressBar.y);
}
/*__Slider_______________________________________________________________________
Usage:
Note that the canvas must be of class SliderParent for touch (which just sets touch-action:pinch-zoom)
var SliderCanvas = document.getElementById('Slider');
Append to the global array of sliders in this file using RegisterSlider(canvas,slider);
var RedSlider = {id:'Red' , t:0.5, onChange:OnR, hue:1/2 , bar: 0.5, grip: 1.0, x: 0, y: 6, width:150, height:12, gap: 8};
var GreenSlider = {id:'Green', t:0.2, onChange:OnG, hue:3/8 , bar: 0.7, grip:-0.5, x:45, y:25, width:100, height:20, gap:15, shape:'goo', steps:6};
var BlueSlider = {id:'Blue' , t:0.8, onChange:OnB, hue:2/3 , bar:-0.8, grip: 0.5, x:30, y:47, width: 80, height:25, gap:20, shape:'ufo'};
var OffSlider = {id:'Off' , t:0.2, hue:3/8 , bar: 1.0, grip:-0.5, x:35, y:75, width:100, height:20, gap:15, shape:'ufo', enabled:false};
var VertSlider = {id:'Vert' , t:0.2, onChange:OnV, hue:3/21, bar:-1.0, grip:-0.5, x:05, y:25, width: 20, height:70, gap:15, shape:'goo', steps:6}; // vertical
const SliderSliders =[RedSlider, GreenSlider, BlueSlider, OffSlider, VertSlider];
RegisterSliders(SliderCanvas, SliderSliders);
function OnR() {RedSlider.hue = RedSlider.t;}
function OnG() { VertSlider.t = GreenSlider.t; OnVG();}
function OnV() {GreenSlider.t = VertSlider.t; OnVG();}
function OnVG() {RedSlider.height = InterpolateRange(VertSlider.t, 12,5); requestAnimationFrame(DrawRegisteredSliders);} // Draw all because linked sliders may shrink
function OnB() {OffSlider.t = 1-BlueSlider.t; DrawSlider(Sliders[OffSlider.index]);}
requestAnimationFrame(DrawRegisteredSliders);
Slider properties:
Position on canvas by setting x,y,width,height in pixels.
Sliders can be horizontal or vertical but can't slide if square!
Odd numbers look best in height (width when vertical) and gap.
t[ 0,1] the current position
hue[ 0,1] is the colour from the spectrum: 0=red; 1/3=green; 2/3=blue
bar[-1,1] lighting brightness. Negative indicates lit interally (glowing)
grip[-1,1] lighting brightness. Negative indicates lit interally (glowing)
gap: bar height in pixels
steps: number of places the grip can stop at (quantised). Lines are drawn at stop points unless they are very close (MinPxPerStep).
shape:
undefined = straight sided bar
'bat'=make bar thinner at the start like a baseball bat
'ufo'=make bar thinner at the ends like a UFO
'goo'=make bar thinner in the middle ("World of Goo" game lines)
onChange (Optional) function called each time the grip is moved (many calls during mouse movement)!
enabled [false,true] (Optional) default is true
The code convention is upper-case Slider is an object holding a canvas and a slider (lower-case s):
Slider = {canvas:canvas, slider:slider} */
const HotDim = 0.6; // [0,1] Multiplier to dim lighting when not hot
const MinPxPerStep = 8; // Don't draw the steps if they're very close together
var CapturedSlider = null; // {canvas,slider} Capture the mouse for this slider
var HotSlider = null; // {canvas,slider} Dim the grip until the mouse is over it. Gets set to the hot Slider.
var Sliders=[]; // Append to this global array of sliders using RegisterSlider:
DrawRegisteredSliders=()=>{for(const slider of Sliders) DrawSlider(slider);} // Draw all registered sliders on all canvases
DrawSliders=(sliders)=>{for(const slider of sliders) DrawSlider(Sliders[slider.index]);} // Draw a particular canvas' sliders
function RegisterSliders(canvas,sliders) {
const context = canvas.getContext('2d',{alpha:false});
for(let slider of sliders) {
const width = ~~(slider.width ); // The 'for' loops would be infinite with fractional width or height!
const height= ~~(slider.height);
slider.index = Sliders.length; // The slider can be located in the global array using, for example: DrawSlider[slider.index]
Sliders.push({
slider: slider,
canvas: canvas,
width: width ,
height: height,
context: context,
imageData: context.getImageData(slider.x, slider.y, width,height, {willReadFrequently: true})
});
}
canvas.addEventListener('wheel' , (e)=>{e.preventDefault(); OnSliderWheel (e); return false;}, {passive:false}); // return false; prevents a selection being dragged out.
canvas.addEventListener('pointerdown', (e)=>{e.preventDefault(); OnSliderMouseDown(e); return false;}, {passive:false}); // return false; prevents a selection being dragged out.
}
document.addEventListener('pointermove', OnSliderMouseMove, {passive:true});
document.addEventListener('pointerup' , OnSliderMouseUp , {passive:true});
//__Helpers____________________________________________________________________
HitBar=(x,y, bar)=> Between(x, bar.x, bar.x + bar.width )
&& Between(y, bar.y, bar.y + bar.height);
CalculateGripX=(slider,t)=> InterpolateRange(t, slider.x+slider.height/2, slider.width -slider.height);
CalculateGripY=(slider,t)=> InterpolateRange(t, slider.y+slider.width /2, slider.height-slider.width );
CalculateGripT=(slider )=> Saturate(slider.vertical ? ParameterizeRange(yMouse, slider.y+slider.width /2, slider.height-slider.width )
: ParameterizeRange(xMouse, slider.x+slider.height/2, slider.width -slider.height));
// Quantised (stepped) movement:
function QuantiseSlider(Slider, t) {
if(HotSlider != Slider) {
let slider = HotSlider; // temporarily reuse slider to swap HotSlider and CapturedSlider:
HotSlider = Slider; // swap this with any HotSlider slider and make the HotSlider one cold:
if(null != slider) DrawSlider(slider); // Redraw the slider that is no longer hot
}
let slider = Slider.slider;
if(!isNaN(slider.steps) && (11) { // Move towards destination:
let t = HermiteCurve(slider.journey);
let old = slider.t;
slider.t = QuantiseSlider(Slider, Interpolate(t, slider.t, slider.destination));
if(Math.abs(slider.t - old) > (1/Math.max(slider.width,slider.height))) {
slider.onChange();
DrawSlider(Slider);
}
}else{ // Reached destination:
// slider.t = slider.destination; <<< OnSliderMouseMove may be changing this
window.clearInterval(slider.timer);
slider.timer = CapturedSlider = null;
}
},22);
}
//__Mouse______________________________________________________________________
function GetMouseSlider(e) {
if(null != CapturedSlider) {
GetMouse(e, CapturedSlider.canvas); // Finds the mouse coordinates
return CapturedSlider;
} // Nothing captured, see which slider the pointer is over (if any):
for(let n=Sliders.length; n--;) {
GetMouse(e, Sliders[n].canvas); // Finds the mouse coordinates
if(HitBar(xMouse,yMouse, Sliders[n].slider)) return Sliders[n];
}
return null;
}
function OnSliderWheel(e) {
let Slider = GetMouseSlider(e); // Check if the mouse is over a slider. ALL if/else options use the mouse coords even if they don't use slider!
if((null===Slider) || (false===Slider.slider.enabled)) return;
let slider = Slider.slider;
let dt = isNaN(slider.steps) ? (slider.vertical ? slider.height : slider.width) : (slider.steps-1);
if((e.wheelDelta < 0) ^ slider.vertical) dt = -dt;
if(0===dt) return;
slider.t = Saturate(slider.t + 1/dt);
slider.onChange();
DrawSlider(Slider); // Redraw the slider that is no longer hot
}
// MouseDown may be far from the Grip, so it starts a journey.
// Once the journey is complete, MouseMove or MouseUp finalise the position.
function OnSliderMouseDown(e) {
if(null != CapturedSlider) OnSliderMouseUp();
CapturedSlider = GetMouseSlider(e); // Capture the mouse for this slider
if((null===CapturedSlider) || (false===CapturedSlider.slider.enabled)
|| !mouseDown // Only react to the primary mouse button
||(null === CapturedSlider)) return;
// Animate the Grip as if sprung:
let slider = CapturedSlider.slider;
if((false===slider.enabled)
|| (null != slider.timer)) return; // Don't overwrite the timer, its' already running!
// Map the mouse point onto the Slider, get the parameter and ensure the range isn't exceeded:
SetSliderPosition(CapturedSlider);
}
function OnSliderMouseMove(e) {
let Slider = GetMouseSlider(e); // Check if the mouse is over a slider. ALL if/else options use the mouse coords even if they don't use slider!
if((null===Slider) || (false===Slider.slider.enabled)) return;
if(null === CapturedSlider) { // Check if any slider has captured the mouse
CapturedSlider = Slider; // Capture the mouse for this slider
if(null === Slider) { // Not currently over a slider:
if(null != HotSlider) { // There is a hot slider!...
Slider = HotSlider;
HotSlider = null; // ...it's no longer hot! draw it cold:
DrawSlider(Slider); // Redraw the slider that is no longer hot
}
return;
}
// Operate the CAPTURED slider:
if(HotSlider != CapturedSlider) {
Slider = HotSlider; // temporarily reuse slider to swap HotSlider and CapturedSlider:
HotSlider = CapturedSlider; // swap this with any HotSlider slider and make the HotSlider one cold:
if(null != Slider) DrawSlider(Slider); // Redraw the slider that is no longer hot
}
DrawSlider(CapturedSlider);
}else{ // A Slider has previously captured the mouse:
if(!mouseDown) { // Only react to the Primary mouse button
OnSliderMouseUp(); // The mouse may be released undetected outside the browser window!
if(null === Slider) { // Not currently over a slider:
if(null != HotSlider) { // There is a hot slider!...
Slider = HotSlider;
HotSlider = null; // ...it's no longer hot! draw it cold:
DrawSlider(Slider); // Redraw the slider that is no longer hot
} }
}else if(null == CapturedSlider.slider.timer) { // The Primary mouse button is down:
let slider = CapturedSlider.slider;
const t = QuantiseSlider(CapturedSlider, CalculateGripT(slider)); // sets HotSlider
if(Math.abs(slider.t-t) > 1/(slider.steps ? (2*slider.steps)
: (slider.vertical ? slider.height : slider.width))) {
slider.t = t;
slider.onChange();
DrawSlider(CapturedSlider);
} } }
/* The animated version drags too slowly and inaccurately:
else if(null===CapturedSlider.slider.timer) { // The Primary mouse button is down:
let Slider = CapturedSlider.slider;
SetSliderPosition(CapturedSlider);
} }
*/
}
// May not get called if released outside the browser window!
function OnSliderMouseUp(e) {
if((null != CapturedSlider)
&& (null == CapturedSlider.slider.timer)) CapturedSlider = null;
}
//__Rendering__________________________________________________________________
// Draw a progress-bar shape (For a real application, optimise by only redrawing the changed pixels)
// hot is boolean; color is changed to the appropriate value for this pixel
function BarPixel(x,y, bar, hot, color) {
// (x,y) is relative to the canvas at this point
if(!HitBar(x,y, bar)) return false; // Not me
function ShapeBar(t,shape) {
switch(shape) {
default: t=1 ; break; // Straight bar
case 'bat': t=0.5 *(1+t); break; // Make the bar thinner at the start
case 'ufo': t=0.5+t*(1-t); break; // Make the bar thinner at the ends
case 'goo': t=1.0-t*(1-t); break; // Make the bar thinner in the middle
}
return t;
}
let radius = bar.gap/2;
hot = hot ? 1 : HotDim; // Change to a brightness level (will be adjusted again if drawing a step bar)
if(bar.vertical) {
x -= bar.x + bar.width/2; // Map x,y to the bar
y -= bar.y;
// (x,y) is relative to the left mid bar at this point
radius*=ShapeBar(y/bar.height, bar.shape);
if(Between(y, radius-1, bar.height-radius+1)) {
if(!isNaN(bar.steps) && (1MinPxPerStep)) { // Draw lines at the steps:
let space = GetStep(bar.steps-1, y-radius, bar.height-bar.gap);
if(space<=1) hot*=HotDim;
else if(space<=2) hot/=HotDim;
}
y = radius/6 * Signed(y/(bar.height-bar.gap)); // Mid-section
}else{
y-=(yMinPxPerStep)) { // Draw lines at the steps:
let space = GetStep(bar.steps-1, x-radius, bar.width-bar.gap);
if(space<=1) hot*=HotDim;
else if(space<=2) hot/=HotDim;
}
x = radius/6 * Signed(x/(bar.width-bar.gap)); // Mid-section
}else{
x-=(x0) PearlSphere(x,y,radius, bar.bar * hot, foreground,color);
else GlassSphere(x,y,radius, -bar.bar * hot, foreground,color);
return true;
}
/* Draw a Slider control (Progress-bar shape with a spherical grip):
NOTE: Slider x & y are temporarily zeroed before this function!!!
hot is boolean; color is changed to the appropriate value for this pixel */
function SliderPixel(x,y, slider, hot, color) {
if(!BarPixel(x,y, slider, hot, color)) return false;
// Draw the grip over the top (so antialiasing works at the edge of the grip):
if(typeof slider.grip != 'number') {
alert("Grip lighting: Slider '"+Slider.id+"' must have a grip[-1,1] property!");
slider.grip=1; // Force success or there will be an alert generated for every pixel!
}
let t = slider.t;
let radius = (slider.vertical ? slider.width : slider.height)/2;
let dx = (slider.vertical ? 0 : CalculateGripX(slider, slider.t));
let dy = (slider.vertical ? CalculateGripY(slider, slider.t) : 0);
if(slider.vertical) x -= slider.x + radius;
else y -= slider.y + radius;
if(slider.vertical ? Between(y, dy-radius, dy+radius)
: Between(x, dx-radius, dx+radius)) {
let foreground = Silver; // Default to disabled
if(false !== slider.enabled) foreground = FromHue((slider.gripHue!==undefined) ? slider.gripHue : slider.hue);
hot = hot ? 1 : HotDim; // Change to a brightness level
if(slider.grip>0) {
PearlSphere(x-dx, y-dy, radius, slider.grip * hot, foreground ,color);
}else{
let glow = slider.grip; // stash
GlassSphere(x-dx, y-dy, radius, -glow * hot, foreground,color);
slider.grip = glow; // restore
}
}
return true;
}
function DrawSlider(Slider) {
const canvas = Slider.canvas;
const slider = Slider.slider;
const width = Slider.width ;
const height = Slider.height;
const context = Slider.context;
const imageData = Slider.imageData;
slider.vertical = (height > width); // size may be changed at any time
const hot = ((null != HotSlider) && (HotSlider.slider === slider)); // Dim grip until the mouse is over it
const X=slider.x; // Stash and zero the Slider's displacement:
const Y=slider.y;
slider.x = slider.y = 0;
for(let y=height; y--;) {
const shade = GetGrooveLevel((Y + y)/canvas.height); // background shade/fade
for(let x=width; x--;) {
const background = {red:shade, green:shade, blue:shade};
SliderPixel(x,y, slider, hot, background);
const i = xyToIndex(imageData, x,y); // locate the point (x,y) in the ImageData array
SetPixel(imageData,i, background);
} }
slider.x = X; slider.y = Y; // Restore the Slider's displacement
context.putImageData(imageData, slider.x, slider.y);
}
//]]>