# HG changeset patch # User Edouard Tisserant # Date 1633095158 -7200 # Node ID 2507e35976c0996244f51213d94b897adfbc2fd8 # Parent b16e9561a3c1e6a9e4feec196203c5aab618648c SVGHMI: Added PathSlider widget diff -r b16e9561a3c1 -r 2507e35976c0 svghmi/widget_pathslider.ysl2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/widget_pathslider.ysl2 Fri Oct 01 15:32:38 2021 +0200 @@ -0,0 +1,173 @@ +// widget_pathslider.ysl2 +widget_desc("PathSlider") { + longdesc + || + PathSlider - + || + + shortdesc > Slide an SVG element along a path by dragging it + + path name="value" accepts="HMI_INT,HMI_REAL" > value + path name="min" count="optional" accepts="HMI_INT,HMI_REAL" > min + path name="max" count="optional" accepts="HMI_INT,HMI_REAL" > max + + arg name="min" count="optional" accepts="int,real" > minimum value + arg name="max" count="optional" accepts="int,real" > maximum value +} + +widget_class("PathSlider") { + || + frequency = 10; + position = undefined; + min = 0; + max = 100; + scannedPoints = []; + pathLength = undefined; + precision = undefined; + origPt = undefined; + + + scanPath() { + this.pathLength = this.path_elt.getTotalLength(); + this.precision = Math.floor(this.pathLength / 10); + + // save linear scan for coarse approximation + for (var scanLength = 0; scanLength <= this.pathLength; scanLength += this.precision) { + this.scannedPoints.push([this.path_elt.getPointAtLength(scanLength), scanLength]); + } + [this.origPt,] = this.scannedPoints[0]; + } + + closestPoint(point) { + var bestPoint, + bestLength, + bestDistance = Infinity, + scanDistance; + + // use linear scan for coarse approximation + for (let [scanPoint, scanLength] of this.scannedPoints){ + if ((scanDistance = distance2(scanPoint)) < bestDistance) { + bestPoint = scanPoint, + bestLength = scanLength, + bestDistance = scanDistance; + } + } + + // binary search for more precise estimate + let precision = this.precision / 2; + while (precision > 0.5) { + var beforePoint, + afterPoint, + beforeLength, + afterLength, + beforeDistance, + afterDistance; + if ((beforeLength = bestLength - precision) >= 0 && + (beforeDistance = distance2(beforePoint = this.path_elt.getPointAtLength(beforeLength))) < bestDistance) { + bestPoint = beforePoint, + bestLength = beforeLength, + bestDistance = beforeDistance; + } else if ((afterLength = bestLength + precision) <= this.pathLength && + (afterDistance = distance2(afterPoint = this.path_elt.getPointAtLength(afterLength))) < bestDistance) { + bestPoint = afterPoint, + bestLength = afterLength, + bestDistance = afterDistance; + } + precision /= 2; + } + + return [bestPoint, bestLength]; + + function distance2(p) { + var dx = p.x - point.x, + dy = p.y - point.y; + return dx * dx + dy * dy; + } + } + + dispatch(value,oldval, index) { + switch(index) { + case 0: + this.position = value; + break; + case 1: + this.min = value; + break; + case 2: + this.max = value; + break; + } + + this.request_animate(); + } + + get_current_point(){ + let currLength = this.pathLength * (this.position - this.min) / (this.max - this.min) + return this.path_elt.getPointAtLength(currLength); + } + + animate(){ + if(this.position == undefined) + return; + + let currPt = this.get_current_point(); + this.cursor_transform.setTranslate(currPt.x - this.origPt.x, currPt.y - this.origPt.y); + } + + init() { + if(this.args.length == 2) + [this.min, this.max]=this.args; + + this.scanPath(); + + this.cursor_transform = svg_root.createSVGTransform(); + + this.cursor_elt.transform.baseVal.appendItem(this.cursor_transform); + + this.cursor_elt.onpointerdown = (e) => this.on_cursor_down(e); + + this.bound_drag = this.drag.bind(this); + this.bound_drop = this.drop.bind(this); + } + + start_dragging_from_event(e){ + let clientPoint = new DOMPoint(e.clientX, e.clientY); + let point = clientPoint.matrixTransform(this.invctm); + let currPt = this.get_current_point(); + this.draggingOffset = new DOMPoint(point.x - currPt.x , point.y - currPt.y); + } + + apply_position_from_event(e){ + let clientPoint = new DOMPoint(e.clientX, e.clientY); + let rawPoint = clientPoint.matrixTransform(this.invctm); + let point = new DOMPoint(rawPoint.x - this.draggingOffset.x , rawPoint.y - this.draggingOffset.y); + let [closestPoint, closestLength] = this.closestPoint(point); + let new_position = this.min + (this.max - this.min) * closestLength / this.pathLength; + this.position = Math.round(Math.max(Math.min(new_position, this.max), this.min)); + this.apply_hmi_value(0, this.position); + } + + on_cursor_down(e){ + // get scrollbar -> root transform + let ctm = this.path_elt.getCTM(); + // root -> path transform + this.invctm = ctm.inverse(); + this.start_dragging_from_event(e); + svg_root.addEventListener("pointerup", this.bound_drop, true); + svg_root.addEventListener("pointermove", this.bound_drag, true); + } + + drop(e) { + svg_root.removeEventListener("pointerup", this.bound_drop, true); + svg_root.removeEventListener("pointermove", this.bound_drag, true); + } + + drag(e) { + this.apply_position_from_event(e); + } + || +} + +widget_defs("PathSlider") { + labels("cursor path"); +} diff -r b16e9561a3c1 -r 2507e35976c0 tests/svghmi_pathslider/beremiz.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi_pathslider/beremiz.xml Fri Oct 01 15:32:38 2021 +0200 @@ -0,0 +1,5 @@ + + + + + diff -r b16e9561a3c1 -r 2507e35976c0 tests/svghmi_pathslider/plc.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi_pathslider/plc.xml Fri Oct 01 15:32:38 2021 +0200 @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + var0 + + + + + + + + + + + + + var1 + + + + + + + + + + + + + + + + + + diff -r b16e9561a3c1 -r 2507e35976c0 tests/svghmi_pathslider/svghmi_0@svghmi/baseconfnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi_pathslider/svghmi_0@svghmi/baseconfnode.xml Fri Oct 01 15:32:38 2021 +0200 @@ -0,0 +1,2 @@ + + diff -r b16e9561a3c1 -r 2507e35976c0 tests/svghmi_pathslider/svghmi_0@svghmi/confnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi_pathslider/svghmi_0@svghmi/confnode.xml Fri Oct 01 15:32:38 2021 +0200 @@ -0,0 +1,2 @@ + + diff -r b16e9561a3c1 -r 2507e35976c0 tests/svghmi_pathslider/svghmi_0@svghmi/messages.pot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi_pathslider/svghmi_0@svghmi/messages.pot Fri Oct 01 15:32:38 2021 +0200 @@ -0,0 +1,17 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2021-02-12 21:55+CET\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: SVGHMI 1.0\n" + + diff -r b16e9561a3c1 -r 2507e35976c0 tests/svghmi_pathslider/svghmi_0@svghmi/svghmi.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/svghmi_pathslider/svghmi_0@svghmi/svghmi.svg Fri Oct 01 15:32:38 2021 +0200 @@ -0,0 +1,882 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + number + + + + + + + + 7 + + + + 4 + + + + 1 + + + + 8 + + + + 5 + + + + 2 + + + + 9 + + + + 6 + + + + 3 + + + + 0 + + + + + Esc + + + + + + + + +/- + + information + + + . + + + + + + 1234 + + + -1 + + + + -10 + + + + +1 + + + + +10 + + + + + 1234 + + + -1 + + + + -10 + + + + +1 + + + + +10 + + + + + 1234 + + + -1 + + + + -10 + + + + +1 + + + + +10 + + + + + + Position + Min + Max + + + + + + + + + + + + +