Spaces:
Runtime error
Runtime error
/** | |
* Knob - jQuery Plugin | |
* Downward compatible, touchable dial | |
* | |
* Version: 1.1.2 (22/05/2012) | |
* Requires: jQuery v1.7+ | |
* | |
* Copyright (c) 2011 Anthony Terrien | |
* Under MIT and GPL licenses: | |
* http://www.opensource.org/licenses/mit-license.php | |
* http://www.gnu.org/licenses/gpl.html | |
* | |
* Thanks to vor, eskimoblood, spiffistan | |
*/ | |
$(function () { | |
// Dial logic | |
var Dial = function (c, opt) { | |
var v = null | |
,ctx = c[0].getContext("2d") | |
,PI2 = 2 * Math.PI | |
,mx ,my ,x ,y | |
,self = this; | |
this.onChange = function () {}; | |
this.onCancel = function () {}; | |
this.onRelease = function () {}; | |
this.val = function (nv) { | |
if (null != nv) { | |
opt.stopper && (nv = Math.max(Math.min(nv, opt.max), opt.min)); | |
v = nv; | |
this.onChange(nv); | |
if(opt.dynamicDraw) this.dynamicDraw(nv); | |
else this.draw(nv); | |
} else { | |
var b, a; | |
b = a = Math.atan2(mx - x, -(my - y - opt.width / 2)) - opt.angleOffset; | |
(a < 0) && (b = a + PI2); | |
nv = Math.round(b * (opt.max - opt.min) / PI2) + opt.min; | |
return (nv > opt.max) ? opt.max : nv; | |
} | |
}; | |
this.change = function (nv) { | |
opt.stopper && (nv = Math.max(Math.min(nv, opt.max), opt.min)); | |
this.onChange(nv); | |
this.draw(nv); | |
}; | |
this.angle = function (nv) { | |
return (nv - opt.min) * PI2 / (opt.max - opt.min); | |
}; | |
this.draw = function (nv) { | |
var a = this.angle(nv) // Angle | |
,sa = 1.5 * Math.PI + opt.angleOffset // Previous start angle | |
,sat = sa // Start angle | |
,ea = sa + this.angle(v) // Previous end angle | |
,eat = sat + a // End angle | |
,r = opt.width / 2 // Radius | |
,lw = r * opt.thickness // Line width | |
,cgcolor = Dial.getCgColor(opt.cgColor) | |
,tick | |
; | |
ctx.clearRect(0, 0, opt.width, opt.width); | |
ctx.lineWidth = lw; | |
// Hook draw | |
if (opt.draw(a, v, opt, ctx)) { return; } | |
for (tick = 0; tick < opt.ticks; tick++) { | |
ctx.beginPath(); | |
if (a > (((2 * Math.PI) / opt.ticks) * tick) && opt.tickColorizeValues) { | |
ctx.strokeStyle = opt.fgColor; | |
} else { | |
ctx.strokeStyle = opt.tickColor; | |
} | |
var tick_sa = (((2 * Math.PI) / opt.ticks) * tick) - (0.5 * Math.PI); | |
ctx.arc( r, r, r-lw-opt.tickLength, tick_sa, tick_sa+opt.tickWidth , false); | |
ctx.stroke(); | |
} | |
opt.cursor | |
&& (sa = ea - 0.3) | |
&& (ea = ea + 0.3) | |
&& (sat = eat - 0.3) | |
&& (eat = eat + 0.3); | |
switch (opt.skin) { | |
case 'default' : | |
ctx.beginPath(); | |
ctx.strokeStyle = opt.bgColor; | |
ctx.arc(r, r, r - lw / 2, 0, PI2, true); | |
ctx.stroke(); | |
if (opt.displayPrevious) { | |
ctx.beginPath(); | |
ctx.strokeStyle = (v == nv) ? opt.fgColor : cgcolor; | |
ctx.arc(r, r, r - lw / 2, sa, ea, false); | |
ctx.stroke(); | |
} | |
ctx.beginPath(); | |
ctx.strokeStyle = opt.fgColor; | |
ctx.arc(r, r, r - lw / 2, sat, eat, false); | |
ctx.stroke(); | |
break; | |
case 'tron' : | |
if (opt.displayPrevious) { | |
ctx.beginPath(); | |
ctx.strokeStyle = (v == nv) ? opt.fgColor : cgcolor; | |
ctx.arc( r, r, r - lw, sa, ea, false); | |
ctx.stroke(); | |
} | |
ctx.beginPath(); | |
ctx.strokeStyle = opt.fgColor; | |
ctx.arc( r, r, r - lw, sat, eat, false); | |
ctx.stroke(); | |
ctx.lineWidth = 2; | |
ctx.beginPath(); | |
ctx.strokeStyle = opt.fgColor; | |
ctx.arc( r, r, r - lw + 1 + lw * 2 / 3, 0, 2 * Math.PI, false); | |
ctx.stroke(); | |
break; | |
} | |
}; | |
var dynamicDrawIndex; | |
var dynamicDrawInterval; | |
this.dynamicDraw = function (nv) { | |
var instanceOfThis = this; | |
dynamicDrawIndex = opt.min; | |
dynamicDrawInterval = setInterval(function() { | |
instanceOfThis.animateDraw(nv); | |
}, 20); | |
}; | |
this.animateDraw = function () { | |
if(dynamicDrawIndex > v) { | |
clearInterval(dynamicDrawInterval); | |
v = dynamicDrawIndex; | |
} else { | |
this.draw(dynamicDrawIndex); | |
this.change(dynamicDrawIndex); | |
dynamicDrawIndex++; | |
} | |
}; | |
this.capture = function (e) { | |
switch (e.type) { | |
case 'mousemove' : | |
case 'mousedown' : | |
mx = e.pageX; | |
my = e.pageY; | |
break; | |
case 'touchmove' : | |
case 'touchstart' : | |
mx = e.originalEvent.touches[0].pageX; | |
my = e.originalEvent.touches[0].pageY; | |
break; | |
} | |
this.change( this.val() ); | |
}; | |
this.cancel = function () { | |
self.val(v); | |
self.onCancel(); | |
}; | |
this.startDrag = function (e) { | |
var p = c.offset() | |
,$doc = $(document); | |
x = p.left + (opt.width / 2); | |
y = p.top; | |
this.capture(e); | |
// Listen mouse and touch events | |
$doc.bind( | |
"mousemove.dial touchmove.dial" | |
,function (e) { | |
self.capture(e); | |
} | |
) | |
.bind( | |
// Escape | |
"keyup.dial" | |
,function (e) { | |
if(e.keyCode === 27) { | |
$doc.unbind("mouseup.dial mousemove.dial keyup.dial"); | |
self.cancel(); | |
} | |
} | |
) | |
.bind( | |
"mouseup.dial touchend.dial" | |
,function (e) { | |
$doc.unbind('mousemove.dial touchmove.dial mouseup.dial touchend.dial keyup.dial'); | |
self.val(self.val()); | |
self.onRelease(v); | |
} | |
); | |
}; | |
}; | |
// Dial static func | |
Dial.getCgColor = function (h) { | |
h = h.substring(1,7); | |
var rgb = [parseInt(h.substring(0,2),16) | |
,parseInt(h.substring(2,4),16) | |
,parseInt(h.substring(4,6),16)]; | |
return "rgba("+rgb[0]+","+rgb[1]+","+rgb[2]+",.5)"; | |
}; | |
// jQuery plugin | |
$.fn.knob = $.fn.dial = function (gopt) { | |
return this.each( | |
function () { | |
var $this = $(this), opt; | |
if ($this.data('dialed')) { return $this; } | |
$this.data('dialed', true); | |
opt = $.extend( | |
{ | |
// Config | |
'min' : $this.data('min') || 0 | |
,'max' : $this.data('max') || 100 | |
,'stopper' : true | |
,'readOnly' : $this.data('readonly') | |
// UI | |
,'cursor' : $this.data('cursor') | |
,'thickness' : $this.data('thickness') || 0.35 | |
,'width' : $this.data('width') || 200 | |
,'displayInput' : $this.data('displayinput') == null || $this.data('displayinput') | |
,'displayPrevious' : $this.data('displayprevious') | |
,'fgColor' : $this.data('fgcolor') || '#87CEEB' | |
,'cgColor' : $this.data('cgcolor') || $this.data('fgcolor') || '#87CEEB' | |
,'bgColor' : $this.data('bgcolor') || '#EEEEEE' | |
,'tickColor' : $this.data('tickColor') || $this.data('fgcolor') || '#DDDDDD' | |
,'ticks' : $this.data('ticks') || 0 | |
,'tickLength' : $this.data('tickLength') || 0 | |
,'tickWidth' : $this.data('tickWidth') || 0.02 | |
,'tickColorizeValues' : $this.data('tickColorizeValues') || true | |
,'skin' : $this.data('skin') || 'default' | |
,'angleOffset': degreeToRadians($this.data('angleoffset')) | |
,'dynamicDraw': $this.data('dynamicdraw') || false | |
// Hooks | |
,'draw' : | |
/** | |
* @param int a angle | |
* @param int v current value | |
* @param array opt plugin options | |
* @param context ctx Canvas context 2d | |
* @return bool true:bypass default draw methode | |
*/ | |
function (a, v, opt, ctx) {} | |
,'change' : | |
/** | |
* @param int v Current value | |
*/ | |
function (v) {} | |
,'release' : | |
/** | |
* @param int v Current value | |
* @param jQuery ipt Input | |
*/ | |
function (v, ipt) {} | |
} | |
,gopt | |
); | |
var c = $('<canvas width="' + opt.width + '" height="' + opt.width + '"></canvas>') | |
,wd = $('<div style=width:' + opt.width + 'px;display:inline;"></div>') | |
,k | |
,vl = $this.val() | |
,initStyle = function () { | |
opt.displayInput | |
&& $this.css({ | |
'width' : opt.width / 2 + 'px' | |
,'position' : 'absolute' | |
,'margin-top' : (opt.width * 5 / 14) + 'px' | |
,'margin-left' : '-' + (opt.width * 3 / 4) + 'px' | |
,'font-size' : (opt.width / 4) + 'px' | |
,'border' : 'none' | |
,'background' : 'none' | |
,'font-family' : 'Arial' | |
,'font-weight' : 'bold' | |
,'text-align' : 'center' | |
,'color' : opt.fgColor | |
,'padding' : '0px' | |
,'-webkit-appearance': 'none' | |
}) | |
|| $this.css({ | |
'width' : '0px' | |
,'visibility' : 'hidden' | |
}); | |
}; | |
// Canvas insert | |
$this.wrap(wd).before(c); | |
initStyle(); | |
// Invoke dial logic | |
k = new Dial(c, opt); | |
vl || (vl = opt.min); | |
$this.val(vl); | |
k.val(vl); | |
k.onRelease = function (v) { | |
opt.release(v, $this); | |
}; | |
k.onChange = function (v) { | |
$this.val(v); | |
opt.change(v); | |
}; | |
// bind change on input | |
$this.bind( | |
'change' | |
,function (e) { | |
k.val($this.val()); | |
} | |
); | |
if (!opt.readOnly) { | |
// canvas | |
c.bind( | |
"mousedown touchstart" | |
,function (e) { | |
e.preventDefault(); | |
k.startDrag(e); | |
} | |
) | |
.bind( | |
"mousewheel DOMMouseScroll" | |
,mw = function (e) { | |
e.preventDefault(); | |
var ori = e.originalEvent | |
,deltaX = ori.detail || ori.wheelDeltaX | |
,deltaY = ori.detail || ori.wheelDeltaY | |
,val = parseInt($this.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0); | |
k.val(val); | |
} | |
); | |
// input | |
var kval, val, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1}; | |
$this | |
.bind( | |
"configure" | |
,function (e, aconf) { | |
var kconf; | |
for (kconf in aconf) { opt[kconf] = aconf[kconf]; } | |
initStyle(); | |
k.val($this.val()); | |
} | |
) | |
.bind( | |
"keydown" | |
,function (e) { | |
var kc = e.keyCode; | |
if (kc >= 96 && kc <= 105) kc -= 48; //numpad | |
kval = parseInt(String.fromCharCode(kc)); | |
if (isNaN(kval)) { | |
(kc !== 13) // enter | |
&& (kc !== 8) // bs | |
&& (kc !== 9) // tab | |
&& (kc !== 189) // - | |
&& e.preventDefault(); | |
// arrows | |
if ($.inArray(kc,[37,38,39,40]) > -1) { | |
k.change(parseInt($this.val()) + kv[kc] * m); | |
// long time keydown speed-up | |
to = window.setTimeout( | |
function () { m < 20 && m++; } | |
,50 | |
); | |
e.preventDefault(); | |
} | |
} | |
} | |
) | |
.bind( | |
"keyup" | |
,function(e) { | |
if (isNaN(kval)) { | |
if (to) { | |
window.clearTimeout(to); | |
to = null; | |
m = 1; | |
k.val($this.val()); | |
k.onRelease($this.val(), $this); | |
} else { | |
// enter | |
(e.keyCode === 13) | |
&& k.onRelease($this.val(), $this); | |
} | |
} else { | |
// kval postcond | |
($this.val() > opt.max && $this.val(opt.max)) | |
|| ($this.val() < opt.min && $this.val(opt.min)); | |
} | |
} | |
) | |
.bind( | |
"mousewheel DOMMouseScroll" | |
,mw | |
); | |
} else { | |
$this.attr('readonly', 'readonly'); | |
} | |
} | |
).parent(); | |
}; | |
function degreeToRadians (angle) { | |
return $.isNumeric(angle) ? angle * Math.PI / 180 : 0; | |
} | |
}); |