svghmi/widget_foreach.ysl2
author Edouard Tisserant
Wed, 01 Dec 2021 09:54:02 +0100
branchRuntimeLists
changeset 3394 9ea29ac18837
parent 3241 fe945f1f48b7
child 3603 f1a00aa8cb3b
permissions -rw-r--r--
RUNTIME: Variable trace now uses limited list and buffer instead of flags in instance tree that was requiring systematical instance tree traversal, and worst size buffer. Forcing and retain still use tree traversal.
// 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();
    }
||