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