SVGHMI: Add a robust ScrollBar widget. HMI:ScrollBar@positionrange@size
// 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");
|,
}