svghmi/widget_slider.ysl2
author Edouard Tisserant
Wed, 01 Dec 2021 09:54:02 +0100
branchRuntimeLists
changeset 3394 9ea29ac18837
parent 3283 71ae6f02a7ff
permissions -rw-r--r--
RUNTIME: Variable trace now uses limited list and buffer instead of flags in instance tree that was requiring systematical instance tree traversal, and worst size buffer. Forcing and retain still use tree traversal.
// widget_slider.ysl2

widget_desc("Slider") {
    longdesc
    || 
    Slider - DEPRECATED - use ScrollBar or PathSlider instead
    ||

    shortdesc > Slider - DEPRECATED - use ScrollBar instead

    path name="value" accepts="HMI_INT" > value
    path name="range" accepts="HMI_INT" > range
    path name="visible" accepts="HMI_INT" > visible
    
}

widget_class("Slider")
    ||
        frequency = 5;
        range = undefined;
        handle_orig = undefined;
        scroll_size = undefined;
        scroll_range = 0;
        scroll_visible = 7;
        min_size = 0.07;
        fi = undefined;
        curr_value = 0;
        drag = false;
        enTimer = false;
        handle_click = undefined;
        last_drag = false;

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

        update_DOM(value, elt){
            let [min,max,start,totallength] = this.range;
            // check if handle is resizeable
            if (this.scroll_size != undefined){ //size changes
                //get parameters
                let length = Math.max(min,Math.min(max,(Number(value)-min)*max/(max-min)));
                let tip = this.range_elt.getPointAtLength(length);
                let handle_min = totallength*this.min_size;

                let step = 1;
                //check if range is bigger than  max displayed and recalculate step
                if ((totallength/handle_min) < (max-min+1)){
                    step = (max-min+1)/(totallength/handle_min-1);
                }

                let kx,ky,offseY,offseX = undefined;
                //scale on x or y axes
                if (this.fi > 0.75){
                    //get scale factor
                    if(step > 1){
                        ky = handle_min/this.handle_orig.height;
                    }
                    else{
                        ky = (totallength-handle_min*(max-min))/this.handle_orig.height;
                    }
                    kx = 1;
                    //get 0 offset to stay inside range
                    offseY = start.y - (this.handle_orig.height + this.handle_orig.y) * ky;
                    offseX = 0;
                    //get distance from value
                    tip.y =this.range_elt.getPointAtLength(0).y - length/step *handle_min;
                }
                else{
                    //get scale factor
                    if(step > 1){
                        kx = handle_min/this.handle_orig.width;
                    }
                    else{
                        kx = (totallength-handle_min*(max-min))/this.handle_orig.width;
                    }
                    ky = 1;
                    //get 0 offset to stay inside range
                    offseX = start.x - (this.handle_orig.x * kx);
                    offseY = 0;
                    //get distance from value
                    tip.x =this.range_elt.getPointAtLength(0).x + length/step *handle_min;
                }
                elt.setAttribute('transform',"matrix("+(kx)+" 0 0 "+(ky)+" "+(tip.x-start.x+offseX)+" "+(tip.y-start.y+offseY)+")");
            }
            else{ //size stays the same
                let length = Math.max(0,Math.min(totallength,(Number(value)-min)*totallength/(max-min)));
                let tip = this.range_elt.getPointAtLength(length);
                elt.setAttribute('transform',"translate("+(tip.x-start.x)+","+(tip.y-start.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){
            var html_dist = 0;
            let [min,max,start,totallength] = this.range;

            //calculate size of widget in html
            var range_borders = this.range_elt.getBoundingClientRect();
            var [minX,minY,maxX,maxY] = [range_borders.left,range_borders.bottom,range_borders.right,range_borders.top];
            var range_length = Math.sqrt( range_borders.height*range_borders.height + range_borders.width*range_borders.width );

            //get range and mouse coordinates
            var mouseX = undefined;
            var 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 position
            if (this.handle_click){ //if clicked on handle
                let moveDist = 0, resizeAdd = 0;
                let range_percent = 1;

                //set paramters for resizeable handle
                if (this.scroll_size != undefined){
                    // add one more object to stay inside range
                    resizeAdd = 1;

                    //chack if range is bigger than display option and
                    // calculate percent of range with out handle
                    if(((max/(max*this.min_size)) < (max-min+1))){
                        range_percent = 1-this.min_size;
                    }
                    else{
                        range_percent = 1-(max-max*this.min_size*(max-min))/max;
                    }
                }

                //calculate value difference on x or y axis
                if(this.fi > 0.7){
                    moveDist = ((max-min+resizeAdd)/(range_length*range_percent))*((this.handle_click[1]-mouseY)/Math.sin(this.fi));
                }
                else{
                    moveDist = ((max-min+resizeAdd)/(range_length*range_percent))*((mouseX-this.handle_click[0])/Math.cos(this.fi));
                }

                this.curr_value = Math.ceil(this.handle_click[2] + moveDist);
            }
            else{ //if clicked on widget
                //get handle distance from mouse position
                if (minX > mouseX && minY < mouseY){
                    html_dist = 0;
                }
                else if (maxX < mouseX && maxY > mouseY){
                    html_dist = range_length;
                }
                else{
                    if(this.fi > 0.7){
                        html_dist = (minY - mouseY)/Math.sin(this.fi);
                    }
                    else{
                        html_dist = (mouseX - minX)/Math.cos(this.fi);
                    }
                }
                //calculate distance
                this.curr_value=Math.ceil((html_dist/range_length)*(this.range[1]-this.range[0])+this.range[0]);
            }

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

            // check if handle was pressed
            if (evt.currentTarget == this.handle_elt){
                //get mouse position on the handle
                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;
                }
                //save coordinates and orig value
                this.handle_click = [mouseX,mouseY,this.curr_value];
            }
            else{
                // get new handle position and reset if handle was not pressed
                this.handle_click = undefined;
                this.update_position(evt);
            }

            //prevent next events
            evt.stopPropagation();

        }


        init() {
            //set min max value if not defined
            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;


            // save initial parameters
            this.range_elt.style.strokeMiterlimit="0";
            this.range = [min, max, this.range_elt.getPointAtLength(0),this.range_elt.getTotalLength()];
            let start = this.range_elt.getPointAtLength(0);
            let end = this.range_elt.getPointAtLength(this.range_elt.getTotalLength());
            this.fi = Math.atan2(start.y-end.y, end.x-start.x);
            this.handle_orig = this.handle_elt.getBBox();

            //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
            if(this.setpoint_elt != undefined){
                this.setpoint_style = this.setpoint_elt.getAttribute("style");
                this.setpoint_elt.setAttribute("style", "display:none");
            }

        }
    ||

widget_defs("Slider") {
    labels("handle range");
    optional_labels("value min max setpoint");
}