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