edouard@3324: // widget_pathslider.ysl2 edouard@3324: widget_desc("PathSlider") { edouard@3324: longdesc edouard@3324: || edouard@3324: PathSlider - edouard@3324: || edouard@3324: edouard@3324: shortdesc > Slide an SVG element along a path by dragging it edouard@3324: edouard@3324: path name="value" accepts="HMI_INT,HMI_REAL" > value edouard@3324: path name="min" count="optional" accepts="HMI_INT,HMI_REAL" > min edouard@3324: path name="max" count="optional" accepts="HMI_INT,HMI_REAL" > max edouard@3324: edouard@3324: arg name="min" count="optional" accepts="int,real" > minimum value edouard@3324: arg name="max" count="optional" accepts="int,real" > maximum value edouard@3324: } edouard@3324: edouard@3324: widget_class("PathSlider") { edouard@3324: || edouard@3324: frequency = 10; edouard@3324: position = undefined; edouard@3324: min = 0; edouard@3324: max = 100; edouard@3324: scannedPoints = []; edouard@3324: pathLength = undefined; edouard@3324: precision = undefined; edouard@3324: origPt = undefined; edouard@3324: edouard@3324: edouard@3324: scanPath() { edouard@3324: this.pathLength = this.path_elt.getTotalLength(); edouard@3324: this.precision = Math.floor(this.pathLength / 10); edouard@3324: edouard@3324: // save linear scan for coarse approximation edouard@3324: for (var scanLength = 0; scanLength <= this.pathLength; scanLength += this.precision) { edouard@3324: this.scannedPoints.push([this.path_elt.getPointAtLength(scanLength), scanLength]); edouard@3324: } edouard@3324: [this.origPt,] = this.scannedPoints[0]; edouard@3324: } edouard@3324: edouard@3324: closestPoint(point) { edouard@3324: var bestPoint, edouard@3324: bestLength, edouard@3324: bestDistance = Infinity, edouard@3324: scanDistance; edouard@3324: edouard@3324: // use linear scan for coarse approximation edouard@3324: for (let [scanPoint, scanLength] of this.scannedPoints){ edouard@3324: if ((scanDistance = distance2(scanPoint)) < bestDistance) { edouard@3324: bestPoint = scanPoint, edouard@3324: bestLength = scanLength, edouard@3324: bestDistance = scanDistance; edouard@3324: } edouard@3324: } edouard@3324: edouard@3324: // binary search for more precise estimate edouard@3324: let precision = this.precision / 2; edouard@3324: while (precision > 0.5) { edouard@3324: var beforePoint, edouard@3324: afterPoint, edouard@3324: beforeLength, edouard@3324: afterLength, edouard@3324: beforeDistance, edouard@3324: afterDistance; edouard@3324: if ((beforeLength = bestLength - precision) >= 0 && edouard@3324: (beforeDistance = distance2(beforePoint = this.path_elt.getPointAtLength(beforeLength))) < bestDistance) { edouard@3324: bestPoint = beforePoint, edouard@3324: bestLength = beforeLength, edouard@3324: bestDistance = beforeDistance; edouard@3324: } else if ((afterLength = bestLength + precision) <= this.pathLength && edouard@3324: (afterDistance = distance2(afterPoint = this.path_elt.getPointAtLength(afterLength))) < bestDistance) { edouard@3324: bestPoint = afterPoint, edouard@3324: bestLength = afterLength, edouard@3324: bestDistance = afterDistance; edouard@3324: } edouard@3324: precision /= 2; edouard@3324: } edouard@3324: edouard@3324: return [bestPoint, bestLength]; edouard@3324: edouard@3324: function distance2(p) { edouard@3324: var dx = p.x - point.x, edouard@3324: dy = p.y - point.y; edouard@3324: return dx * dx + dy * dy; edouard@3324: } edouard@3324: } edouard@3324: edouard@3324: dispatch(value,oldval, index) { edouard@3324: switch(index) { edouard@3324: case 0: edouard@3324: this.position = value; edouard@3324: break; edouard@3324: case 1: edouard@3324: this.min = value; edouard@3324: break; edouard@3324: case 2: edouard@3324: this.max = value; edouard@3324: break; edouard@3324: } edouard@3324: edouard@3324: this.request_animate(); edouard@3324: } edouard@3324: edouard@3324: get_current_point(){ edouard@3324: let currLength = this.pathLength * (this.position - this.min) / (this.max - this.min) edouard@3324: return this.path_elt.getPointAtLength(currLength); edouard@3324: } edouard@3324: edouard@3324: animate(){ edouard@3324: if(this.position == undefined) edouard@3324: return; edouard@3324: edouard@3324: let currPt = this.get_current_point(); edouard@3324: this.cursor_transform.setTranslate(currPt.x - this.origPt.x, currPt.y - this.origPt.y); edouard@3324: } edouard@3324: edouard@3324: init() { edouard@3324: if(this.args.length == 2) edouard@3324: [this.min, this.max]=this.args; edouard@3324: edouard@3324: this.scanPath(); edouard@3324: edouard@3324: this.cursor_transform = svg_root.createSVGTransform(); edouard@3324: edouard@3324: this.cursor_elt.transform.baseVal.appendItem(this.cursor_transform); edouard@3324: edouard@3324: this.cursor_elt.onpointerdown = (e) => this.on_cursor_down(e); edouard@3324: edouard@3324: this.bound_drag = this.drag.bind(this); edouard@3324: this.bound_drop = this.drop.bind(this); edouard@3324: } edouard@3324: edouard@3324: start_dragging_from_event(e){ edouard@3324: let clientPoint = new DOMPoint(e.clientX, e.clientY); edouard@3324: let point = clientPoint.matrixTransform(this.invctm); edouard@3324: let currPt = this.get_current_point(); edouard@3324: this.draggingOffset = new DOMPoint(point.x - currPt.x , point.y - currPt.y); edouard@3324: } edouard@3324: edouard@3324: apply_position_from_event(e){ edouard@3324: let clientPoint = new DOMPoint(e.clientX, e.clientY); edouard@3324: let rawPoint = clientPoint.matrixTransform(this.invctm); edouard@3324: let point = new DOMPoint(rawPoint.x - this.draggingOffset.x , rawPoint.y - this.draggingOffset.y); edouard@3324: let [closestPoint, closestLength] = this.closestPoint(point); edouard@3324: let new_position = this.min + (this.max - this.min) * closestLength / this.pathLength; edouard@3324: this.position = Math.round(Math.max(Math.min(new_position, this.max), this.min)); edouard@3324: this.apply_hmi_value(0, this.position); edouard@3324: } edouard@3324: edouard@3324: on_cursor_down(e){ edouard@3324: // get scrollbar -> root transform edouard@3324: let ctm = this.path_elt.getCTM(); edouard@3324: // root -> path transform edouard@3324: this.invctm = ctm.inverse(); edouard@3324: this.start_dragging_from_event(e); edouard@3324: svg_root.addEventListener("pointerup", this.bound_drop, true); edouard@3324: svg_root.addEventListener("pointermove", this.bound_drag, true); edouard@3324: } edouard@3324: edouard@3324: drop(e) { edouard@3324: svg_root.removeEventListener("pointerup", this.bound_drop, true); edouard@3324: svg_root.removeEventListener("pointermove", this.bound_drag, true); edouard@3324: } edouard@3324: edouard@3324: drag(e) { edouard@3324: this.apply_position_from_event(e); edouard@3324: } edouard@3324: || edouard@3324: } edouard@3324: edouard@3324: widget_defs("PathSlider") { edouard@3324: labels("cursor path"); edouard@3324: }