# HG changeset patch # User Edouard Tisserant # Date 1607183967 -3600 # Node ID 6b1b23971960c2cc487fb07385e5348af97452a1 # Parent 1ae4a871b6f9142e5f3c20cd86f284c8d68c756e SVGHMI: Rewrote button widget. diff -r 1ae4a871b6f9 -r 6b1b23971960 svghmi/gen_index_xhtml.xslt --- 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,134 +1675,219 @@ } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + switch (this.state) { + + + } + + + + case " + + ": + + + break; + + + + if(value == + + ) { + + + } + + + + + switch (this.state) { + + + + + } + + + + + case " + + ": + + + break; + + + + + + + this.state = " + + "; + + this. + + _action(); + + + + + + + + + _action(){ + + + } + + + + this.display = " + + "; + + this.request_animate(); + + + + this.apply_hmi_value(0, + + ); + + + class ButtonWidget extends Widget{ frequency = 5; - state_plc = 0; - - state_hmi = 0; - - plc_lock = false; - - active_style = undefined; - - inactive_style = undefined; - - + display = "inactive"; + + state = "init"; dispatch(value) { - this.state_plc = value; - - if(this.plc_lock){ - - if(this.state_plc == 1){ - - this.apply_hmi_value(0, 0); - - this.plc_lock = false; - - } - - } - - - - //redraw button - - this.state_hmi = this.state_plc; - - this.request_animate(); - - } - - - + + } + + onmouseup(evt) { + + svg_root.removeEventListener("pointerup", this.bound_onmouseup, true); + + + + + } + + onmousedown(evt) { + + svg_root.addEventListener("pointerup", this.bound_onmouseup, true); + + + + + } + + animate(){ - if (this.active_style && this.inactive_style) { - - // redraw button on screen refresh - - if (this.state_hmi) { - - this.active_elt.setAttribute("style", this.active_style); - - this.inactive_elt.setAttribute("style", "display:none"); - - } else { - - this.inactive_elt.setAttribute("style", this.inactive_style); - - this.active_elt.setAttribute("style", "display:none"); - - } - - } - - } - - - - on_click(evt) { - - //set state and apply if plc is 0 - - this.plc_lock = true; - - if(this.state_plc == 0){ - - this.apply_hmi_value(0, 1); - - } - - //redraw button - - this.request_animate(); - - } - - - - on_press(evt) { - - //set graphic - - this.state_hmi = 1; - - //redraw button - - this.request_animate(); - - } - - - - init() { - - 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)"); - - } + if (this.active_elt && this.inactive_elt) { + + + if(this.display == " + + ") + + this. + + _elt.style.display = ""; + + else + + this. + + _elt.style.display = "none"; + + + } + + } + + init() { + + this.bound_onmouseup = this.onmouseup.bind(this); + + this.element.addEventListener("pointerdown", this.onmousedown.bind(this)); + + } } @@ -6645,12 +6730,16 @@ function prepare_svg() { + // prevents context menu from appearing on right click and long touch + document.body.addEventListener('contextmenu', e => { e.preventDefault(); }); + + for(let eltid in detachable_elements){ let [element,parent] = detachable_elements[eltid]; diff -r 1ae4a871b6f9 -r 6b1b23971960 svghmi/widget_button.ysl2 --- 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 @@ // widget_button.ysl2 +// Finite state machine +decl fsm(name); +decl state(name); +decl on_mouse(position); +decl on_dispatch(value); +decl jump(state); +decl show(eltname); +decl hmi_value(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 { + state "init" { + on_dispatch "false" jump "released"; + on_dispatch "true" jump "pressed"; + } + + state "pressing" { + // show "waitactive"; + hmi_value "true"; + on_dispatch "true" jump "pressed"; + on_mouse "up" jump "shortpress"; + } + state "pressed" { + show "active"; + on_mouse "up" jump "releasing"; + on_dispatch "false" jump "released"; + } + state "shortpress" { + on_dispatch "true" jump "releasing"; + on_mouse "down" jump "pressing"; + } + + state "releasing" { + // show "waitinactive"; + hmi_value "false"; + on_dispatch "false" jump "released"; + on_mouse "down" jump "shortrelease"; + } + state "released" { + show "inactive"; + on_mouse "down" jump "pressing"; + on_dispatch "true" jump "pressed"; + } + state "shortrelease" { + 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" { + | case "«@name»": + apply "on-dispatch"; + | break; +} +template "on-dispatch" { + | if(value == «@value») { + apply "jump", mode="transition"; + | } +} + +template "fsm", mode="mouse_transition" { + param "position"; + | switch (this.state) { + apply "state", mode="mouse_transition" with "position", "$position"; + | } +} +template "state", mode="mouse_transition" { + param "position"; + | case "«@name»": + apply "on-mouse[@position = $position]"; + | break; +} +template "on-mouse" { + // 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" { + | «@name»_action(){ + //| 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{ - frequency = 5; - state_plc = 0; - state_hmi = 0; - plc_lock = false; - active_style = undefined; - inactive_style = undefined; + const "fsm","exsl:node-set($_button_fsm)"; + | class ButtonWidget extends Widget{ + | frequency = 5; - dispatch(value) { - this.state_plc = value; - if(this.plc_lock){ - if(this.state_plc == 1){ - this.apply_hmi_value(0, 0); - this.plc_lock = false; - } - } + | display = "inactive"; + | state = "init"; - //redraw button - this.state_hmi = this.state_plc; - this.request_animate(); - } + | dispatch(value) { + // | console.log("dispatch"+value); + apply "$fsm", mode="dispatch_transition"; + | } - animate(){ - if (this.active_style && this.inactive_style) { - // redraw button on screen refresh - if (this.state_hmi) { - this.active_elt.setAttribute("style", this.active_style); - this.inactive_elt.setAttribute("style", "display:none"); - } else { - this.inactive_elt.setAttribute("style", this.inactive_style); - this.active_elt.setAttribute("style", "display:none"); - } - } - } + | onmouseup(evt) { + | svg_root.removeEventListener("pointerup", this.bound_onmouseup, true); + // | console.log("onmouseup"); + apply "$fsm", mode="mouse_transition" with "position", "'up'"; + | } + | onmousedown(evt) { + | svg_root.addEventListener("pointerup", this.bound_onmouseup, true); + // | console.log("onmousedown"); + apply "$fsm", mode="mouse_transition" with "position", "'down'"; + | } - on_click(evt) { - //set state and apply if plc is 0 - this.plc_lock = true; - if(this.state_plc == 0){ - this.apply_hmi_value(0, 1); - } - //redraw button - this.request_animate(); - } + apply "$fsm", mode="actions"; - on_press(evt) { - //set graphic - this.state_hmi = 1; - //redraw button - this.request_animate(); - } + | animate(){ + | if (this.active_elt && this.inactive_elt) { + foreach "str:split('active inactive')" { + | if(this.display == "«.»") + | this.«.»_elt.style.display = ""; + | else + | this.«.»_elt.style.display = "none"; + } + | } + | } - init() { - 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)"); - } - } - || + | init() { + | this.bound_onmouseup = this.onmouseup.bind(this); + | this.element.addEventListener("pointerdown", this.onmousedown.bind(this)); + | } + | } } diff -r 1ae4a871b6f9 -r 6b1b23971960 tests/svghmi/svghmi_0@svghmi/svghmi.svg --- 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:pageopacity="0" inkscape:pageshadow="2" inkscape:document-units="px" - inkscape:current-layer="hmi0" + inkscape:current-layer="g443" showgrid="false" units="px" - 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" inkscape:window-x="72" @@ -2442,19 +2442,19 @@ + transform="matrix(0.28590269,0,0,0.28590269,1047.3881,408.87609)"> 8 + style="text-align:end;text-anchor:end;stroke-width:1px">8 - - - - + + + - + up + style="text-align:center;text-anchor:middle;fill:#ff6600;stroke-width:0.99999994px">up