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)); + | } + | } }