--- a/svghmi/gen_index_xhtml.xslt Wed Dec 02 14:33:24 2020 +0100
+++ b/svghmi/gen_index_xhtml.xslt Sat Dec 05 16:59:27 2020 +0100
@@ -1675,135 +1675,220 @@
+ <xsl:variable name="_button_fsm"> + <on-dispatch value="false"> + <jump state="released"/> + <on-dispatch value="true"> + <jump state="pressed"/> + <state name="pressing"> + <hmi-value value="true"/> + <on-dispatch value="true"> + <jump state="pressed"/> + <on-mouse position="up"> + <jump state="shortpress"/> + <show eltname="active"/> + <on-mouse position="up"> + <jump state="releasing"/> + <on-dispatch value="false"> + <jump state="released"/> + <state name="shortpress"> + <on-dispatch value="true"> + <jump state="releasing"/> + <on-mouse position="down"> + <jump state="pressing"/> + <state name="releasing"> + <hmi-value value="false"/> + <on-dispatch value="false"> + <jump state="released"/> + <on-mouse position="down"> + <jump state="shortrelease"/> + <state name="released"> + <show eltname="inactive"/> + <on-mouse position="down"> + <jump state="pressing"/> + <on-dispatch value="true"> + <jump state="pressed"/> + <state name="shortrelease"> + <on-dispatch value="false"> + <jump state="pressing"/> + <on-mouse position="up"> + <jump state="releasing"/> + <xsl:template mode="dispatch_transition" match="fsm"> + <xsl:text> switch (this.state) { + <xsl:apply-templates mode="dispatch_transition" select="state"/> + <xsl:template mode="dispatch_transition" match="state"> + <xsl:text> case "</xsl:text> + <xsl:value-of select="@name"/> + <xsl:apply-templates select="on-dispatch"/> + <xsl:template match="on-dispatch"> + <xsl:text> if(value == </xsl:text> + <xsl:value-of select="@value"/> + <xsl:apply-templates mode="transition" select="jump"/> + <xsl:template mode="mouse_transition" match="fsm"> + <xsl:param name="position"/> + <xsl:text> switch (this.state) { + <xsl:apply-templates mode="mouse_transition" select="state"> + <xsl:with-param name="position" select="$position"/> + <xsl:template mode="mouse_transition" match="state"> + <xsl:param name="position"/> + <xsl:text> case "</xsl:text> + <xsl:value-of select="@name"/> + <xsl:apply-templates select="on-mouse[@position = $position]"/> + <xsl:template match="on-mouse"> + <xsl:apply-templates mode="transition" select="jump"/> + <xsl:template mode="transition" match="jump"> + <xsl:text> this.state = "</xsl:text> + <xsl:value-of select="@state"/> + <xsl:text> this.</xsl:text> + <xsl:value-of select="@state"/> + <xsl:template mode="actions" match="fsm"> + <xsl:apply-templates mode="actions" select="state"/> + <xsl:template mode="actions" match="state"> + <xsl:value-of select="@name"/> + <xsl:apply-templates mode="actions" select="*"/> + <xsl:template mode="actions" match="show"> + <xsl:text> this.display = "</xsl:text> + <xsl:value-of select="@eltname"/> + <xsl:text> this.request_animate(); + <xsl:template mode="actions" match="hmi-value"> + <xsl:text> this.apply_hmi_value(0, </xsl:text> + <xsl:value-of select="@value"/> <xsl:template mode="widget_class" match="widget[@type='Button']">
+ <xsl:variable name="fsm" select="exsl:node-set($_button_fsm)"/> <xsl:text>class ButtonWidget extends Widget{
<xsl:text> frequency = 5;
- <xsl:text> state_plc = 0;
- <xsl:text> state_hmi = 0;
- <xsl:text> plc_lock = false;
- <xsl:text> active_style = undefined;
- <xsl:text> inactive_style = undefined;
+ <xsl:text> display = "inactive"; + <xsl:text> state = "init"; <xsl:text> dispatch(value) {
- <xsl:text> this.state_plc = value;
- <xsl:text> if(this.plc_lock){
- <xsl:text> if(this.state_plc == 1){
- <xsl:text> this.apply_hmi_value(0, 0);
- <xsl:text> this.plc_lock = false;
+ <xsl:apply-templates mode="dispatch_transition" select="$fsm"/> + <xsl:text> onmouseup(evt) { + <xsl:text> svg_root.removeEventListener("pointerup", this.bound_onmouseup, true); + <xsl:apply-templates mode="mouse_transition" select="$fsm"> + <xsl:with-param name="position" select="'up'"/> + <xsl:text> onmousedown(evt) { + <xsl:text> svg_root.addEventListener("pointerup", this.bound_onmouseup, true); + <xsl:apply-templates mode="mouse_transition" select="$fsm"> + <xsl:with-param name="position" select="'down'"/> + <xsl:apply-templates mode="actions" select="$fsm"/> + <xsl:text> if (this.active_elt && this.inactive_elt) { + <xsl:for-each select="str:split('active inactive')"> + <xsl:text> if(this.display == "</xsl:text> + <xsl:value-of select="."/> + <xsl:text> this.</xsl:text> + <xsl:value-of select="."/> + <xsl:text>_elt.style.display = ""; + <xsl:text> this.</xsl:text> + <xsl:value-of select="."/> + <xsl:text>_elt.style.display = "none";
- <xsl:text> //redraw button
- <xsl:text> this.state_hmi = this.state_plc;
- <xsl:text> this.request_animate();
- <xsl:text> if (this.active_style && this.inactive_style) {
- <xsl:text> // redraw button on screen refresh
- <xsl:text> if (this.state_hmi) {
- <xsl:text> this.active_elt.setAttribute("style", this.active_style);
- <xsl:text> this.inactive_elt.setAttribute("style", "display:none");
- <xsl:text> this.inactive_elt.setAttribute("style", this.inactive_style);
- <xsl:text> this.active_elt.setAttribute("style", "display:none");
+ <xsl:text> this.bound_onmouseup = this.onmouseup.bind(this); + <xsl:text> this.element.addEventListener("pointerdown", this.onmousedown.bind(this));
- <xsl:text> on_click(evt) {
- <xsl:text> //set state and apply if plc is 0
- <xsl:text> this.plc_lock = true;
- <xsl:text> if(this.state_plc == 0){
- <xsl:text> this.apply_hmi_value(0, 1);
- <xsl:text> //redraw button
- <xsl:text> this.request_animate();
- <xsl:text> on_press(evt) {
- <xsl:text> //set graphic
- <xsl:text> this.state_hmi = 1;
- <xsl:text> //redraw button
- <xsl:text> this.request_animate();
- <xsl:text> this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
- <xsl:text> this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
- <xsl:text> if (this.active_style && this.inactive_style) {
- <xsl:text> this.active_elt.setAttribute("style", "display:none");
- <xsl:text> this.inactive_elt.setAttribute("style", this.inactive_style);
- <xsl:text> this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
- <xsl:text> this.element.setAttribute("onmousedown", "hmi_widgets['"+this.element_id+"'].on_press(evt)");
@@ -6645,12 +6730,16 @@
<xsl:text>function prepare_svg() {
+ <xsl:text> // prevents context menu from appearing on right click and long touch <xsl:text> document.body.addEventListener('contextmenu', e => {
<xsl:text> e.preventDefault();
<xsl:text> for(let eltid in detachable_elements){
<xsl:text> let [element,parent] = detachable_elements[eltid];
--- a/svghmi/widget_button.ysl2 Wed Dec 02 14:33:24 2020 +0100
+++ b/svghmi/widget_button.ysl2 Sat Dec 05 16:59:27 2020 +0100
@@ -1,73 +1,152 @@
+decl on_mouse(position); +decl on_dispatch(value); +// State machine to drive HMI_BOOL on a potentially laggy connection +// TODO: make more robust in case other widget or PLC change value on their own +const "_button_fsm" fsm { + on_dispatch "false" jump "released"; + on_dispatch "true" jump "pressed"; + on_dispatch "true" jump "pressed"; + on_mouse "up" jump "shortpress"; + on_mouse "up" jump "releasing"; + on_dispatch "false" jump "released"; + on_dispatch "true" jump "releasing"; + on_mouse "down" jump "pressing"; + // show "waitinactive"; + on_dispatch "false" jump "released"; + on_mouse "down" jump "shortrelease"; + on_mouse "down" jump "pressing"; + on_dispatch "true" jump "pressed"; + on_dispatch "false" jump "pressing"; + on_mouse "up" jump "releasing"; +template "fsm", mode="dispatch_transition" { + | switch (this.state) { + apply "state", mode="dispatch_transition"; +template "state", mode="dispatch_transition" { +template "on-dispatch" { + | if(value == «@value») { + apply "jump", mode="transition"; +template "fsm", mode="mouse_transition" { + | switch (this.state) { + apply "state", mode="mouse_transition" with "position", "$position"; +template "state", mode="mouse_transition" { + apply "on-mouse[@position = $position]"; + // up or down state is already assumed because apply statement filters it + apply "jump", mode="transition"; +template "jump", mode="transition" { + | this.state = "«@state»"; + | this.«@state»_action(); +template "fsm", mode="actions" { + apply "state", mode="actions"; +template "state", mode="actions" { + //| console.log("Entering state «@name»"); + apply "*", mode="actions"; +template "show", mode="actions" { + | this.display = "«@eltname»"; + | this.request_animate(); +template "hmi-value", mode="actions" { + | this.apply_hmi_value(0, «@value»); template "widget[@type='Button']", mode="widget_class"{
- class ButtonWidget extends Widget{
- active_style = undefined;
- inactive_style = undefined;
+ const "fsm","exsl:node-set($_button_fsm)"; + | class ButtonWidget extends Widget{
- this.state_plc = value;
- if(this.state_plc == 1){
- this.apply_hmi_value(0, 0);
+ | display = "inactive";
- this.state_hmi = this.state_plc;
- this.request_animate();
+ // | console.log("dispatch"+value); + apply "$fsm", mode="dispatch_transition";
- if (this.active_style && this.inactive_style) {
- // redraw button on screen refresh
- this.active_elt.setAttribute("style", this.active_style);
- this.inactive_elt.setAttribute("style", "display:none");
- this.inactive_elt.setAttribute("style", this.inactive_style);
- this.active_elt.setAttribute("style", "display:none");
+ | svg_root.removeEventListener("pointerup", this.bound_onmouseup, true); + // | console.log("onmouseup"); + apply "$fsm", mode="mouse_transition" with "position", "'up'"; + | svg_root.addEventListener("pointerup", this.bound_onmouseup, true); + // | console.log("onmousedown"); + apply "$fsm", mode="mouse_transition" with "position", "'down'"; + apply "$fsm", mode="actions";
- //set state and apply if plc is 0
- if(this.state_plc == 0){
- this.apply_hmi_value(0, 1);
- this.request_animate();
+ | if (this.active_elt && this.inactive_elt) { + foreach "str:split('active inactive')" { + | if(this.display == "«.»") + | this.«.»_elt.style.display = ""; + | this.«.»_elt.style.display = "none";
- this.request_animate();
- this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
- this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
- if (this.active_style && this.inactive_style) {
- this.active_elt.setAttribute("style", "display:none");
- this.inactive_elt.setAttribute("style", this.inactive_style);
- this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
- this.element.setAttribute("onmousedown", "hmi_widgets['"+this.element_id+"'].on_press(evt)");
+ | this.bound_onmouseup = this.onmouseup.bind(this); + | this.element.addEventListener("pointerdown", this.onmousedown.bind(this)); --- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg Wed Dec 02 14:33:24 2020 +0100
+++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg Sat Dec 05 16:59:27 2020 +0100
@@ -197,12 +197,12 @@
inkscape:document-units="px"
- inkscape:current-layer="hmi0"
+ inkscape:current-layer="g443" - inkscape:zoom="0.27282898"
- inkscape:cx="646.17826"
- inkscape:cy="652.93449"
+ inkscape:zoom="0.77167689" + inkscape:cx="379.87087" + inkscape:cy="462.91635" inkscape:window-width="1848"
inkscape:window-height="1016"
@@ -2442,19 +2442,19 @@
inkscape:label="HMI:Input@/SELECTION"
- transform="matrix(0.28590269,0,0,0.28590269,487.38811,348.87609)">
+ transform="matrix(0.28590269,0,0,0.28590269,1047.3881,408.87609)"> - style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" inkscape:label="value"><tspan
- style="stroke-width:1px">8</tspan></text>
+ style="text-align:end;text-anchor:end;stroke-width:1px">8</tspan></text> @@ -2477,7 +2477,7 @@
@@ -2519,41 +2519,46 @@
inkscape:transform-center-x="1.0089177e-06" />
- transform="matrix(0.28590269,0,0,0.28590269,170.16209,215.31977)"
+ transform="matrix(0.57180538,0,0,0.57180538,417.18774,31.574523)" inkscape:label="HMI:Button@/SELECTION"
- style="stroke-width:2">
+ style="stroke-width:1"> + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + inkscape:label="inactive" + inkscape:label="active" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#fdfdfd;fill-opacity:1;fill-rule:nonzero;stroke:#ffd0b2;stroke-width:28.60938356;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
- style="stroke-width:2">
- style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
- inkscape:label="button"
- style="stroke-width:2">
- inkscape:label="setting_jmp"
+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;display:inline;fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:1.99999988px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
- xml:space="preserve"><tspan
- style="text-align:center;text-anchor:middle;fill:#ff6600;stroke-width:1.99999988px"
+ inkscape:label="setting_jmp"><tspan
- sodipodi:role="line">up</tspan></text>
+ style="text-align:center;text-anchor:middle;fill:#ff6600;stroke-width:0.99999994px">up</tspan></text>