Commit 70be1eeb authored by neil.fraser@gmail.com's avatar neil.fraser@gmail.com

Upgrade slider to animate and support touch events.

git-svn-id: http://blockly.googlecode.com/svn/trunk@1706 c400ca83-b69d-9dd7-9705-49c6b8615e23
parent e35dcbb2
......@@ -37,12 +37,15 @@ var Slider = function(x, y, width, svgParent, opt_changeFunc) {
this.KNOB_Y_ = y - 12;
this.KNOB_MIN_X_ = x + 8;
this.KNOB_MAX_X_ = x + width - 8;
this.TARGET_OVERHANG = 20;
this.value_ = 0.5;
this.changeFunc_ = opt_changeFunc;
this.animationTasks_ = [];
// Draw the slider.
/*
<line class="sliderTrack" x1="10" y1="35" x2="140" y2="35" />
<rect style="fill: none" x="5" y="25" width="150" height="20" />
<path id="knob"
transform="translate(67, 23)"
d="m 8,0 l -8,8 v 12 h 16 v -12 z" />
......@@ -55,11 +58,27 @@ var Slider = function(x, y, width, svgParent, opt_changeFunc) {
track.setAttribute('y2', y);
svgParent.appendChild(track);
this.track_ = track;
var rect = document.createElementNS(Slider.SVG_NS_, 'rect');
rect.setAttribute('style', 'opacity: 0');
rect.setAttribute('x', x - this.TARGET_OVERHANG);
rect.setAttribute('y', y - this.TARGET_OVERHANG);
rect.setAttribute('width', width + 2 * this.TARGET_OVERHANG);
rect.setAttribute('height', 2 * this.TARGET_OVERHANG);
rect.setAttribute('rx', this.TARGET_OVERHANG);
rect.setAttribute('ry', this.TARGET_OVERHANG);
svgParent.appendChild(rect);
this.trackTarget_ = rect;
var knob = document.createElementNS(Slider.SVG_NS_, 'path');
knob.setAttribute('class', 'sliderKnob');
knob.setAttribute('d', 'm 0,0 l -8,8 v 12 h 16 v -12 z');
svgParent.appendChild(knob);
this.knob_ = knob;
var circle = document.createElementNS(Slider.SVG_NS_, 'circle');
circle.setAttribute('style', 'opacity: 0');
circle.setAttribute('r', this.TARGET_OVERHANG);
circle.setAttribute('cy', y);
svgParent.appendChild(circle);
this.knobTarget_ = circle;
this.setValue(0.5);
// Find the root SVG object.
......@@ -69,15 +88,17 @@ var Slider = function(x, y, width, svgParent, opt_changeFunc) {
this.SVG_ = svgParent;
// Bind the events to this slider.
var thisSlider = this;
Slider.bindEvent_(this.knob_, 'mousedown', function(e) {
return thisSlider.knobMouseDown_(e);
});
Slider.bindEvent_(this.SVG_, 'mouseup', Slider.knobMouseUp_);
Slider.bindEvent_(this.SVG_, 'mousemove', Slider.knobMouseMove_);
Slider.bindEvent_(document, 'mouseover', Slider.mouseOver_);
Slider.bindEvent_(this.knobTarget_, 'mousedown', this, this.knobMouseDown_);
Slider.bindEvent_(this.knobTarget_, 'touchstart', this, this.knobMouseDown_);
Slider.bindEvent_(this.trackTarget_, 'mousedown', this, this.rectMouseDown_);
Slider.bindEvent_(this.SVG_, 'mouseup', null, Slider.knobMouseUp_);
Slider.bindEvent_(this.SVG_, 'touchend', null, Slider.knobMouseUp_);
Slider.bindEvent_(this.SVG_, 'mousemove', null, Slider.knobMouseMove_);
Slider.bindEvent_(this.SVG_, 'touchmove', null, Slider.knobMouseMove_);
Slider.bindEvent_(document, 'mouseover', null, Slider.mouseOver_);
};
Slider.SVG_NS_ = 'http://www.w3.org/2000/svg';
Slider.activeSlider_ = null;
......@@ -90,6 +111,12 @@ Slider.startKnobX_ = 0;
* @private
*/
Slider.prototype.knobMouseDown_ = function(e) {
if (e.type == 'touchstart') {
if (e.changedTouches.length != 1) {
return;
}
Slider.touchToMouse_(e)
}
Slider.activeSlider_ = this;
Slider.startMouseX_ = this.mouseToSvg_(e).x;
Slider.startKnobX_ = 0;
......@@ -100,9 +127,9 @@ Slider.prototype.knobMouseDown_ = function(e) {
Slider.startKnobX_ = Number(r[1]);
}
}
// Stop browser from attempting to drag the knob.
// Stop browser from attempting to drag the knob or
// from scrolling/zooming the page.
e.preventDefault();
return false;
};
/**
......@@ -143,15 +170,33 @@ Slider.knobMouseMove_ = function(e) {
if (!thisSlider) {
return;
}
if (e.type == 'touchmove') {
if (e.changedTouches.length != 1) {
return;
}
Slider.touchToMouse_(e)
}
var x = thisSlider.mouseToSvg_(e).x - Slider.startMouseX_ +
Slider.startKnobX_;
x = Math.min(Math.max(x, thisSlider.KNOB_MIN_X_), thisSlider.KNOB_MAX_X_);
thisSlider.knob_.setAttribute('transform',
'translate(' + x + ',' + thisSlider.KNOB_Y_ + ')');
thisSlider.setValue((x - thisSlider.KNOB_MIN_X_) /
(thisSlider.KNOB_MAX_X_ - thisSlider.KNOB_MIN_X_));
};
thisSlider.value_ = (x - thisSlider.KNOB_MIN_X_) /
(thisSlider.KNOB_MAX_X_ - thisSlider.KNOB_MIN_X_);
thisSlider.changeFunc_ && thisSlider.changeFunc_(thisSlider.value_);
/**
* Jump to a new value when the track is clicked.
* @param {!Event} e Mouse-down event.
* @private
*/
Slider.prototype.rectMouseDown_ = function(e) {
if (e.type == 'touchstart') {
if (e.changedTouches.length != 1) {
return;
}
Slider.touchToMouse_(e)
}
var x = this.mouseToSvg_(e).x;
this.animateValue((x - this.KNOB_MIN_X_) /
(this.KNOB_MAX_X_ - this.KNOB_MIN_X_));
};
/**
......@@ -162,6 +207,30 @@ Slider.prototype.getValue = function() {
return this.value_;
};
/**
* Animates the slider's value (0.0 - 1.0).
* @param {number} value New value.
*/
Slider.prototype.animateValue = function(value) {
// Clear any ongoing animations.
while (this.animationTasks_.length) {
clearTimeout(this.animationTasks_.pop());
}
var duration = 200; // Milliseconds to animate for.
var steps = 10; // Number of steps to animate.
var oldValue = this.getValue();
var thisSlider = this;
var stepFunc = function(i) {
return function() {
var newVal = i * (value - oldValue) / (steps - 1) + oldValue;
thisSlider.setValue(newVal);
};
}
for (var i = 0; i < steps; i++) {
this.animationTasks_.push(setTimeout(stepFunc(i), i * duration / steps));
}
};
/**
* Sets the slider's value (0.0 - 1.0).
* @param {number} value New value.
......@@ -172,6 +241,8 @@ Slider.prototype.setValue = function(value) {
(this.KNOB_MAX_X_ - this.KNOB_MIN_X_) * this.value_;
this.knob_.setAttribute('transform',
'translate(' + x + ',' + this.KNOB_Y_ + ')');
this.knobTarget_.setAttribute('cx', x);
this.changeFunc_ && this.changeFunc_(this.value_);
};
/**
......@@ -190,11 +261,25 @@ Slider.prototype.mouseToSvg_ = function(e) {
/**
* Bind an event to a function call.
* @param {!Element} element Element upon which to listen.
* @param {!Node} node Node upon which to listen.
* @param {string} name Event name to listen to (e.g. 'mousedown').
* @param {Object} thisObject The value of 'this' in the function.
* @param {!Function} func Function to call when event is triggered.
* @private
*/
Slider.bindEvent_ = function(element, name, func) {
element.addEventListener(name, func, false);
Slider.bindEvent_ = function(node, name, thisObject, func) {
var wrapFunc = function(e) {
func.apply(thisObject, arguments);
};
node.addEventListener(name, wrapFunc, false);
};
/**
* Map the touch event's properties to be compatible with a mouse event.
* @param {TouchEvent} e Event to modify.
*/
Slider.touchToMouse_ = function(e) {
var touchPoint = e.changedTouches[0];
e.clientX = touchPoint.clientX;
e.clientY = touchPoint.clientY;
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment