svghmi/widget_circularslider.ysl2
author Edouard Tisserant
Wed, 13 Jan 2021 10:28:09 +0100
branchsvghmi
changeset 3109 6c39d718e8cb
parent 3062 9ec338a99a18
child 3232 7bdb766c2a4d
permissions -rw-r--r--
Removed harmful assert in ProcessLogger.

ProcessLogger was having an assert in constructor when missing logger, leading to systematic exception when testing options accepted by compiler. This exception was silenced in ProjectController, and then MatIEC was always called without options.
// widget_circuralslider.ysl2

template "widget[@type='CircularSlider']", mode="widget_class"
    ||
    class CircularSliderWidget extends Widget{
        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");
            }

        }
    }
    ||

template "widget[@type='CircularSlider']", mode="widget_defs" {
    param "hmi_element";
    labels("handle range");
    optional_labels("value min max setpoint");
    |,
}