svghmi/widget_pathslider.ysl2
changeset 3356 2507e35976c0
child 3454 0b5ab53007a9
equal deleted inserted replaced
3355:b16e9561a3c1 3356:2507e35976c0
       
     1 // widget_pathslider.ysl2
       
     2 widget_desc("PathSlider") {
       
     3     longdesc
       
     4     || 
       
     5     PathSlider - 
       
     6     ||
       
     7 
       
     8     shortdesc > Slide an SVG element along a path by dragging it
       
     9 
       
    10     path name="value" accepts="HMI_INT,HMI_REAL" > value
       
    11     path name="min" count="optional" accepts="HMI_INT,HMI_REAL" > min
       
    12     path name="max" count="optional" accepts="HMI_INT,HMI_REAL" > max
       
    13     
       
    14     arg name="min" count="optional" accepts="int,real" > minimum value
       
    15     arg name="max" count="optional" accepts="int,real" > maximum value
       
    16 }
       
    17 
       
    18 widget_class("PathSlider") {
       
    19     ||
       
    20         frequency = 10;
       
    21         position = undefined;
       
    22         min = 0;
       
    23         max = 100;
       
    24         scannedPoints = [];
       
    25         pathLength = undefined;
       
    26         precision = undefined;
       
    27         origPt = undefined;
       
    28         
       
    29 
       
    30         scanPath() {
       
    31           this.pathLength = this.path_elt.getTotalLength();
       
    32           this.precision = Math.floor(this.pathLength / 10);
       
    33 
       
    34           // save linear scan for coarse approximation
       
    35           for (var scanLength = 0; scanLength <= this.pathLength; scanLength += this.precision) {
       
    36             this.scannedPoints.push([this.path_elt.getPointAtLength(scanLength), scanLength]);
       
    37           }
       
    38           [this.origPt,] = this.scannedPoints[0];
       
    39         }
       
    40 
       
    41         closestPoint(point) {
       
    42           var bestPoint,
       
    43               bestLength,
       
    44               bestDistance = Infinity,
       
    45               scanDistance;
       
    46 
       
    47           // use linear scan for coarse approximation
       
    48           for (let [scanPoint, scanLength] of this.scannedPoints){
       
    49             if ((scanDistance = distance2(scanPoint)) < bestDistance) {
       
    50               bestPoint = scanPoint,
       
    51               bestLength = scanLength,
       
    52               bestDistance = scanDistance;
       
    53             }
       
    54           }
       
    55 
       
    56           // binary search for more precise estimate
       
    57           let precision = this.precision / 2;
       
    58           while (precision > 0.5) {
       
    59             var beforePoint,
       
    60                 afterPoint,
       
    61                 beforeLength,
       
    62                 afterLength,
       
    63                 beforeDistance,
       
    64                 afterDistance;
       
    65             if ((beforeLength = bestLength - precision) >= 0 &&
       
    66                 (beforeDistance = distance2(beforePoint = this.path_elt.getPointAtLength(beforeLength))) < bestDistance) {
       
    67               bestPoint = beforePoint,
       
    68               bestLength = beforeLength,
       
    69               bestDistance = beforeDistance;
       
    70             } else if ((afterLength = bestLength + precision) <= this.pathLength && 
       
    71                        (afterDistance = distance2(afterPoint = this.path_elt.getPointAtLength(afterLength))) < bestDistance) {
       
    72               bestPoint = afterPoint,
       
    73               bestLength = afterLength,
       
    74               bestDistance = afterDistance;
       
    75             }
       
    76             precision /= 2;
       
    77           }
       
    78 
       
    79           return [bestPoint, bestLength];
       
    80 
       
    81           function distance2(p) {
       
    82             var dx = p.x - point.x,
       
    83                 dy = p.y - point.y;
       
    84             return dx * dx + dy * dy;
       
    85           }
       
    86         }
       
    87 
       
    88         dispatch(value,oldval, index) {
       
    89             switch(index) {
       
    90                 case 0:
       
    91                     this.position = value;
       
    92                     break;
       
    93                 case 1:
       
    94                     this.min = value;
       
    95                     break;
       
    96                 case 2:
       
    97                     this.max = value;
       
    98                     break;
       
    99             }
       
   100 
       
   101             this.request_animate();
       
   102         }
       
   103 
       
   104         get_current_point(){
       
   105             let currLength = this.pathLength * (this.position - this.min) / (this.max - this.min)
       
   106             return this.path_elt.getPointAtLength(currLength);
       
   107         }
       
   108 
       
   109         animate(){
       
   110             if(this.position == undefined)
       
   111                 return;
       
   112 
       
   113             let currPt = this.get_current_point();
       
   114             this.cursor_transform.setTranslate(currPt.x - this.origPt.x, currPt.y - this.origPt.y);
       
   115         }
       
   116 
       
   117         init() {
       
   118             if(this.args.length == 2)
       
   119                 [this.min, this.max]=this.args;
       
   120 
       
   121             this.scanPath();
       
   122 
       
   123             this.cursor_transform = svg_root.createSVGTransform();
       
   124 
       
   125             this.cursor_elt.transform.baseVal.appendItem(this.cursor_transform);
       
   126 
       
   127             this.cursor_elt.onpointerdown = (e) => this.on_cursor_down(e);
       
   128 
       
   129             this.bound_drag = this.drag.bind(this);
       
   130             this.bound_drop = this.drop.bind(this);
       
   131         }
       
   132 
       
   133         start_dragging_from_event(e){
       
   134             let clientPoint = new DOMPoint(e.clientX, e.clientY);
       
   135             let point = clientPoint.matrixTransform(this.invctm);
       
   136             let currPt = this.get_current_point();
       
   137             this.draggingOffset = new DOMPoint(point.x - currPt.x , point.y - currPt.y);
       
   138         }
       
   139 
       
   140         apply_position_from_event(e){
       
   141             let clientPoint = new DOMPoint(e.clientX, e.clientY);
       
   142             let rawPoint = clientPoint.matrixTransform(this.invctm);
       
   143             let point = new DOMPoint(rawPoint.x - this.draggingOffset.x , rawPoint.y - this.draggingOffset.y);
       
   144             let [closestPoint, closestLength] = this.closestPoint(point);
       
   145             let new_position = this.min + (this.max - this.min) * closestLength / this.pathLength;
       
   146             this.position = Math.round(Math.max(Math.min(new_position, this.max), this.min));
       
   147             this.apply_hmi_value(0, this.position);
       
   148         }
       
   149 
       
   150         on_cursor_down(e){
       
   151             // get scrollbar -> root transform
       
   152             let ctm = this.path_elt.getCTM();
       
   153             // root -> path transform
       
   154             this.invctm = ctm.inverse();
       
   155             this.start_dragging_from_event(e);
       
   156             svg_root.addEventListener("pointerup", this.bound_drop, true);
       
   157             svg_root.addEventListener("pointermove", this.bound_drag, true);
       
   158         }
       
   159 
       
   160         drop(e) {
       
   161             svg_root.removeEventListener("pointerup", this.bound_drop, true);
       
   162             svg_root.removeEventListener("pointermove", this.bound_drag, true);
       
   163         }
       
   164 
       
   165         drag(e) {
       
   166             this.apply_position_from_event(e);
       
   167         }
       
   168     ||
       
   169 }
       
   170 
       
   171 widget_defs("PathSlider") {
       
   172     labels("cursor path");
       
   173 }