svghmi/widget_slider.ysl2
author usveticic
Thu, 01 Oct 2020 14:23:27 +0200
branchsvghmi
changeset 3062 9ec338a99a18
parent 3059 e0db3f6a5f39
child 3232 7bdb766c2a4d
permissions -rw-r--r--
Button fix if no active or inactive state,
Widget animate changed to use anitmateTransform and added option to change rotation
Widget circular slider fixed so it is working on got and reprogramed so it similar to normal slider
Widget slider added support for changing size still need some changes to work properly
Added slider to svghmi test project
Changed svg in svhgmi_v2 project
// widget_slider.ysl2

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

        }
    }
    ||

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