svghmi/widget_button.ysl2
changeset 3302 c89fc366bebd
parent 3241 fe945f1f48b7
child 3413 2e84a2782295
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widget_button.ysl2	Thu Sep 02 21:36:29 2021 +0200
@@ -0,0 +1,172 @@
+// widget_button.ysl2
+
+widget_desc("Button") {
+    longdesc
+    ||
+    Button widget takes one boolean variable path, and reflect current true
+    or false value by showing "active" or "inactive" labeled element
+    respectively. Pressing and releasing button changes variable to true and
+    false respectively. Potential inconsistency caused by quick consecutive
+    presses on the button is mitigated by using a state machine that wait for
+    previous state change to be reflected on variable before applying next one.
+    ||
+
+    shortdesc > Push button reflecting consistently given boolean variable
+
+    path name="value" accepts="HMI_BOOL" > Boolean variable
+    
+}
+
+// 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);
+
+gen_index_xhtml {
+
+// State machine to drive HMI_BOOL on a potentially laggy connection
+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»);
+}
+
+}
+
+widget_class("Button"){
+    const "fsm","exsl:node-set($_button_fsm)";
+    |     frequency = 5;
+
+    |     display = "inactive";
+    |     state = "init";
+
+    |     dispatch(value) {
+    // |         console.log("dispatch"+value);
+    apply "$fsm", mode="dispatch_transition";
+    |     }
+
+    |     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'";
+    |     }
+
+    apply "$fsm", mode="actions";
+
+    |     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.bound_onmouseup = this.onmouseup.bind(this);
+    |         this.element.addEventListener("pointerdown", this.onmousedown.bind(this));
+    |     }
+}
+
+widget_defs("Button") {
+    optional_labels("active inactive");
+}