SVGHMI: In order to allow widget signature and description to coexist in same ysl2 file, introduced widget_class, widget_defs to declare widget codegen templates and gen_index_xhtml to mark templates that are only usefull in gen_index_xhtml.xslt.
--- a/svghmi/detachable_pages.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/detachable_pages.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -193,8 +193,7 @@
| "«@id»": detachable_elements["«@id»"]`if "position()!=last()" > ,`
- /* TODO generate some code for init() instead */
- apply "$parsed_widgets/widget[@id = $all_page_widgets/@id]", mode="per_page_widget_template"{
+ apply "$parsed_widgets/widget[@id = $all_page_widgets/@id]", mode="widget_page"{ with "page_desc", "$desc";
| }`if "position()!=last()" > ,`
@@ -208,7 +207,7 @@
-template "*", mode="per_page_widget_template";
+template "*", mode="widget_page"; emit "debug:detachable-pages" {
--- a/svghmi/widget_animate.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_animate.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
-template "widget[@type='Animate']", mode="widget_class"{
+widget_class("Animate") { - class AnimateWidget extends Widget{
@@ -42,12 +41,6 @@
let widget_pos = this.element.getBBox();
this.widget_center = [(widget_pos.x+widget_pos.width/2), (widget_pos.y+widget_pos.height/2)];
-template "widget[@type='Animate']", mode="widget_defs" {
--- a/svghmi/widget_animaterotation.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_animaterotation.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
// widget_animaterotation.ysl2
-template "widget[@type='AnimateRotation']", mode="widget_class"{
+widget_class("AnimateRotation") { - class AnimateRotationWidget extends Widget{
widget_center = undefined;
@@ -40,12 +39,11 @@
let widget_pos = this.element.getBBox();
this.widget_center = [(widget_pos.x+widget_pos.width/2), (widget_pos.y+widget_pos.height/2)];
-template "widget[@type='AnimateRotation']", mode="widget_defs" {
+widget_defs("AnimateRotation") { --- a/svghmi/widget_back.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_back.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
-template "widget[@type='Back']", mode="widget_class"
- class BackWidget extends Widget{
if(jump_history.length > 1){
@@ -13,5 +12,4 @@
this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
--- a/svghmi/widget_button.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_button.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -9,6 +9,8 @@
// State machine to drive HMI_BOOL on a potentially laggy connection
const "_button_fsm" fsm {
@@ -104,9 +106,10 @@
| this.apply_hmi_value(0, «@value»);
-template "widget[@type='Button']", mode="widget_class"{
const "fsm","exsl:node-set($_button_fsm)";
- | class ButtonWidget extends Widget{
@@ -145,11 +148,9 @@
| this.bound_onmouseup = this.onmouseup.bind(this);
| this.element.addEventListener("pointerdown", this.onmousedown.bind(this));
-template "widget[@type='Button']", mode="widget_defs" {
optional_labels("active inactive");
--- a/svghmi/widget_circularbar.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_circularbar.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
// widget_circularbar.ysl2
-template "widget[@type='CircularBar']", mode="widget_class"{
+widget_class("CircularBar") { - class CircularBarWidget extends Widget{
@@ -49,11 +48,10 @@
this.proportions = [rx, ry];
-template "widget[@type='CircularBar']", mode="widget_defs" {
+widget_defs("CircularBar") { optional_labels("value min max");
--- a/svghmi/widget_circularslider.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_circularslider.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
// widget_circuralslider.ysl2
-template "widget[@type='CircularSlider']", mode="widget_class"
+widget_class("CircularSlider") - class CircularSliderWidget extends Widget{
@@ -228,10 +227,9 @@
-template "widget[@type='CircularSlider']", mode="widget_defs" {
+widget_defs("CircularSlider") { optional_labels("value min max setpoint");
--- a/svghmi/widget_customhtml.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_customhtml.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
// widget_customhtml.ysl2
-template "widget[@type='CustomHtml']", mode="widget_class"{
+widget_class("CustomHtml"){ - class CustomHtmlWidget extends Widget{
@@ -21,13 +20,11 @@
this.code_elt.textContent+
-template "widget[@type='CustomHtml']", mode="widget_defs" {
+widget_defs("CustomHtml") { labels("container code");
--- a/svghmi/widget_display.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_display.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,18 +1,16 @@
-template "widget[@type='Display']", mode="widget_class"
- class DisplayWidget extends Widget{
dispatch(value, oldval, index) {
this.fields[index] = value;
-template "widget[@type='Display']", mode="widget_defs" {
+widget_defs("Display") { const "format" optional_labels("format");
--- a/svghmi/widget_dropdown.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_dropdown.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,11 +1,7 @@
-template "widget[@type='DropDown']", mode="widget_class"{
+widget_class("DropDown") { - function numb_event(e) {
- class DropDownWidget extends Widget{
if(!this.opened) this.set_selection(value);
@@ -119,8 +115,8 @@
// Stop hogging all click events
- svg_root.removeEventListener("pointerdown", numb_event, true);
- svg_root.removeEventListener("pointerup", numb_event, true);
+ svg_root.removeEventListener("pointerdown", this.numb_event, true); + svg_root.removeEventListener("pointerup", this.numb_event, true); svg_root.removeEventListener("click", this.bound_close_on_click_elsewhere, true);
// Restore position and sixe of widget elements
@@ -237,6 +233,9 @@
let length = this.content.length;
// systematically reset text, to strip eventual whitespace spans
@@ -264,8 +263,8 @@
// Rise widget to top by moving it to last position among siblings
this.element.parentNode.appendChild(this.element.parentNode.removeChild(this.element));
// disable interaction with background
- svg_root.addEventListener("pointerdown", numb_event, true);
- svg_root.addEventListener("pointerup", numb_event, true);
+ svg_root.addEventListener("pointerdown", this.numb_event, true); + svg_root.addEventListener("pointerup", this.numb_event, true); svg_root.addEventListener("click", this.bound_close_on_click_elsewhere, true);
this.highlight_selection();
@@ -329,11 +328,10 @@
// b.width.baseVal.value = 2 * lmargin + m.width;
b.height.baseVal.value = 2 * tmargin + m.height;
-template "widget[@type='DropDown']", mode="widget_defs" {
+widget_defs("DropDown") { labels("text box button highlight");
// It is assumed that list content conforms to Array interface.
--- a/svghmi/widget_foreach.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_foreach.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,5 +1,5 @@
-template "widget[@type='ForEach']", mode="widget_defs" {
+widget_defs("ForEach") { if "count(path) != 1" error > ForEach widget «$hmi_element/@id» must have one HMI path given.
@@ -49,9 +49,8 @@
-template "widget[@type='ForEach']", mode="widget_class"
-class ForEachWidget extends Widget{
for(let item of this.items){
@@ -117,6 +116,5 @@
jumps_need_update = true;
--- a/svghmi/widget_input.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_input.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,44 +1,43 @@
-template "widget[@type='Input']", mode="widget_class"{
- class InputWidget extends Widget{
- this.change_hmi_value(0, opstr);
- edit_callback(new_val) {
- this.apply_hmi_value(0, new_val);
- this.is_inhibited = true;
- setTimeout(() => this.stopalert(), 1000);
- this.request_animate();
- this.is_inhibited = false;
- this.display = this.last_value;
- this.request_animate();
- overshot(new_val, max) {
- undershot(new_val, min) {
-template "widget[@type='Input']", mode="widget_defs" {
+ this.change_hmi_value(0, opstr); + edit_callback(new_val) { + this.apply_hmi_value(0, new_val); + this.is_inhibited = true; + setTimeout(() => this.stopalert(), 1000); + this.request_animate(); + this.is_inhibited = false; + this.display = this.last_value; + this.request_animate(); + overshot(new_val, max) { + undershot(new_val, min) { const "value_elt" optional_labels("value");
const "have_value","string-length($value_elt)>0";
--- a/svghmi/widget_jsontable.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_jsontable.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
-template "widget[@type='JsonTable']", mode="widget_class"
+widget_class("JsonTable") - class JsonTableWidget extends Widget{
// arbitrary defaults to avoid missing entries in query
@@ -83,9 +82,10 @@
// on_click(evt, ...options) {
// this.do_http_request(...options);
template "svg:*", mode="json_table_elt_render" {
error > JsonTable Widget can't contain element of type «local-name()».
@@ -259,7 +259,9 @@
-template "widget[@type='JsonTable']", mode="widget_defs" {
+widget_defs("JsonTable") { const "data_elt", "$result_svg_ns//*[@id = $hmi_element/@id]/*[@inkscape:label = 'data']";
--- a/svghmi/widget_jump.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_jump.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,9 +1,7 @@
-template "widget[@type='Jump']", mode="widget_class"{
- class JumpWidget extends Widget{
@@ -58,11 +56,10 @@
-template "widget[@type='Jump']", mode="widget_defs" {
const "activity" optional_labels("active inactive");
const "have_activity","string-length($activity)>0";
@@ -90,7 +87,7 @@
-template "widget[@type='Jump']", mode="per_page_widget_template"{
/* check that given path is compatible with page's reference path */
--- a/svghmi/widget_keypad.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_keypad.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -13,10 +13,8 @@
-template "widget[@type='Keypad']", mode="widget_class"
- class KeypadWidget extends Widget{
var syms = symbols.split(" ");
@@ -110,10 +108,9 @@
(this.caps?this.activate_activable:this.inactivate_activable)(this.CapsLock_sub);
-template "widget[@type='Keypad']", mode="widget_defs" {
labels("Esc Enter BackSpace Keys Info Value");
optional_labels("Sign Space NumDot");
--- a/svghmi/widget_list.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_list.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,6 +1,6 @@
-template "widget[@type='List']", mode="widget_defs" {
foreach "$hmi_element/*[@inkscape:label]" {
@@ -9,7 +9,7 @@
-template "widget[@type='TextStyleList']", mode="widget_defs" {
+widget_defs("TextStyleList") { foreach "$hmi_element/*[@inkscape:label]" {
--- a/svghmi/widget_meter.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_meter.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
-template "widget[@type='Meter']", mode="widget_class"{
- class MeterWidget extends Widget{
@@ -29,12 +28,10 @@
this.range = [min, max, this.range_elt.getTotalLength()]
this.origin = this.needle_elt.getPointAtLength(0);
-template "widget[@type='Meter']", mode="widget_defs" {
optional_labels("value min max");
--- a/svghmi/widget_multistate.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_multistate.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
// widget_multistate.ysl2
-template "widget[@type='MultiState']", mode="widget_class"
+widget_class("MultiState") - class MultiStateWidget extends Widget{
@@ -41,10 +40,9 @@
this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
-template "widget[@type='MultiState']", mode="widget_defs" {
+widget_defs("MultiState") { const "regex",!"'^(\"[^\"].*\"|\-?[0-9]+|false|true)(#.*)?$'"!;
--- a/svghmi/widget_scrollbar.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_scrollbar.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
-template "widget[@type='ScrollBar']", mode="widget_class"{
+widget_class("ScrollBar") { - class ScrollBarWidget extends Widget{
@@ -89,11 +88,10 @@
this.dragpos += movement * units / pixels;
this.apply_position(this.dragpos);
-template "widget[@type='ScrollBar']", mode="widget_defs" {
+widget_defs("ScrollBar") { --- a/svghmi/widget_slider.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_slider.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,6 +1,6 @@
-template "widget[@type='Slider']", mode="widget_class"
class SliderWidget extends Widget{
@@ -339,9 +339,8 @@
-template "widget[@type='Slider']", mode="widget_defs" {
optional_labels("value min max setpoint");
--- a/svghmi/widget_switch.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_switch.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,8 +1,7 @@
-template "widget[@type='Switch']", mode="widget_class"
- class SwitchWidget extends Widget{
for(let choice of this.choices){
@@ -13,10 +12,9 @@
-template "widget[@type='Switch']", mode="widget_defs" {
const "regex",!"'^(\"[^\"].*\"|\-?[0-9]+|false|true)(#.*)?$'"!;
--- a/svghmi/widget_tooglebutton.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widget_tooglebutton.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -1,9 +1,8 @@
// widget_tooglebutton.ysl2
-template "widget[@type='ToggleButton']", mode="widget_class"{
+widget_class("ToggleButton") { - class ToggleButtonWidget extends Widget{
active_style = undefined;
@@ -41,11 +40,10 @@
this.element.onclick = (evt) => this.on_click(evt);
-template "widget[@type='ToggleButton']", mode="widget_defs" {
+widget_defs("ToggleButton") { optional_labels("active inactive");
--- a/svghmi/widgets_common.ysl2 Thu Apr 15 09:15:23 2021 +0200
+++ b/svghmi/widgets_common.ysl2 Sun May 02 23:01:08 2021 +0200
@@ -21,6 +21,26 @@
+in xsl decl widget_class(%name, *clsname="%nameWidget", match="widget[@type='%name']", mode="widget_class") alias template { + | class `text **clsname` extends Widget{ +in xsl decl widget_defs(%name, match="widget[@type='%name']", mode="widget_defs") alias template { +in xsl decl widget_page(%name, match="widget[@type='%name']", mode="widget_page") alias template { +decl gen_index_xhtml alias - { template "svg:*", mode="hmi_widgets" {
const "widget", "func:widget(@id)";