include yslt_noindent.yml2 // 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"{text *ptr}; }; in xsl decl optional_labels(*ptr, name="defs_by_labels") alias call-template { with "hmi_element", "$hmi_element"; with "labels"{text *ptr}; with "mandatory","'no'"; }; in xsl decl svgtmpl(match, xmlns="http://www.w3.org/2000/svg") alias template; in xsl decl svgfunc(name, xmlns="http://www.w3.org/2000/svg") alias template; !! debug_output_calls = [] def gen_debug_calls(): # '&bug' is a workaround for pyPEG that choke on # yml2 python results not parsing down into a single yml2 call return ("&bug {"+ "\n".join([""" comment { | | %s: call "%s"; | }"""%(n,n) for n in debug_output_calls])+ "}") !! istylesheet /* From Inkscape */ xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:xhtml="http://www.w3.org/1999/xhtml" /* Our namespace to invoke python code */ xmlns:ns="beremiz" extension-element-prefixes="ns func exsl regexp str dyn" exclude-result-prefixes="ns str regexp exsl func dyn" { const "hmi_elements", "//svg:*[starts-with(@inkscape:label, 'HMI:')]"; include hmi_tree.ysl2 include geometry.ysl2 include detachable_pages.ysl2 include inline_svg.ysl2 include widget_common.ysl2 template "/" { comment > Made with SVGHMI. https://beremiz.org // use python to call all debug output from included definitions python gen_debug_calls; html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" { head; body style="margin:0;overflow:hidden;" { copy "$result_svg"; script{ call "scripts"; } } } } /* Parses: "HMI:WidgetType:param1:param2@path1@path2" Into: widget type="WidgetType" { arg value="param1"; arg value="param2"; path value="path1"; path value="path2"; } */ function "scripts" { | //(function(){ | | id = idstr => document.getElementById(idstr); | | var hmi_hash = [«$hmitree/@hash»]; /* TODO re-enable || function evaluate_js_from_descriptions() { var Page; var Input; var Display; var res = []; || const "midmark" > \n«$mark» apply """//*[contains(child::svg:desc, $midmark) or \ starts-with(child::svg:desc, $mark)]""",2 mode="code_from_descs"; || return res; } || */ | var hmi_widgets = { apply "$hmi_elements", mode="hmi_elements"; | } | | var heartbeat_index = «$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index»; | | var hmitree_types = [ foreach "$indexed_hmitree/*" { | /* «@index» «@hmipath» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,` } | ] | | var detachable_elements = { foreach "$detachable_elements"{ | "«@id»":[id("«@id»"), id("«../@id»")]`if "position()!=last()" > ,` } | } | | var page_desc = { apply "$hmi_pages", mode="page_desc"; | } | | var default_page = "«$default_page»"; | var svg_root = id("«/svg:svg/@id»"); include text svghmi.js | //})(); } // template "*", mode="code_from_descs" { // || // { // var path, role, name, priv; // var id = "«@id»"; // || // /* if label is used, use it as default name */ // if "@inkscape:label" // |> name = "«@inkscape:label»"; // | /* -------------- */ // // this breaks indent, but fixing indent could break string literals // value "substring-after(svg:desc, $mark)"; // // nobody reads generated code anyhow... // || // /* -------------- */ // res.push({ // path:path, // role:role, // name:name, // priv:priv // }) // } // || // } template "widget[@type='Display']", mode="widget_defs" { param "hmi_element"; | frequency: 5, | dispatch: function(value) { choose { when "$hmi_element[self::svg:text]"{ // TODO : care about ? | this.element.textContent = String(value); } otherwise { warning > Display widget as a group not implemented } } | }, } template "widget[@type='Meter']", mode="widget_defs" { param "hmi_element"; | frequency: 10, labels("needle range"); optional_labels("value min max"); | dispatch: function(value) { | if(this.value_elt) | this.value_elt.textContent = String(value); | let [min,max,totallength] = this.range; | let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min))); | let tip = this.range_elt.getPointAtLength(length); | this.needle_elt.setAttribute('d', "M "+this.origin.x+","+this.origin.y+" "+tip.x+","+tip.y); | }, | origin: undefined, | range: undefined, | init: function() { | let min = this.min_elt ? | Number(this.min_elt.textContent) : | this.args.length >= 1 ? this.args[0] : 0; | let max = this.max_elt ? | Number(this.max_elt.textContent) : | this.args.length >= 2 ? this.args[1] : 100; | this.range = [min, max, this.range_elt.getTotalLength()] | this.origin = this.needle_elt.getPointAtLength(0); | }, } def "func:escape_quotes" { param "txt"; // have to use a python string to enter escaped quote const "frst", !"substring-before($txt,'\"')"!; const "frstln", "string-length($frst)"; choose { when "$frstln > 0 and string-length($txt) > $frstln" { result !"concat($frst,'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!; } otherwise { result "$txt"; } } } template "widget[@type='Input']", mode="widget_defs" { param "hmi_element"; const "value_elt" { optional_labels("value"); } const "have_value","string-length($value_elt)>0"; value "$value_elt"; if "$have_value" | frequency: 5, | dispatch: function(value) { if "$have_value" | this.value_elt.textContent = String(value); | }, const "edit_elt_id","$hmi_element/*[@inkscape:label='edit'][1]/@id"; | init: function() { if "$edit_elt_id" { | id("«$edit_elt_id»").addEventListener( | "click", | evt => alert('XXX TODO : Edit value')); } foreach "$hmi_element/*[regexp:test(@inkscape:label,'^[=+\-].+')]" { | id("«@id»").addEventListener( | "click", | evt => {let new_val = change_hmi_value(this.indexes[0], "«func:escape_quotes(@inkscape:label)»"); if "$have_value"{ | this.value_elt.textContent = String(new_val); } | }); /* TODO gray out value until refreshed */ } | }, } template "widget[@type='Button']", mode="widget_defs" { } template "widget[@type='Toggle']", mode="widget_defs" { | frequency: 5, } template "widget[@type='Switch']", mode="widget_defs" { param "hmi_element"; | frequency: 5, | dispatch: function(value) { | for(let choice of this.choices){ | if(value != choice.value){ | choice.elt.setAttribute("style", "display:none"); | } else { | choice.elt.setAttribute("style", choice.style); | } | } | }, | init: function() { | // Hello Switch | }, | choices: [ const "regex",!"'^(\"[^\"].*\"|\-?[0-9]+)(#.*)?$'"!; foreach "$hmi_element/*[regexp:test(@inkscape:label,$regex)]" { const "literal", "regexp:match(@inkscape:label,$regex)[2]"; | { | elt:id("«@id»"), | style:"«@style»", | value:«$literal» | }`if "position()!=last()" > ,` } | ], } template "widget[@type='Jump']", mode="widget_defs" { param "hmi_element"; | on_click: function(evt) { | switch_page(this.args[0], this.indexes[0]); | }, | init: function() { /* registering event this way doies not "click" through svg:use | this.element.onclick = evt => switch_page(this.args[0]); event must be registered by adding attribute to element instead TODO : generalize mouse event handling by global event capture + getElementsAtPoint() */ | this.element.setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_click(evt)"); | }, } }