--- a/LPCSVGHMI/analyse_widget.xslt Fri Jul 25 13:32:50 2025 +0200
+++ b/LPCSVGHMI/analyse_widget.xslt Fri Aug 01 12:30:31 2025 +0200
@@ -1110,6 +1110,42 @@
<func:result select="$res"/>
+ <xsl:template match="widget[@type='AnimateRotation']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>AnimateRotation widget animates rotation of an SVG element. Widget is a group with label + <xsl:text>HMI:AnimateRotation:optional_args + <xsl:text>Element to rotate is a part of that group labeled "animate". + <xsl:text>Optional element of that group is a graphic whose label is one of: "center:top_left", "center:top_right", + <xsl:text>"center:bottom_left", "center:bottom_right" or "center:center". Label indicates which point of that element + <xsl:text>will be used as a center of rotation for "animate" element. If omitted, "animate" element's center will be used. + <xsl:text>Optional arguments are: + <xsl:text>- duration=value: duration of a single loop in ms (if omitted, 2000 is set) + <xsl:text>- iterations=value: number of loops to be performed (if omitted, infinite number is set) + <xsl:text>- frame_rate=value: number of animation frames per second (if omitted, 10 will be used) + <xsl:text>The higher the frame rate, the higher CPU usage will be. + <xsl:text>Rotation animation</xsl:text> <xsl:template match="widget[@type='CloudImage']" mode="widget_desc">
<xsl:value-of select="@type"/>
@@ -1133,41 +1169,51 @@
<xsl:text>Swipe widget detects left, right, up and down swiping motion and executes
- <xsl:text>associated actions.
+ <xsl:text>associated actions. The widget should be placed on top of the area where the - <xsl:text>For each of the motions to be detected there must exist a group inside the
+ <xsl:text>movement should be detected. It is a group containing a graphical element - <xsl:text>widget with the label "dir_{direction}" where {direction} is from the set:
- <xsl:text>left, right, up, down.
+ <xsl:text>"area" which defines the area where the swipe should be detected. - <xsl:text>Inside each such group, there should be an element with the label
+ <xsl:text>For each of the motions to be detected there must exist several parameters + <xsl:text>named "{direction}_{command}={value}" where {direction} is from the set: + <xsl:text>left, right, up, down; and {command} is from the set: xthreshold (in percents + <xsl:text>of widget width), ythreshold (also percentage), jump (value should be name of - <xsl:text>"thresholds:{x},{y}", where {x} and {y} represent pixel count along X and Y
+ <xsl:text>the page to jump to), change (value should be the change to apply, e.g. +2 to + <xsl:text>increase by 2, or -1 to decrement) or set (value should be the value to set - <xsl:text>axis that define the motion needed to be labelled as swipe.
+ <xsl:text>to). change and set commands should also be accompanied by paths with the + <xsl:text>same name and their values should be variable names to apply the command to. - <xsl:text>Alongside that, one can add up to one element with the label
+ <xsl:text>Additional parameters to add are: - <xsl:text>"jump:{pagename}", where {pagename} is the name of the page which is the
+ <xsl:text> - movethreshold: Percentage of the widget dimensions that define the pointer - <xsl:text>target of a jump.
+ <xsl:text> movement. Anything below that value will not be considered
+ <xsl:text> a movement. If omitted, 5 will be used. + <xsl:text> - presstimeout: Time in milliseconds which will be measured on the pointer - <xsl:text>There can also be any number of elements with the label
+ <xsl:text> down event. If time elapses without any significant movement - <xsl:text>"{expression}:{variable}", where {expression} is a mathematical expression
+ <xsl:text> (defined by movethreshold), the pointer down/click event - <xsl:text>to be applied to {variable}. All variables that appear in such elements
+ <xsl:text> will be propagated on an element on a lower level than the - <xsl:text>must also be listed as paths in the widget label.
+ <xsl:text> swiping area. Similar thing will happen on pointer up event + <xsl:text> if there was no significant movement. @@ -1175,37 +1221,19 @@
- <xsl:text>HMI:Swipe@/VAR0@/VAR1
- <xsl:text> - thresholds:-100,20
- <xsl:text> - thresholds:20,100
+ <xsl:text>HMI:Swipe:movethreshold=3:left_xthreshold=30:left_ythreshold=5:left_jump=Home:up_xthreshold=25:up_ythreshold=5:up_change=+2@up_change=/VAR0 - <xsl:text>This detects left and up swipe motion. To detect swipe left, movement must
+ <xsl:text>This detects left and right swipe motion. To detect swipe left, movement must - <xsl:text>be at least 100 pixels to the left and at most 20 pixels up or down. If
- <xsl:text>detected, it will increase VAR0 by 1 and jump to a page named Home. To
+ <xsl:text>be at least 30% of the widget width to the left and at most 5% of the widget - <xsl:text>detect swipe up, movement must be at most 20 pixels left or right, and at
+ <xsl:text>height up or down. If detected, it will jump to a page named Home. To detect - <xsl:text>least 100 pixels up. If detected, it will decrease VAR0 by 1 and set VAR1
+ <xsl:text>swipe up, movement must be at most 5% of the widget width left or right, and
+ <xsl:text>at least 25% of the widget height up. If detected, it will increase VAR0 by 2. @@ -1213,7 +1241,6 @@
<xsl:text>Detect swipe motion and react accordingly</xsl:text>
- <path name="variable" count="optional" accepts="all"/>
<xsl:template mode="document" match="@* | node()">
--- a/LPCSVGHMI/gen_index_xhtml.xslt Fri Jul 25 13:32:50 2025 +0200
+++ b/LPCSVGHMI/gen_index_xhtml.xslt Fri Aug 01 12:30:31 2025 +0200
@@ -9380,6 +9380,206 @@
+ <xsl:template match="widget[@type='AnimateRotation']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>AnimateRotation widget animates rotation of an SVG element. Widget is a group with label + <xsl:text>HMI:AnimateRotation:optional_args + <xsl:text>Element to rotate is a part of that group labeled "animate". + <xsl:text>Optional element of that group is a graphic whose label is one of: "center:top_left", "center:top_right", + <xsl:text>"center:bottom_left", "center:bottom_right" or "center:center". Label indicates which point of that element + <xsl:text>will be used as a center of rotation for "animate" element. If omitted, "animate" element's center will be used. + <xsl:text>Optional arguments are: + <xsl:text>- duration=value: duration of a single loop in ms (if omitted, 2000 is set) + <xsl:text>- iterations=value: number of loops to be performed (if omitted, infinite number is set) + <xsl:text>- frame_rate=value: number of animation frames per second (if omitted, 10 will be used) + <xsl:text>The higher the frame rate, the higher CPU usage will be. + <xsl:text>Rotation animation</xsl:text> + <xsl:template match="widget[@type='AnimateRotation']" mode="widget_class"> + <xsl:text>class </xsl:text> + <xsl:text>AnimateRotationWidget</xsl:text> + <xsl:text> extends Widget{ + <xsl:text> duration = 2000; + <xsl:text> iterations = "infinite"; + <xsl:text> center_x = null; + <xsl:text> center_y = null; + <xsl:text> frame_rate = 10; + <xsl:template match="widget[@type='AnimateRotation']" mode="widget_defs"> + <xsl:param name="hmi_element"/> + <xsl:variable name="disability"> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>/disabled</xsl:text> + <xsl:with-param name="mandatory" select="'no'"/> + <xsl:value-of select="$disability"/> + <xsl:variable name="has_disability" select="string-length($disability)>0"/> + <xsl:variable name="widget_type" select="@type"/> + <xsl:variable name="widget_id" select="@id"/> + <xsl:text> const widget_pos = this.element.getBBox(); + <xsl:text> this.center_x = widget_pos.x + widget_pos.width / 2; + <xsl:text> this.center_y = widget_pos.y + widget_pos.height / 2; + <xsl:for-each select="arg[contains(@value, '=')]"> + <xsl:variable name="name" select="substring-before(@value, '=')"/> + <xsl:variable name="value" select="substring-after(@value, '=')"/> + <xsl:text> this.</xsl:text> + <xsl:value-of select="$name"/> + <xsl:text> = </xsl:text> + <xsl:value-of select="$value"/> + <xsl:variable name="center_element" select="$hmi_element/*[starts-with(@inkscape:label, 'center:')]"/> + <xsl:if test="count($center_element) = 1"> + <xsl:text> var el = id("</xsl:text> + <xsl:value-of select="$center_element/@id"/> + <xsl:variable name="lab" select="substring-after($center_element/@inkscape:label, 'center:')"/> + <xsl:text> var el_label = "</xsl:text> + <xsl:value-of select="$lab"/> + <xsl:text> const el_pos = el.getBBox(); + <xsl:text> switch (el_label) { + <xsl:text> case "top_left": + <xsl:text> this.center_x = el_pos.x; + <xsl:text> this.center_y = el_pos.y; + <xsl:text> case "top_right": + <xsl:text> this.center_x = el_pos.x + el_pos.width; + <xsl:text> this.center_y = el_pos.y; + <xsl:text> case "bottom_left": + <xsl:text> this.center_x = el_pos.x; + <xsl:text> this.center_y = el_pos.y + el_pos.height; + <xsl:text> case "bottom_right": + <xsl:text> this.center_x = el_pos.x + el_pos.width; + <xsl:text> this.center_y = el_pos.y + el_pos.height; + <xsl:text> case "center": + <xsl:text> this.center_x = el_pos.x + el_pos.width / 2; + <xsl:text> this.center_y = el_pos.y + el_pos.height / 2; + <xsl:text> this.element.removeChild(el); + <xsl:if test="count($center_element) > 1"> + <xsl:variable name="errmsg"> + <xsl:value-of select="$widget_type"/> + <xsl:text> widget (id=</xsl:text> + <xsl:value-of select="$widget_id"/> + <xsl:text>) has more than one center element</xsl:text> + <xsl:message terminate="yes"> + <xsl:value-of select="$errmsg"/> + <xsl:variable name="animate_element" select="$hmi_element/*[@inkscape:label = 'animate']"/> + <xsl:if test="count($animate_element) != 1"> + <xsl:variable name="errmsg"> + <xsl:value-of select="$widget_type"/> + <xsl:text> widget (id=</xsl:text> + <xsl:value-of select="$widget_id"/> + <xsl:text>) must have exactly one animate element</xsl:text> + <xsl:message terminate="yes"> + <xsl:value-of select="$errmsg"/> + <xsl:text> var anim_el = id("</xsl:text> + <xsl:value-of select="$animate_element/@id"/> + <xsl:text> anim_el.style.transformOrigin = vsprintf("%.2fpx %.2fpx", [this.center_x, this.center_y]); + <xsl:text> anim_el.style.animation = vsprintf("animateRotation %.3fs steps(%s) %s", [this.duration / 1000.0, this.frame_rate, this.iterations]); + <cssdefs:animaterotation/> + <xsl:template match="cssdefs:animaterotation"> + <xsl:text>/* </xsl:text> + <xsl:value-of select="local-name()"/> + <xsl:text>@keyframes animateRotation { + <xsl:text> 100% { transform: rotate(360deg); } <xsl:template match="widget[@type='CloudImage']" mode="widget_desc">
<xsl:value-of select="@type"/>
@@ -9456,41 +9656,51 @@
<xsl:text>Swipe widget detects left, right, up and down swiping motion and executes
- <xsl:text>associated actions.
- <xsl:text>For each of the motions to be detected there must exist a group inside the
- <xsl:text>widget with the label "dir_{direction}" where {direction} is from the set:
- <xsl:text>left, right, up, down.
- <xsl:text>Inside each such group, there should be an element with the label
- <xsl:text>"thresholds:{x},{y}", where {x} and {y} represent pixel count along X and Y
- <xsl:text>axis that define the motion needed to be labelled as swipe.
- <xsl:text>Alongside that, one can add up to one element with the label
- <xsl:text>"jump:{pagename}", where {pagename} is the name of the page which is the
- <xsl:text>target of a jump.
- <xsl:text>There can also be any number of elements with the label
- <xsl:text>"{expression}:{variable}", where {expression} is a mathematical expression
- <xsl:text>to be applied to {variable}. All variables that appear in such elements
- <xsl:text>must also be listed as paths in the widget label.
+ <xsl:text>associated actions. The widget should be placed on top of the area where the + <xsl:text>movement should be detected. It is a group containing a graphical element + <xsl:text>"area" which defines the area where the swipe should be detected. + <xsl:text>For each of the motions to be detected there must exist several parameters + <xsl:text>named "{direction}_{command}={value}" where {direction} is from the set: + <xsl:text>left, right, up, down; and {command} is from the set: xthreshold (in percents + <xsl:text>of widget width), ythreshold (also percentage), jump (value should be name of + <xsl:text>the page to jump to), change (value should be the change to apply, e.g. +2 to + <xsl:text>increase by 2, or -1 to decrement) or set (value should be the value to set + <xsl:text>to). change and set commands should also be accompanied by paths with the + <xsl:text>same name and their values should be variable names to apply the command to. + <xsl:text>Additional parameters to add are: + <xsl:text> - movethreshold: Percentage of the widget dimensions that define the pointer + <xsl:text> movement. Anything below that value will not be considered + <xsl:text> a movement. If omitted, 5 will be used. + <xsl:text> - presstimeout: Time in milliseconds which will be measured on the pointer + <xsl:text> down event. If time elapses without any significant movement + <xsl:text> (defined by movethreshold), the pointer down/click event + <xsl:text> will be propagated on an element on a lower level than the + <xsl:text> swiping area. Similar thing will happen on pointer up event + <xsl:text> if there was no significant movement. @@ -9498,37 +9708,19 @@
- <xsl:text>HMI:Swipe@/VAR0@/VAR1
- <xsl:text> - thresholds:-100,20
- <xsl:text> - thresholds:20,100
- <xsl:text>This detects left and up swipe motion. To detect swipe left, movement must
- <xsl:text>be at least 100 pixels to the left and at most 20 pixels up or down. If
- <xsl:text>detected, it will increase VAR0 by 1 and jump to a page named Home. To
- <xsl:text>detect swipe up, movement must be at most 20 pixels left or right, and at
- <xsl:text>least 100 pixels up. If detected, it will decrease VAR0 by 1 and set VAR1
+ <xsl:text>HMI:Swipe:movethreshold=3:left_xthreshold=30:left_ythreshold=5:left_jump=Home:up_xthreshold=25:up_ythreshold=5:up_change=+2@up_change=/VAR0 + <xsl:text>This detects left and right swipe motion. To detect swipe left, movement must + <xsl:text>be at least 30% of the widget width to the left and at most 5% of the widget + <xsl:text>height up or down. If detected, it will jump to a page named Home. To detect + <xsl:text>swipe up, movement must be at most 5% of the widget width left or right, and + <xsl:text>at least 25% of the widget height up. If detected, it will increase VAR0 by 2. @@ -9536,7 +9728,6 @@
<xsl:text>Detect swipe motion and react accordingly</xsl:text>
- <path name="variable" count="optional" accepts="all"/>
<xsl:template match="widget[@type='Swipe']" mode="widget_class">
<xsl:text>class </xsl:text>
@@ -9549,15 +9740,25 @@
+ <xsl:text> moveThreshold = 5; + <xsl:text> pressTimeout = 300; + <xsl:text> touchTimer = null; - <xsl:text> x_threshold: Number.MIN_SAFE_INTEGER,
- <xsl:text> y_threshold: 0
+ <xsl:text> xThreshold: 100, + <xsl:text> yThreshold: 0, @@ -9565,9 +9766,9 @@
- <xsl:text> x_threshold: Number.MAX_SAFE_INTEGER,
- <xsl:text> y_threshold: 0
+ <xsl:text> xThreshold: 100, + <xsl:text> yThreshold: 0, @@ -9575,9 +9776,9 @@
- <xsl:text> x_threshold: 0,
- <xsl:text> y_threshold: Number.MIN_SAFE_INTEGER
+ <xsl:text> xThreshold: 0, + <xsl:text> yThreshold: 100, @@ -9585,55 +9786,129 @@
- <xsl:text> x_threshold: 0,
- <xsl:text> y_threshold: Number.MAX_SAFE_INTEGER
+ <xsl:text> xThreshold: 0, + <xsl:text> yThreshold: 100, - <xsl:text> onmouseup(evt) {
- <xsl:text> var xDiff = evt.pageX - this.startX;
- <xsl:text> var yDiff = evt.pageY - this.startY;
- <xsl:text> svg_root.removeEventListener("pointerup", this.bound_onmouseup, true);
+ <xsl:text> propagateMouseDownEvent(simulateUp) { + <xsl:text> const elements = document.elementsFromPoint(this.startX, this.startY); + <xsl:text> if (elements.length > 1) { + <xsl:text> const eventDown = new MouseEvent("pointerdown", { + <xsl:text> view: window, + <xsl:text> bubbles: true, + <xsl:text> cancelable: true, + <xsl:text> const eventClick = new MouseEvent("click", { + <xsl:text> view: window, + <xsl:text> bubbles: true, + <xsl:text> cancelable: true, + <xsl:text> const eventUp = new MouseEvent("pointerup", { + <xsl:text> view: window, + <xsl:text> bubbles: true, + <xsl:text> cancelable: true, + <xsl:text> const cb = document.getElementById(elements[1].id); + <xsl:text> cb.dispatchEvent(eventDown); + <xsl:text> cb.dispatchEvent(eventClick); + <xsl:text> if (simulateUp) { + <xsl:text> window.setTimeout(() => { + <xsl:text> cb.dispatchEvent(eventUp); + <xsl:text> onMouseUp(evt) { + <xsl:text> window.clearTimeout(this.touchTimer); + <xsl:text> this.touchTimer = null; + <xsl:text> svg_root.removeEventListener("pointerup", this.boundOnMouseUp, true); + <xsl:text> svg_root.removeEventListener("pointermove", this.boundOnMouseMove, true); + <xsl:text> const area = this.element.getBoundingClientRect(); + <xsl:text> var xDiff = (evt.pageX - this.startX) * 100.0 / area.width; + <xsl:text> var yDiff = (evt.pageY - this.startY) * 100.0 / area.height; <xsl:text> var action = null;
- <xsl:text> if (xDiff <= this.settings.left.x_threshold && Math.abs(yDiff) < this.settings.left.y_threshold) {
+ <xsl:text> if (xDiff < 0 && Math.abs(xDiff) >= this.settings.left.xThreshold && Math.abs(yDiff) < this.settings.left.yThreshold) { <xsl:text> action = "left";
- <xsl:text> } else if (xDiff >= this.settings.right.x_threshold && Math.abs(yDiff) < this.settings.right.y_threshold) {
+ <xsl:text> } else if (xDiff > 0 && Math.abs(xDiff) >= this.settings.right.xThreshold && Math.abs(yDiff) < this.settings.right.yThreshold) { <xsl:text> action = "right";
- <xsl:text> } else if (yDiff <= this.settings.up.y_threshold && Math.abs(xDiff) < this.settings.up.x_threshold) {
+ <xsl:text> } else if (yDiff < 0 && Math.abs(yDiff) >= this.settings.up.yThreshold && Math.abs(xDiff) < this.settings.up.xThreshold) { <xsl:text> action = "up";
- <xsl:text> } else if (yDiff >= this.settings.down.y_threshold && Math.abs(xDiff) < this.settings.down.x_threshold) {
+ <xsl:text> } else if (yDiff > 0 && Math.abs(yDiff) >= this.settings.down.yThreshold && Math.abs(xDiff) < this.settings.down.xThreshold) { <xsl:text> action = "down";
+ <xsl:text> } else if (Math.abs(xDiff) < this.moveThreshold && Math.abs(yDiff) < this.moveThreshold) { + <xsl:text> this.propagateMouseDownEvent(true); <xsl:text> for (var a of this.settings[action].actions) {
- <xsl:text> if (a.action == "jump")
+ <xsl:text> if (a.action == "jump") { <xsl:text> fading_page_switch(a.target);
- <xsl:text> else if (a.action == "change") {
- <xsl:text> this.change_hmi_value(a.var_idx, a.change);
+ <xsl:text> } else if (a.action == "change") { + <xsl:text> this.change_hmi_value(a.var_idx, a.value); + <xsl:text> } else if (a.action == "set") { + <xsl:text> this.apply_hmi_value(a.var_idx, a.value); @@ -9645,13 +9920,51 @@
- <xsl:text> onmousedown(evt) {
+ <xsl:text> onMouseMove(evt) { + <xsl:text> this.currX = evt.pageX; + <xsl:text> this.currY = evt.pageY; + <xsl:text> onMouseDown(evt) { <xsl:text> this.startX = evt.pageX;
<xsl:text> this.startY = evt.pageY;
- <xsl:text> svg_root.addEventListener("pointerup", this.bound_onmouseup, true);
+ <xsl:text> this.currX = evt.pageX; + <xsl:text> this.currY = evt.pageY; + <xsl:text> svg_root.addEventListener("pointerup", this.boundOnMouseUp, true); + <xsl:text> svg_root.addEventListener("pointermove", this.boundOnMouseMove, true); + <xsl:text> this.touchTimer = window.setTimeout(() => { + <xsl:text> const area = this.element.getBBox(); + <xsl:text> var xDiff = (this.currX - this.startX) * 100.0 / area.width; + <xsl:text> var yDiff = (this.currY - this.startY) * 100.0 / area.height; + <xsl:text> if (Math.abs(xDiff) < this.moveThreshold && Math.abs(yDiff) < this.moveThreshold) { + <xsl:text> svg_root.removeEventListener("pointerup", this.boundOnMouseUp, true); + <xsl:text> svg_root.removeEventListener("pointermove", this.boundOnMouseMove, true); + <xsl:text> this.propagateMouseDownEvent(false); + <xsl:text> this.touchTimer = null; + <xsl:text> }, this.pressTimeout); <xsl:text> this.request_animate();
@@ -9675,67 +9988,145 @@
<xsl:variable name="has_disability" select="string-length($disability)>0"/>
<xsl:text> init: function() {
- <xsl:text> this.bound_onmouseup = this.onmouseup.bind(this);
- <xsl:text> this.element.addEventListener("pointerdown", this.onmousedown.bind(this));
- <xsl:for-each select="$hmi_element/*[starts-with(@inkscape:label,'dir_')]">
- <xsl:variable name="name" select="@inkscape:label"/>
- <xsl:variable name="dir" select="substring-after(@inkscape:label,'_')"/>
- <xsl:for-each select="$hmi_element/*[@inkscape:label = $name]/descendant::svg:*">
- <xsl:if test="starts-with(@inkscape:label,'thresholds:')">
- <xsl:variable name="values" select="substring-after(@inkscape:label,':')"/>
- <xsl:variable name="threshx" select="substring-before($values,',')"/>
- <xsl:variable name="threshy" select="substring-after($values,',')"/>
- <xsl:text> this.settings["</xsl:text>
- <xsl:value-of select="$dir"/>
- <xsl:text>"].x_threshold = </xsl:text>
- <xsl:value-of select="$threshx"/>
- <xsl:text> this.settings["</xsl:text>
- <xsl:value-of select="$dir"/>
- <xsl:text>"].y_threshold = </xsl:text>
- <xsl:value-of select="$threshy"/>
- <xsl:if test="starts-with(@inkscape:label,'jump:')">
- <xsl:variable name="target" select="substring-after(@inkscape:label,':')"/>
- <xsl:text> this.settings["</xsl:text>
- <xsl:value-of select="$dir"/>
- <xsl:text>"].actions.push({
- <xsl:text> action: "jump",
- <xsl:text> target: "</xsl:text>
- <xsl:value-of select="$target"/>
- <xsl:if test="regexp:test(@inkscape:label,'^[=+\-].+')">
- <xsl:variable name="var" select="substring-after(@inkscape:label,'@')"/>
- <xsl:variable name="change" select="substring-before(@inkscape:label,'@')"/>
- <xsl:text> this.settings["</xsl:text>
- <xsl:value-of select="$dir"/>
- <xsl:text>"].actions.push({
- <xsl:text> action: "change",
- <xsl:text> var_idx: this.var_indices["</xsl:text>
- <xsl:value-of select="$var"/>
- <xsl:text> change: "</xsl:text>
- <xsl:value-of select="$change"/>
+ <xsl:text> this.boundOnMouseUp = this.onMouseUp.bind(this); + <xsl:text> this.boundOnMouseMove = this.onMouseMove.bind(this); + <xsl:text> this.element.addEventListener("pointerdown", this.onMouseDown.bind(this)); + <xsl:text> const dirs = ["left", "right", "up", "down"]; + <xsl:text> var properDir = false; + <xsl:text> var pathIndex = -1; + <xsl:variable name="paths" select="path"/> + <xsl:for-each select="arg[contains(@value, '=')]"> + <xsl:variable name="name" select="substring-before(@value, '=')"/> + <xsl:variable name="value" select="substring-after(@value, '=')"/> + <xsl:variable name="index"> + <xsl:for-each select="$paths"> + <xsl:if test="@assign = $name"> + <xsl:value-of select="position()-1"/> + <xsl:variable name="direction" select="substring-before($name, '_')"/> + <xsl:variable name="command" select="substring-after($name, '_')"/> + <xsl:text> if ("</xsl:text> + <xsl:value-of select="$index"/> + <xsl:text>".length > 0) { + <xsl:text> pathIndex = Number("</xsl:text> + <xsl:value-of select="$index"/> + <xsl:text> properDir = dirs.findIndex((x) => x == "</xsl:text> + <xsl:value-of select="$direction"/> + <xsl:text> if (properDir) { + <xsl:text> switch ("</xsl:text> + <xsl:value-of select="$command"/> + <xsl:text> case "xthreshold": + <xsl:text> this.settings["</xsl:text> + <xsl:value-of select="$direction"/> + <xsl:text>"].xThreshold = </xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> case "ythreshold": + <xsl:text> this.settings["</xsl:text> + <xsl:value-of select="$direction"/> + <xsl:text>"].yThreshold = </xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> case "jump": + <xsl:text> this.settings["</xsl:text> + <xsl:value-of select="$direction"/> + <xsl:text>"].actions.push({ + <xsl:text> action: "jump", + <xsl:text> target: "</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> case "change": + <xsl:text> this.settings["</xsl:text> + <xsl:value-of select="$direction"/> + <xsl:text>"].actions.push({ + <xsl:text> action: "change", + <xsl:text> var_idx: pathIndex, + <xsl:text> value: "</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> this.settings["</xsl:text> + <xsl:value-of select="$direction"/> + <xsl:text>"].actions.push({ + <xsl:text> action: "set", + <xsl:text> var_idx: pathIndex, + <xsl:text> value: "</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> if ("</xsl:text> + <xsl:value-of select="$name"/> + <xsl:text>" == "movethreshold") { + <xsl:text> this.moveThreshold = "</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> } else if ("</xsl:text> + <xsl:value-of select="$name"/> + <xsl:text>" == "presstimeout") { + <xsl:text> this.pressTimeout = "</xsl:text> + <xsl:value-of select="$value"/> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/LPCSVGHMI/widget_animaterotation.ysl2 Fri Aug 01 12:30:31 2025 +0200
@@ -0,0 +1,96 @@
+// widget_animaterotation.ysl2 +widget_desc("AnimateRotation") { + AnimateRotation widget animates rotation of an SVG element. Widget is a group with label + HMI:AnimateRotation:optional_args + Element to rotate is a part of that group labeled "animate". + Optional element of that group is a graphic whose label is one of: "center:top_left", "center:top_right", + "center:bottom_left", "center:bottom_right" or "center:center". Label indicates which point of that element + will be used as a center of rotation for "animate" element. If omitted, "animate" element's center will be used. + Optional arguments are: + - duration=value: duration of a single loop in ms (if omitted, 2000 is set) + - iterations=value: number of loops to be performed (if omitted, infinite number is set) + - frame_rate=value: number of animation frames per second (if omitted, 10 will be used) + The higher the frame rate, the higher CPU usage will be. + shortdesc > Rotation animation +widget_class("AnimateRotation") + iterations = "infinite"; +widget_defs("AnimateRotation") { + const "widget_type", "@type"; + const "widget_id", "@id"; + | const widget_pos = this.element.getBBox(); + | this.center_x = widget_pos.x + widget_pos.width / 2; + | this.center_y = widget_pos.y + widget_pos.height / 2; + foreach "arg[contains(@value, '=')]" { + const "name", "substring-before(@value, '=')"; + const "value", "substring-after(@value, '=')"; + | this.«$name» = «$value»; + const "center_element", "$hmi_element/*[starts-with(@inkscape:label, 'center:')]"; + if "count($center_element) = 1" { + | var el = id("«$center_element/@id»"); + const "lab", "substring-after($center_element/@inkscape:label, 'center:')"; + | var el_label = "«$lab»"; + | const el_pos = el.getBBox(); + | this.center_x = el_pos.x; + | this.center_y = el_pos.y; + | this.center_x = el_pos.x + el_pos.width; + | this.center_y = el_pos.y; + | this.center_x = el_pos.x; + | this.center_y = el_pos.y + el_pos.height; + | this.center_x = el_pos.x + el_pos.width; + | this.center_y = el_pos.y + el_pos.height; + | this.center_x = el_pos.x + el_pos.width / 2; + | this.center_y = el_pos.y + el_pos.height / 2; + | this.element.removeChild(el); + if "count($center_element) > 1" { + const "errmsg" > «$widget_type» widget (id=«$widget_id») has more than one center element + const "animate_element", "$hmi_element/*[@inkscape:label = 'animate']"; + if "count($animate_element) != 1" { + const "errmsg" > «$widget_type» widget (id=«$widget_id») must have exactly one animate element + | var anim_el = id("«$animate_element/@id»"); + | anim_el.style.transformOrigin = vsprintf("%.2fpx %.2fpx", [this.center_x, this.center_y]); + | anim_el.style.animation = vsprintf("animateRotation %.3fs steps(%s) %s", [this.duration / 1000.0, this.frame_rate, this.iterations]); +emit "cssdefs:animaterotation" { +@keyframes animateRotation { + 100% { transform: rotate(360deg); } --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/LPCSVGHMI/widget_swipe.ysl2 Fri Aug 01 12:30:31 2025 +0200
@@ -0,0 +1,225 @@
+ Swipe widget detects left, right, up and down swiping motion and executes + associated actions. The widget should be placed on top of the area where the + movement should be detected. It is a group containing a graphical element + "area" which defines the area where the swipe should be detected. + For each of the motions to be detected there must exist several parameters + named "{direction}_{command}={value}" where {direction} is from the set: + left, right, up, down; and {command} is from the set: xthreshold (in percents + of widget width), ythreshold (also percentage), jump (value should be name of + the page to jump to), change (value should be the change to apply, e.g. +2 to + increase by 2, or -1 to decrement) or set (value should be the value to set + to). change and set commands should also be accompanied by paths with the + same name and their values should be variable names to apply the command to. + Additional parameters to add are: + - movethreshold: Percentage of the widget dimensions that define the pointer + movement. Anything below that value will not be considered + a movement. If omitted, 5 will be used. + - presstimeout: Time in milliseconds which will be measured on the pointer + down event. If time elapses without any significant movement + (defined by movethreshold), the pointer down/click event + will be propagated on an element on a lower level than the + swiping area. Similar thing will happen on pointer up event + if there was no significant movement. + HMI:Swipe:movethreshold=3:left_xthreshold=30:left_ythreshold=5:left_jump=Home:up_xthreshold=25:up_ythreshold=5:up_change=+2@up_change=/VAR0 + This detects left and right swipe motion. To detect swipe left, movement must + be at least 30% of the widget width to the left and at most 5% of the widget + height up or down. If detected, it will jump to a page named Home. To detect + swipe up, movement must be at most 5% of the widget width left or right, and + at least 25% of the widget height up. If detected, it will increase VAR0 by 2. + shortdesc > Detect swipe motion and react accordingly + propagateMouseDownEvent(simulateUp) { + const elements = document.elementsFromPoint(this.startX, this.startY); + if (elements.length > 1) { + const eventDown = new MouseEvent("pointerdown", { + const eventClick = new MouseEvent("click", { + const eventUp = new MouseEvent("pointerup", { + const cb = document.getElementById(elements[1].id); + cb.dispatchEvent(eventDown); + cb.dispatchEvent(eventClick); + window.setTimeout(() => { + cb.dispatchEvent(eventUp); + window.clearTimeout(this.touchTimer); + this.touchTimer = null; + svg_root.removeEventListener("pointerup", this.boundOnMouseUp, true); + svg_root.removeEventListener("pointermove", this.boundOnMouseMove, true); + const area = this.element.getBoundingClientRect(); + var xDiff = (evt.pageX - this.startX) * 100.0 / area.width; + var yDiff = (evt.pageY - this.startY) * 100.0 / area.height; + if (xDiff < 0 && Math.abs(xDiff) >= this.settings.left.xThreshold && Math.abs(yDiff) < this.settings.left.yThreshold) { + } else if (xDiff > 0 && Math.abs(xDiff) >= this.settings.right.xThreshold && Math.abs(yDiff) < this.settings.right.yThreshold) { + } else if (yDiff < 0 && Math.abs(yDiff) >= this.settings.up.yThreshold && Math.abs(xDiff) < this.settings.up.xThreshold) { + } else if (yDiff > 0 && Math.abs(yDiff) >= this.settings.down.yThreshold && Math.abs(xDiff) < this.settings.down.xThreshold) { + } else if (Math.abs(xDiff) < this.moveThreshold && Math.abs(yDiff) < this.moveThreshold) { + this.propagateMouseDownEvent(true); + for (var a of this.settings[action].actions) { + if (a.action == "jump") { + fading_page_switch(a.target); + } else if (a.action == "change") { + this.change_hmi_value(a.var_idx, a.value); + } else if (a.action == "set") { + this.apply_hmi_value(a.var_idx, a.value); + this.currX = evt.pageX; + this.currY = evt.pageY; + this.startX = evt.pageX; + this.startY = evt.pageY; + this.currX = evt.pageX; + this.currY = evt.pageY; + svg_root.addEventListener("pointerup", this.boundOnMouseUp, true); + svg_root.addEventListener("pointermove", this.boundOnMouseMove, true); + this.touchTimer = window.setTimeout(() => { + const area = this.element.getBBox(); + var xDiff = (this.currX - this.startX) * 100.0 / area.width; + var yDiff = (this.currY - this.startY) * 100.0 / area.height; + if (Math.abs(xDiff) < this.moveThreshold && Math.abs(yDiff) < this.moveThreshold) { + svg_root.removeEventListener("pointerup", this.boundOnMouseUp, true); + svg_root.removeEventListener("pointermove", this.boundOnMouseMove, true); + this.propagateMouseDownEvent(false); + this.touchTimer = null; + this.request_animate(); + | this.boundOnMouseUp = this.onMouseUp.bind(this); + | this.boundOnMouseMove = this.onMouseMove.bind(this); + | this.element.addEventListener("pointerdown", this.onMouseDown.bind(this)); + | const dirs = ["left", "right", "up", "down"]; + | var properDir = false; + foreach "arg[contains(@value, '=')]" { + const "name", "substring-before(@value, '=')"; + const "value", "substring-after(@value, '=')"; + const "index" foreach "$paths" if "@assign = $name" value "position()-1"; + const "direction", "substring-before($name, '_')"; + const "command", "substring-after($name, '_')"; + | if ("«$index»".length > 0) { + | pathIndex = Number("«$index»"); + | properDir = dirs.findIndex((x) => x == "«$direction»") > -1; + | switch ("«$command»") { + | this.settings["«$direction»"].xThreshold = «$value»; + | this.settings["«$direction»"].yThreshold = «$value»; + | this.settings["«$direction»"].actions.push({ + | this.settings["«$direction»"].actions.push({ + | this.settings["«$direction»"].actions.push({ + | if ("«$name»" == "movethreshold") { + | this.moveThreshold = "«$value»"; + | } else if ("«$name»" == "presstimeout") { + | this.pressTimeout = "«$value»"; \ No newline at end of file