lpcmanager

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