# 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 =&gt; 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();