450 lines
17 KiB
JavaScript
450 lines
17 KiB
JavaScript
|
/**
|
||
|
* 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' : '2px 0px 0px 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;
|
||
|
}
|
||
|
});
|