/*
* This file is part of AUX.
*
* AUX 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.
*
* AUX 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
* 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
*/
/* jshint -W018 */
/* jshint -W086 */
import { DragCapture } from './dragcapture.js';
import { setGlobalCursor, unsetGlobalCursor } from '../utils/global_cursor.js';
import { warn } from '../utils/log.js';
import { toggleClass } from '../utils/dom.js';
import { domScheduler } from '../dom_scheduler.js';
import { MASK_RENDER } from '../scheduler/scheduler.js';
function startDrag(value) {
if (!value) return;
const O = this.options;
const ranged = O.range.call(this);
this._position = ranged.get('transformation').valueToPixel(O.get.call(this));
this.emit('startdrag', this.drag_state.start);
if (O.events) O.events.call(this).emit('startdrag', this.drag_state.start);
}
function applyPosition(O, range, position) {
const { transformation } = range.options;
const { limit, set } = O;
let value = transformation.pixelToValue(position);
let clamped = false;
if (limit) {
const clampedValue = transformation.clampValue(value);
if (clampedValue !== value) {
clamped = true;
value = clampedValue;
}
}
set.call(this, value);
return clamped;
}
/* This version integrates movements, instead
* of using the global change since the beginning */
function moveCaptureInt(O, range, state) {
/* O.direction is always 'polar' here */
/* movement since last event */
const v = state.prevDistance();
const rangeOptions = range.options;
const { _direction, absolute, _cutoff } = O;
if (!v[0] && !v[1]) return;
let distance = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const c = (_direction[0] * v[0] - _direction[1] * v[1]) / distance;
if (Math.abs(c) > _cutoff) return;
if (v[0] * _direction[1] + v[1] * _direction[0] < 0) distance = -distance;
const { step } = rangeOptions;
distance *= step || 1;
const e = state.current;
if (e.ctrlKey || e.altKey) {
distance *= rangeOptions.shift_down;
} else if (e.shiftKey) {
distance *= rangeOptions.shift_up;
}
const position = this._position + distance;
const clamped = applyPosition.call(this, O, range, position);
if (!absolute && clamped) return;
this._position = position;
}
function moveCaptureAbs(O, range, state) {
const { direction, reverse } = O;
const rangeOptions = range.options;
const { step } = rangeOptions;
const vDistance = state.vDistance();
let distance = direction === 'vertical' ? -vDistance[1] : vDistance[0];
if (reverse) distance = -distance;
distance *= step || 1;
const e = state.current;
if (e.ctrlKey && e.shiftKey) {
distance *= rangeOptions.shift_down;
} else if (e.shiftKey) {
distance *= rangeOptions.shift_up;
}
applyPosition.call(this, O, range, this._position + distance);
}
function moveCapture(state) {
const O = this.options;
if (O.active === false) return false;
state = this.drag_state;
const range = O.range.call(this);
if (O.direction === 'polar') {
moveCaptureInt.call(this, O, range, state);
} else {
moveCaptureAbs.call(this, O, range, state);
}
this.emit('dragging', state.current);
if (O.events) O.events.call(this).emit('dragging', state.current);
}
function stopDrag(state, ev) {
this.emit('stopdrag', ev);
const O = this.options;
if (O.events) O.events.call(this).emit('stopdrag', ev);
}
function setCursor() {
switch (this.options.direction) {
case 'vertical':
setGlobalCursor('row-resize');
break;
case 'horizontal':
setGlobalCursor('col-resize');
break;
case 'polar':
setGlobalCursor('move');
break;
}
}
function removeCursor() {
unsetGlobalCursor('row-resize');
unsetGlobalCursor('col-resize');
unsetGlobalCursor('move');
}
/**
* DragValue enables dragging an element and setting a
* value according to the dragged distance. DragValue is for example
* used in {@link Knob} and {@link ValueButton}.
*
* @class 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 Ranged} object for
* calculating the value. Returns its parent (usually having
* {@link Ranged}-features) by default.
* @property {Function} [options.events] - Returns an element firing the
* events <code>startdrag</code>, <code>dragging</code> and <code>stopdrag</code>.
* By default it returns <code>this.parent</code>.
* @property {Element|boolean} [options.classes=false] - While dragging, the class
* <code>.aux-dragging</code> will be added to this element. If set to <code>false</code>
* the class will be set on <code>options.node</code>.
* @property {Function} [options.get] - Callback function returning the value to drag.
* By default it returns <code>this.parent.options.value</code>.
* @property {Function} [options.set] - Callback function for setting the value.
* By default it calls <code>this.parent.userset("value", [value]);</code>.
* @property {String} [options.direction="polar"] - Direction for changing the value.
* Can be <code>polar</code>, <code>vertical</code> or <code>horizontal</code>.
* @property {Boolean} [options.active=true] - If false, dragging is deactivated.
* @property {Boolean} [options.cursor=false] - If true, a global cursor is set while dragging.
* @property {Number} [options.blind_angle=20] - If options.direction is <code>polar</code>,
* 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 DragCapture
*/
/**
* Is fired while a user is dragging.
*
* @event DragValue#dragging
*
* @param {DOMEvent} event - The native DOM event.
*/
/**
* Is fired when a user starts dragging.
*
* @event DragValue#startdrag
*
* @param {DOMEvent} event - The native DOM event.
*/
/**
* Is fired when a user stops dragging.
*
* @event DragValue#stopdrag
*
* @param {DOMEvent} event - The native DOM event.
*/
export class DragValue extends DragCapture {
static get _options() {
return {
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',
absolute: 'boolean',
dragging: 'boolean',
};
}
static get options() {
return {
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,
absolute: false,
dragging: false,
};
}
static get static_events() {
return {
set_state: startDrag,
set_dragging: function () {
domScheduler.schedule(MASK_RENDER, () => {
if (this.isDestructed()) return;
const O = this.options;
const node = O.classes || O.node;
const dragging = O.dragging;
toggleClass(node, 'aux-dragging', dragging);
if (dragging) {
setCursor.call(this);
} else {
removeCursor();
}
});
},
stopcapture: stopDrag,
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 () {
this.set('dragging', true);
},
stopdrag: function () {
this.set('dragging', false);
},
};
}
initialize(widget, options) {
super.initialize(widget, options);
this._position = 0;
const O = this.options;
this.set('rotation', O.rotation);
this.set('blind_angle', O.blind_angle);
}
}