# HG changeset patch # User Edouard Tisserant # Date 1572288763 -3600 # Node ID dc78ffa5253d90bc12da09433eeaf20aa0faaddd # Parent 7fa21b3b5f9fd0684f62040c0e97d33582ffaa4d SVGHMI: SVG viewport now defined so that HMI take scales and fit to the view. Implemented page switch through viewport change, no hiding of widget for now. diff -r 7fa21b3b5f9f -r dc78ffa5253d svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Mon Oct 28 10:30:20 2019 +0100 +++ b/svghmi/gen_index_xhtml.xslt Mon Oct 28 19:52:43 2019 +0100 @@ -3,6 +3,7 @@ <xsl:output method="xml" cdata-section-elements="xhtml:script"/> <xsl:variable name="geometry" select="ns:GetSVGGeometry()"/> <xsl:variable name="hmitree" select="ns:GetHMITree()"/> + <xsl:variable name="svg_root_id" select="/svg:svg/@id"/> <xsl:variable name="hmi_elements" select="//svg:*[starts-with(@inkscape:label, 'HMI:')]"/> <xsl:variable name="hmi_geometry" select="$geometry[@Id = $hmi_elements/@id]"/> <xsl:variable name="hmi_pages" select="$hmi_elements[func:parselabel(@inkscape:label)/widget/@type = 'Page']"/> @@ -97,9 +98,25 @@ </xsl:with-param> </xsl:apply-templates> </xsl:template> - <xsl:template mode="identity_svg" match="@* | node()"> + <xsl:template mode="inline_svg" match="@* | node()"> <xsl:copy> - <xsl:apply-templates mode="identity_svg" select="@* | node()"/> + <xsl:apply-templates mode="inline_svg" select="@* | node()"/> + </xsl:copy> + </xsl:template> + <xsl:template mode="inline_svg" match="svg:svg/@width"/> + <xsl:template mode="inline_svg" match="svg:svg/@height"/> + <xsl:template mode="inline_svg" match="svg:svg"> + <xsl:copy> + <xsl:attribute name="preserveAspectRatio"> + <xsl:text>none</xsl:text> + </xsl:attribute> + <xsl:attribute name="height"> + <xsl:text>100vh</xsl:text> + </xsl:attribute> + <xsl:attribute name="width"> + <xsl:text>100vw</xsl:text> + </xsl:attribute> + <xsl:apply-templates mode="inline_svg" select="@* | node()"/> </xsl:copy> </xsl:template> <xsl:template match="/"> @@ -108,19 +125,8 @@ </xsl:comment> <html xmlns="http://www.w3.org/1999/xhtml"> <head/> - <body style="margin:0;"> - <xsl:copy> - <xsl:comment> - <xsl:apply-templates mode="testgeo" select="$hmi_geometry"/> - </xsl:comment> - <xsl:comment> - <xsl:apply-templates mode="testtree" select="$hmitree"/> - </xsl:comment> - <xsl:comment> - <xsl:apply-templates mode="testtree" select="$indexed_hmitree"/> - </xsl:comment> - <xsl:apply-templates mode="identity_svg" select="@* | node()"/> - </xsl:copy> + <body style="margin:0;overflow:hidden;"> + <xsl:apply-templates mode="inline_svg" select="svg:svg"/> <script> <xsl:call-template name="scripts"/> </script> @@ -288,6 +294,16 @@ <xsl:value-of select="@id"/> <xsl:text>", </xsl:text> + <xsl:text> bbox: [</xsl:text> + <xsl:value-of select="$p/@x"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$p/@y"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$p/@w"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$p/@h"/> + <xsl:text>], +</xsl:text> <xsl:text> widgets: [ </xsl:text> <xsl:for-each select="$page_ids"> @@ -316,6 +332,10 @@ <xsl:value-of select="$default_page"/> <xsl:text>"; </xsl:text> + <xsl:text>var svg_root = document.getElementById("</xsl:text> + <xsl:value-of select="$svg_root_id"/> + <xsl:text>"); +</xsl:text> <xsl:text>// svghmi.js </xsl:text> <xsl:text> @@ -696,20 +716,26 @@ </xsl:text> <xsl:text> } </xsl:text> - <xsl:text> /* add new subsribers if any */ -</xsl:text> - <xsl:text> if(new_desc) for(let widget of new_desc.widgets){ -</xsl:text> - <xsl:text> for(let index of widget.indexes){ -</xsl:text> - <xsl:text> subscribers[index].add(widget); + <xsl:text> +</xsl:text> + <xsl:text> if(new_desc) { +</xsl:text> + <xsl:text> /* add new subsribers if any */ +</xsl:text> + <xsl:text> for(let widget of new_desc.widgets){ +</xsl:text> + <xsl:text> for(let index of widget.indexes){ +</xsl:text> + <xsl:text> subscribers[index].add(widget); +</xsl:text> + <xsl:text> } </xsl:text> <xsl:text> } </xsl:text> + <xsl:text> svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); +</xsl:text> <xsl:text> } </xsl:text> - <xsl:text> -</xsl:text> <xsl:text> current_page = page_name; </xsl:text> <xsl:text> @@ -829,34 +855,14 @@ </xsl:with-param> </xsl:apply-templates> </xsl:template> - <xsl:template mode="widget_defs" match="widget[@type='Display']"> + <xsl:template name="defs_by_labels"> + <xsl:param name="labels" select="''"/> + <xsl:param name="mandatory" select="'yes'"/> <xsl:param name="hmi_element"/> - <xsl:text>frequency: 5, -</xsl:text> - <xsl:text>dispatch: function(value) { -</xsl:text> - <xsl:choose> - <xsl:when test="$hmi_element[self::svg:text]"> - <xsl:text> this.element.textContent = String(value); -</xsl:text> - </xsl:when> - <xsl:otherwise> - <xsl:message terminate="yes"> - <xsl:text>Display widget as a group not implemented</xsl:text> - </xsl:message> - </xsl:otherwise> - </xsl:choose> - <xsl:text>}, -</xsl:text> - </xsl:template> - <xsl:template mode="widget_defs" match="widget[@type='Meter']"> - <xsl:param name="hmi_element"/> - <xsl:text>frequency: 10, -</xsl:text> - <xsl:for-each select="str:split('value min max needle range')"> + <xsl:for-each select="str:split($labels)"> <xsl:variable name="name" select="."/> <xsl:variable name="elt_id" select="$hmi_element//*[@inkscape:label=$name][1]/@id"/> - <xsl:if test="not($elt_id)"> + <xsl:if test="$mandatory='yes' and not($elt_id)"> <xsl:message terminate="yes"> <xsl:text>Meter widget must have a </xsl:text> <xsl:value-of select="$name"/> @@ -869,6 +875,37 @@ <xsl:text>"), </xsl:text> </xsl:for-each> + </xsl:template> + <xsl:template mode="widget_defs" match="widget[@type='Display']"> + <xsl:param name="hmi_element"/> + <xsl:text>frequency: 5, +</xsl:text> + <xsl:text>dispatch: function(value) { +</xsl:text> + <xsl:choose> + <xsl:when test="$hmi_element[self::svg:text]"> + <xsl:text> this.element.textContent = String(value); +</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>Display widget as a group not implemented</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + <xsl:text>}, +</xsl:text> + </xsl:template> + <xsl:template mode="widget_defs" match="widget[@type='Meter']"> + <xsl:param name="hmi_element"/> + <xsl:text>frequency: 10, +</xsl:text> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <test>value min max needle range</test> + </xsl:with-param> + </xsl:call-template> <xsl:text>dispatch: function(value) { </xsl:text> <xsl:text> this.value_elt.textContent = String(value); @@ -900,16 +937,12 @@ <xsl:param name="hmi_element"/> <xsl:text>frequency: 5, </xsl:text> - <xsl:variable name="value_elt_id" select="$hmi_element//*[self::svg:text][@inkscape:label='value'][1]/@id"/> - <xsl:if test="not($value_elt_id)"> - <xsl:message terminate="yes"> - <xsl:text>Input widget must have a text element</xsl:text> - </xsl:message> - </xsl:if> - <xsl:text>value_elt: document.getElementById("</xsl:text> - <xsl:value-of select="$value_elt_id"/> - <xsl:text>"), -</xsl:text> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <test>value</test> + </xsl:with-param> + </xsl:call-template> <xsl:text>dispatch: function(value) { </xsl:text> <xsl:text> this.value_elt.textContent = String(value); @@ -955,4 +988,16 @@ <xsl:text> frequency: 5, </xsl:text> </xsl:template> + <xsl:template mode="widget_defs" match="widget[@type='Jump']"> + <xsl:text>init: function() { +</xsl:text> + <xsl:text> this.element.addEventListener( +</xsl:text> + <xsl:text> "click", +</xsl:text> + <xsl:text> evt => switch_page(this.args[0])); +</xsl:text> + <xsl:text>}, +</xsl:text> + </xsl:template> </xsl:stylesheet> diff -r 7fa21b3b5f9f -r dc78ffa5253d svghmi/gen_index_xhtml.ysl2 --- a/svghmi/gen_index_xhtml.ysl2 Mon Oct 28 10:30:20 2019 +0100 +++ b/svghmi/gen_index_xhtml.ysl2 Mon Oct 28 19:52:43 2019 +0100 @@ -3,6 +3,11 @@ // overrides yslt's output function to set CDATA decl output(method, cdata-section-elements="xhtml:script"); +in xsl decl labels(*ptr, name="defs_by_labels") alias call-template { + with "hmi_element", "$hmi_element"; + with "labels"{test *ptr}; +}; + istylesheet /* From Inkscape */ xmlns:dc="http://purl.org/dc/elements/1.1/" @@ -25,6 +30,7 @@ const "geometry", "ns:GetSVGGeometry()"; const "hmitree", "ns:GetHMITree()"; + const "svg_root_id", "/svg:svg/@id"; const "hmi_elements", "//svg:*[starts-with(@inkscape:label, 'HMI:')]"; const "hmi_geometry", "$geometry[@Id = $hmi_elements/@id]"; @@ -96,9 +102,19 @@ * - copy every attributes * - copy every sub-elements */ - template "@* | node()", mode="identity_svg" { + template "@* | node()", mode="inline_svg" { /* use real xsl:copy instead copy-of alias from yslt.yml2 */ - xsl:copy apply "@* | node()", mode="identity_svg"; + xsl:copy apply "@* | node()", mode="inline_svg"; + } + + /* replaces inkscape's height and width hints. forces fit */ + template "svg:svg/@width", mode="inline_svg"; + template "svg:svg/@height", mode="inline_svg"; + template "svg:svg", mode="inline_svg" xsl:copy { + attrib "preserveAspectRatio" > none + attrib "height" > 100vh + attrib "width" > 100vw + apply "@* | node()", mode="inline_svg"; } /*const "mark" > =HMI=\n*/ @@ -106,21 +122,21 @@ /* copy root node and add geometry as comment for a test */ template "/" { comment > Made with SVGHMI. https://beremiz.org + /* DEBUG DATA + comment { + apply "$hmi_geometry", mode="testgeo"; + } + comment { + apply "$hmitree", mode="testtree"; + } + comment { + apply "$indexed_hmitree", mode="testtree"; + } + */ html xmlns="http://www.w3.org/1999/xhtml" { head; - body style="margin:0;" { - xsl:copy { - comment { - apply "$hmi_geometry", mode="testgeo"; - } - comment { - apply "$hmitree", mode="testtree"; - } - comment { - apply "$indexed_hmitree", mode="testtree"; - } - apply "@* | node()", mode="identity_svg"; - } + body style="margin:0;overflow:hidden;" { + apply "svg:svg", mode="inline_svg"; script{ call "scripts"; } @@ -230,6 +246,7 @@ const "page_elements", "$hmi_elements[@id = $page_ids]"; | "«$desc/arg[1]/@value»": { | id: "«@id»", + | bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»], | widgets: [ foreach "$page_ids" { | hmi_widgets.«.»`if "position()!=last()" > ,` @@ -241,7 +258,7 @@ | | var default_page = "«$default_page»"; - + | var svg_root = document.getElementById("«$svg_root_id»"); include text svghmi.js | //})(); } @@ -307,6 +324,20 @@ }; } + + function "defs_by_labels" { + param "labels","''"; + param "mandatory","'yes'"; + param "hmi_element"; + foreach "str:split($labels)" { + const "name","."; + const "elt_id","$hmi_element//*[@inkscape:label=$name][1]/@id"; + if "$mandatory='yes' and not($elt_id)" error > Meter widget must have a «$name» element + | «$name»_elt: document.getElementById("«$elt_id»"), + } + } + + template "widget[@type='Display']", mode="widget_defs" { param "hmi_element"; | frequency: 5, @@ -326,12 +357,7 @@ template "widget[@type='Meter']", mode="widget_defs" { param "hmi_element"; | frequency: 10, - foreach "str:split('value min max needle range')" { - const "name","."; - const "elt_id","$hmi_element//*[@inkscape:label=$name][1]/@id"; - if "not($elt_id)" error > Meter widget must have a «$name» element - | «$name»_elt: document.getElementById("«$elt_id»"), - } + labels("value min max needle range"); | dispatch: function(value) { | this.value_elt.textContent = String(value); | let [min,max,totallength] = this.range; @@ -350,9 +376,7 @@ template "widget[@type='Input']", mode="widget_defs" { param "hmi_element"; | frequency: 5, - const "value_elt_id","$hmi_element//*[self::svg:text][@inkscape:label='value'][1]/@id"; - if "not($value_elt_id)" error > Input widget must have a text element - | value_elt: document.getElementById("«$value_elt_id»"), + labels("value"); | dispatch: function(value) { | this.value_elt.textContent = String(value); | }, @@ -378,16 +402,13 @@ | frequency: 5, } template "widget[@type='Change']", mode="widget_defs" { - // HMI:Change:-10@/PUMP/VALUE - // HMI:Change:+1@/PUMP/VALUE - // HMI:Change:=42@/PUMP/VALUE | frequency: 5, } - // | frequency: 10`apply ".", mode="refresh_frequency"`, - // template "widget", mode="refresh_frequency" > 10 - /* - template "widget[@type='Meter']", mode="refresh_frequency" > 10 - template "widget[@type='Display']", mode="refresh_frequency" > 5 - template "widget[@type='Input']", mode="refresh_frequency" > 5 - */ + template "widget[@type='Jump']", mode="widget_defs" { + | init: function() { + | this.element.addEventListener( + | "click", + | evt => switch_page(this.args[0])); + | }, + } } diff -r 7fa21b3b5f9f -r dc78ffa5253d svghmi/svghmi.js --- a/svghmi/svghmi.js Mon Oct 28 10:30:20 2019 +0100 +++ b/svghmi/svghmi.js Mon Oct 28 19:52:43 2019 +0100 @@ -188,13 +188,16 @@ subscribers[index].delete(widget); } } - /* add new subsribers if any */ - if(new_desc) for(let widget of new_desc.widgets){ - for(let index of widget.indexes){ - subscribers[index].add(widget); - } - } - + + if(new_desc) { + /* add new subsribers if any */ + for(let widget of new_desc.widgets){ + for(let index of widget.indexes){ + subscribers[index].add(widget); + } + } + svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); + } current_page = page_name; update_subscriptions();