svghmi/widget_circularslider.ysl2
author Edouard Tisserant <edouard.tisserant@gmail.com>
Tue, 19 Oct 2021 12:58:22 +0200
changeset 3366 1ca385429bba
parent 3241 fe945f1f48b7
permissions -rw-r--r--
OPCUA: Add browsing of variables exposed by client when selecting location from variable panel.
// widget_circuralslider.ysl2

widget_desc("CircularSlider") {
    longdesc
    ||
    CircularSlider - DEPRECATED, to be replaced by PathSlider
    This widget moves "handle" labeled group along "range" labeled
    arc, according to value of the single accepted variable.

    If "min" a "max" labeled texts are provided, or if first and second
    argument are given, then they are used as respective minimum and maximum
    value. Otherwise, value is expected to be in between 0 and 100.

    If "value" labeled text is found, then its content is replaced by value.
    During drag, "setpoint" labeled group is moved to position defined by user
    while "handle" reflects current value from variable.
    ||

    shortdesc > CircularSlider - DEPRECATED

    arg name="min" count="optional" accepts="int,real" > minimum value

    arg name="min" count="optional" accepts="int,real" > maximum value

    // TODO: add printf-like format

    path name="value" accepts="HMI_INT,HMI_REAL" > Value to display
    
}

widget_class("CircularSlider")
    ||
        frequency = 5;
        range = undefined;
        circle = undefined;
        handle_pos = 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);

            //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){
            let [min,max,totalDistance] = this.range;
            let length = Math.max(0,Math.min((totalDistance),(Number(value)-min)/(max-min)*(totalDistance)));
            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){
                        this.setpoint_elt.setAttribute("style", this.setpoint_style);
                    }else{
                        this.setpoint_elt.setAttribute("style", "display:none");
                    }
                    this.last_drag = this.drag;
                }
            }
        }

        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);
            }
        }

        update_position(evt){
            if(this.drag && this.enTimer){
                var svg_dist = 0;

                //calculate center of widget in html
                // --TODO maybe it would be better to bind this part to window change size event ???
                let [xdest,ydest,svgWidth,svgHeight] = page_desc[current_visible_page].bbox;
                let [cX, cY,fiStart,fiEnd,minMax,x1,y1,width,height] = this.circle;
                let htmlCirc = this.range_elt.getBoundingClientRect();
                let cxHtml = ((htmlCirc.right-htmlCirc.left)/(width)*(cX-x1))+htmlCirc.left;
                let cyHtml = ((htmlCirc.bottom-htmlCirc.top)/(height)*(cY-y1))+htmlCirc.top;


                //get mouse coordinates
                let mouseX = undefined;
                let mouseY = undefined;
                if (evt.type.startsWith("touch")){
                    mouseX = Math.ceil(evt.touches[0].clientX);
                    mouseY = Math.ceil(evt.touches[0].clientY);
                }
                else{
                    mouseX = evt.pageX;
                    mouseY = evt.pageY;
                }

                //calculate angle
                let fi = Math.atan2(cyHtml-mouseY, mouseX-cxHtml);

                // transform from 0 to 2PI
                if (fi > 0){
                    fi = 2*Math.PI-fi;
                }
                else{
                    fi = -fi;
                }

                //offset it to 0
                fi = fi - fiStart;
                if (fi < 0){
                    fi = fi + 2*Math.PI;
                }

                //get handle distance from mouse position
                if(fi<fiEnd){
                   this.curr_value=(fi)/(fiEnd)*(this.range[1]-this.range[0]);
                }
                else if(fiEnd<fi && fi<fiEnd+minMax){
                    this.curr_value = this.range[1];
                }
                else{
                    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(){
            // 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("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() {
            //get min max
            let min = this.min_elt ?
                        Number(this.min_elt.textContent) :
                        this.args.length >= 1 ? this.args[0] : 0;
            let max = this.max_elt ?
                        Number(this.max_elt.textContent) :
                        this.args.length >= 2 ? this.args[1] : 100;

            //fiStart ==> offset
            let fiStart = Number(this.range_elt.getAttribute('sodipodi:start'));
            let fiEnd = Number(this.range_elt.getAttribute('sodipodi:end'));
            fiEnd = fiEnd - fiStart;

            //fiEnd ==> size of angle
            if (fiEnd < 0){
                fiEnd = 2*Math.PI + fiEnd;
            }

            //min max barrier angle
            let minMax = (2*Math.PI - fiEnd)/2;

            //get parameters from svg
            let cX = Number(this.range_elt.getAttribute('sodipodi:cx'));
            let cY = Number(this.range_elt.getAttribute('sodipodi:cy'));
            this.range_elt.style.strokeMiterlimit="0"; //eliminates some weird border around html object
            this.range = [min, max,this.range_elt.getTotalLength()];
            let cPos = this.range_elt.getBBox();
            this.handle_pos = this.range_elt.getPointAtLength(0);
            this.circle = [cX, cY,fiStart,fiEnd,minMax,cPos.x,cPos.y,cPos.width,cPos.height];

            //bind functions
            this.bound_on_select = this.on_select.bind(this);
            this.bound_on_release = this.on_release.bind(this);
            this.on_bound_drag = this.on_drag.bind(this);

            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");
            }

        }
    ||

widget_defs("CircularSlider") {
    labels("handle range");
    optional_labels("value min max setpoint");
    |,
}