# HG changeset patch # User Edouard Tisserant # Date 1586418754 -7200 # Node ID ddce4ebdf01098e7b4e3581f64d2e63bbc3f9e5b # Parent 2670f5c53caf598e29a74c96bc71c8d56d263b82 SVGHMI: intermediate commit while working on dropdown widget. Here is the plan : HMI:DropDown : svg:g of svg:rect + svg:text rect is extended to match content size, and if content size exceed page size, user can scroll HMI:List : either HMI:List:ListName as svg:text, one tspan per list entry or HMI:List:ListName:Foreach:HMI_CLASS:SUB/PATH/TO/VALUE@/ROOT/PATH as empty svg:g diff -r 2670f5c53caf -r ddce4ebdf010 svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Tue Apr 07 10:01:23 2020 +0200 +++ b/svghmi/gen_index_xhtml.xslt Thu Apr 09 09:52:34 2020 +0200 @@ -817,6 +817,55 @@ <xsl:text> }, </xsl:text> </xsl:template> + <xsl:template mode="widget_defs" match="widget[@type='DropDown']"> + <xsl:param name="hmi_element"/> + <xsl:call-template name="defs_by_labels"> + <xsl:with-param name="hmi_element" select="$hmi_element"/> + <xsl:with-param name="labels"> + <xsl:text>text box</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:text> dispatch: function(value) { +</xsl:text> + <xsl:text> this.text_elt.textContent = String(value); +</xsl:text> + <xsl:text> }, +</xsl:text> + <xsl:text> init: function() { +</xsl:text> + <xsl:text> this.element.setAttribute("onclick", "hmi_widgets['</xsl:text> + <xsl:value-of select="$hmi_element/@id"/> + <xsl:text>'].on_click()"); +</xsl:text> + <xsl:text> }, +</xsl:text> + <xsl:variable name="box_elt" select="$hmi_element/*[@inkscape:label='box'][1]"/> + <xsl:variable name="g" select="$geometry[@Id = $box_elt/@id]"/> + <xsl:text> original_box: [</xsl:text> + <xsl:value-of select="$g/@x"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$g/@y"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$g/@w"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$g/@h"/> + <xsl:text>], +</xsl:text> + <xsl:text> on_click: function() { +</xsl:text> + <xsl:text> let [x,y,w,h] = page_desc[current_visible_page].bbox; +</xsl:text> + <xsl:text> let p = new DOMPoint(this.box_elt.x.baseVal.value, this.box_elt.y.baseVal.value); +</xsl:text> + <xsl:text> let k = DOMMatrix.fromMatrix(this.box_elt.getCTM()); +</xsl:text> + <xsl:text> let l = DOMMatrix.fromMatrix(this.box_elt.getScreenCTM()); +</xsl:text> + <xsl:text> console.log(p, k.transformPoint(p), l.transformPoint(p)); +</xsl:text> + <xsl:text> }, +</xsl:text> + </xsl:template> <xsl:template mode="widget_defs" match="widget[@type='ForEach']"> <xsl:param name="hmi_element"/> <xsl:variable name="widgets" select="func:refered_elements($forEach_widgets)[not(@id = $forEach_widgets_ids)]"/> @@ -2157,573 +2206,567 @@ </xsl:text> <xsl:text> let old_val = cache[index] </xsl:text> - <xsl:text> console.log("apply", index, new_val); -</xsl:text> - <xsl:text> if(new_val != undefined && old_val != new_val){ -</xsl:text> - <xsl:text> console.log("sending", new_val); + <xsl:text> if(new_val != undefined && old_val != new_val) </xsl:text> <xsl:text> send_hmi_value(index, new_val); </xsl:text> + <xsl:text> return new_val; +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function change_hmi_value(index, opstr) { +</xsl:text> + <xsl:text> let op = opstr[0]; +</xsl:text> + <xsl:text> let given_val = opstr.slice(1); +</xsl:text> + <xsl:text> let old_val = cache[index] +</xsl:text> + <xsl:text> let new_val; +</xsl:text> + <xsl:text> switch(op){ +</xsl:text> + <xsl:text> case "=": +</xsl:text> + <xsl:text> eval("new_val"+opstr); +</xsl:text> + <xsl:text> break; +</xsl:text> + <xsl:text> case "+": +</xsl:text> + <xsl:text> case "-": +</xsl:text> + <xsl:text> case "*": +</xsl:text> + <xsl:text> case "/": +</xsl:text> + <xsl:text> if(old_val != undefined) +</xsl:text> + <xsl:text> new_val = eval("old_val"+opstr); +</xsl:text> + <xsl:text> break; +</xsl:text> <xsl:text> } </xsl:text> + <xsl:text> if(new_val != undefined && old_val != new_val) +</xsl:text> + <xsl:text> send_hmi_value(index, new_val); +</xsl:text> <xsl:text> return new_val; </xsl:text> <xsl:text>} </xsl:text> <xsl:text> </xsl:text> - <xsl:text>function change_hmi_value(index, opstr) { -</xsl:text> - <xsl:text> let op = opstr[0]; -</xsl:text> - <xsl:text> let given_val = opstr.slice(1); -</xsl:text> - <xsl:text> let old_val = cache[index] -</xsl:text> - <xsl:text> let new_val; -</xsl:text> - <xsl:text> switch(op){ -</xsl:text> - <xsl:text> case "=": -</xsl:text> - <xsl:text> eval("new_val"+opstr); -</xsl:text> - <xsl:text> break; -</xsl:text> - <xsl:text> case "+": -</xsl:text> - <xsl:text> case "-": -</xsl:text> - <xsl:text> case "*": -</xsl:text> - <xsl:text> case "/": -</xsl:text> - <xsl:text> if(old_val != undefined) -</xsl:text> - <xsl:text> new_val = eval("old_val"+opstr); -</xsl:text> - <xsl:text> break; + <xsl:text>var current_visible_page; +</xsl:text> + <xsl:text>var current_subscribed_page; +</xsl:text> + <xsl:text>var current_page_index; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function prepare_svg() { +</xsl:text> + <xsl:text> for(let eltid in detachable_elements){ +</xsl:text> + <xsl:text> let [element,parent] = detachable_elements[eltid]; +</xsl:text> + <xsl:text> parent.removeChild(element); </xsl:text> <xsl:text> } </xsl:text> - <xsl:text> if(new_val != undefined && old_val != new_val) -</xsl:text> - <xsl:text> send_hmi_value(index, new_val); -</xsl:text> - <xsl:text> return new_val; + <xsl:text>}; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function switch_page(page_name, page_index) { +</xsl:text> + <xsl:text> if(current_subscribed_page != current_visible_page){ +</xsl:text> + <xsl:text> /* page switch already going */ +</xsl:text> + <xsl:text> /* TODO LOG ERROR */ +</xsl:text> + <xsl:text> return false; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(page_name == undefined) +</xsl:text> + <xsl:text> page_name = current_subscribed_page; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> let old_desc = page_desc[current_subscribed_page]; +</xsl:text> + <xsl:text> let new_desc = page_desc[page_name]; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(new_desc == undefined){ +</xsl:text> + <xsl:text> /* TODO LOG ERROR */ +</xsl:text> + <xsl:text> return false; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(page_index == undefined){ +</xsl:text> + <xsl:text> page_index = new_desc.page_index; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(old_desc){ +</xsl:text> + <xsl:text> old_desc.absolute_widgets.map(w=>w.unsub()); +</xsl:text> + <xsl:text> old_desc.relative_widgets.map(w=>w.unsub()); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> new_desc.absolute_widgets.map(w=>w.sub()); +</xsl:text> + <xsl:text> var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index; +</xsl:text> + <xsl:text> new_desc.relative_widgets.map(w=>w.sub(new_offset)); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> update_subscriptions(); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> current_subscribed_page = page_name; +</xsl:text> + <xsl:text> current_page_index = page_index; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> jumps_need_update = true; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> requestHMIAnimation(); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> jump_history.push([page_name, page_index]); +</xsl:text> + <xsl:text> if(jump_history.length > 42) +</xsl:text> + <xsl:text> jump_history.shift(); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> return true; +</xsl:text> + <xsl:text>}; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function* chain(a,b){ +</xsl:text> + <xsl:text> yield* a; +</xsl:text> + <xsl:text> yield* b; +</xsl:text> + <xsl:text>}; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function unsubscribe(){ +</xsl:text> + <xsl:text> /* remove subsribers */ +</xsl:text> + <xsl:text> for(let index of this.indexes){ +</xsl:text> + <xsl:text> let idx = index + this.offset; +</xsl:text> + <xsl:text> subscribers[idx].delete(this); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> this.offset = 0; </xsl:text> <xsl:text>} </xsl:text> <xsl:text> </xsl:text> - <xsl:text>var current_visible_page; -</xsl:text> - <xsl:text>var current_subscribed_page; -</xsl:text> - <xsl:text>var current_page_index; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function prepare_svg() { -</xsl:text> - <xsl:text> for(let eltid in detachable_elements){ -</xsl:text> - <xsl:text> let [element,parent] = detachable_elements[eltid]; -</xsl:text> - <xsl:text> parent.removeChild(element); + <xsl:text>function subscribe(new_offset=0){ +</xsl:text> + <xsl:text> /* set the offset because relative */ +</xsl:text> + <xsl:text> this.offset = new_offset; +</xsl:text> + <xsl:text> /* add this's subsribers */ +</xsl:text> + <xsl:text> for(let index of this.indexes){ +</xsl:text> + <xsl:text> subscribers[index + new_offset].add(this); </xsl:text> <xsl:text> } </xsl:text> + <xsl:text> need_cache_apply.push(this); +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function foreach_unsubscribe(){ +</xsl:text> + <xsl:text> for(let item of this.items){ +</xsl:text> + <xsl:text> for(let widget of item) { +</xsl:text> + <xsl:text> unsubscribe.call(widget); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> this.offset = 0; +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function foreach_widgets_do(new_offset, todo){ +</xsl:text> + <xsl:text> this.offset = new_offset; +</xsl:text> + <xsl:text> for(let i = 0; i < this.items.length; i++) { +</xsl:text> + <xsl:text> let item = this.items[i]; +</xsl:text> + <xsl:text> let orig_item_index = this.index_pool[i]; +</xsl:text> + <xsl:text> let item_index = this.index_pool[i+this.item_offset]; +</xsl:text> + <xsl:text> let item_index_offset = item_index - orig_item_index; +</xsl:text> + <xsl:text> for(let widget of item) { +</xsl:text> + <xsl:text> todo.call(widget, new_offset + item_index_offset); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function foreach_subscribe(new_offset=0){ +</xsl:text> + <xsl:text> foreach_widgets_do.call(this, new_offset, subscribe); +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function widget_apply_cache() { +</xsl:text> + <xsl:text> for(let index of this.indexes){ +</xsl:text> + <xsl:text> /* dispatch current cache in newly opened page widgets */ +</xsl:text> + <xsl:text> let realindex = index+this.offset; +</xsl:text> + <xsl:text> let cached_val = cache[realindex]; +</xsl:text> + <xsl:text> if(cached_val != undefined) +</xsl:text> + <xsl:text> dispatch_value_to_widget(this, realindex, cached_val, cached_val); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function foreach_apply_cache() { +</xsl:text> + <xsl:text> foreach_widgets_do.call(this, this.offset, widget_apply_cache); +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function foreach_onclick(opstr, evt) { +</xsl:text> + <xsl:text> new_item_offset = eval(String(this.item_offset)+opstr) +</xsl:text> + <xsl:text> if(new_item_offset + this.items.length > this.index_pool.length) { +</xsl:text> + <xsl:text> if(this.item_offset + this.items.length == this.index_pool.length) +</xsl:text> + <xsl:text> new_item_offset = 0; +</xsl:text> + <xsl:text> else +</xsl:text> + <xsl:text> new_item_offset = this.index_pool.length - this.items.length; +</xsl:text> + <xsl:text> } else if(new_item_offset < 0) { +</xsl:text> + <xsl:text> if(this.item_offset == 0) +</xsl:text> + <xsl:text> new_item_offset = this.index_pool.length - this.items.length; +</xsl:text> + <xsl:text> else +</xsl:text> + <xsl:text> new_item_offset = 0; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> this.item_offset = new_item_offset; +</xsl:text> + <xsl:text> off = this.offset; +</xsl:text> + <xsl:text> foreach_unsubscribe.call(this); +</xsl:text> + <xsl:text> foreach_subscribe.call(this,off); +</xsl:text> + <xsl:text> update_subscriptions(); +</xsl:text> + <xsl:text> need_cache_apply.push(this); +</xsl:text> + <xsl:text> jumps_need_update = true; +</xsl:text> + <xsl:text> requestHMIAnimation(); +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function switch_visible_page(page_name) { +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> let old_desc = page_desc[current_visible_page]; +</xsl:text> + <xsl:text> let new_desc = page_desc[page_name]; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(old_desc){ +</xsl:text> + <xsl:text> for(let eltid in old_desc.required_detachables){ +</xsl:text> + <xsl:text> if(!(eltid in new_desc.required_detachables)){ +</xsl:text> + <xsl:text> let [element, parent] = old_desc.required_detachables[eltid]; +</xsl:text> + <xsl:text> parent.removeChild(element); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> for(let eltid in new_desc.required_detachables){ +</xsl:text> + <xsl:text> if(!(eltid in old_desc.required_detachables)){ +</xsl:text> + <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; +</xsl:text> + <xsl:text> parent.appendChild(element); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> }else{ +</xsl:text> + <xsl:text> for(let eltid in new_desc.required_detachables){ +</xsl:text> + <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; +</xsl:text> + <xsl:text> parent.appendChild(element); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); +</xsl:text> + <xsl:text> current_visible_page = page_name; +</xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text>function switch_page(page_name, page_index) { -</xsl:text> - <xsl:text> if(current_subscribed_page != current_visible_page){ -</xsl:text> - <xsl:text> /* page switch already going */ -</xsl:text> - <xsl:text> /* TODO LOG ERROR */ -</xsl:text> - <xsl:text> return false; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(page_name == undefined) -</xsl:text> - <xsl:text> page_name = current_subscribed_page; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> let old_desc = page_desc[current_subscribed_page]; -</xsl:text> - <xsl:text> let new_desc = page_desc[page_name]; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(new_desc == undefined){ -</xsl:text> - <xsl:text> /* TODO LOG ERROR */ -</xsl:text> - <xsl:text> return false; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(page_index == undefined){ -</xsl:text> - <xsl:text> page_index = new_desc.page_index; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(old_desc){ -</xsl:text> - <xsl:text> old_desc.absolute_widgets.map(w=>w.unsub()); -</xsl:text> - <xsl:text> old_desc.relative_widgets.map(w=>w.unsub()); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> new_desc.absolute_widgets.map(w=>w.sub()); -</xsl:text> - <xsl:text> var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index; -</xsl:text> - <xsl:text> new_desc.relative_widgets.map(w=>w.sub(new_offset)); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> update_subscriptions(); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> current_subscribed_page = page_name; -</xsl:text> - <xsl:text> current_page_index = page_index; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> jumps_need_update = true; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> requestHMIAnimation(); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> jump_history.push([page_name, page_index]); -</xsl:text> - <xsl:text> if(jump_history.length > 42) -</xsl:text> - <xsl:text> jump_history.shift(); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> return true; + <xsl:text>function update_jumps() { +</xsl:text> + <xsl:text> page_desc[current_visible_page].jumps.map(w=>w.notify_page_change(current_visible_page,current_page_index)); +</xsl:text> + <xsl:text> jumps_need_update = false; </xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text>function* chain(a,b){ -</xsl:text> - <xsl:text> yield* a; -</xsl:text> - <xsl:text> yield* b; + <xsl:text> +</xsl:text> + <xsl:text>// Once connection established +</xsl:text> + <xsl:text>ws.onopen = function (evt) { +</xsl:text> + <xsl:text> init_widgets(); +</xsl:text> + <xsl:text> send_reset(); +</xsl:text> + <xsl:text> // show main page +</xsl:text> + <xsl:text> prepare_svg(); +</xsl:text> + <xsl:text> switch_page(default_page); </xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text>function unsubscribe(){ -</xsl:text> - <xsl:text> /* remove subsribers */ -</xsl:text> - <xsl:text> for(let index of this.indexes){ -</xsl:text> - <xsl:text> let idx = index + this.offset; -</xsl:text> - <xsl:text> subscribers[idx].delete(this); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> this.offset = 0; -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function subscribe(new_offset=0){ -</xsl:text> - <xsl:text> /* set the offset because relative */ -</xsl:text> - <xsl:text> this.offset = new_offset; -</xsl:text> - <xsl:text> /* add this's subsribers */ -</xsl:text> - <xsl:text> for(let index of this.indexes){ -</xsl:text> - <xsl:text> subscribers[index + new_offset].add(this); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> need_cache_apply.push(this); -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function foreach_unsubscribe(){ -</xsl:text> - <xsl:text> for(let item of this.items){ -</xsl:text> - <xsl:text> for(let widget of item) { -</xsl:text> - <xsl:text> unsubscribe.call(widget); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> this.offset = 0; -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function foreach_widgets_do(new_offset, todo){ -</xsl:text> - <xsl:text> this.offset = new_offset; -</xsl:text> - <xsl:text> for(let i = 0; i < this.items.length; i++) { -</xsl:text> - <xsl:text> let item = this.items[i]; -</xsl:text> - <xsl:text> let orig_item_index = this.index_pool[i]; -</xsl:text> - <xsl:text> let item_index = this.index_pool[i+this.item_offset]; -</xsl:text> - <xsl:text> let item_index_offset = item_index - orig_item_index; -</xsl:text> - <xsl:text> for(let widget of item) { -</xsl:text> - <xsl:text> todo.call(widget, new_offset + item_index_offset); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function foreach_subscribe(new_offset=0){ -</xsl:text> - <xsl:text> foreach_widgets_do.call(this, new_offset, subscribe); -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function widget_apply_cache() { -</xsl:text> - <xsl:text> for(let index of this.indexes){ -</xsl:text> - <xsl:text> /* dispatch current cache in newly opened page widgets */ -</xsl:text> - <xsl:text> let realindex = index+this.offset; -</xsl:text> - <xsl:text> let cached_val = cache[realindex]; -</xsl:text> - <xsl:text> if(cached_val != undefined) -</xsl:text> - <xsl:text> dispatch_value_to_widget(this, realindex, cached_val, cached_val); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function foreach_apply_cache() { -</xsl:text> - <xsl:text> foreach_widgets_do.call(this, this.offset, widget_apply_cache); -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function foreach_onclick(opstr, evt) { -</xsl:text> - <xsl:text> new_item_offset = eval(String(this.item_offset)+opstr) -</xsl:text> - <xsl:text> if(new_item_offset + this.items.length > this.index_pool.length) { -</xsl:text> - <xsl:text> if(this.item_offset + this.items.length == this.index_pool.length) -</xsl:text> - <xsl:text> new_item_offset = 0; -</xsl:text> - <xsl:text> else -</xsl:text> - <xsl:text> new_item_offset = this.index_pool.length - this.items.length; -</xsl:text> - <xsl:text> } else if(new_item_offset < 0) { -</xsl:text> - <xsl:text> if(this.item_offset == 0) -</xsl:text> - <xsl:text> new_item_offset = this.index_pool.length - this.items.length; -</xsl:text> - <xsl:text> else -</xsl:text> - <xsl:text> new_item_offset = 0; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> this.item_offset = new_item_offset; -</xsl:text> - <xsl:text> off = this.offset; -</xsl:text> - <xsl:text> foreach_unsubscribe.call(this); -</xsl:text> - <xsl:text> foreach_subscribe.call(this,off); -</xsl:text> - <xsl:text> update_subscriptions(); -</xsl:text> - <xsl:text> need_cache_apply.push(this); -</xsl:text> - <xsl:text> jumps_need_update = true; -</xsl:text> - <xsl:text> requestHMIAnimation(); -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function switch_visible_page(page_name) { -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> let old_desc = page_desc[current_visible_page]; -</xsl:text> - <xsl:text> let new_desc = page_desc[page_name]; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(old_desc){ -</xsl:text> - <xsl:text> for(let eltid in old_desc.required_detachables){ -</xsl:text> - <xsl:text> if(!(eltid in new_desc.required_detachables)){ -</xsl:text> - <xsl:text> let [element, parent] = old_desc.required_detachables[eltid]; -</xsl:text> - <xsl:text> parent.removeChild(element); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> for(let eltid in new_desc.required_detachables){ -</xsl:text> - <xsl:text> if(!(eltid in old_desc.required_detachables)){ -</xsl:text> - <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; -</xsl:text> - <xsl:text> parent.appendChild(element); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> }else{ -</xsl:text> - <xsl:text> for(let eltid in new_desc.required_detachables){ -</xsl:text> - <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; -</xsl:text> - <xsl:text> parent.appendChild(element); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); -</xsl:text> - <xsl:text> current_visible_page = page_name; + <xsl:text>ws.onclose = function (evt) { +</xsl:text> + <xsl:text> // TODO : add visible notification while waiting for reload +</xsl:text> + <xsl:text> console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s."); +</xsl:text> + <xsl:text> // TODO : re-enable auto reload when not in debug +</xsl:text> + <xsl:text> //window.setTimeout(() => location.reload(true), 10000); +</xsl:text> + <xsl:text> alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+"."); +</xsl:text> + <xsl:text> </xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text>function update_jumps() { -</xsl:text> - <xsl:text> page_desc[current_visible_page].jumps.map(w=>w.notify_page_change(current_visible_page,current_page_index)); -</xsl:text> - <xsl:text> jumps_need_update = false; + <xsl:text>var xmlns = "http://www.w3.org/2000/svg"; +</xsl:text> + <xsl:text>var edit_callback; +</xsl:text> + <xsl:text>function edit_value(path, valuetype, callback, initial) { +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> let [keypadid, xcoord, ycoord] = keypads[valuetype]; +</xsl:text> + <xsl:text> console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid); +</xsl:text> + <xsl:text> edit_callback = callback; +</xsl:text> + <xsl:text> let widget = hmi_widgets[keypadid]; +</xsl:text> + <xsl:text> widget.start_edit(path, valuetype, callback, initial); </xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>// Once connection established -</xsl:text> - <xsl:text>ws.onopen = function (evt) { -</xsl:text> - <xsl:text> init_widgets(); -</xsl:text> - <xsl:text> send_reset(); -</xsl:text> - <xsl:text> // show main page -</xsl:text> - <xsl:text> prepare_svg(); -</xsl:text> - <xsl:text> switch_page(default_page); + <xsl:text>var current_modal; /* TODO stack ?*/ +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function show_modal() { +</xsl:text> + <xsl:text> let [element, parent] = detachable_elements[this.element.id]; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> tmpgrp = document.createElementNS(xmlns,"g"); +</xsl:text> + <xsl:text> tmpgrpattr = document.createAttribute("transform"); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> let [xcoord,ycoord] = this.coordinates; +</xsl:text> + <xsl:text> let [xdest,ydest] = page_desc[current_visible_page].bbox; +</xsl:text> + <xsl:text> tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")"; +</xsl:text> + <xsl:text> tmpgrp.setAttributeNode(tmpgrpattr); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> tmpgrp.appendChild(element); +</xsl:text> + <xsl:text> parent.appendChild(tmpgrp); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> current_modal = [this.element.id, tmpgrp]; </xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text>ws.onclose = function (evt) { -</xsl:text> - <xsl:text> // TODO : add visible notification while waiting for reload -</xsl:text> - <xsl:text> console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s."); -</xsl:text> - <xsl:text> // TODO : re-enable auto reload when not in debug -</xsl:text> - <xsl:text> //window.setTimeout(() => location.reload(true), 10000); -</xsl:text> - <xsl:text> alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+"."); -</xsl:text> - <xsl:text> + <xsl:text>function end_modal() { +</xsl:text> + <xsl:text> let [eltid, tmpgrp] = current_modal; +</xsl:text> + <xsl:text> let [element, parent] = detachable_elements[this.element.id]; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> parent.removeChild(tmpgrp); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> current_modal = undefined; </xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text>var xmlns = "http://www.w3.org/2000/svg"; -</xsl:text> - <xsl:text>var edit_callback; -</xsl:text> - <xsl:text>function edit_value(path, valuetype, callback, initial) { -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> let [keypadid, xcoord, ycoord] = keypads[valuetype]; -</xsl:text> - <xsl:text> console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid); -</xsl:text> - <xsl:text> edit_callback = callback; -</xsl:text> - <xsl:text> let widget = hmi_widgets[keypadid]; -</xsl:text> - <xsl:text> widget.start_edit(path, valuetype, callback, initial); + <xsl:text>function widget_active_activable(eltsub) { +</xsl:text> + <xsl:text> if(eltsub.inactive_style === undefined) +</xsl:text> + <xsl:text> eltsub.inactive_style = eltsub.inactive.getAttribute("style"); +</xsl:text> + <xsl:text> eltsub.inactive.setAttribute("style", "display:none"); +</xsl:text> + <xsl:text> if(eltsub.active_style !== undefined) +</xsl:text> + <xsl:text> eltsub.active.setAttribute("style", eltsub.active_style); +</xsl:text> + <xsl:text> console.log("active", eltsub); </xsl:text> <xsl:text>}; </xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>var current_modal; /* TODO stack ?*/ -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function show_modal() { -</xsl:text> - <xsl:text> let [element, parent] = detachable_elements[this.element.id]; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> tmpgrp = document.createElementNS(xmlns,"g"); -</xsl:text> - <xsl:text> tmpgrpattr = document.createAttribute("transform"); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> let [xcoord,ycoord] = this.coordinates; -</xsl:text> - <xsl:text> let [xdest,ydest] = page_desc[current_visible_page].bbox; -</xsl:text> - <xsl:text> tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")"; -</xsl:text> - <xsl:text> tmpgrp.setAttributeNode(tmpgrpattr); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> tmpgrp.appendChild(element); -</xsl:text> - <xsl:text> parent.appendChild(tmpgrp); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> current_modal = [this.element.id, tmpgrp]; + <xsl:text>function widget_inactive_activable(eltsub) { +</xsl:text> + <xsl:text> if(eltsub.active_style === undefined) +</xsl:text> + <xsl:text> eltsub.active_style = eltsub.active.getAttribute("style"); +</xsl:text> + <xsl:text> eltsub.active.setAttribute("style", "display:none"); +</xsl:text> + <xsl:text> if(eltsub.inactive_style !== undefined) +</xsl:text> + <xsl:text> eltsub.inactive.setAttribute("style", eltsub.inactive_style); +</xsl:text> + <xsl:text> console.log("inactive", eltsub); </xsl:text> <xsl:text>}; </xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function end_modal() { -</xsl:text> - <xsl:text> let [eltid, tmpgrp] = current_modal; -</xsl:text> - <xsl:text> let [element, parent] = detachable_elements[this.element.id]; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> parent.removeChild(tmpgrp); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> current_modal = undefined; -</xsl:text> - <xsl:text>}; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function widget_active_activable(eltsub) { -</xsl:text> - <xsl:text> if(eltsub.inactive_style === undefined) -</xsl:text> - <xsl:text> eltsub.inactive_style = eltsub.inactive.getAttribute("style"); -</xsl:text> - <xsl:text> eltsub.inactive.setAttribute("style", "display:none"); -</xsl:text> - <xsl:text> if(eltsub.active_style !== undefined) -</xsl:text> - <xsl:text> eltsub.active.setAttribute("style", eltsub.active_style); -</xsl:text> - <xsl:text> console.log("active", eltsub); -</xsl:text> - <xsl:text>}; -</xsl:text> - <xsl:text>function widget_inactive_activable(eltsub) { -</xsl:text> - <xsl:text> if(eltsub.active_style === undefined) -</xsl:text> - <xsl:text> eltsub.active_style = eltsub.active.getAttribute("style"); -</xsl:text> - <xsl:text> eltsub.active.setAttribute("style", "display:none"); -</xsl:text> - <xsl:text> if(eltsub.inactive_style !== undefined) -</xsl:text> - <xsl:text> eltsub.inactive.setAttribute("style", eltsub.inactive_style); -</xsl:text> - <xsl:text> console.log("inactive", eltsub); -</xsl:text> - <xsl:text>}; -</xsl:text> </xsl:template> </xsl:stylesheet> diff -r 2670f5c53caf -r ddce4ebdf010 svghmi/widget_dropdown.ysl2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/widget_dropdown.ysl2 Thu Apr 09 09:52:34 2020 +0200 @@ -0,0 +1,24 @@ +// widget_dropdown.ysl2 + +template "widget[@type='DropDown']", mode="widget_defs" { + param "hmi_element"; + labels("text box"); + | dispatch: function(value) { + /* TODO : get selection text by index */ + | this.text_elt.textContent = String(value); + | }, + | init: function() { + | this.element.setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_click()"); + | }, + const "box_elt","$hmi_element/*[@inkscape:label='box'][1]"; + const "g", "$geometry[@Id = $box_elt/@id]"; + | original_box: [«$g/@x», «$g/@y», «$g/@w», «$g/@h»], + | on_click: function() { + | let [x,y,w,h] = page_desc[current_visible_page].bbox; + | let p = new DOMPoint(this.box_elt.x.baseVal.value, this.box_elt.y.baseVal.value); + | let k = DOMMatrix.fromMatrix(this.box_elt.getCTM()); + | let l = DOMMatrix.fromMatrix(this.box_elt.getScreenCTM()); + | console.log(p, k.transformPoint(p), l.transformPoint(p)); + | }, +} + diff -r 2670f5c53caf -r ddce4ebdf010 tests/svghmi/svghmi_0@svghmi/svghmi.svg --- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg Tue Apr 07 10:01:23 2020 +0200 +++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg Thu Apr 09 09:52:34 2020 +0200 @@ -2490,4 +2490,108 @@ id="tspan469" sodipodi:role="line">information</tspan></text> </g> + <g + id="g14237" + inkscape:label="HMI:DropDown:Primes"> + <rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#53676c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect14212" + width="299.99988" + height="136.9433" + x="956.00842" + y="917.98993" + rx="7" + ry="7" + inkscape:label="box" /> + <text + id="text14183" + y="1011.9975" + x="1080.6298" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:80px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve" + inkscape:label="text"><tspan + style="stroke-width:1px" + y="1011.9975" + x="1080.6298" + id="tspan14181" + sodipodi:role="line">sel_0</tspan></text> + </g> + <g + id="g14232" + inkscape:label="HMI:ScrollbarTemplate"> + <rect + ry="7" + rx="7" + y="938.1615" + x="1676.4542" + height="412.77173" + width="59.554077" + id="rect14179" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#010000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1, 1;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + inkscape:label="border" /> + <rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect14189" + width="34.127792" + height="137.37276" + x="1689.1526" + y="1005.6711" + rx="7" + ry="7" + inkscape:label="cursor" /> + <path + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="rect14207" + d="m 1706.2165,965.67108 17.0639,17.37276 h -34.1278 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + inkscape:label="up" /> + <path + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 1706.2165,1323.0438 17.0639,-17.3727 h -34.1278 z" + id="path14210" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" + inkscape:label="down" /> + </g> + <text + id="text14183-9" + y="845.03906" + x="1493.8926" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve" + inkscape:label="HMI:List:Primes"><tspan + style="stroke-width:1px" + y="845.03906" + x="1493.8926" + id="tspan14181-1" + sodipodi:role="line">one</tspan><tspan + style="stroke-width:1px" + y="895.03906" + x="1493.8926" + sodipodi:role="line" + id="tspan14257">two</tspan><tspan + style="stroke-width:1px" + y="945.03906" + x="1493.8926" + sodipodi:role="line" + id="tspan14259">three</tspan><tspan + style="stroke-width:1px" + y="995.03906" + x="1493.8926" + sodipodi:role="line" + id="tspan14261">five</tspan><tspan + style="stroke-width:1px" + y="1045.0391" + x="1493.8926" + sodipodi:role="line" + id="tspan14263">seven</tspan><tspan + style="stroke-width:1px" + y="1095.0391" + x="1493.8926" + sodipodi:role="line" + id="tspan14265">eleven</tspan></text> + <g + id="g14274" + inkscape:label="HMI:List:HoodNames:ForEach:HOOD:NAME@/" /> </svg>