--- a/svghmi/analyse_widget.xslt Tue Oct 04 11:06:04 2022 +0200
+++ b/svghmi/analyse_widget.xslt Wed Oct 05 09:06:18 2022 +0200
@@ -262,6 +262,42 @@
<xsl:text>speed</xsl:text>
+ <xsl:template match="widget[@type='Assign']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>Arguments are either: + <xsl:text>- name=value: setting variable with literal value. + <xsl:text>- name=other_name: copy variable content into another + <xsl:text>"active"+"inactive" labeled elements can be provided to show feedback when pressed + <xsl:text>HMI:Assign:notify=1@notify=/PLCVAR + <xsl:text>HMI:Assign:ack=2:notify=1@ack=.local_var@notify=/PLCVAR + <xsl:text>Assign variables on click</xsl:text> <xsl:template match="widget[@type='Back']" mode="widget_desc">
<xsl:value-of select="@type"/>
--- a/svghmi/gen_index_xhtml.xslt Tue Oct 04 11:06:04 2022 +0200
+++ b/svghmi/gen_index_xhtml.xslt Wed Oct 05 09:06:18 2022 +0200
@@ -555,6 +555,23 @@
<xsl:variable name="candidates" select="$geometry[@Id != $elt/@id]"/>
<func:result select="$candidates[(@Id = $groups/@id and (func:intersect($g, .) = 9)) or (not(@Id = $groups/@id) and (func:intersect($g, .) > 0 ))]"/>
+ <func:function name="func:offset"> + <xsl:param name="elt1"/> + <xsl:param name="elt2"/> + <xsl:variable name="g1" select="$geometry[@Id = $elt1/@id]"/> + <xsl:variable name="g2" select="$geometry[@Id = $elt2/@id]"/> + <xsl:variable name="result"> + <xsl:attribute name="x"> + <xsl:value-of select="$g2/@x - $g1/@x"/> + <xsl:attribute name="y"> + <xsl:value-of select="$g2/@y - $g1/@y"/> + <func:result select="exsl:node-set($result)"/> <xsl:variable name="hmi_lists_descs" select="$parsed_widgets/widget[@type = 'List']"/>
<xsl:variable name="hmi_lists" select="$hmi_elements[@id = $hmi_lists_descs/@id]"/>
<xsl:variable name="hmi_textlists_descs" select="$parsed_widgets/widget[@type = 'TextList']"/>
@@ -657,7 +674,8 @@
<xsl:variable name="page_overlapping_geometry" select="$overlapping_geometry/elt[@id = $page/@id]/*"/>
<xsl:variable name="page_overlapping_elements" select="//svg:*[@id = $page_overlapping_geometry/@Id]"/>
- <xsl:variable name="page_sub_elements" select="func:refered_elements($page | $page_overlapping_elements)"/>
+ <xsl:variable name="page_widgets_elements" select=" $hmi_elements[not(@id=$page/@id) and descendant-or-self::svg:*/@id = $page_overlapping_elements/@id] /descendant-or-self::svg:*"/> + <xsl:variable name="page_sub_elements" select="func:refered_elements($page | $page_overlapping_elements | $page_widgets_elements)"/> <func:result select="$page_sub_elements"/>
<func:function name="func:required_elements">
@@ -890,6 +908,14 @@
+ <xsl:text>DISCARDABLES: + <xsl:for-each select="$discardable_elements"> + <xsl:value-of select="@id"/> <xsl:for-each select="$in_forEach_widget_ids">
@@ -945,6 +971,21 @@
<xsl:value-of select="substring(., 2)"/>
+ <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:rect[@inkscape:label='reference' or @inkscape:label='frame']"/> + <xsl:template xmlns="http://www.w3.org/2000/svg" mode="inline_svg" match="svg:g[svg:rect/@inkscape:label='frame']"> + <xsl:variable name="reference_rect" select="(../svg:rect | ../svg:g/svg:rect)[@inkscape:label='reference']"/> + <xsl:variable name="frame_rect" select="svg:rect[@inkscape:label='frame']"/> + <xsl:variable name="offset" select="func:offset($frame_rect, $reference_rect)"/> + <xsl:attribute name="svghmi_x_offset"> + <xsl:value-of select="$offset/vector/@x"/> + <xsl:attribute name="svghmi_y_offset"> + <xsl:value-of select="$offset/vector/@y"/> + <xsl:apply-templates mode="inline_svg" select="@* | node()"/> <xsl:variable name="targets_not_to_unlink" select="$hmi_lists/descendant-or-self::svg:*"/>
<xsl:variable name="to_unlink" select="$hmi_widgets/descendant-or-self::svg:use"/>
<func:function name="func:is_unlinkable">
@@ -1516,8 +1557,6 @@
<xsl:text>var cache = hmitree_types.map(_ignored => undefined);
- <xsl:text>var updates = new Map();
<xsl:text>function page_local_index(varname, pagename){
@@ -1530,7 +1569,7 @@
<xsl:text> new_index = next_available_index++;
- <xsl:text> hmi_locals[pagename] = {[varname]:new_index}
+ <xsl:text> hmi_locals[pagename] = {[varname]:new_index}; @@ -1556,8 +1595,6 @@
<xsl:text> cache[new_index] = defaultval;
- <xsl:text> updates.set(new_index, defaultval);
<xsl:text> if(persistent_locals.has(varname))
<xsl:text> persistent_indexes.set(new_index, varname);
@@ -2656,6 +2693,199 @@
+ <xsl:template match="widget[@type='Assign']" mode="widget_desc"> + <xsl:value-of select="@type"/> + <xsl:text>Arguments are either: + <xsl:text>- name=value: setting variable with literal value. + <xsl:text>- name=other_name: copy variable content into another + <xsl:text>"active"+"inactive" labeled elements can be provided to show feedback when pressed + <xsl:text>HMI:Assign:notify=1@notify=/PLCVAR + <xsl:text>HMI:Assign:ack=2:notify=1@ack=.local_var@notify=/PLCVAR + <xsl:text>Assign variables on click</xsl:text> + <xsl:template match="widget[@type='Assign']" mode="widget_class"> + <xsl:text>class </xsl:text> + <xsl:text>AssignWidget</xsl:text> + <xsl:text> extends Widget{ + <xsl:text> frequency = 2; + <xsl:text> onmouseup(evt) { + <xsl:text> svg_root.removeEventListener("pointerup", this.bound_onmouseup, true); + <xsl:text> if(this.enable_state) { + <xsl:text> this.activity_state = false + <xsl:text> this.request_animate(); + <xsl:text> this.assign(); + <xsl:text> onmousedown(){ + <xsl:text> if(this.enable_state) { + <xsl:text> svg_root.addEventListener("pointerup", this.bound_onmouseup, true); + <xsl:text> this.activity_state = true; + <xsl:text> this.request_animate(); + <xsl:template match="widget[@type='Assign']" mode="widget_defs"> + <xsl:param name="hmi_element"/> + <xsl:variable name="disability"> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>/disabled</xsl:text> + <xsl:with-param name="mandatory" select="'no'"/> + <xsl:value-of select="$disability"/> + <xsl:variable name="has_disability" select="string-length($disability)>0"/> + <xsl:text> activable_sub:{ + <xsl:variable name="activity"> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>/active /inactive</xsl:text> + <xsl:with-param name="mandatory"> + <xsl:text>no</xsl:text> + <xsl:value-of select="$activity"/> + <xsl:variable name="has_activity" select="string-length($activity)>0"/> + <xsl:text> has_activity: </xsl:text> + <xsl:value-of select="$has_activity"/> + <xsl:text> init: function() { + <xsl:text> this.bound_onmouseup = this.onmouseup.bind(this); + <xsl:text> this.element.addEventListener("pointerdown", this.onmousedown.bind(this)); + <xsl:text> assignments: {}, + <xsl:text> dispatch: function(value, oldval, varnum) { + <xsl:variable name="widget" select="."/> + <xsl:for-each select="path"> + <xsl:variable name="varid" select="generate-id()"/> + <xsl:variable name="varnum" select="position()-1"/> + <xsl:if test="@assign"> + <xsl:for-each select="$widget/path[@assign]"> + <xsl:if test="$varid = generate-id()"> + <xsl:text> if(varnum == </xsl:text> + <xsl:value-of select="$varnum"/> + <xsl:text>) this.assignments["</xsl:text> + <xsl:value-of select="@assign"/> + <xsl:text> assign: function() { + <xsl:variable name="paths" select="path"/> + <xsl:for-each select="arg[contains(@value,'=')]"> + <xsl:variable name="name" select="substring-before(@value,'=')"/> + <xsl:variable name="value" select="substring-after(@value,'=')"/> + <xsl:variable name="index"> + <xsl:for-each select="$paths"> + <xsl:if test="@assign = $name"> + <xsl:value-of select="position()-1"/> + <xsl:variable name="isVarName" select="regexp:test($value,'^[a-zA-Z_][a-zA-Z0-9_]+$')"/> + <xsl:when test="$isVarName"> + <xsl:text> const </xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> = this.assignments["</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> if(</xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> != undefined) + <xsl:text> this.apply_hmi_value(</xsl:text> + <xsl:value-of select="$index"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$value"/> + <xsl:text> this.apply_hmi_value(</xsl:text> + <xsl:value-of select="$index"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$value"/> <xsl:template match="widget[@type='Back']" mode="widget_desc">
<xsl:value-of select="@type"/>
@@ -5923,39 +6153,49 @@
<xsl:text> frequency = 2;
- <xsl:text> make_on_click() {
- <xsl:text> let that = this;
- <xsl:text> const name = this.args[0];
- <xsl:text> return function(evt){
- <xsl:text> /* TODO: in order to allow jumps to page selected through
- <xsl:text> for exemple a dropdown, support path pointing to local
- <xsl:text> variable whom value would be an HMI_TREE index and then
- <xsl:text> jump to a relative page not hard-coded in advance
- <xsl:text> if(that.enable_state) {
- <xsl:text> const index =
- <xsl:text> (that.is_relative && that.indexes.length > 0) ?
- <xsl:text> that.indexes[0] + that.offset : undefined;
- <xsl:text> fading_page_switch(name, index);
- <xsl:text> that.notify();
+ <xsl:text> target_page_is_current_page = false; + <xsl:text> button_beeing_pressed = false; + <xsl:text> onmouseup(evt) { + <xsl:text> svg_root.removeEventListener("pointerup", this.bound_onmouseup, true); + <xsl:text> if(this.enable_state) { + <xsl:text> const index = + <xsl:text> (this.is_relative && this.indexes.length > 0) ? + <xsl:text> this.indexes[0] + this.offset : undefined; + <xsl:text> this.button_beeing_pressed = false; + <xsl:text> this.activity_state = this.target_page_is_current_page || this.button_beeing_pressed; + <xsl:text> fading_page_switch(this.args[0], index); + <xsl:text> this.notify(); + <xsl:text> onmousedown(){ + <xsl:text> if(this.enable_state) { + <xsl:text> svg_root.addEventListener("pointerup", this.bound_onmouseup, true); + <xsl:text> this.button_beeing_pressed = true; + <xsl:text> this.activity_state = true; + <xsl:text> this.request_animate(); @@ -5973,7 +6213,9 @@
<xsl:text> const ref_name = this.args[0];
- <xsl:text> this.activity_state = ((ref_name == undefined || ref_name == page_name) && index == ref_index);
+ <xsl:text> this.target_page_is_current_page = ((ref_name == undefined || ref_name == page_name) && index == ref_index); + <xsl:text> this.activity_state = this.target_page_is_current_page || this.button_beeing_pressed; <xsl:text> // Since called from animate, update activity directly
@@ -6031,7 +6273,9 @@
<xsl:variable name="jump_disability" select="$has_activity and $has_disability"/>
<xsl:text> init: function() {
- <xsl:text> this.element.onclick = this.make_on_click();
+ <xsl:text> this.bound_onmouseup = this.onmouseup.bind(this); + <xsl:text> this.element.addEventListener("pointerdown", this.onmousedown.bind(this)); <xsl:if test="$has_activity">
<xsl:text> this.activable = true;
@@ -11091,22 +11335,6 @@
- <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
- <xsl:text>function apply_updates() {
- <xsl:text> updates.forEach((value, index) => {
- <xsl:text> dispatch_value(index, value);
- <xsl:text> updates.clear();
<xsl:text>// Called on requestAnimationFrame, modifies DOM
<xsl:text>var requestAnimationFrameID = null;
@@ -11251,7 +11479,7 @@
<xsl:text> let [value, bytesize] = dvgetter(dv,i);
- <xsl:text> updates.set(index, value);
+ <xsl:text> dispatch_value(index, value); <xsl:text> i += bytesize;
@@ -11265,8 +11493,6 @@
- <xsl:text> apply_updates();
<xsl:text> // register for rendering on next frame, since there are updates
<xsl:text> } catch(err) {
@@ -12081,10 +12307,92 @@
+ <xsl:text>/* From https://jsfiddle.net/ibowankenobi/1mmh7rs6/6/ */ + <xsl:text>function getAbsoluteCTM(element){ + <xsl:text> var height = svg_root.height.baseVal.value, + <xsl:text> width = svg_root.width.baseVal.value, + <xsl:text> viewBoxRect = svg_root.viewBox.baseVal, + <xsl:text> vHeight = viewBoxRect.height, + <xsl:text> vWidth = viewBoxRect.width; + <xsl:text> if(!vWidth || !vHeight){ + <xsl:text> return element.getCTM(); + <xsl:text> var sH = height/vHeight, + <xsl:text> sW = width/vWidth, + <xsl:text> matrix = svg_root.createSVGMatrix(); + <xsl:text> matrix.a = sW; + <xsl:text> matrix.d = sH + <xsl:text> var realCTM = element.getCTM().multiply(matrix.inverse()); + <xsl:text> realCTM.e = realCTM.e/sW + viewBoxRect.x; + <xsl:text> realCTM.f = realCTM.f/sH + viewBoxRect.y; + <xsl:text> return realCTM; + <xsl:text>function apply_reference_frames(){ + <xsl:text> const matches = svg_root.querySelectorAll("g[svghmi_x_offset]"); + <xsl:text> matches.forEach((group) => { + <xsl:text> let [x,y] = ["x", "y"].map((axis) => Number(group.getAttribute("svghmi_"+axis+"_offset"))); + <xsl:text> let ctm = getAbsoluteCTM(group); + <xsl:text> // zero translation part of CTM + <xsl:text> // to only apply rotation/skewing to offset vector + <xsl:text> let invctm = ctm.inverse(); + <xsl:text> let vect = new DOMPoint(x, y); + <xsl:text> let newvect = vect.matrixTransform(invctm); + <xsl:text> let transform = svg_root.createSVGTransform(); + <xsl:text> transform.setTranslate(newvect.x, newvect.y); + <xsl:text> group.transform.baseVal.appendItem(transform); + <xsl:text> ["x", "y"].forEach((axis) => group.removeAttribute("svghmi_"+axis+"_offset")); <xsl:text>// Once connection established
<xsl:text>ws.onopen = function (evt) {
+ <xsl:text> apply_reference_frames(); <xsl:text> init_widgets();