merge svghmi
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Tue, 20 Oct 2020 00:24:49 +0200
branchsvghmi
changeset 3067 2263f2ecf9bb
parent 3063 466c3df67835 (current diff)
parent 3066 d7b9c2ceb3fb (diff)
child 3068 81758c94f3df
merge
--- a/svghmi/gen_index_xhtml.xslt	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Tue Oct 20 00:24:49 2020 +0200
@@ -1475,6 +1475,180 @@
       </xsl:otherwise>
     </xsl:choose>
   </func:function>
+  <xsl:template mode="widget_class" match="widget[@type='Animate']">
+    <xsl:text>class AnimateWidget extends Widget{
+</xsl:text>
+    <xsl:text>    frequency = 5;
+</xsl:text>
+    <xsl:text>    speed = 0;
+</xsl:text>
+    <xsl:text>    start = false;
+</xsl:text>
+    <xsl:text>    widget_center = undefined;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    dispatch(value) {
+</xsl:text>
+    <xsl:text>        this.speed = value / 5;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //reconfigure animation
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    animate(){
+</xsl:text>
+    <xsl:text>       // change animation properties
+</xsl:text>
+    <xsl:text>       for(let child of this.element.children){
+</xsl:text>
+    <xsl:text>            if(child.nodeName.startsWith("animate")){
+</xsl:text>
+    <xsl:text>                if(this.speed != 0 &amp;&amp; !this.start){
+</xsl:text>
+    <xsl:text>                    this.start = true;
+</xsl:text>
+    <xsl:text>                    this.element.beginElement();
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>                if(this.speed &gt; 0){
+</xsl:text>
+    <xsl:text>                    child.setAttribute("dur", this.speed+"s");
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else if(this.speed &lt; 0){
+</xsl:text>
+    <xsl:text>                    child.setAttribute("dur", (-1)*this.speed+"s");
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else{
+</xsl:text>
+    <xsl:text>                    this.start = false;
+</xsl:text>
+    <xsl:text>                    this.element.endElement();
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>       }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    init() {
+</xsl:text>
+    <xsl:text>        let widget_pos = this.element.getBBox();
+</xsl:text>
+    <xsl:text>        this.widget_center = [(widget_pos.x+widget_pos.width/2), (widget_pos.y+widget_pos.height/2)];
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="widget_defs" match="widget[@type='Animate']">
+    <xsl:param name="hmi_element"/>
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="widget_class" match="widget[@type='AnimateRotation']">
+    <xsl:text>class AnimateRotationWidget extends Widget{
+</xsl:text>
+    <xsl:text>    frequency = 5;
+</xsl:text>
+    <xsl:text>    speed = 0;
+</xsl:text>
+    <xsl:text>    widget_center = undefined;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    dispatch(value) {
+</xsl:text>
+    <xsl:text>        this.speed = value / 5;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //reconfigure animation
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    animate(){
+</xsl:text>
+    <xsl:text>       // change animation properties
+</xsl:text>
+    <xsl:text>       for(let child of this.element.children){
+</xsl:text>
+    <xsl:text>            if(child.nodeName == "animateTransform"){
+</xsl:text>
+    <xsl:text>                if(this.speed &gt; 0){
+</xsl:text>
+    <xsl:text>                    child.setAttribute("dur", this.speed+"s");
+</xsl:text>
+    <xsl:text>                    child.setAttribute("from", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+</xsl:text>
+    <xsl:text>                    child.setAttribute("to", "360 "+this.widget_center[0]+" "+this.widget_center[1]);
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else if(this.speed &lt; 0){
+</xsl:text>
+    <xsl:text>                    child.setAttribute("dur", (-1)*this.speed+"s");
+</xsl:text>
+    <xsl:text>                    child.setAttribute("from", "360 "+this.widget_center[0]+" "+this.widget_center[1]);
+</xsl:text>
+    <xsl:text>                    child.setAttribute("to", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>                else{
+</xsl:text>
+    <xsl:text>                    child.setAttribute("from", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+</xsl:text>
+    <xsl:text>                    child.setAttribute("to", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+</xsl:text>
+    <xsl:text>                }
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>       }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    init() {
+</xsl:text>
+    <xsl:text>        let widget_pos = this.element.getBBox();
+</xsl:text>
+    <xsl:text>        this.widget_center = [(widget_pos.x+widget_pos.width/2), (widget_pos.y+widget_pos.height/2)];
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="widget_defs" match="widget[@type='AnimateRotation']">
+    <xsl:param name="hmi_element"/>
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
   <xsl:template mode="widget_class" match="widget[@type='Back']">
     <xsl:text>class BackWidget extends Widget{
 </xsl:text>
@@ -1506,7 +1680,9 @@
 </xsl:text>
     <xsl:text>    frequency = 5;
 </xsl:text>
-    <xsl:text>    state = 0;
+    <xsl:text>    state_plc = 0;
+</xsl:text>
+    <xsl:text>    state_hmi = 0;
 </xsl:text>
     <xsl:text>    plc_lock = false;
 </xsl:text>
@@ -1518,89 +1694,113 @@
 </xsl:text>
     <xsl:text>    dispatch(value) {
 </xsl:text>
-    <xsl:text>        if(value){
-</xsl:text>
-    <xsl:text>            this.button_release();
+    <xsl:text>        this.state_plc = value;
+</xsl:text>
+    <xsl:text>        if(this.plc_lock){
+</xsl:text>
+    <xsl:text>            if(this.state_plc == 1){
+</xsl:text>
+    <xsl:text>                this.apply_hmi_value(0, 0);
+</xsl:text>
+    <xsl:text>                this.plc_lock = false;
+</xsl:text>
+    <xsl:text>            }
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>     on_mouse_down(evt) {
-</xsl:text>
-    <xsl:text>         if (this.active_style &amp;&amp; this.inactive_style) {
-</xsl:text>
-    <xsl:text>             this.active_elt.setAttribute("style", this.active_style);
-</xsl:text>
-    <xsl:text>             this.inactive_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>         }
-</xsl:text>
-    <xsl:text>         this.apply_hmi_value(0, 1);
-</xsl:text>
-    <xsl:text>         this.plc_lock = false;
-</xsl:text>
-    <xsl:text>     }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>     on_mouse_up(evt) {
-</xsl:text>
-    <xsl:text>         this.button_release();
-</xsl:text>
-    <xsl:text>     }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>     button_release(){
-</xsl:text>
-    <xsl:text>        if(!this.plc_lock){
-</xsl:text>
-    <xsl:text>            this.plc_lock = true;
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //redraw button
+</xsl:text>
+    <xsl:text>        this.state_hmi = this.state_plc;
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    animate(){
+</xsl:text>
+    <xsl:text>        if (this.active_style &amp;&amp; this.inactive_style) {
+</xsl:text>
+    <xsl:text>           // redraw button on screen refresh
+</xsl:text>
+    <xsl:text>           if (this.state_hmi) {
+</xsl:text>
+    <xsl:text>               this.active_elt.setAttribute("style", this.active_style);
+</xsl:text>
+    <xsl:text>               this.inactive_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>           } else {
+</xsl:text>
+    <xsl:text>               this.inactive_elt.setAttribute("style", this.inactive_style);
+</xsl:text>
+    <xsl:text>               this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>           }
+</xsl:text>
+    <xsl:text>       }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    on_click(evt) {
+</xsl:text>
+    <xsl:text>        //set state and apply if plc is 0
+</xsl:text>
+    <xsl:text>        this.plc_lock = true;
+</xsl:text>
+    <xsl:text>        if(this.state_plc == 0){
+</xsl:text>
+    <xsl:text>            this.apply_hmi_value(0, 1);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>        else{
-</xsl:text>
-    <xsl:text>            if (this.active_style &amp;&amp; this.inactive_style) {
-</xsl:text>
-    <xsl:text>                 this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>                 this.inactive_elt.setAttribute("style", this.inactive_style);
-</xsl:text>
-    <xsl:text>             }
-</xsl:text>
-    <xsl:text>             this.apply_hmi_value(0, 0);
+    <xsl:text>        //redraw button
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    on_press(evt) {
+</xsl:text>
+    <xsl:text>        //set graphic
+</xsl:text>
+    <xsl:text>        this.state_hmi = 1;
+</xsl:text>
+    <xsl:text>        //redraw button
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>     init() {
+</xsl:text>
+    <xsl:text>        this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
+</xsl:text>
+    <xsl:text>        this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        if (this.active_style &amp;&amp; this.inactive_style) {
+</xsl:text>
+    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>            this.inactive_elt.setAttribute("style", this.inactive_style);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>     }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>     init() {
-</xsl:text>
-    <xsl:text>        this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
-</xsl:text>
-    <xsl:text>        this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        if (this.active_style &amp;&amp; this.inactive_style) {
-</xsl:text>
-    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>            this.inactive_elt.setAttribute("style", this.inactive_style);
-</xsl:text>
-    <xsl:text>        }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        this.element.setAttribute("onmousedown", "hmi_widgets["+this.element_id+"].on_mouse_down(evt)");
-</xsl:text>
-    <xsl:text>        this.element.setAttribute("onmouseup", "hmi_widgets["+this.element_id+"].on_mouse_up(evt)");
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
+</xsl:text>
+    <xsl:text>        this.element.setAttribute("onmousedown", "hmi_widgets['"+this.element_id+"'].on_press(evt)");
 </xsl:text>
     <xsl:text>     }
 </xsl:text>
@@ -1738,7 +1938,7 @@
 </xsl:text>
     <xsl:text>    handle_pos = undefined;
 </xsl:text>
-    <xsl:text>    svg_dist = undefined;
+    <xsl:text>    curr_value = 0;
 </xsl:text>
     <xsl:text>    drag = false;
 </xsl:text>
@@ -1750,13 +1950,47 @@
 </xsl:text>
     <xsl:text>    dispatch(value) {
 </xsl:text>
+    <xsl:text>        let [min,max,start,totallength] = this.range;
+</xsl:text>
+    <xsl:text>        //save current value inside widget
+</xsl:text>
+    <xsl:text>        this.curr_value = value;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //check if in range
+</xsl:text>
+    <xsl:text>        if (this.curr_value &gt; max){
+</xsl:text>
+    <xsl:text>            this.curr_value = max;
+</xsl:text>
+    <xsl:text>            this.apply_hmi_value(0, this.curr_value);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else if (this.curr_value &lt; min){
+</xsl:text>
+    <xsl:text>            this.curr_value = min;
+</xsl:text>
+    <xsl:text>            this.apply_hmi_value(0, this.curr_value);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>        if(this.value_elt)
 </xsl:text>
     <xsl:text>            this.value_elt.textContent = String(value);
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        this.update_DOM(value, this.handle_elt);
+    <xsl:text>        //don't update if draging and setpoint ghost doesn't exist
+</xsl:text>
+    <xsl:text>        if(!this.drag || (this.setpoint_elt != undefined)){
+</xsl:text>
+    <xsl:text>            this.update_DOM(value, this.handle_elt);
+</xsl:text>
+    <xsl:text>        }
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
@@ -1774,6 +2008,8 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>        // show or hide ghost if exists
+</xsl:text>
     <xsl:text>        if(this.setpoint_elt != undefined){
 </xsl:text>
     <xsl:text>            if(this.last_drag!= this.drag){
@@ -1800,6 +2036,8 @@
 </xsl:text>
     <xsl:text>    on_release(evt) {
 </xsl:text>
+    <xsl:text>        //unbind events
+</xsl:text>
     <xsl:text>        window.removeEventListener("touchmove", this.on_bound_drag, true);
 </xsl:text>
     <xsl:text>        window.removeEventListener("mousemove", this.on_bound_drag, true);
@@ -1812,12 +2050,20 @@
 </xsl:text>
     <xsl:text>        window.removeEventListener("touchcancel", this.bound_on_release, true);
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //reset drag flag
+</xsl:text>
     <xsl:text>        if(this.drag){
 </xsl:text>
     <xsl:text>            this.drag = false;
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        // get final position
+</xsl:text>
     <xsl:text>        this.update_position(evt);
 </xsl:text>
     <xsl:text>    }
@@ -1826,10 +2072,14 @@
 </xsl:text>
     <xsl:text>    on_drag(evt){
 </xsl:text>
+    <xsl:text>        //ignore drag event for X amount of time and if not selected
+</xsl:text>
     <xsl:text>        if(this.enTimer &amp;&amp; this.drag){
 </xsl:text>
     <xsl:text>            this.update_position(evt);
 </xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>            //reset timer
 </xsl:text>
     <xsl:text>            this.enTimer = false;
@@ -1930,37 +2180,35 @@
 </xsl:text>
     <xsl:text>            if(fi&lt;fiEnd){
 </xsl:text>
-    <xsl:text>                this.svg_dist=(fi)/(fiEnd)*(this.range[1]-this.range[0]);
+    <xsl:text>               this.curr_value=(fi)/(fiEnd)*(this.range[1]-this.range[0]);
 </xsl:text>
     <xsl:text>            }
 </xsl:text>
     <xsl:text>            else if(fiEnd&lt;fi &amp;&amp; fi&lt;fiEnd+minMax){
 </xsl:text>
-    <xsl:text>                this.svg_dist = this.range[1];
+    <xsl:text>                this.curr_value = this.range[1];
 </xsl:text>
     <xsl:text>            }
 </xsl:text>
     <xsl:text>            else{
 </xsl:text>
-    <xsl:text>                this.svg_dist = this.range[0];
+    <xsl:text>                this.curr_value = this.range[0];
 </xsl:text>
     <xsl:text>            }
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            this.apply_hmi_value(0, Math.ceil(this.svg_dist));
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>            // update ghost cursor
-</xsl:text>
-    <xsl:text>            if(this.setpoint_elt != undefined){
-</xsl:text>
-    <xsl:text>                this.request_animate();
-</xsl:text>
-    <xsl:text>            }
+    <xsl:text>            //apply value to hmi
+</xsl:text>
+    <xsl:text>            this.apply_hmi_value(0, Math.ceil(this.curr_value));
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            //redraw handle
+</xsl:text>
+    <xsl:text>            this.request_animate();
+</xsl:text>
+    <xsl:text>
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
@@ -1972,7 +2220,21 @@
 </xsl:text>
     <xsl:text>    animate(){
 </xsl:text>
-    <xsl:text>        this.update_DOM(this.svg_dist, this.setpoint_elt);
+    <xsl:text>        // redraw handle on screen refresh
+</xsl:text>
+    <xsl:text>        // check if setpoint(ghost) handle exsist otherwise update main handle
+</xsl:text>
+    <xsl:text>        if(this.setpoint_elt != undefined){
+</xsl:text>
+    <xsl:text>            this.update_DOM(this.curr_value, this.setpoint_elt);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else{
+</xsl:text>
+    <xsl:text>            this.update_DOM(this.curr_value, this.handle_elt);
+</xsl:text>
+    <xsl:text>        }
 </xsl:text>
     <xsl:text>    }
 </xsl:text>
@@ -1980,24 +2242,40 @@
 </xsl:text>
     <xsl:text>    on_select(evt){
 </xsl:text>
+    <xsl:text>        //enable drag flag and timer
+</xsl:text>
     <xsl:text>        this.drag = true;
 </xsl:text>
     <xsl:text>        this.enTimer = true;
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //bind events
+</xsl:text>
     <xsl:text>        window.addEventListener("touchmove", this.on_bound_drag, true);
 </xsl:text>
     <xsl:text>        window.addEventListener("mousemove", this.on_bound_drag, true);
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        window.addEventListener("mouseup", this.bound_on_release, true)
+    <xsl:text>        window.addEventListener("mouseup", this.bound_on_release, true);
 </xsl:text>
     <xsl:text>        window.addEventListener("touchend", this.bound_on_release, true);
 </xsl:text>
     <xsl:text>        window.addEventListener("touchcancel", this.bound_on_release, true);
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //update postion on mouse press
+</xsl:text>
     <xsl:text>        this.update_position(evt);
 </xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //prevent next events
+</xsl:text>
+    <xsl:text>        evt.stopPropagation();
+</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>
@@ -2074,13 +2352,21 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        //init events
+    <xsl:text>        this.handle_elt.addEventListener("mousedown", this.bound_on_select);
 </xsl:text>
     <xsl:text>        this.element.addEventListener("mousedown", this.bound_on_select);
 </xsl:text>
     <xsl:text>        this.element.addEventListener("touchstart", this.bound_on_select);
 </xsl:text>
-    <xsl:text>
+    <xsl:text>        //touch recognised as page drag without next command
+</xsl:text>
+    <xsl:text>        document.body.addEventListener("touchstart", function(e){}, false);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //save ghost style
+</xsl:text>
+    <xsl:text>        //save ghost style
 </xsl:text>
     <xsl:text>        if(this.setpoint_elt != undefined){
 </xsl:text>
@@ -2092,22 +2378,6 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        window.addEventListener("touchmove", hmi_widgets[this.element_id].update_position.bind(this));
-</xsl:text>
-    <xsl:text>        window.addEventListener("mousemove", hmi_widgets[this.element_id].update_position.bind(this));
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        window.addEventListener("mouseup", hmi_widgets[this.element_id].on_release.bind(this))
-</xsl:text>
-    <xsl:text>        window.addEventListener("touchend", hmi_widgets[this.element_id].on_release.bind(this));
-</xsl:text>
-    <xsl:text>        window.addEventListener("touchcancel", hmi_widgets[this.element_id].on_release.bind(this));
-</xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>}
@@ -2124,13 +2394,66 @@
     <xsl:call-template name="defs_by_labels">
       <xsl:with-param name="hmi_element" select="$hmi_element"/>
       <xsl:with-param name="labels">
-        <xsl:text>value min max</xsl:text>
+        <xsl:text>value min max setpoint</xsl:text>
       </xsl:with-param>
       <xsl:with-param name="mandatory" select="'no'"/>
     </xsl:call-template>
     <xsl:text>
 </xsl:text>
   </xsl:template>
+  <xsl:template mode="widget_class" match="widget[@type='CustomHtml']">
+    <xsl:text>class CustomHtmlWidget extends Widget{
+</xsl:text>
+    <xsl:text>    frequency = 5;
+</xsl:text>
+    <xsl:text>    widget_size = undefined;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    dispatch(value) {
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    animate(){
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    init() {
+</xsl:text>
+    <xsl:text>        this.widget_size = this.container_elt.getBBox();
+</xsl:text>
+    <xsl:text>        this.element.innerHTML ='&lt;foreignObject x="'+
+</xsl:text>
+    <xsl:text>            this.widget_size.x+'" y="'+this.widget_size.y+
+</xsl:text>
+    <xsl:text>            '" width="'+this.widget_size.width+'" height="'+this.widget_size.height+'"&gt; '+
+</xsl:text>
+    <xsl:text>            this.code_elt.textContent+
+</xsl:text>
+    <xsl:text>            ' &lt;/foreignObject&gt;';
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="widget_defs" match="widget[@type='CustomHtml']">
+    <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>container code</xsl:text>
+      </xsl:with-param>
+    </xsl:call-template>
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
   <xsl:template mode="widget_class" match="widget[@type='Display']">
     <xsl:text>class DisplayWidget extends Widget{
 </xsl:text>
@@ -2162,7 +2485,9 @@
           <xsl:when test="@type='HMI_STRING'">
             <xsl:text>""</xsl:text>
           </xsl:when>
-          <xsl:otherwise>0</xsl:otherwise>
+          <xsl:otherwise>
+            <xsl:text>0</xsl:text>
+          </xsl:otherwise>
         </xsl:choose>
         <xsl:if test="position()!=last()">
           <xsl:text>,</xsl:text>
@@ -3501,7 +3826,7 @@
   <xsl:template mode="widget_class" match="widget[@type='JsonTable']">
     <xsl:text>class JsonTableWidget extends Widget{
 </xsl:text>
-    <xsl:text>    cache = [];
+    <xsl:text>    cache = [100,50];
 </xsl:text>
     <xsl:text>    do_http_request(...opt) {
 </xsl:text>
@@ -3509,7 +3834,9 @@
 </xsl:text>
     <xsl:text>            args: this.args,
 </xsl:text>
-    <xsl:text>            vars: this.cache,
+    <xsl:text>            range: this.cache[1],
+</xsl:text>
+    <xsl:text>            position: this.cache[2],
 </xsl:text>
     <xsl:text>            visible: this.visible,
 </xsl:text>
@@ -3848,6 +4175,8 @@
 </xsl:text>
     <xsl:text>        this.apply_hmi_value(2, position);
 </xsl:text>
+    <xsl:text>        this.apply_hmi_value(3, this.visible);
+</xsl:text>
     <xsl:text>        console.log(range,position,jdata);
 </xsl:text>
     <xsl:apply-templates mode="json_table_render_except_comments" select="$data_elt">
@@ -4756,7 +5085,11 @@
 </xsl:text>
     <xsl:text>    handle_orig = undefined;
 </xsl:text>
-    <xsl:text>    scroll_size = 10;
+    <xsl:text>    scroll_size = undefined;
+</xsl:text>
+    <xsl:text>    scroll_range = 0;
+</xsl:text>
+    <xsl:text>    scroll_visible = 7;
 </xsl:text>
     <xsl:text>    min_size = 0.07;
 </xsl:text>
@@ -4774,17 +5107,59 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>    dispatch(value) {
-</xsl:text>
-    <xsl:text>        //save current value inside widget
-</xsl:text>
-    <xsl:text>        this.curr_value = value;
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>        if(this.value_elt)
-</xsl:text>
-    <xsl:text>            this.value_elt.textContent = String(value);
+    <xsl:text>    dispatch(value,oldval, index) {
+</xsl:text>
+    <xsl:text>        if (index == 0){
+</xsl:text>
+    <xsl:text>            let [min,max,start,totallength] = this.range;
+</xsl:text>
+    <xsl:text>            //save current value inside widget
+</xsl:text>
+    <xsl:text>            this.curr_value = value;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            //check if in range
+</xsl:text>
+    <xsl:text>            if (this.curr_value &gt; max){
+</xsl:text>
+    <xsl:text>                this.curr_value = max;
+</xsl:text>
+    <xsl:text>                this.apply_hmi_value(0, this.curr_value);
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>            else if (this.curr_value &lt; min){
+</xsl:text>
+    <xsl:text>                this.curr_value = min;
+</xsl:text>
+    <xsl:text>                this.apply_hmi_value(0, this.curr_value);
+</xsl:text>
+    <xsl:text>            }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>            if(this.value_elt)
+</xsl:text>
+    <xsl:text>                this.value_elt.textContent = String(value);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else if(index == 1){
+</xsl:text>
+    <xsl:text>            this.scroll_range = value;
+</xsl:text>
+    <xsl:text>            this.set_scroll();
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else if(index == 2){
+</xsl:text>
+    <xsl:text>            this.scroll_visible = value;
+</xsl:text>
+    <xsl:text>            this.set_scroll();
+</xsl:text>
+    <xsl:text>        }
 </xsl:text>
     <xsl:text>
 </xsl:text>
@@ -4792,7 +5167,7 @@
 </xsl:text>
     <xsl:text>        if(!this.drag || (this.setpoint_elt != undefined)){
 </xsl:text>
-    <xsl:text>            this.update_DOM(value, this.handle_elt);
+    <xsl:text>            this.update_DOM(this.curr_value, this.handle_elt);
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
@@ -4800,6 +5175,34 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>    set_scroll(){
+</xsl:text>
+    <xsl:text>        //check if range is bigger than visible and set scroll size
+</xsl:text>
+    <xsl:text>        if(this.scroll_range &gt; this.scroll_visible){
+</xsl:text>
+    <xsl:text>            this.scroll_size = this.scroll_range - this.scroll_visible;
+</xsl:text>
+    <xsl:text>            this.range[0] = 0;
+</xsl:text>
+    <xsl:text>            this.range[1] = this.scroll_size;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        else{
+</xsl:text>
+    <xsl:text>            this.scroll_size = 1;
+</xsl:text>
+    <xsl:text>            this.range[0] = 0;
+</xsl:text>
+    <xsl:text>            this.range[1] = 1;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>    update_DOM(value, elt){
 </xsl:text>
     <xsl:text>        let [min,max,start,totallength] = this.range;
@@ -4946,7 +5349,7 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        window.removeEventListener("mouseup", this.bound_on_release, true)
+    <xsl:text>        window.removeEventListener("mouseup", this.bound_on_release, true);
 </xsl:text>
     <xsl:text>        window.removeEventListener("touchend", this.bound_on_release, true);
 </xsl:text>
@@ -5138,7 +5541,7 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        //check if in range
+    <xsl:text>        //check if in range and apply
 </xsl:text>
     <xsl:text>        if (this.curr_value &gt; max){
 </xsl:text>
@@ -5152,8 +5555,6 @@
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>
-</xsl:text>
     <xsl:text>        this.apply_hmi_value(0, this.curr_value);
 </xsl:text>
     <xsl:text>
@@ -5208,7 +5609,7 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
-    <xsl:text>        window.addEventListener("mouseup", this.bound_on_release, true)
+    <xsl:text>        window.addEventListener("mouseup", this.bound_on_release, true);
 </xsl:text>
     <xsl:text>        window.addEventListener("touchend", this.bound_on_release, true);
 </xsl:text>
@@ -5264,7 +5665,11 @@
 </xsl:text>
     <xsl:text>        evt.stopPropagation();
 </xsl:text>
-    <xsl:text>    }
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
 </xsl:text>
     <xsl:text>
 </xsl:text>
@@ -5286,6 +5691,8 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>        // save initial parameters
 </xsl:text>
     <xsl:text>        this.range_elt.style.strokeMiterlimit="0";
@@ -5318,7 +5725,13 @@
 </xsl:text>
     <xsl:text>        this.element.addEventListener("touchstart", this.bound_on_select);
 </xsl:text>
-    <xsl:text>
+    <xsl:text>        //touch recognised as page drag without next command
+</xsl:text>
+    <xsl:text>        document.body.addEventListener("touchstart", function(e){}, false);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //save ghost style
 </xsl:text>
     <xsl:text>        if(this.setpoint_elt != undefined){
 </xsl:text>
@@ -5425,68 +5838,84 @@
 </xsl:text>
     <xsl:text>    dispatch(value) {
 </xsl:text>
-    <xsl:text>        if(this.state != value){
-</xsl:text>
-    <xsl:text>            this.state = value;
-</xsl:text>
-    <xsl:text>            if (this.state) {
-</xsl:text>
-    <xsl:text>                this.active_elt.setAttribute("style", this.active_style);
-</xsl:text>
-    <xsl:text>                this.inactive_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>            } else {
-</xsl:text>
-    <xsl:text>                this.inactive_elt.setAttribute("style", this.inactive_style);
-</xsl:text>
-    <xsl:text>                this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>            }
+    <xsl:text>        this.state = value;
+</xsl:text>
+    <xsl:text>        //redraw toggle button
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    on_click(evt) {
+</xsl:text>
+    <xsl:text>        //toggle state and apply
+</xsl:text>
+    <xsl:text>        if (this.state) {
+</xsl:text>
+    <xsl:text>            this.state = 0;
+</xsl:text>
+    <xsl:text>        } else {
+</xsl:text>
+    <xsl:text>            this.state = 1;
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    on_click(evt) {
-</xsl:text>
-    <xsl:text>        if (this.state) {
+    <xsl:text>        this.apply_hmi_value(0, this.state);
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        //redraw toggle button
+</xsl:text>
+    <xsl:text>        this.request_animate();
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    animate(){
+</xsl:text>
+    <xsl:text>       // redraw toggle button on screen refresh
+</xsl:text>
+    <xsl:text>       if (this.state) {
+</xsl:text>
+    <xsl:text>           this.active_elt.setAttribute("style", this.active_style);
+</xsl:text>
+    <xsl:text>           this.inactive_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>       } else {
+</xsl:text>
+    <xsl:text>           this.inactive_elt.setAttribute("style", this.inactive_style);
+</xsl:text>
+    <xsl:text>           this.active_elt.setAttribute("style", "display:none");
+</xsl:text>
+    <xsl:text>       }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    init() {
+</xsl:text>
+    <xsl:text>        this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
+</xsl:text>
+    <xsl:text>        this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>        if (this.active_style &amp;&amp; this.inactive_style) {
+</xsl:text>
+    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
 </xsl:text>
     <xsl:text>            this.inactive_elt.setAttribute("style", this.inactive_style);
 </xsl:text>
-    <xsl:text>            this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>            this.state = 0;
-</xsl:text>
-    <xsl:text>        } else {
-</xsl:text>
-    <xsl:text>            this.active_elt.setAttribute("style", this.active_style);
-</xsl:text>
-    <xsl:text>            this.inactive_elt.setAttribute("style", "display:none");
-</xsl:text>
-    <xsl:text>            this.state = 1;
-</xsl:text>
     <xsl:text>        }
 </xsl:text>
-    <xsl:text>        this.apply_hmi_value(0, this.state);
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>    init() {
-</xsl:text>
-    <xsl:text>        this.active_style = this.active_elt.style.cssText;
-</xsl:text>
-    <xsl:text>        this.inactive_style = this.inactive_elt.style.cssText;
+    <xsl:text>
 </xsl:text>
     <xsl:text>        this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
 </xsl:text>
-    <xsl:text>        this.inactive_elt.setAttribute("style", this.inactive_style);
-</xsl:text>
-    <xsl:text>        this.active_elt.setAttribute("style", "display:none");
-</xsl:text>
     <xsl:text>    }
 </xsl:text>
     <xsl:text>}
@@ -5499,6 +5928,7 @@
       <xsl:with-param name="labels">
         <xsl:text>active inactive</xsl:text>
       </xsl:with-param>
+      <xsl:with-param name="mandatory" select="'no'"/>
     </xsl:call-template>
     <xsl:text>
 </xsl:text>
--- a/svghmi/hmi_tree.ysl2	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/hmi_tree.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -73,8 +73,9 @@
 //  widget type="WidgetType" id="blah456" {
 //      arg value="param1";
 //      arg value="param2";
-//      path value="path1" index="345";
-//      path value="path2";
+//      path value=".path1" index=".path1" type="PAGE_LOCAL";
+//      path value="/path1" index="348" type="HMI_INT";
+//      path value="path4" index="path4" type="HMI_LOCAL";
 //  }
 //
 template "*", mode="parselabel" {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widget_animate.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -0,0 +1,53 @@
+// widget_animate.ysl2
+
+template "widget[@type='Animate']", mode="widget_class"{
+    ||
+    class AnimateWidget extends Widget{
+        frequency = 5;
+        speed = 0;
+        start = false;
+        widget_center = undefined;
+
+        dispatch(value) {
+            this.speed = value / 5;
+
+            //reconfigure animation
+            this.request_animate();
+        }
+
+        animate(){
+           // change animation properties
+           for(let child of this.element.children){
+                if(child.nodeName.startsWith("animate")){
+                    if(this.speed != 0 && !this.start){
+                        this.start = true;
+                        this.element.beginElement();
+                    }
+
+                    if(this.speed > 0){
+                        child.setAttribute("dur", this.speed+"s");
+                    }
+                    else if(this.speed < 0){
+                        child.setAttribute("dur", (-1)*this.speed+"s");
+                    }
+                    else{
+                        this.start = false;
+                        this.element.endElement();
+                    }
+                }
+           }
+        }
+
+        init() {
+            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" {
+    param "hmi_element";
+    |,
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widget_animaterotation.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -0,0 +1,51 @@
+// widget_animaterotation.ysl2
+
+template "widget[@type='AnimateRotation']", mode="widget_class"{
+    ||
+    class AnimateRotationWidget extends Widget{
+        frequency = 5;
+        speed = 0;
+        widget_center = undefined;
+
+        dispatch(value) {
+            this.speed = value / 5;
+
+            //reconfigure animation
+            this.request_animate();
+        }
+
+        animate(){
+           // change animation properties
+           for(let child of this.element.children){
+                if(child.nodeName == "animateTransform"){
+                    if(this.speed > 0){
+                        child.setAttribute("dur", this.speed+"s");
+                        child.setAttribute("from", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+                        child.setAttribute("to", "360 "+this.widget_center[0]+" "+this.widget_center[1]);
+                    }
+                    else if(this.speed < 0){
+                        child.setAttribute("dur", (-1)*this.speed+"s");
+                        child.setAttribute("from", "360 "+this.widget_center[0]+" "+this.widget_center[1]);
+                        child.setAttribute("to", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+                    }
+                    else{
+                        child.setAttribute("from", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+                        child.setAttribute("to", "0 "+this.widget_center[0]+" "+this.widget_center[1]);
+                    }
+                }
+           }
+        }
+
+        init() {
+            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" {
+    param "hmi_element";
+    |,
+}
--- a/svghmi/widget_button.ysl2	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/widget_button.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -4,42 +4,55 @@
     ||
     class ButtonWidget extends Widget{
         frequency = 5;
-        state = 0;
+        state_plc = 0;
+        state_hmi = 0;
         plc_lock = false;
         active_style = undefined;
         inactive_style = undefined;
 
         dispatch(value) {
-            if(value){
-                this.button_release();
+            this.state_plc = value;
+            if(this.plc_lock){
+                if(this.state_plc == 1){
+                    this.apply_hmi_value(0, 0);
+                    this.plc_lock = false;
+                }
             }
+
+            //redraw button
+            this.state_hmi = this.state_plc;
+            this.request_animate();
         }
 
-         on_mouse_down(evt) {
-             if (this.active_style && this.inactive_style) {
-                 this.active_elt.setAttribute("style", this.active_style);
-                 this.inactive_elt.setAttribute("style", "display:none");
-             }
-             this.apply_hmi_value(0, 1);
-             this.plc_lock = false;
-         }
+        animate(){
+            if (this.active_style && this.inactive_style) {
+               // redraw button on screen refresh
+               if (this.state_hmi) {
+                   this.active_elt.setAttribute("style", this.active_style);
+                   this.inactive_elt.setAttribute("style", "display:none");
+               } else {
+                   this.inactive_elt.setAttribute("style", this.inactive_style);
+                   this.active_elt.setAttribute("style", "display:none");
+               }
+           }
+        }
 
-         on_mouse_up(evt) {
-             this.button_release();
-         }
+        on_click(evt) {
+            //set state and apply if plc is 0
+            this.plc_lock = true;
+            if(this.state_plc == 0){
+                this.apply_hmi_value(0, 1);
+            }
+            //redraw button
+            this.request_animate();
+        }
 
-         button_release(){
-            if(!this.plc_lock){
-                this.plc_lock = true;
-            }
-            else{
-                if (this.active_style && this.inactive_style) {
-                     this.active_elt.setAttribute("style", "display:none");
-                     this.inactive_elt.setAttribute("style", this.inactive_style);
-                 }
-                 this.apply_hmi_value(0, 0);
-            }
-         }
+        on_press(evt) {
+            //set graphic
+            this.state_hmi = 1;
+            //redraw button
+            this.request_animate();
+        }
 
          init() {
             this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
@@ -50,8 +63,8 @@
                 this.inactive_elt.setAttribute("style", this.inactive_style);
             }
 
-            this.element.setAttribute("onmousedown", "hmi_widgets["+this.element_id+"].on_mouse_down(evt)");
-            this.element.setAttribute("onmouseup", "hmi_widgets["+this.element_id+"].on_mouse_up(evt)");
+            this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
+            this.element.setAttribute("onmousedown", "hmi_widgets['"+this.element_id+"'].on_press(evt)");
          }
     }
     ||
--- a/svghmi/widget_circularslider.ysl2	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/widget_circularslider.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -7,16 +7,33 @@
         range = undefined;
         circle = undefined;
         handle_pos = undefined;
-        svg_dist = undefined;
+        curr_value = 0;
         drag = false;
         enTimer = false;
         last_drag = false;
 
         dispatch(value) {
+            let [min,max,start,totallength] = this.range;
+            //save current value inside widget
+            this.curr_value = value;
+
+            //check if in range
+            if (this.curr_value > max){
+                this.curr_value = max;
+                this.apply_hmi_value(0, this.curr_value);
+            }
+            else if (this.curr_value < min){
+                this.curr_value = min;
+                this.apply_hmi_value(0, this.curr_value);
+            }
+
             if(this.value_elt)
                 this.value_elt.textContent = String(value);
 
-            this.update_DOM(value, this.handle_elt);
+            //don't update if draging and setpoint ghost doesn't exist
+            if(!this.drag || (this.setpoint_elt != undefined)){
+                this.update_DOM(value, this.handle_elt);
+            }
         }
 
         update_DOM(value, elt){
@@ -25,6 +42,7 @@
             let tip = this.range_elt.getPointAtLength(length);
             elt.setAttribute('transform',"translate("+(tip.x-this.handle_pos.x)+","+(tip.y-this.handle_pos.y)+")");
 
+            // show or hide ghost if exists
             if(this.setpoint_elt != undefined){
                 if(this.last_drag!= this.drag){
                     if(this.drag){
@@ -38,21 +56,28 @@
         }
 
         on_release(evt) {
+            //unbind events
             window.removeEventListener("touchmove", this.on_bound_drag, true);
             window.removeEventListener("mousemove", this.on_bound_drag, true);
 
             window.removeEventListener("mouseup", this.bound_on_release, true)
             window.removeEventListener("touchend", this.bound_on_release, true);
             window.removeEventListener("touchcancel", this.bound_on_release, true);
+
+            //reset drag flag
             if(this.drag){
                 this.drag = false;
             }
+
+            // get final position
             this.update_position(evt);
         }
 
         on_drag(evt){
+            //ignore drag event for X amount of time and if not selected
             if(this.enTimer && this.drag){
                 this.update_position(evt);
+
                 //reset timer
                 this.enTimer = false;
                 setTimeout("{hmi_widgets['"+this.element_id+"'].enTimer = true;}", 100);
@@ -103,40 +128,54 @@
 
                 //get handle distance from mouse position
                 if(fi<fiEnd){
-                    this.svg_dist=(fi)/(fiEnd)*(this.range[1]-this.range[0]);
+                   this.curr_value=(fi)/(fiEnd)*(this.range[1]-this.range[0]);
                 }
                 else if(fiEnd<fi && fi<fiEnd+minMax){
-                    this.svg_dist = this.range[1];
+                    this.curr_value = this.range[1];
                 }
                 else{
-                    this.svg_dist = this.range[0];
-                }
-
-
-                this.apply_hmi_value(0, Math.ceil(this.svg_dist));
-
-                // update ghost cursor
-                if(this.setpoint_elt != undefined){
-                    this.request_animate();
-                }
+                    this.curr_value = this.range[0];
+                }
+
+                //apply value to hmi
+                this.apply_hmi_value(0, Math.ceil(this.curr_value));
+
+                //redraw handle
+                this.request_animate();
+
             }
 
         }
 
         animate(){
-            this.update_DOM(this.svg_dist, this.setpoint_elt);
+            // redraw handle on screen refresh
+            // check if setpoint(ghost) handle exsist otherwise update main handle
+            if(this.setpoint_elt != undefined){
+                this.update_DOM(this.curr_value, this.setpoint_elt);
+            }
+            else{
+                this.update_DOM(this.curr_value, this.handle_elt);
+            }
         }
 
         on_select(evt){
+            //enable drag flag and timer
             this.drag = true;
             this.enTimer = true;
+
+            //bind events
             window.addEventListener("touchmove", this.on_bound_drag, true);
             window.addEventListener("mousemove", this.on_bound_drag, true);
 
-            window.addEventListener("mouseup", this.bound_on_release, true)
+            window.addEventListener("mouseup", this.bound_on_release, true);
             window.addEventListener("touchend", this.bound_on_release, true);
             window.addEventListener("touchcancel", this.bound_on_release, true);
+
+            //update postion on mouse press
             this.update_position(evt);
+
+            //prevent next events
+            evt.stopPropagation();
         }
 
         init() {
@@ -175,23 +214,19 @@
             this.bound_on_release = this.on_release.bind(this);
             this.on_bound_drag = this.on_drag.bind(this);
 
-            //init events
+            this.handle_elt.addEventListener("mousedown", this.bound_on_select);
             this.element.addEventListener("mousedown", this.bound_on_select);
             this.element.addEventListener("touchstart", this.bound_on_select);
-
+            //touch recognised as page drag without next command
+            document.body.addEventListener("touchstart", function(e){}, false);
+
+            //save ghost style
+            //save ghost style
             if(this.setpoint_elt != undefined){
                 this.setpoint_style = this.setpoint_elt.getAttribute("style");
                 this.setpoint_elt.setAttribute("style", "display:none");
             }
 
-
-            window.addEventListener("touchmove", hmi_widgets[this.element_id].update_position.bind(this));
-            window.addEventListener("mousemove", hmi_widgets[this.element_id].update_position.bind(this));
-
-            window.addEventListener("mouseup", hmi_widgets[this.element_id].on_release.bind(this))
-            window.addEventListener("touchend", hmi_widgets[this.element_id].on_release.bind(this));
-            window.addEventListener("touchcancel", hmi_widgets[this.element_id].on_release.bind(this));
-
         }
     }
     ||
@@ -199,6 +234,6 @@
 template "widget[@type='CircularSlider']", mode="widget_defs" {
     param "hmi_element";
     labels("handle range");
-    optional_labels("value min max");
+    optional_labels("value min max setpoint");
     |,
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/widget_customhtml.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -0,0 +1,33 @@
+// widget_customhtml.ysl2
+
+template "widget[@type='CustomHtml']", mode="widget_class"{
+    ||
+    class CustomHtmlWidget extends Widget{
+        frequency = 5;
+        widget_size = undefined;
+
+        dispatch(value) {
+            this.request_animate();
+        }
+
+        animate(){
+        }
+
+        init() {
+            this.widget_size = this.container_elt.getBBox();
+            this.element.innerHTML ='<foreignObject x="'+
+                this.widget_size.x+'" y="'+this.widget_size.y+
+                '" width="'+this.widget_size.width+'" height="'+this.widget_size.height+'"> '+
+                this.code_elt.textContent+
+                ' </foreignObject>';
+        }
+    }
+    ||
+}
+
+
+template "widget[@type='CustomHtml']", mode="widget_defs" {
+    param "hmi_element";
+    labels("container code");
+    |,
+}
--- a/svghmi/widget_display.ysl2	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/widget_display.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -20,7 +20,7 @@
     const "field_initializer" foreach "path" {
         choose{
             when "@type='HMI_STRING'" > ""
-            otherwise 0
+            otherwise > 0
         }
         if "position()!=last()" > ,
     }
--- a/svghmi/widget_jsontable.ysl2	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/widget_jsontable.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -3,11 +3,12 @@
 template "widget[@type='JsonTable']", mode="widget_class"
     ||
     class JsonTableWidget extends Widget{
-        cache = [];
+        cache = [100,50];
         do_http_request(...opt) {
             const query = {
                 args: this.args,
-                vars: this.cache,
+                range: this.cache[1],
+                position: this.cache[2],
                 visible: this.visible,
                 options: opt
             };
@@ -218,6 +219,7 @@
     |         let [range,position,jdata] = janswer;
     |         this.apply_hmi_value(1, range);
     |         this.apply_hmi_value(2, position);
+    |         this.apply_hmi_value(3, this.visible);
     |         console.log(range,position,jdata);
     apply "$data_elt", mode="json_table_render_except_comments" {
         with "expressions","$initexpr_ns";
--- a/svghmi/widget_slider.ysl2	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/widget_slider.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -6,7 +6,9 @@
         frequency = 5;
         range = undefined;
         handle_orig = undefined;
-        scroll_size = 10;
+        scroll_size = undefined;
+        scroll_range = 0;
+        scroll_visible = 7;
         min_size = 0.07;
         fi = undefined;
         curr_value = 0;
@@ -15,16 +17,51 @@
         handle_click = undefined;
         last_drag = false;
 
-        dispatch(value) {
-            //save current value inside widget
-            this.curr_value = value;
-
-            if(this.value_elt)
-                this.value_elt.textContent = String(value);
+        dispatch(value,oldval, index) {
+            if (index == 0){
+                let [min,max,start,totallength] = this.range;
+                //save current value inside widget
+                this.curr_value = value;
+
+                //check if in range
+                if (this.curr_value > max){
+                    this.curr_value = max;
+                    this.apply_hmi_value(0, this.curr_value);
+                }
+                else if (this.curr_value < min){
+                    this.curr_value = min;
+                    this.apply_hmi_value(0, this.curr_value);
+                }
+
+                if(this.value_elt)
+                    this.value_elt.textContent = String(value);
+            }
+            else if(index == 1){
+                this.scroll_range = value;
+                this.set_scroll();
+            }
+            else if(index == 2){
+                this.scroll_visible = value;
+                this.set_scroll();
+            }
 
             //don't update if draging and setpoint ghost doesn't exist
             if(!this.drag || (this.setpoint_elt != undefined)){
-                this.update_DOM(value, this.handle_elt);
+                this.update_DOM(this.curr_value, this.handle_elt);
+            }
+        }
+
+        set_scroll(){
+            //check if range is bigger than visible and set scroll size
+            if(this.scroll_range > this.scroll_visible){
+                this.scroll_size = this.scroll_range - this.scroll_visible;
+                this.range[0] = 0;
+                this.range[1] = this.scroll_size;
+            }
+            else{
+                this.scroll_size = 1;
+                this.range[0] = 0;
+                this.range[1] = 1;
             }
         }
 
@@ -101,7 +138,7 @@
             window.removeEventListener("touchmove", this.on_bound_drag, true);
             window.removeEventListener("mousemove", this.on_bound_drag, true);
 
-            window.removeEventListener("mouseup", this.bound_on_release, true)
+            window.removeEventListener("mouseup", this.bound_on_release, true);
             window.removeEventListener("touchend", this.bound_on_release, true);
             window.removeEventListener("touchcancel", this.bound_on_release, true);
 
@@ -197,14 +234,13 @@
                 this.curr_value=Math.ceil((html_dist/range_length)*(this.range[1]-this.range[0])+this.range[0]);
             }
 
-            //check if in range
+            //check if in range and apply
             if (this.curr_value > max){
                 this.curr_value = max;
             }
             else if (this.curr_value < min){
                 this.curr_value = min;
             }
-
             this.apply_hmi_value(0, this.curr_value);
 
             //redraw handle
@@ -232,7 +268,7 @@
             window.addEventListener("touchmove", this.on_bound_drag, true);
             window.addEventListener("mousemove", this.on_bound_drag, true);
 
-            window.addEventListener("mouseup", this.bound_on_release, true)
+            window.addEventListener("mouseup", this.bound_on_release, true);
             window.addEventListener("touchend", this.bound_on_release, true);
             window.addEventListener("touchcancel", this.bound_on_release, true);
 
@@ -260,7 +296,9 @@
 
             //prevent next events
             evt.stopPropagation();
-        }
+
+        }
+
 
         init() {
             //set min max value if not defined
@@ -271,6 +309,7 @@
                         Number(this.max_elt.textContent) :
                         this.args.length >= 2 ? this.args[1] : 100;
 
+
             // save initial parameters
             this.range_elt.style.strokeMiterlimit="0";
             this.range = [min, max, this.range_elt.getPointAtLength(0),this.range_elt.getTotalLength()];
@@ -287,7 +326,10 @@
             this.handle_elt.addEventListener("mousedown", this.bound_on_select);
             this.element.addEventListener("mousedown", this.bound_on_select);
             this.element.addEventListener("touchstart", this.bound_on_select);
-
+            //touch recognised as page drag without next command
+            document.body.addEventListener("touchstart", function(e){}, false);
+
+            //save ghost style
             if(this.setpoint_elt != undefined){
                 this.setpoint_style = this.setpoint_elt.getAttribute("style");
                 this.setpoint_elt.setAttribute("style", "display:none");
--- a/svghmi/widget_tooglebutton.ysl2	Tue Oct 20 00:23:52 2020 +0200
+++ b/svghmi/widget_tooglebutton.ysl2	Tue Oct 20 00:24:49 2020 +0200
@@ -10,37 +10,45 @@
         inactive_style = undefined;
 
         dispatch(value) {
-            if(this.state != value){
-                this.state = value;
-                if (this.state) {
-                    this.active_elt.setAttribute("style", this.active_style);
-                    this.inactive_elt.setAttribute("style", "display:none");
-                } else {
-                    this.inactive_elt.setAttribute("style", this.inactive_style);
-                    this.active_elt.setAttribute("style", "display:none");
-                }
-            }
+            this.state = value;
+            //redraw toggle button
+            this.request_animate();
         }
 
         on_click(evt) {
+            //toggle state and apply
             if (this.state) {
-                this.inactive_elt.setAttribute("style", this.inactive_style);
-                this.active_elt.setAttribute("style", "display:none");
                 this.state = 0;
             } else {
-                this.active_elt.setAttribute("style", this.active_style);
-                this.inactive_elt.setAttribute("style", "display:none");
                 this.state = 1;
             }
             this.apply_hmi_value(0, this.state);
+
+            //redraw toggle button
+            this.request_animate();
+        }
+
+        animate(){
+           // redraw toggle button on screen refresh
+           if (this.state) {
+               this.active_elt.setAttribute("style", this.active_style);
+               this.inactive_elt.setAttribute("style", "display:none");
+           } else {
+               this.inactive_elt.setAttribute("style", this.inactive_style);
+               this.active_elt.setAttribute("style", "display:none");
+           }
         }
 
         init() {
-            this.active_style = this.active_elt.style.cssText;
-            this.inactive_style = this.inactive_elt.style.cssText;
+            this.active_style = this.active_elt ? this.active_elt.style.cssText : undefined;
+            this.inactive_style = this.inactive_elt ? this.inactive_elt.style.cssText : undefined;
+
+            if (this.active_style && this.inactive_style) {
+                this.active_elt.setAttribute("style", "display:none");
+                this.inactive_elt.setAttribute("style", this.inactive_style);
+            }
+
             this.element.setAttribute("onclick", "hmi_widgets['"+this.element_id+"'].on_click(evt)");
-            this.inactive_elt.setAttribute("style", this.inactive_style);
-            this.active_elt.setAttribute("style", "display:none");
         }
     }
     ||
@@ -48,6 +56,6 @@
 
 template "widget[@type='ToggleButton']", mode="widget_defs" {
     param "hmi_element";
-    labels("active inactive");
+    optional_labels("active inactive");
     |,
 }
--- a/tests/svghmi/py_ext_0@py_ext/pyfile.xml	Tue Oct 20 00:23:52 2020 +0200
+++ b/tests/svghmi/py_ext_0@py_ext/pyfile.xml	Tue Oct 20 00:24:49 2020 +0200
@@ -30,8 +30,9 @@
     def render_POST(self, request):
         newstr = request.content.getvalue()
         newdata = json.loads(newstr)
-        vars = newdata[u'vars']
         args = newdata[u'args']
+        range_feedback = newdata[u'range']
+        slider_position = newdata[u'position']
         visible = newdata[u'visible']
         options = newdata[u'options']
 
@@ -40,14 +41,11 @@
             if action == "onClick[acknowledge]":
                 AlarmIndex[int(alarmid)][2] = "ack"
 
-        svars = (vars + [0,0])[:3]
-        range_feedback = svars[1]
-        slider_position = svars[2]
-        answer = self.renderTable(range_feedback, slider_position, visible, *(args+svars[3:]))
+        answer = self.renderTable(range_feedback, slider_position, visible)
         janswer = json.dumps(answer)
         return janswer
 
-    def renderTable(self, old_range, old_position, visible, *options):
+    def renderTable(self, old_range, old_position, visible):
         new_range = len(Alarms)
         delta = new_range - visible
         new_position = 0 if delta <= 0 else delta if old_position > delta else old_position
--- a/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Tue Oct 20 00:23:52 2020 +0200
+++ b/tests/svghmi/svghmi_0@svghmi/svghmi.svg	Tue Oct 20 00:24:49 2020 +0200
@@ -16,7 +16,7 @@
    version="1.1"
    id="hmi0"
    sodipodi:docname="svghmi.svg"
-   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+   inkscape:version="0.92.5 (0.92.5+68)">
   <metadata
      id="metadata4542">
     <rdf:RDF>
@@ -197,16 +197,16 @@
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
      inkscape:document-units="px"
-     inkscape:current-layer="g1384"
+     inkscape:current-layer="hmi0"
      showgrid="false"
      units="px"
-     inkscape:zoom="1.0913159"
-     inkscape:cx="-911.00114"
-     inkscape:cy="181.96708"
-     inkscape:window-width="1800"
-     inkscape:window-height="836"
-     inkscape:window-x="0"
-     inkscape:window-y="27"
+     inkscape:zoom="2.1826317"
+     inkscape:cx="-408.38959"
+     inkscape:cy="176.28106"
+     inkscape:window-width="1863"
+     inkscape:window-height="1176"
+     inkscape:window-x="57"
+     inkscape:window-y="24"
      inkscape:window-maximized="1"
      showguides="true"
      inkscape:guide-bbox="true" />
@@ -5223,6 +5223,9 @@
   <g
      id="g908"
      inkscape:label="HMI:VarInit:100@.range" />
+  <g
+     inkscape:label="HMI:VarInit:7@.visibleAlarms"
+     id="g906-3" />
   <rect
      style="color:#000000;fill:#4d4d4d"
      id="rect2015"
@@ -5549,32 +5552,53 @@
        sodipodi:type="star" />
   </g>
   <g
-     id="g1553"
-     transform="matrix(0.5,0,0,0.5,-774.48108,252.30551)">
+     id="g1766"
+     inkscape:label="HMI:Slider@.position@.range@.alarmVisible">
+    <g
+       transform="matrix(0.620824,0,0,0.5,-963.61047,260.72872)"
+       id="g1752"
+       inkscape:label="HMI:Input@.position">
+      <path
+         inkscape:label="+1"
+         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:#ff6600;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.55573034px;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 1175.2115,143.25263 34.1278,56.73732 h -68.2556 z"
+         id="path1266"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cccc" />
+      <path
+         inkscape:label="-1"
+         sodipodi:nodetypes="cccc"
+         inkscape:connector-curvature="0"
+         id="path1268"
+         d="m 1175.2115,851.99803 34.1278,-54.90445 h -68.2556 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:#ff6600;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.51411843px;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" />
+    </g>
+    <path
+       style="opacity:0;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.00058591px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M -234.01102,648.56465 V 371.89445"
+       id="path1772"
+       inkscape:connector-curvature="0"
+       inkscape:label="range" />
     <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:#bc8f8f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2px;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"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.30952382;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.03627348px;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="rect1264-3"
+       width="42.374725"
+       height="276.64423"
+       x="-255.19838"
+       y="371.91068"
+       rx="7.6034913"
+       ry="6.8822322"
+       inkscape:label="background" />
+    <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:#ff6600;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.11429262px;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="rect1264"
-       width="68.255615"
-       height="165.68298"
-       x="1141.0836"
-       y="420.78394"
-       rx="12.247418"
-       ry="14"
-       inkscape:label="cursor" />
-    <path
-       sodipodi:nodetypes="cccc"
-       inkscape:connector-curvature="0"
-       id="path1266"
-       d="m 1175.2115,371.25263 34.1278,34.74552 h -68.2556 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:#bc8f8f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2px;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="backward" />
-    <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:#bc8f8f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2px;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 1175.2115,635.99803 34.1278,-34.7453 h -68.2556 z"
-       id="path1268"
-       inkscape:connector-curvature="0"
-       sodipodi:nodetypes="cccc"
-       inkscape:label="forward" />
+       width="42.374725"
+       height="82.841492"
+       x="-255.19838"
+       y="565.71338"
+       rx="7.6034913"
+       ry="7"
+       inkscape:label="handle" />
   </g>
   <g
      id="g893"
@@ -6029,16 +6053,6 @@
        x="-546.47461"
        id="tspan2172"
        sodipodi:role="line">Status</tspan></text>
-  <text
-     xml:space="preserve"
-     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:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
-     x="-241.25107"
-     y="513.61072"
-     id="text2184"><tspan
-       sodipodi:role="line"
-       id="tspan2182"
-       x="-241.25107"
-       y="513.61072">TODO</tspan></text>
   <g
      transform="matrix(0.57180538,0,0,0.57180538,226.35945,-231.48695)"
      inkscape:label="HMI:Jump:AlarmPage"
--- a/tests/svghmi_v2/plc.xml	Tue Oct 20 00:23:52 2020 +0200
+++ b/tests/svghmi_v2/plc.xml	Tue Oct 20 00:24:49 2020 +0200
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2019-08-06T14:23:42"/>
-  <contentHeader name="Unnamed" modificationDateTime="2020-09-15T14:59:06">
+  <contentHeader name="Unnamed" modificationDateTime="2020-09-30T13:04:27">
     <coordinateInfo>
       <fbd>
         <scaling x="5" y="5"/>
@@ -75,6 +75,11 @@
                 <INT/>
               </type>
             </variable>
+            <variable name="Speed">
+              <type>
+                <derived name="HMI_INT"/>
+              </type>
+            </variable>
           </localVars>
         </interface>
         <body>
--- a/tests/svghmi_v2/svghmi_0@svghmi/svghmi.svg	Tue Oct 20 00:23:52 2020 +0200
+++ b/tests/svghmi_v2/svghmi_0@svghmi/svghmi.svg	Tue Oct 20 00:24:49 2020 +0200
@@ -92,10 +92,10 @@
        y2="4.0725975" />
     <inkscape:perspective
        sodipodi:type="inkscape:persp3d"
-       inkscape:vp_x="-173.06414 : 591.30354 : 1"
+       inkscape:vp_x="-470.06413 : 851.30353 : 1"
        inkscape:vp_y="0 : 1319.7648 : 0"
-       inkscape:vp_z="1192.2994 : 402.34211 : 1"
-       inkscape:persp3d-origin="671.58536 : 432.93175 : 1"
+       inkscape:vp_z="895.29941 : 662.3421 : 1"
+       inkscape:persp3d-origin="374.58537 : 692.93174 : 1"
        id="perspective503-6" />
   </defs>
   <sodipodi:namedview
@@ -109,9 +109,9 @@
      inkscape:current-layer="hmi0"
      showgrid="false"
      units="px"
-     inkscape:zoom="1.4142136"
-     inkscape:cx="462.89448"
-     inkscape:cy="318.79031"
+     inkscape:zoom="1"
+     inkscape:cx="379.07861"
+     inkscape:cy="265.09897"
      inkscape:window-width="2503"
      inkscape:window-height="1416"
      inkscape:window-x="57"
@@ -129,8 +129,8 @@
      inkscape:label="HMI:Page:Home"
      sodipodi:insensitive="true" />
   <g
-     inkscape:label="HMI:Slider@/PUMP0/SLOTH"
-     transform="matrix(7.5590552,0,0,7.5590552,-710.78539,551.61779)"
+     inkscape:label="HMI:Slider@/SPEED"
+     transform="matrix(7.5590552,0,0,7.5590552,-780.78539,561.61779)"
      id="g110-0">
     <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;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ff0000;stroke-width:0.52916664;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"
@@ -189,15 +189,15 @@
      inkscape:label="HMI:Input@/SOMEPLCGLOBAL">
     <text
        inkscape:label="value"
-       transform="scale(1.1201068,0.89277203)"
+       transform="scale(1.1201068,0.89277202)"
        id="text2398"
-       y="446.98395"
-       x="347.5253"
+       y="479.46704"
+       x="247.53484"
        style="font-style:normal;font-weight:normal;font-size:124.08008575px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.10200214px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
        xml:space="preserve"><tspan
          style="stroke-width:3.10200214px"
-         y="446.98395"
-         x="347.5253"
+         y="479.46704"
+         x="247.53484"
          id="tspan2396"
          sodipodi:role="line">Test</tspan></text>
     <rect
@@ -205,8 +205,8 @@
        id="rect4559"
        width="323.85489"
        height="132.93608"
-       x="369.10974"
-       y="299.97858"
+       x="257.10974"
+       y="328.97858"
        inkscape:label="edit" />
     <rect
        style="opacity:0;fill:#de2cc9;fill-opacity:1;stroke:none;stroke-width:3.45667744"
@@ -1366,7 +1366,7 @@
   <g
      id="g1047"
      inkscape:label="HMI:CircularBar@/PUMP0/SLOTH"
-     transform="matrix(0.39840034,0,0,0.35920948,224.04409,96.134885)">
+     transform="matrix(0.39840034,0,0,0.35920948,-97.955902,106.13488)">
     <path
        inkscape:label="range"
        sodipodi:open="true"
@@ -1410,7 +1410,7 @@
   <g
      id="g1047-6"
      inkscape:label="HMI:CircularSlider@/PUMP0/SLOTH"
-     transform="matrix(0.45707797,0,0,0.45707797,330.74411,340.99474)">
+     transform="matrix(0.45707797,0,0,0.45707797,33.744118,80.994747)">
     <path
        inkscape:label="range"
        d="M 970.29569,399.76446 A 184.25998,167.44942 0 0 1 866.26395,284.77467 184.25998,167.44942 0 0 1 904.10823,139.93753"
@@ -1438,43 +1438,43 @@
          sodipodi:type="inkscape:box3dside"
          id="path932-9"
          inkscape:box3dsidetype="6"
-         d="m 919.8592,371.09874 v 61.75093 l 51.05152,-25.59855 V 348.7668 Z"
-         points="919.8592,432.84967 970.91072,407.25112 970.91072,348.7668 919.8592,371.09874 "
+         d="m 919.8592,371.09875 v 61.75093 l 51.05152,-25.59855 v -58.48432 z"
+         points="919.8592,432.84968 970.91072,407.25113 970.91072,348.76681 919.8592,371.09875 "
          style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:21.82598114px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
       <path
          sodipodi:type="inkscape:box3dside"
          id="path940-1"
          inkscape:box3dsidetype="13"
-         d="m 919.8592,432.84967 49.77111,22.08625 49.54589,-27.39008 -48.26548,-20.29472 z"
-         points="969.63031,454.93592 1019.1762,427.54584 970.91072,407.25112 919.8592,432.84967 "
+         d="m 919.8592,432.84968 49.77112,22.08624 49.54588,-27.39007 -48.26548,-20.29472 z"
+         points="969.63032,454.93592 1019.1762,427.54585 970.91072,407.25113 919.8592,432.84968 "
          style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:21.82598114px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
       <path
          sodipodi:type="inkscape:box3dside"
          id="path942-2"
          inkscape:box3dsidetype="11"
-         d="m 970.91072,348.7668 48.26548,18.93314 v 59.8459 l -48.26548,-20.29472 z"
-         points="1019.1762,367.69994 1019.1762,427.54584 970.91072,407.25112 970.91072,348.7668 "
+         d="m 970.91072,348.76681 48.26548,18.93313 v 59.84591 l -48.26548,-20.29472 z"
+         points="1019.1762,367.69994 1019.1762,427.54585 970.91072,407.25113 970.91072,348.76681 "
          style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:21.82598114px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
       <path
          sodipodi:type="inkscape:box3dside"
          id="path934-7"
          inkscape:box3dsidetype="5"
-         d="m 919.8592,371.09874 49.77111,20.56633 49.54589,-23.96513 -48.26548,-18.93314 z"
-         points="969.63031,391.66507 1019.1762,367.69994 970.91072,348.7668 919.8592,371.09874 "
+         d="m 919.8592,371.09875 49.77112,20.56633 49.54588,-23.96514 -48.26548,-18.93313 z"
+         points="969.63032,391.66508 1019.1762,367.69994 970.91072,348.76681 919.8592,371.09875 "
          style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:21.82598114px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
       <path
          sodipodi:type="inkscape:box3dside"
          id="path938-0"
          inkscape:box3dsidetype="14"
-         d="m 969.63031,391.66507 v 63.27085 l 49.54589,-27.39008 v -59.8459 z"
-         points="969.63031,454.93592 1019.1762,427.54584 1019.1762,367.69994 969.63031,391.66507 "
+         d="m 969.63032,391.66508 v 63.27084 l 49.54588,-27.39007 v -59.84591 z"
+         points="969.63032,454.93592 1019.1762,427.54585 1019.1762,367.69994 969.63032,391.66508 "
          style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:21.82598114px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
       <path
          sodipodi:type="inkscape:box3dside"
          id="path936-9"
          inkscape:box3dsidetype="3"
-         d="m 919.8592,371.09874 49.77111,20.56633 v 63.27085 L 919.8592,432.84967 Z"
-         points="969.63031,391.66507 969.63031,454.93592 919.8592,432.84967 919.8592,371.09874 "
+         d="m 919.8592,371.09875 49.77112,20.56633 v 63.27084 L 919.8592,432.84968 Z"
+         points="969.63032,391.66508 969.63032,454.93592 919.8592,432.84968 919.8592,371.09875 "
          style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:21.82598114px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
     </g>
     <text
@@ -1492,27 +1492,7 @@
          style="fill:#ff00ca;fill-opacity:1;stroke:none;stroke-width:2.25346255px;stroke-opacity:1">000</tspan></text>
   </g>
   <g
-     id="g4791"
-     inkscape:label="HMI:ToggleButton@/TOGGLE2">
-    <rect
-       inkscape:label="inactive"
-       y="46.127251"
-       x="906.51086"
-       height="44.547726"
-       width="45.254833"
-       id="rect4772"
-       style="opacity:1;fill:#ff0015;fill-opacity:1;stroke:none" />
-    <rect
-       inkscape:label="active"
-       y="46.127251"
-       x="906.51086"
-       height="44.547726"
-       width="45.254833"
-       id="rect4772-3"
-       style="opacity:1;fill:#00ff03;fill-opacity:1;stroke:none" />
-  </g>
-  <g
-     transform="translate(-67.175138,-1.0606552)"
+     transform="translate(-289.17513,-33.060654)"
      id="g4791-6"
      inkscape:label="HMI:ToggleButton@/TOGGLE1">
     <rect
@@ -1533,16 +1513,16 @@
        style="opacity:1;fill:#00ff03;fill-opacity:1;stroke:none" />
   </g>
   <g
-     transform="translate(63.639613)"
-     id="g4791-3"
-     inkscape:label="HMI:ToggleButton@/TOGGLE">
+     transform="translate(-287.05529,41.033314)"
+     id="g479hgjk"
+     inkscape:label="HMI:Button@/TOGGLE">
     <rect
        inkscape:label="active"
        y="46.127251"
        x="906.51086"
        height="44.547726"
        width="45.254833"
-       id="rect4772-3-5"
+       id="rect47fuzkj"
        style="opacity:1;fill:#00ff03;fill-opacity:1;stroke:none" />
     <rect
        inkscape:label="inactive"
@@ -1550,7 +1530,90 @@
        x="906.51086"
        height="44.547726"
        width="45.254833"
-       id="rect4772-6"
+       id="rect477hjoj"
        style="opacity:1;fill:#ff0015;fill-opacity:1;stroke:none" />
   </g>
+  <g
+     id="g1112"
+     inkscape:label="HMI:AnimateRotation@/SPEED">
+    <circle
+       r="32.057827"
+       cy="436.18585"
+       cx="747.05347"
+       id="path1380"
+       style="fill:#ececec;fill-opacity:1;stroke:#ff0000;stroke-width:2.95733476;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <rect
+       y="286.18585"
+       x="597.05353"
+       height="300"
+       width="300"
+       id="rect1382"
+       style="opacity:0;fill:#ececec;fill-opacity:1;stroke:none;stroke-width:3.69000006;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    <path
+       sodipodi:nodetypes="sssssss"
+       inkscape:connector-curvature="0"
+       id="path1388"
+       d="m 719.75481,403.83452 c 1.9692,9.54564 9.417,-4.37059 26.6751,-4.06174 27.2477,0.48762 30.0401,21.24497 35.5749,12.81174 6.6594,-10.14673 12.6699,-22.7446 14.75,-33.25 13.5509,-68.43783 -46.4736,-97.18589 -72,-91.49999 -40.88858,9.10778 -49.54078,47.21136 -31.99998,71.75 13.16428,18.41615 23.37448,26.67508 26.99998,44.24999 z"
+       style="fill:#fd0000;fill-opacity:1;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       sodipodi:nodetypes="sssssss"
+       inkscape:connector-curvature="0"
+       id="path1388-9"
+       d="m 789.45321,432.25975 c -8.9783,-3.79302 -1.7422,10.23457 -11.7862,24.27224 -15.8577,22.16324 -34.5364,12.68834 -30.7308,22.03024 4.5788,11.24 11.5443,23.3361 19.0162,31.0083 48.6752,49.9808 106.3992,16.8549 116.1963,-7.3926 15.6932,-38.84015 -10.7791,-67.57972 -40.9378,-67.05341 -22.634,0.39495 -35.2273,4.11873 -51.7577,-2.86477 z"
+       style="fill:#fd0000;fill-opacity:1;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <path
+       sodipodi:nodetypes="sssssss"
+       inkscape:connector-curvature="0"
+       id="path1388-9-8"
+       d="m 730.85671,475.85643 c 7.5732,-6.1355 -8.2092,-6.3552 -15.8654,-21.82523 -12.0882,-24.42445 5.0646,-36.44319 -4.9688,-37.48364 -12.07218,-1.25186 -26.02318,-0.80116 -36.30958,2.17903 -67.0109,19.41388 -64.9607,85.93594 -48.1806,105.99474 26.8787,32.1304 64.6969,22.3051 78.43058,-4.5502 10.3071,-20.1549 12.9505,-33.0184 26.8938,-44.3147 z"
+       style="fill:#fd0000;fill-opacity:1;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+    <animateTransform
+       attributeName="transform"
+       attributeType="XML"
+       type="rotate"
+       from="0 1049 278"
+       to="360 1049 278"
+       dur="1s"
+       repeatCount="indefinite" />
+  </g>
+  <g
+     id="g1093"
+     inkscape:label="HMI:CustomHtml">
+    <rect
+       inkscape:label="container"
+       y="12"
+       x="818"
+       height="323"
+       width="452"
+       id="rect1072"
+       style="opacity:0.29800002;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:1.11057007" />
+    <text
+       inkscape:label="code"
+       transform="scale(0.57360572,1.7433578)"
+       id="text1076"
+       y="23.059681"
+       x="1433.04"
+       style="font-style:normal;font-weight:normal;font-size:9.29032898px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.87096828px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       xml:space="preserve"><tspan
+         style="stroke-width:0.87096828px"
+         id="tspan1078"
+         y="23.059681"
+         x="1433.04"
+         sodipodi:role="line">    &lt;img xmlns=&quot;http://www.w3.org/1999/xhtml&quot; id=&quot;img&quot; src=&quot;https://thumbs.gfycat.com/ImpoliteSoupyKakapo-size_restricted.gif&quot;  width=&quot;100%&quot; height=&quot;80%&quot; /&gt;</tspan><tspan
+         style="stroke-width:0.87096828px"
+         id="tspan1080"
+         y="34.672592"
+         x="1433.04"
+         sodipodi:role="line">    &lt;a xmlns=&quot;http://www.w3.org/1999/xhtml&quot; href='www.gmail.com'&gt;Gmail&lt;/a&gt;</tspan><tspan
+         style="stroke-width:0.87096828px"
+         id="tspan1082"
+         y="46.285503"
+         x="1433.04"
+         sodipodi:role="line">    &lt;p xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;Koj kurac to ne dela&lt;/p&gt;</tspan><tspan
+         style="stroke-width:0.87096828px"
+         id="tspan1084"
+         y="57.898415"
+         x="1433.04"
+         sodipodi:role="line" /></text>
+  </g>
 </svg>