# 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