svghmi/detachable_pages.ysl2
author Edouard Tisserant
Mon, 01 Mar 2021 15:45:13 +0100
branchsvghmi
changeset 3169 91207ee5b6af
parent 3165 2db69e2c5673
child 3170 aaa203270ab0
permissions -rw-r--r--
IDE: Make ST code generation more verbose, since it can be really long in case of big programs, and it is better to let the user know build is still in progress.
// detachable_pages.ysl2
//
// compute what elements are required by pages
// and decide where to cut when removing/attaching
// pages elements on page switch

const "hmi_pages_descs", "$parsed_widgets/widget[@type = 'Page']";
const "hmi_pages", "$hmi_elements[@id = $hmi_pages_descs/@id]";

const "default_page" choose {
    when "count($hmi_pages) > 1" {
        choose {
            when "$hmi_pages_descs/arg[1]/@value = 'Home'" > Home
            otherwise {
                error > No Home page defined!
            }
        }
    }
    when "count($hmi_pages) = 0" {
        error > No page defined!
    }
    otherwise > «func:widget($hmi_pages/@id)/arg[1]/@value»
}

emit "preamble:default-page" {
    |
    | var default_page = "«$default_page»";
}

const "keypads_descs", "$parsed_widgets/widget[@type = 'Keypad']";
const "keypads", "$hmi_elements[@id = $keypads_descs/@id]";

// returns all directly or indirectly refered elements
def "func:refered_elements" {
    param "elems";
    const "descend", "$elems/descendant-or-self::svg:*";
    const "clones", "$descend[self::svg:use]";
    const "originals", "//svg:*[concat('#',@id) = $clones/@xlink:href]";
    choose {
        when "$originals"
            result "$descend | func:refered_elements($originals)";
        otherwise
            result "$descend";
    }
}

// variable "overlapping_geometry" was added for optimization.
// It avoids calling func:overlapping_geometry 3 times for each page
// (apparently libxml doesn't cache exslt function results)
// in order to optimize further, func:overlapping_geometry 
// should be implemented in python or even C,
// as this is still the main bottleneck here
const "_overlapping_geometry" {
    const "k", "'overlapping'";
    const "m", "'computing belonging of widgets to pages'";
    value "ns:ProgressStart($k, $m)";
    foreach "$hmi_pages | $keypads" {
        const "k2", "concat('overlapping:', @id)";
        const "m2", "concat('collecting membership of ', @inkscape:label)";
        value "ns:ProgressStart($k2, $m2)";
        elt {
            attrib "id" > «@id»
            copy "func:overlapping_geometry(.)";
        }
        value "ns:ProgressEnd($k2, $m2)";
    }
    value "ns:ProgressEnd($k, $m)";
}

const "overlapping_geometry", "exsl:node-set($_overlapping_geometry)";

def "func:all_related_elements" {
    param "page";
    const "page_overlapping_geometry", "$overlapping_geometry/elt[@id = $page/@id]/*";
    const "page_overlapping_elements", "//svg:*[@id = $page_overlapping_geometry/@Id]";
    const "page_sub_elements", "func:refered_elements($page | $page_overlapping_elements)";
    result "$page_sub_elements";
}


def "func:required_elements" {
    param "pages"; 
    choose{
        when "$pages"{
            result """func:all_related_elements($pages[1])
                      | func:required_elements($pages[position()!=1])""";
        }otherwise{
            result "/..";
        }
    }
}

const "required_elements",
    """//svg:defs/descendant-or-self::svg:*
       | func:required_elements($hmi_pages | $keypads)/ancestor-or-self::svg:*""";

const "discardable_elements", "//svg:*[not(@id = $required_elements/@id)]";

def "func:sumarized_elements" {
    param "elements";
    const "short_list", "$elements[not(ancestor::*/@id = $elements/@id)]";
    const "filled_groups", """$short_list/parent::*[
        not(child::*[
            not(@id = $discardable_elements/@id) and
            not(@id = $short_list/@id)
        ])]""";
    const "groups_to_add", "$filled_groups[not(ancestor::*/@id = $filled_groups/@id)]";
    result "$groups_to_add | $short_list[not(ancestor::*/@id = $filled_groups/@id)]";
}

def "func:detachable_elements" {
    param "pages";
    choose{
        when "$pages"{
            result """func:sumarized_elements(func:all_related_elements($pages[1]))
                      | func:detachable_elements($pages[position()!=1])""";
        }otherwise{
            result "/..";
        }
    }
}

// Avoid nested detachables
const "_detachable_elements", "func:detachable_elements($hmi_pages | $keypads)";
const "detachable_elements", "$_detachable_elements[not(ancestor::*/@id = $_detachable_elements/@id)]";

emit "declarations:detachable-elements" {
    |
    | var detachable_elements = {
    foreach "$detachable_elements"{
    |     "«@id»":[id("«@id»"), id("«../@id»")]`if "position()!=last()" > ,`
    }
    | }
}

const "forEach_widgets_ids", "$parsed_widgets/widget[@type = 'ForEach']/@id";
const "forEach_widgets", "$hmi_widgets[@id = $forEach_widgets_ids]";
const "in_forEach_widget_ids", "func:refered_elements($forEach_widgets)[not(@id = $forEach_widgets_ids)]/@id";

template "svg:*", mode="page_desc" {
    if "ancestor::*[@id = $hmi_pages/@id]" error > HMI:Page «@id» is nested in another HMI:Page


    const "desc", "func:widget(@id)";
    const "msg", "concat('generating page description ', $desc/arg[1]/@value)";
    value "ns:ProgressStart($msg)";
    const "page", ".";
    const "p", "$geometry[@Id = $page/@id]";

    const "page_all_elements", "func:all_related_elements($page)";

    const "all_page_widgets","$hmi_widgets[@id = $page_all_elements/@id and @id != $page/@id]";
    const "page_managed_widgets","$all_page_widgets[not(@id=$in_forEach_widget_ids)]";
    const "page_relative_widgets",
        "$page_managed_widgets[func:is_descendant_path(func:widget(@id)/path/@value, $desc/path/@value)]";

    // Take closest ancestor in detachable_elements
    // since nested detachable elements are filtered out
    const "sumarized_page", 
        """func:sumarized_elements($page_all_elements)""";

    const "required_detachables", 
        """$sumarized_page/
           ancestor-or-self::*[@id = $detachable_elements/@id]""";

    |   "«$desc/arg[1]/@value»": {
    //|     widget: hmi_widgets["«@id»"],
    |     bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»],
    if "$desc/path/@value" {
        if "count($desc/path/@index)=0"
            warning > Page id="«$page/@id»" : No match for path "«$desc/path/@value»" in HMI tree
    |     page_index: «$desc/path/@index»,
    }
    |     widgets: [
    foreach "$page_managed_widgets" {
        const "widget_paths_relativeness" 
            foreach "func:widget(@id)/path" {
                value "func:is_descendant_path(@value, $desc/path/@value)";
                if "position()!=last()" > ,
            }
    |         [hmi_widgets["«@id»"], [«$widget_paths_relativeness»]]`if "position()!=last()" > ,`
    }
    |     ],
    |     jumps: [
    foreach "$parsed_widgets/widget[@id = $all_page_widgets/@id and @type='Jump']" {
    |         hmi_widgets["«@id»"]`if "position()!=last()" > ,`
    }
    |     ],
    |     required_detachables: {
    foreach "$required_detachables" {
    |         "«@id»": detachable_elements["«@id»"]`if "position()!=last()" > ,`
    }
    |     }
    /* TODO generate some code for init() instead */
    apply "$parsed_widgets/widget[@id = $all_page_widgets/@id]", mode="per_page_widget_template"{
        with "page_desc", "$desc";
    }
    |   }`if "position()!=last()" > ,`
    value "ns:ProgressEnd($msg)";
}

emit "definitions:page-desc" {
    |
    | var page_desc = {
    apply "$hmi_pages", mode="page_desc";
    | }
}

template "*", mode="per_page_widget_template";


emit "debug:detachable-pages" {
    |
    | DETACHABLES:
    foreach "$detachable_elements"{
        |  «@id»
    }
    | In Foreach:
    foreach "$in_forEach_widget_ids"{
        |  «.»
    }
    | Overlapping 
    apply "$overlapping_geometry", mode="testtree";
}