# HG changeset patch # User Edouard Tisserant # Date 1728634737 -7200 # Node ID 92b3701fceed50fbffba2b408bed917cdf7fc512 # Parent f9c6bbf66eea3b041c1b9922ef823ae8865b11e5 SVGHMI: Extend ForEach widget to expose range, position and size in a way comparable with ScrollBar widget. diff -r f9c6bbf66eea -r 92b3701fceed svghmi/svghmi.js --- a/svghmi/svghmi.js Fri Oct 11 09:31:34 2024 +0200 +++ b/svghmi/svghmi.js Fri Oct 11 10:18:57 2024 +0200 @@ -143,7 +143,7 @@ var ws = null; function send_blob(data) { - if(ws && data.length > 0) { + if(data.length > 0 && ws && ws.readyState == WebSocket.OPEN) { ws.send(new Blob([hmi_hash_u8].concat(data))); }; }; @@ -178,6 +178,7 @@ }; var subscriptions = []; +var subscriptions_update_requested = false; function subscribers(index) { let entry = subscriptions[index]; @@ -312,7 +313,10 @@ function update_subscriptions() { let delta = []; - if(!ws) + + subscriptions_update_requested = false; + + if(!ws || ws.readyState != WebSocket.OPEN) // dont' change subscriptions if not connected return; @@ -349,6 +353,14 @@ send_blob(delta); }; +function request_subscriptions_update(){ + if(!subscriptions_update_requested){ + subscriptions_update_requested = true; + Promise.resolve().then(update_subscriptions); + } +} + + function send_hmi_value(index, value) { if(index > last_remote_index){ dispatch_value(index, value); @@ -513,7 +525,7 @@ new_desc.widgets.map(([widget,relativeness])=>widget.sub(new_offset,relativeness,container_id)); - update_subscriptions(); + request_subscriptions_update(); current_subscribed_page = page_name; current_page_index = page_index; diff -r f9c6bbf66eea -r 92b3701fceed svghmi/widget_foreach.ysl2 --- a/svghmi/widget_foreach.ysl2 Fri Oct 11 09:31:34 2024 +0200 +++ b/svghmi/widget_foreach.ysl2 Fri Oct 11 10:18:57 2024 +0200 @@ -19,6 +19,9 @@ Direct sub-elements can be either groups of widget to be spanned, labeled "ClassName:offset", or buttons to control the spanning, labeled "ClassName:+/-number". + + In case of "ClassName:offset", offset for first element is 1. + || shortdesc > span widgets over a set of repeated HMI_NODEs @@ -30,7 +33,7 @@ widget_defs("ForEach") { - if "count(path) != 1" error > ForEach widget «$hmi_element/@id» must have one HMI path given. + if "count(path) < 1" error > ForEach widget «$hmi_element/@id» must have one HMI path given. if "count(arg) != 1" error > ForEach widget «$hmi_element/@id» must have one argument given : a class name. const "class","arg[1]/@value"; @@ -65,6 +68,7 @@ const "item_path", "$items_paths[$pos]"; | [ /* item="«$elt_label»" path="«$item_path»" */ if "count($elt)=0" error > Missing item labeled «$elt_label» in ForEach widget «$hmi_element/@id» + if "count($elt)>1" error > Duplicate item labeled «$elt_label» in ForEach widget «$hmi_element/@id» foreach "func:refered_elements($elt)[@id = $hmi_elements/@id][not(@id = $elt/@id)]" { if "not(func:is_descendant_path(func:widget(@id)/path/@value, $item_path))" error > Widget id="«@id»" label="«@inkscape:label»" is having wrong path. Accroding to ForEach widget ancestor id="«$hmi_element/@id»", path should be descendant of "«$item_path»". @@ -74,75 +78,116 @@ } | ] | }, - | item_offset: 0, + | range: «count($hmi_index_items)», + | size: «count($unordered_items)», + | position: 0, } widget_class("ForEach") || + items_subscribed = false; unsub_items(){ - for(let item of this.items){ - for(let widget of item) { - widget.unsub(); + if(this.items_subscribed){ + for(let item of this.items){ + for(let widget of item) { + widget.unsub(); + } + } + this.items_subscribed = false; + } + } + + unsub(){ + super.unsub() + this.unsub_items(); + } + + sub_items(){ + if(!this.items_subscribed){ + for(let i = 0; i < this.size; i++) { + let item = this.items[i]; + let orig_item_index = this.index_pool[i]; + let item_index = this.index_pool[i+this.position]; + let item_index_offset = item_index - orig_item_index; + if(this.relativeness[0]) + item_index_offset += this.offset; + for(let widget of item) { + /* all variables of all widgets in a ForEach are all relative. + Really. + + TODO: allow absolute variables in ForEach widgets + */ + widget.sub(item_index_offset, widget.indexes.map(_=>true)); + } } } } - unsub(){ - this.unsub_items(); - this.offset = 0; - this.relativeness = undefined; + sub(new_offset, relativeness, container_id){ + let position_given = this.indexes.length > 2; + + // sub() will call apply_cache() and then dispatch() + // undefining position forces dispatch() to call apply_position() + if(position_given) + this.position = undefined; + + super.sub(new_offset, relativeness, container_id); + + // if position isn't given as a variable + // dispatch() to call apply_position() aren't called + // and items must be subscibed now. + if(!position_given) + this.sub_items(); + + // as soon as subribed apply range and size once for all + this.apply_hmi_value(1, this.range); + this.apply_hmi_value(3, this.size); } - sub_items(){ - for(let i = 0; i < this.items.length; i++) { - let item = this.items[i]; - let orig_item_index = this.index_pool[i]; - let item_index = this.index_pool[i+this.item_offset]; - let item_index_offset = item_index - orig_item_index; - if(this.relativeness[0]) - item_index_offset += this.offset; - for(let widget of item) { - /* all variables of all widgets in a ForEach are all relative. - Really. + apply_position(new_position){ + let old_position = this.position; + let limited_position = Math.round(Math.max(Math.min(new_position, this.range - this.size), 0)); + if(this.position == limited_position){ + return false; + } + this.unsub_items(); + this.position = limited_position; + this.sub_items(); + request_subscriptions_update(); + jumps_need_update = true; + this.request_animate(); + return true; + } - TODO: allow absolute variables in ForEach widgets - */ - widget.sub(item_index_offset, widget.indexes.map(_=>true)); + on_click(opstr, evt) { + let new_position = eval(String(this.position)+opstr); + if(new_position + this.size > this.range) { + if(this.position + this.size == this.range) + new_position = 0; + else + new_position = this.range - this.size; + } else if(new_position < 0) { + if(this.position == 0) + new_position = this.range - this.size; + else + new_position = 0; + } + if(this.apply_position(new_position)){ + this.apply_hmi_value(2, this.position); + } + } + + dispatch(value, oldval, index) { + // Only care about position, others are constants + if(index == 2){ + this.apply_position(value); + if(this.position != value){ + // widget refused or apply different value, force it back + this.apply_hmi_value(2, this.position); } } } - sub(new_offset=0, relativeness=[]){ - this.offset = new_offset; - this.relativeness = relativeness; - this.sub_items(); - } - - apply_cache() { - this.items.forEach(item=>item.forEach(widget=>widget.apply_cache())); - } - - on_click(opstr, evt) { - let new_item_offset = eval(String(this.item_offset)+opstr); - if(new_item_offset + this.items.length > this.index_pool.length) { - if(this.item_offset + this.items.length == this.index_pool.length) - new_item_offset = 0; - else - new_item_offset = this.index_pool.length - this.items.length; - } else if(new_item_offset < 0) { - if(this.item_offset == 0) - new_item_offset = this.index_pool.length - this.items.length; - else - new_item_offset = 0; - } - this.item_offset = new_item_offset; - this.unsub_items(); - this.sub_items(); - update_subscriptions(); - this.apply_cache(); - jumps_need_update = true; - requestHMIAnimation(); - } ||