/* * This file is part of Toolkit. * * Toolkit is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * Toolkit is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General * Public License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ "use strict"; (function(w, TK){ function start_drag(value) { if (!value) return; var O = this.options; this.start_pos = O.range.call(this).val2px(O.get.call(this)); this.fire_event("startdrag", this.drag_state.start); if (O.events) O.events.call(this).fire_event("startdrag", this.drag_state.start); } /* This version integrates movements, instead * of using the global change since the beginning */ function movecapture_int(O, range, state) { /* O.direction is always 'polar' here */ /* movement since last event */ var v = state.prev_distance(); var RO = range.options; if (!v[0] && !v[1]) return; var V = O._direction; var dist = Math.sqrt(v[0]*v[0] + v[1]*v[1]); var c = (V[0]*v[0] - V[1]*v[1]) / dist; if (Math.abs(c) > O._cutoff) return; if (v[0] * V[1] + v[1] * V[0] < 0) dist = -dist; var multi = RO.step || 1; var e = state.current; if (e.ctrlKey || e.altKey) { multi *= RO.shift_down; } else if (e.shiftKey) { multi *= RO.shift_up; } dist *= multi; var v = this.start_pos + dist; var nval = range.px2val(v); if (O.limit) O.set.call(this, Math.min(RO.max, Math.max(RO.min, nval))); else O.set.call(this, nval); if (!(nval > RO.min) || !(nval < RO.max)) return; this.start_pos = v; } function movecapture_abs(O, range, state) { var dist; var RO = range.options switch(O.direction) { case "vertical": dist = -state.vdistance()[1]; break; default: TK.warn("Unsupported direction:", O.direction); case "horizontal": dist = state.vdistance()[0]; break; } if (O.reverse) dist *= -1; var multi = RO.step || 1; var e = state.current; if (e.ctrlKey && e.shiftKey) { multi *= RO.shift_down; } else if (e.shiftKey) { multi *= RO.shift_up; } dist *= multi; var nval = range.px2val(this.start_pos + dist); if (O.limit) O.set.call(this, Math.min(RO.max, Math.max(RO.min, nval))); else O.set.call(this, nval); } function movecapture(state) { var O = this.options; if (O.active === false) return false; var state = this.drag_state; var range = O.range.call(this); if (O.direction === "polar") { movecapture_int.call(this, O, range, state); } else { movecapture_abs.call(this, O, range, state); } this.fire_event("dragging", state.current); if (O.events) O.events.call(this).fire_event("dragging", state.current); } function stop_drag(state, ev) { this.fire_event("stopdrag", ev); var O = this.options; if (O.events) O.events.call(this).fire_event("stopdrag", ev); } function angle_diff(a, b) { // returns an unsigned difference between two angles var d = (Math.abs(a - b) + 360) % 360; return d > 180 ? 360 - d : d; } TK.DragValue = TK.class({ /** * TK.DragValue enables dragging an element and setting a * value according to the dragged distance. TK.DragValue is for example * used in {@link TK.Knob} and {@link TK.ValueButton}. * * @class TK.DragValue * * @param {Object} [options={ }] - An object containing initial options. * * @property {Element} options.node - The DOM node used for dragging. * All DOM events are registered with this Element. * @property {Function} [options.range] - A function returning a * {@link TK.Range} object for * calculating the value. Returns its parent (usually having * {@link TK.Ranged}-features) by default. * @property {Function} [options.events] - Returns an element firing the * events startdrag, dragging and stopdrag. * By default it returns this.parent. * @property {Element|boolean} [options.classes=false] - While dragging, the class * toolkit-dragging will be added to this element. If set to false * the class will be set on options.node. * @property {Function} [options.get] - Callback function returning the value to drag. * By default it returns this.parent.options.value. * @property {Function} [options.set] - Callback function for setting the value. * By default it calls this.parent.userset("value", [value]);. * @property {String} [options.direction="polar"] - Direction for changing the value. * Can be polar, vertical or horizontal. * @property {Boolean} [options.active=true] - If false, dragging is deactivated. * @property {Boolean} [options.cursor=false] - If true, a {@link TK.GlobalCursor} is set while dragging. * @property {Number} [options.blind_angle=20] - If options.direction is polar, * this is the angle of separation between positive and negative value changes * @property {Number} [options.rotation=45] - Defines the angle of the center of the positive value * changes. 0 means straight upward. For instance, a value of 45 leads to increasing value when * moving towards top and right. * @property {Boolean} [options.reverse=false] - If true, the difference of pointer travel is inverted. * @property {Boolean} [options.limit=false] - Limit the returned value to min and max of the range. * * @extends TK.Module * * @mixes TK.GlobalCursor */ _class: "DragValue", Extends: TK.DragCapture, Implements: TK.GlobalCursor, _options: { get: "function", set: "function", range: "function", events: "function", classes: "object|boolean", direction: "string", active: "boolean", cursor: "boolean", blind_angle: "number", rotation: "number", reverse: "boolean", limit: "boolean", }, options: { range: function () { return this.parent; }, classes: false, get: function () { return this.parent.options.value; }, set: function (v) { this.parent.userset("value", v); }, events: function () { return this.parent; }, direction: "polar", active: true, cursor: false, blind_angle: 20, rotation: 45, reverse: false, limit: false, }, /** * Is fired while a user is dragging. * * @event TK.DragValue#dragging * * @param {DOMEvent} event - The native DOM event. */ /** * Is fired when a user starts dragging. * * @event TK.DragValue#startdrag * * @param {DOMEvent} event - The native DOM event. */ /** * Is fired when a user stops dragging. * * @event TK.DragValue#stopdrag * * @param {DOMEvent} event - The native DOM event. */ static_events: { set_state: start_drag, stopcapture: stop_drag, startcapture: function() { if (this.options.active) return true; }, set_rotation: function(v) { v *= Math.PI / 180; this.set("_direction", [ -Math.sin(v), Math.cos(v) ]); }, set_blind_angle: function(v) { v *= Math.PI / 360; this.set("_cutoff", Math.cos(v)); }, movecapture: movecapture, startdrag: function(ev) { TK.S.add(function() { var O = this.options; TK.add_class(O.classes || O.node, "toolkit-dragging"); if (O.cursor) { if (O.direction === "vertical") { this.global_cursor("row-resize"); } else { this.global_cursor("col-resize"); } } }.bind(this), 1); }, stopdrag: function() { TK.S.add(function() { var O = this.options; TK.remove_class(O.classes || O.node, "toolkit-dragging"); if (O.cursor) { if (O.direction === "vertical") { this.remove_cursor("row-resize"); } else { this.remove_cursor("col-resize"); } } }.bind(this), 1); }, }, initialize: function (widget, options) { TK.DragCapture.prototype.initialize.call(this, widget, options); this.start_pos = 0; var O = this.options; this.set("rotation", O.rotation); this.set("blind_angle", O.blind_angle); }, }); })(this, this.TK);