/** * Blockly Apps: SVG Slider * * Copyright 2012 Google Inc. * http://blockly.googlecode.com/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview A slider control in SVG. * @author fraser@google.com (Neil Fraser) */ 'use strict'; /** * Object representing a horizontal slider widget. * @param {number} x The horizontal offset of the slider. * @param {number} y The vertical offset of the slider. * @param {number} width The total width of the slider. * @param {!Element} svgParent The SVG element to append the slider to. * @param {Function} opt_changeFunc Optional callback function that will be * called when the slider is moved. The current value is passed. * @constructor */ 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.value_ = 0.5; this.changeFunc_ = opt_changeFunc; // Draw the slider. /* */ var track = document.createElementNS(Slider.SVG_NS_, 'line'); track.setAttribute('class', 'sliderTrack'); track.setAttribute('x1', x); track.setAttribute('y1', y); track.setAttribute('x2', x + width); track.setAttribute('y2', y); svgParent.appendChild(track); this.track_ = track; 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; this.setValue(0.5); // Find the root SVG object. while (svgParent && svgParent.nodeName.toLowerCase() != 'svg') { svgParent = svgParent.parentNode; } 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.SVG_NS_ = 'http://www.w3.org/2000/svg'; Slider.activeSlider_ = null; Slider.startMouseX_ = 0; Slider.startKnobX_ = 0; /** * Start a drag when clicking down on the knob. * @param {!Event} e Mouse-down event. * @private */ Slider.prototype.knobMouseDown_ = function(e) { Slider.activeSlider_ = this; Slider.startMouseX_ = this.mouseToSvg_(e).x; Slider.startKnobX_ = 0; var transform = this.knob_.getAttribute('transform'); if (transform) { var r = transform.match(/translate\(\s*([-\d.]+)/); if (r) { Slider.startKnobX_ = Number(r[1]); } } // Stop browser from attempting to drag the knob. e.preventDefault(); return false; }; /** * Stop a drag when clicking up anywhere. * @param {Event} e Mouse-up event. * @private */ Slider.knobMouseUp_ = function(e) { Slider.activeSlider_ = null; }; /** * Stop a drag when the mouse enters a node not part of the SVG. * @param {Event} e Mouse-up event. * @private */ Slider.mouseOver_ = function(e) { if (!Slider.activeSlider_) { return; } var node = e.target; // Find the root SVG object. do { if (node == Slider.activeSlider_.SVG_) { return; } } while (node = node.parentNode); Slider.knobMouseUp_(e); }; /** * Drag the knob to follow the mouse. * @param {!Event} e Mouse-move event. * @private */ Slider.knobMouseMove_ = function(e) { var thisSlider = Slider.activeSlider_; if (!thisSlider) { return; } 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.value_ = (x - thisSlider.KNOB_MIN_X_) / (thisSlider.KNOB_MAX_X_ - thisSlider.KNOB_MIN_X_); thisSlider.changeFunc_ && thisSlider.changeFunc_(thisSlider.value_); }; /** * Returns the slider's value (0.0 - 1.0). * @return {number} Current value. */ Slider.prototype.getValue = function() { return this.value_; }; /** * Sets the slider's value (0.0 - 1.0). * @param {number} value New value. */ Slider.prototype.setValue = function(value) { this.value_ = Math.min(Math.max(value, 0), 1); var x = this.KNOB_MIN_X_ + (this.KNOB_MAX_X_ - this.KNOB_MIN_X_) * this.value_; this.knob_.setAttribute('transform', 'translate(' + x + ',' + this.KNOB_Y_ + ')'); }; /** * Convert the mouse coordinates into SVG coordinates. * @param {!Object} e Object with x and y mouse coordinates. * @return {!Object} Object with x and y properties in SVG coordinates. * @private */ Slider.prototype.mouseToSvg_ = function(e) { var svgPoint = this.SVG_.createSVGPoint(); svgPoint.x = e.clientX; svgPoint.y = e.clientY; var matrix = this.SVG_.getScreenCTM().inverse(); return svgPoint.matrixTransform(matrix); }; /** * Bind an event to a function call. * @param {!Element} element Element upon which to listen. * @param {string} name Event name to listen to (e.g. 'mousedown'). * @param {!Function} func Function to call when event is triggered. * @private */ Slider.bindEvent_ = function(element, name, func) { element.addEventListener(name, func, false); };