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.
--- 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>
--- 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]));
+ | },
+ }
}
--- 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();