svghmi/widget_foreach.ysl2
changeset 3302 c89fc366bebd
parent 3241 fe945f1f48b7
child 3603 f1a00aa8cb3b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widget_foreach.ysl2	Thu Sep 02 21:36:29 2021 +0200
@@ -0,0 +1,148 @@
+// widget_foreach.ysl2
+
+widget_desc("ForEach") {
+
+    longdesc
+    ||
+    ForEach widget is used to span a small set of widget over a larger set of
+    repeated HMI_NODEs. 
+
+    Idea is somewhat similar to relative page, but it all happens inside the
+    ForEach widget, no page involved.
+
+    Together with relative Jump widgets it can be used to build a menu to reach
+    relative pages covering many identical HMI_NODES siblings.
+
+    ForEach widget takes a HMI_CLASS name as argument and a HMI_NODE path as
+    variable.
+
+    Direct sub-elements can be either groups of widget to be spanned, labeled
+    "ClassName:offset", or buttons to control the spanning, labeled
+    "ClassName:+/-number".
+    ||
+
+    shortdesc > span widgets over a set of repeated HMI_NODEs
+
+    arg name="class_name" accepts="string" > HMI_CLASS name
+
+    path name="root" accepts="HMI_NODE" >  where to find HMI_NODEs whose HMI_CLASS is class_name
+}
+
+widget_defs("ForEach") {
+
+    if "count(path) != 1" error > ForEach widget «$hmi_element/@id» must have one HMI path given.
+    if "count(arg) != 1" error > ForEach widget «$hmi_element/@id» must have one argument given : a class name.
+
+    const "class","arg[1]/@value";
+
+    const "base_path","path/@value";
+    const "hmi_index_base", "$indexed_hmitree/*[@hmipath = $base_path]";
+    const "hmi_tree_base", "$hmitree/descendant-or-self::*[@path = $hmi_index_base/@path]";
+    const "hmi_tree_items", "$hmi_tree_base/*[@class = $class]";
+    const "hmi_index_items", "$indexed_hmitree/*[@path = $hmi_tree_items/@path]"; 
+    const "items_paths", "$hmi_index_items/@hmipath"; 
+    |     index_pool: [
+    foreach "$hmi_index_items" {
+    |       «@index»`if "position()!=last()" > ,`
+    }
+    |     ],
+    |     init: function() {
+    const "prefix","concat($class,':')";
+    const "buttons_regex","concat('^',$prefix,'[+\-][0-9]+')";
+    const "buttons", "$hmi_element/*[regexp:test(@inkscape:label, $buttons_regex)]"; 
+    foreach "$buttons" {
+        const "op","substring-after(@inkscape:label, $prefix)";
+    |         id("«@id»").setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_click('«$op»', evt)");
+    }
+    |
+    |         this.items = [
+    const "items_regex","concat('^',$prefix,'[0-9]+')";
+    const "unordered_items","$hmi_element//*[regexp:test(@inkscape:label, $items_regex)]";
+    foreach "$unordered_items" {
+        const "elt_label","concat($prefix, string(position()))"; 
+        const "elt","$unordered_items[@inkscape:label = $elt_label]";
+        const "pos","position()";
+        const "item_path", "$items_paths[$pos]";
+    |           [ /* item="«$elt_label»" path="«$item_path»" */
+        if "count($elt)=0" error > Missing item labeled «$elt_label» in ForEach widget «$hmi_element/@id»
+        foreach "func:refered_elements($elt)[@id = $hmi_elements/@id][not(@id = $elt/@id)]" {
+            if "not(func:is_descendant_path(func:widget(@id)/path/@value, $item_path))"
+                error > Widget id="«@id»" label="«@inkscape:label»" is having wrong path. Accroding to ForEach widget ancestor id="«$hmi_element/@id»", path should be descendant of "«$item_path»".
+    |             hmi_widgets["«@id»"]`if "position()!=last()" > ,`
+        }
+    |           ]`if "position()!=last()" > ,`
+    }
+    |         ]
+    |     },
+    |     item_offset: 0,
+}
+
+widget_class("ForEach")
+||
+
+    unsub_items(){
+        for(let item of this.items){
+            for(let widget of item) {
+                widget.unsub();
+            }
+        }
+    }
+
+    unsub(){
+        this.unsub_items();
+        this.offset = 0;
+        this.relativeness = undefined;
+    }
+
+    sub_items(){
+        for(let i = 0; i < this.items.length; i++) {
+            let item = this.items[i];
+            let orig_item_index = this.index_pool[i];
+            let item_index = this.index_pool[i+this.item_offset];
+            let item_index_offset = item_index - orig_item_index;
+            if(this.relativeness[0])
+                item_index_offset += this.offset;
+            for(let widget of item) {
+                /* all variables of all widgets in a ForEach are all relative. 
+                   Really.
+
+                   TODO: allow absolute variables in ForEach widgets
+                */
+                widget.sub(item_index_offset, widget.indexes.map(_=>true));
+            }
+        }
+    }
+
+    sub(new_offset=0, relativeness=[]){
+        this.offset = new_offset;
+        this.relativeness = relativeness;
+        this.sub_items();
+    }
+
+    apply_cache() {
+        this.items.forEach(item=>item.forEach(widget=>widget.apply_cache()));
+    }
+
+    on_click(opstr, evt) {
+        let new_item_offset = eval(String(this.item_offset)+opstr);
+        if(new_item_offset + this.items.length > this.index_pool.length) {
+            if(this.item_offset + this.items.length == this.index_pool.length)
+                new_item_offset = 0;
+            else
+                new_item_offset = this.index_pool.length - this.items.length;
+        } else if(new_item_offset < 0) {
+            if(this.item_offset == 0)
+                new_item_offset = this.index_pool.length - this.items.length;
+            else
+                new_item_offset = 0;
+        }
+        this.item_offset = new_item_offset;
+        this.unsub_items();
+        this.sub_items();
+        update_subscriptions();
+        need_cache_apply.push(this);
+        jumps_need_update = true;
+        requestHMIAnimation();
+    }
+||
+