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