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