其他分享
首页 > 其他分享> > js封装原生画布 Canvas

js封装原生画布 Canvas

作者:互联网

   1 "use strict";
   2 
   3 import {UTILS, ColorRefTable, TreeStruct, Box, Circle, Polygon, Point, RGBColor, Timer} from './Utils.js';
   4 
   5 
   6 /* CanvasAnimateEvent (触发过程中可以安全的删除自己)
   7 遇到的坑: 
   8     1: canvas css属性缩放: 使用 .setScale(x, y); x,y: 大于1放大canvas, 小于1缩小canvas
   9     2: canvas 的某个父存在滚动条时需要: scrollElem.onscroll = () => CanvasAnimateRender.updateCanvas();
  10 
  11 parameter:
  12     domElement: CanvasAnimateRender.domElement; //必须
  13     box: CanvasAnimateRender.domElementRect; //必须
  14     ||或者第一个参数为 CanvasAnimateRender
  15 
  16     与.initEvent(domElement, box)参数一样
  17 
  18 attribute:
  19     domElement //CanvasAnimateRender.domElement
  20     box: Box; //忽略box以外的ca, 通常为 CanvasAnimateRender.domElementRect 的引用
  21 
  22 method:
  23     add(ca: CanvasAnimate, eventName: String, callback: Function): ca; //ca添加事件
  24     remove(ca: CanvasAnimate, eventName: String, callback: Function): ca; //ca删除事件
  25         eventName: 可能的值为 CanvasAnimateEvent.canvasEventsList 的属性名
  26         callback: 参数 event, ca
  27     
  28     clear(ca: CanvasAnimate, eventName: String): ca; //ca 必须; eventName 可选, 如果未定义则清空ca的所有事件;
  29     disposeEvent(eventName): this; //.initEvent(domElement, box)调用一次此方法
  30     initEvent(domElement, box): this; //每次更换 domElement, box 后应调用此方法; (CanvasAnimateEvent初始化时自动调用一次)
  31     setScale(x, y: Number): undefiend; //
  32 
  33 event:
  34     (如果注册了 "out"|"over" 事件, 在弃用时(CanvasAnimateRender 不在使用): 
  35     必需要调用.disposeEvent(eventName)方法清理注册的dom事件, 
  36     因为这两个事件用的是 pointermove 而不是 onpointermove);
  37 
  38     CanvasAnimateEvent.canvasEventsList
  39 
  40 demo:
  41     const car = new CanvasAnimateRender({width: 100, height: 100}),
  42     cae = new CanvasAnimateEvent(car),
  43     ca = car.add(new CanvasAnimate(image));
  44 
  45     //ca添加点击事件
  46     cae.add(ca, 'click', (event, target) => console.log(event, target));
  47 
  48     car.render();
  49 
  50 */
  51 class CanvasAnimateEvent{
  52 
  53     static bind(obj, is){
  54         obj._eventList = {}
  55 
  56         if(is === true){
  57             let k, evns = CanvasAnimateEvent.canvasEventsList;
  58             for(k in evns) obj._eventList[k] = [];
  59         }
  60             
  61     }
  62 
  63     static canvasEventsList = {
  64         down: "onpointerdown",
  65         move: "onpointermove",
  66         up: "onpointerup",
  67         click: "onclick",
  68         wheel: "onmousewheel",
  69         out: "pointermove", //移出 
  70         over: "pointermove", //移入
  71 
  72     }
  73     
  74     constructor(domElement, box){
  75         this._box = new Box();
  76         this._scale = {x: 1, y: 1};
  77         this._running = "";
  78         this._delList = [];
  79         this.initEvent(domElement, box);
  80         CanvasAnimateEvent.bind(this);
  81 
  82     }
  83 
  84     setScale(x, y){
  85         this._scale.x = x || 1;
  86         this._scale.y = y || 1;
  87 
  88         if(this.domElement){
  89             this.domElement.style.width = this.domElement.width * this._scale.x + "px";
  90             this.domElement.style.height = this.domElement.height * this._scale.y + "px";
  91         }
  92 
  93     }
  94 
  95     initEvent(domElement, box){
  96         this.disposeEvent();
  97 
  98         if(CanvasAnimateRender.prototype.isPrototypeOf(domElement)){
  99             this.domElement = domElement.domElement;
 100             this.box = domElement.domElementRect;
 101         }
 102 
 103         else{
 104             this.domElement = domElement;
 105             this.box = box;
 106         }
 107 
 108         if(this._eventList !== undefined){
 109             for(let evn in this._eventList){
 110                 if(this._eventList[evn] !== undefined) this._createEvent(evn);
 111             }
 112 
 113         }
 114         
 115         return this;
 116     }
 117 
 118     add(ca, eventName, callback){
 119         if(CanvasAnimateEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasAnimateEvent: 参数错误 "+ eventName);
 120         if(typeof callback !== "function") return console.warn("CanvasAnimateEvent: 事件添加失败,参数错误");
 121         
 122         this._add(ca, eventName);
 123         this._addCA(ca, eventName, callback);
 124         
 125         return ca;
 126     }
 127     
 128     remove(ca, eventName, callback){
 129         if(CanvasAnimateEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasAnimateEvent: 参数错误 "+ eventName);
 130         if(typeof callback !== "function") return console.warn("CanvasAnimateEvent: 事件添加失败,参数错误");
 131         if(this._running !== eventName){
 132             this._remove(ca, eventName);
 133             this._removeCA(ca, eventName, callback);
 134         }
 135 
 136         else this._delList.push(ca, eventName, callback);
 137 
 138         return ca;
 139     }
 140 
 141     disposeEvent(eventName){
 142         if(!this.domElement) return this;
 143         
 144         if(eventName === "over" || eventName === "out"){
 145 
 146             if(typeof this["_"+eventName] === "function"){
 147                 this.domElement.removeEventListener(CanvasAnimateEvent.canvasEventsList[eventName], this["_"+eventName]);
 148                 delete this["_"+eventName];
 149             }
 150 
 151         }
 152 
 153         else{
 154 
 155             if(typeof this["_over"] === "function"){
 156                 this.domElement.removeEventListener(CanvasAnimateEvent.canvasEventsList["over"], this["_over"]);
 157                 delete this["_over"];
 158             }
 159 
 160             if(typeof this["_out"] === "function"){
 161                 this.domElement.removeEventListener(CanvasAnimateEvent.canvasEventsList["out"], this["_out"]);
 162                 delete this["_out"];
 163             }
 164 
 165         }
 166 
 167         return this;
 168     }
 169     
 170     clear(ca, eventName){
 171         if(eventName === undefined){
 172             var k; for(k in this._eventList){
 173                 this._remove(ca, k);
 174             }
 175             
 176             if(ca._eventList !== undefined) delete ca._eventList; //CanvasAnimateEvent.bind(ca, true);
 177             
 178         }
 179 
 180         else if(CanvasAnimateEvent.canvasEventsList[eventName] !== undefined){
 181             this._remove(ca, eventName);
 182 
 183             if(ca._eventList !== undefined) ca._eventList[eventName].length = 0;
 184             
 185         }
 186 
 187         return ca;
 188     }
 189 
 190     _addCA(ca, eventName, callback){
 191         if(ca._eventList === undefined) CanvasAnimateEvent.bind(ca);
 192         if(ca._eventList[eventName] === undefined) ca._eventList[eventName] = [];
 193         ca._eventList[eventName].push(callback);
 194 
 195     }
 196 
 197     _removeCA(ca, eventName, callback){
 198         if(ca._eventList !== undefined && ca._eventList[eventName] !== undefined){
 199             for(let k = 0, len = ca._eventList[eventName].length; k < len; k++){
 200                 if(ca._eventList[eventName][k] === callback){
 201                     ca._eventList[eventName].splice(k, 1);
 202                     break;
 203                 }
 204             }
 205         }
 206 
 207     }
 208 
 209     _add(ca, eventName){
 210         if(this._eventList[eventName] === undefined){
 211             this._eventList[eventName] = [];
 212             this._createEvent(eventName);
 213 
 214         }
 215 
 216         if(this._eventList[eventName].includes(ca) === false) this._eventList[eventName].push(ca);
 217 
 218     }
 219 
 220     _remove(ca, eventName){
 221         if(this._eventList[eventName] !== undefined){
 222             let key = this._eventList[eventName].indexOf(ca);
 223             if(key !== -1) this._eventList[eventName].splice(key, 1);
 224             if(key === 0){
 225                 if(eventName == "over" || eventName === "out") this.disposeEvent(eventName);
 226                 else this.domElement[CanvasAnimateEvent.canvasEventsList[eventName]] = null;
 227                 delete this._eventList[eventName];
 228             }
 229 
 230         }
 231 
 232     }
 233 
 234     _createEvent(evn){
 235         var k, len, ca, arr, tar = null, oldTar = null, _run = null, _box = this._box, _scale = this._scale;
 236 
 237         const run = event => {
 238             len = this["_eventList"][evn].length;
 239             _box.set(this.box.x, this.box.y, this.box.w * _scale.x, this.box.h * _scale.y);
 240             
 241             if(len !== 0 && _box["containsPoint"](event["pageX"], event["pageY"]) === true){
 242                 tar = null;
 243                 for(k = 0; k < len; k++){
 244                     ca = this["_eventList"][evn][k];
 245                     _box.set(ca["box"]["x"] * _scale["x"], ca["box"]["y"] * _scale["y"], ca["box"]["w"] * _scale["x"], ca["box"]["h"] * _scale["y"]);
 246                     
 247                     if(ca["visible"] === true && _box["containsPoint"](event["pageX"] - this["box"]["x"], event["pageY"] - this["box"]["y"]) === true){
 248                         
 249                         if(tar === null || tar["index"] < ca["index"]) tar = ca;
 250                         
 251                     }
 252     
 253                 }
 254                 
 255                 if(_run !== null) _run();
 256                 if(tar !== null){
 257                     this._running = evn;
 258                     arr = tar["_eventList"][evn]; 
 259                     len = arr.length;
 260                     for(k = 0; k < len; k++) arr[k](event, tar);
 261                     tar = null;
 262 
 263                     len = this._delList.length;
 264                     for(k = 0; k < len; k += 3){
 265                         this._remove(this._delList[k], this._delList[k+1]);
 266                         this._removeCA(this._delList[k], this._delList[k+1], this._delList[k+2]);
 267                     }
 268                     this._running = "";
 269                     this._delList.length = 0;
 270                 }
 271 
 272             }
 273             
 274         }
 275 
 276         if(evn == "over" || evn === "out"){
 277             this.domElement.addEventListener(CanvasAnimateEvent.canvasEventsList[evn], run);
 278             this["_"+evn] = run;
 279             if(evn === "over"){
 280                 _run = ()=>{
 281                     if(tar !== null){
 282                         if(oldTar !== null){
 283                             if(oldTar !== tar) oldTar = tar;
 284                             else tar = null;
 285                         }
 286                         else oldTar = tar;
 287                     }
 288                     else if(oldTar !== null) oldTar = null;
 289     
 290                 }
 291 
 292             }
 293 
 294             else{
 295                 let _tar = null;
 296                 _run = ()=>{
 297                     if(tar !== null){
 298                         if(oldTar !== null){
 299                             if(oldTar !== tar){
 300                                 _tar = tar;
 301                                 tar = oldTar;
 302                                 oldTar = _tar;
 303                             }
 304                             else tar = null;
 305                         }
 306                         else{
 307                             oldTar = tar;
 308                             tar = null;
 309                         }
 310                     }
 311                     else if(oldTar !== null){
 312                         tar = oldTar;
 313                         oldTar = null;
 314                     }
 315     
 316                 }
 317 
 318             }
 319 
 320             /* _run = ()=>{
 321                 if(tar !== null){
 322                     if(oldTar !== null){
 323 
 324                         if(oldTar !== tar){
 325                             if(evn === "over") oldTar = tar;
 326                             else{
 327                                 let _tar = tar;
 328                                 tar = oldTar;
 329                                 oldTar = _tar;
 330                             }
 331                         }
 332 
 333                         else tar = null;
 334 
 335                     }
 336 
 337                     else{
 338                         oldTar = tar;
 339                         if(evn === "out") tar = null;
 340                         
 341                     }
 342                     
 343                 }
 344 
 345                 else{
 346                     if(oldTar !== null){
 347                         if(evn === "out") tar = oldTar;
 348                         oldTar = null;
 349                     }
 350                     
 351                 }
 352 
 353             } */
 354             
 355         }
 356 
 357         else this.domElement[CanvasAnimateEvent.canvasEventsList[evn]] = run;
 358 
 359     }
 360 
 361 }
 362 
 363 
 364 
 365 
 366 /* CanvasAnimateRender (渲染 CanvasAnimate)
 367 注意:
 368     this.box = new Box();                 //本类绘制时用这box检测 (ca的box是否与这个box相交, 如果相交才有可能绘制这个ca)
 369     this.domElementRect = new Box();     //CanvasAnimateEvent 用这个box检测 (鼠标的位置是否在这个box范围内, 如果在才有可能触发这个ca的事件)
 370 
 371 parameter: 
 372     option = {
 373         canvas //默认新的canvas
 374         width, height //默认1
 375         className //canvas的 css 类名 默认 ""
 376         id //
 377     }
 378 
 379 attribute:
 380     list: Array[CanvasAnimate]; //渲染队列
 381     box: Box; //canvas的位置和范围(.x.y是0; .w.h是canvas的宽高, 在渲染时检测ca的box是否与此box相交);
 382     context: CanvasContext;
 383     domElement: Canvas;
 384 
 385 method:
 386     isDraw(ca): Bool; //ca是否满足绘制条件
 387     isCanvasImage(img: Object): Bool; //img是否是canvas可绘制的图片;
 388     add(ca): ca; //添加ca (添加多个: const len = this.list.length; this.list.push(caA, caB); this.initList(len))
 389     remove(ca): ca; //删除ca (删除多个: this.list.splice(i, len); this.initList(i));
 390     updateCanvas(): this; //更新canvas的box, canvas添加到dom树后才有效; (如果你没有用.pos(x, y)和.size(w, h, setElem)设置canvas的位置和宽高的话, 那么你需要调用一次此方法, 否则 事件错误 等异常)
 391     initList(index): this; //初始化ca列表; (如果你没有用.add(ca)添加 或 .remove(cd)删除 那么你需要调用一次此方法, 否则 删除错误, 事件错误 等异常)
 392     pos(x, y): this; //设置canvas的位置和this.box的位置
 393     size(w, h: Number, setElem: Bool): this; //设置this.box的宽高; setElem: 是否同时设置canvas的宽高; 默认true
 394     render(parentElem): this; //绘制一次画布, 并把画布添加到parentElem, parentElem 默认 body, 然后调用一次 this.updateCanvas() 方法;
 395     clear(): undefiend; //清除画布
 396     draw(): undefiend; //绘制画布
 397     redraw(): this; //清除并绘制画布; 一般在动画循环里面调用此方法
 398     clearTarget(ca): undefiend; //清除 ca; 适应静态视图
 399     drawTarget(ca): undefiend; //绘制 ca; 适应静态视图 
 400     computeOverlaps(ca)
 401 
 402     //以下方法即将弃用
 403     shear(ca, canvas, x, y): canvas; //this.canvas 的 ca 范围剪切到 canvas 的 x,y位置; ca默认this; canvas模型新的canvas; x, y默认0;
 404     getData(box)
 405     putData(data, x, y)
 406 
 407     //参数名:
 408     ca: CanvasAnimate; 包括它的子类
 409     box: Box;
 410 
 411 demo:
 412 
 413     //更新ca.box(注意执行顺序, 这很重要):
 414     root.clearTarget(ca);
 415     ca.box.set(10, 50, 100, 100);
 416     root.drawTarget(ca);
 417 
 418     //显示:
 419     ca.visible = false;
 420     root.clearTarget(ca);
 421 
 422     ca.visible = true;
 423     root.drawTarget(ca);
 424 
 425     //或者定义一个空的ca:
 426     const emptyCA = new CanvasAnimate();
 427     ca.visible = true;
 428     emptyCA.box.copy(ca.box);
 429     root.drawTarget(ca);
 430 
 431 以以上方法绘制ca的利与弊:
 432     利: 比.redraw()效率更高; .redraw()每次绘制全部可绘制的ca, 而此方法每次只绘制与ca.box重叠的ca数;
 433     弊: 如果目标ca的box覆盖了整个canvas那么像上面这样绘制反而会比.redraw()慢;
 434 
 435 更多例子: class CanvasAnimateUI 使用此方法更新画布
 436 
 437 */
 438 class CanvasAnimateRender{
 439 
 440     constructor(option = {}){
 441         this.list = [];
 442         this.box = new Box();
 443         this.domElementRect = new Box();
 444         this.context = CanvasAnimateRender.getContext(option.canvas, option.className, option.id);
 445         this.domElement = this.context.canvas;
 446         
 447         //init
 448         //this.size(option.width, option.height)
 449         if(option.width !== undefined) this.domElement.width = option.width;
 450         if(option.height !== undefined) this.domElement.height = option.height;
 451         
 452         this.box.size(this.domElement.width, this.domElement.height);
 453         this.domElementRect.size(this.box.w, this.box.h);
 454 
 455         //this.domElement.style.position = "absolute";
 456         //this.domElement.style.background = "rgb(127,127,127)";
 457         CanvasAnimateRender.setDefaultStyles(this.context);
 458         
 459     }
 460 
 461     isDraw(ca){
 462 
 463         return CanvasAnimateRender.isCA(ca) && ca["visible"] === true && CanvasAnimateRender.isCanvasImage(ca["image"]) && this["box"]["intersectsBox"](ca["box"]);
 464 
 465     }
 466 
 467     isCA(ca){
 468         
 469         return CanvasAnimateRender.isCA(ca);
 470 
 471     }
 472 
 473     isCanvasImage(img){
 474 
 475         return CanvasAnimateRender.isCanvasImage(img);
 476 
 477     }
 478 
 479     updateCanvas(){
 480         //this.box.size(this.domElement.width, this.domElement.height);
 481         const rect = this.domElement.getBoundingClientRect();
 482         if(rect.width !== 0) this.domElementRect.set(rect.x, rect.y, this.box.w, this.box.h);
 483         
 484         return this;
 485     }
 486 
 487     getImageData(box = this.box){
 488         
 489         return this.context.getImageData(box.x, box.y, box.w, box.h);
 490 
 491     }
 492 
 493     putImageData(data, x, y){
 494 
 495         return this.context.putImageData(data, x, y);
 496 
 497     }
 498 
 499     pos(x, y){
 500         this.domElement.style.position = "absolute";
 501         this.domElement.style.left = x + "px";
 502         this.domElement.style.top = y + "px";
 503 
 504         //this.box.pos(0, 0);
 505         const rect = this.domElement.getBoundingClientRect();
 506         this.domElementRect.pos(rect.x, rect.y);
 507 
 508         return this;
 509     }
 510 
 511     size(w, h, setElem){
 512         //重置canvas的宽高时; 系统会自动将其恢复默认状态(其属性); 还会清理整个画布;
 513 
 514         if(typeof w !== "number" || w < 1) w = 1;
 515         if(typeof h !== "number" || h < 1) h = 1;
 516         //setElem = typeof setElem === "boolean" ? setElem : true;
 517 
 518         if(setElem !== false){
 519             this.domElement.width = w;
 520             this.domElement.height = h;
 521             CanvasAnimateRender.setDefaultStyles(this.context);
 522         }
 523 
 524         this.box.size(w, h);
 525         this.domElementRect.size(w, h);
 526         
 527         return this;
 528     }
 529 
 530     add(ca){
 531         if(this.list.includes(ca) === false){
 532             const len = this.list.length;
 533             
 534             if(this.list[ca.index] === undefined){
 535                 ca.index = len;
 536                 this.list.push(ca);
 537 
 538             }
 539 
 540             else{
 541                 const arr = this.list.splice(ca.index);
 542                 this.list.push(ca);
 543                 for(let k = 0, c = arr.length; k < c; k++){
 544                     this.list.push(arr[k]);
 545                     arr[k].index++;
 546                 }
 547 
 548             }
 549 
 550         }
 551         
 552         return ca;
 553     }
 554 
 555     remove(ca){
 556         var i = ca.index;
 557 
 558         if(this.list[i] !== ca) i = this.list.indexOf(ca);
 559 
 560         if(i !== -1){
 561             this.list.splice(i, 1);
 562             for(let k = i, len = this.list.length; k < len; k++) this.list[k].index -= 1;
 563 
 564         }
 565 
 566         return ca;
 567     }
 568 
 569     render(parentElem){
 570         parentElem = parentElem || document.body;
 571         parentElem.appendChild(this.domElement);
 572         this.updateCanvas();
 573         this.redraw();
 574         return this;
 575     }
 576 
 577     initList(i){
 578         if(i === undefined || i < 0) i = 0;
 579         for(let k = i, len = this.list.length; k < len; k++) this.list[k].index = k;
 580         return this;
 581     }
 582 
 583     clear(){
 584 
 585         this['context']['clearRect'](0, 0, this['box']['w'], this['box']['h']);
 586 
 587     }
 588 
 589     _draw(ca){
 590         if(ca["opacity"] === 1) this["context"]["drawImage"](ca["image"], ca["box"]["x"], ca["box"]["y"], ca["box"]["w"], ca["box"]["h"]);
 591 
 592         else if(ca["opacity"] > 0){
 593             this["context"]["globalAlpha"] = ca["opacity"];
 594             this["context"]["drawImage"](ca["image"], ca["box"]["x"], ca["box"]["y"], ca["box"]["w"], ca["box"]["h"]);
 595             this["context"]["globalAlpha"] = 1;
 596         }
 597 
 598     }
 599 
 600     draw(){
 601         const len = this["list"]["length"];
 602         for(let k = 0, ca; k < len; k++){
 603             ca = this["list"][k];
 604             if(this["isDraw"](ca) === true) this["_draw"](ca);
 605         }
 606         
 607     }
 608 
 609     _drawTarget(ca){
 610         const len = this["list"]["length"];
 611         this["context"]["clearRect"](ca["overlap"]["box"]["x"], ca["overlap"]["box"]["y"], ca["overlap"]["box"]["w"], ca["overlap"]["box"]["h"]);
 612 
 613         for(let k = 0; k < len; k++){
 614             ca = this["list"][k];
 615             if(ca["overlap"]["draw"] === true) this._draw(ca);
 616             
 617         }
 618 
 619     }
 620 
 621     computeOverlap(tar){
 622         //1 如果检索到与box相交的ca时, 合并其box执行以下步骤: 
 623         //2 检索 已检索过的 并且 没有相交的ca
 624         //3 如果存在相交的ca就合并box并继续第 2 步; 如果不存在继续第 1 步
 625         if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
 626         const _list = CanvasAnimateRender.emptyArrA, list = CanvasAnimateRender.emptyArrB, len = this.list.length, box = tar["overlap"]["box"]["copy"](tar["box"]);
 627 
 628         for(let k = 0, i = 0, c = 0, _c = 0, a = _list, b = list, loop = false; k < len; k++){
 629             tar = this["list"][k];
 630             if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false};
 631             
 632             if(this.isDraw(tar) === false){
 633                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
 634                 continue;
 635             }
 636 
 637             if(box["intersectsBox"](tar["box"]) === true){
 638                 if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
 639                 box["expand"](tar["box"]);
 640                 loop = true;
 641 
 642                 while(loop === true){
 643                     b["length"] = 0;
 644                     loop = false;
 645                     c = _c;
 646 
 647                     for(i = 0; i < c; i++){
 648                         tar = a[i];
 649 
 650                         if(box["intersectsBox"](tar["box"]) === true){
 651                             if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true;
 652                             box["expand"](tar["box"]);
 653                             loop = true; _c--;
 654                         }
 655 
 656                         else b.push(tar);
 657                         
 658                     }
 659 
 660                     a = a === _list ? list : _list;
 661                     b = b === _list ? list : _list;
 662 
 663                 }
 664                 
 665             }
 666 
 667             else{
 668                 _c++;
 669                 a["push"](tar);
 670                 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false;
 671             }
 672 
 673         }
 674         
 675         _list.length = list.length = 0;
 676 
 677     }
 678 
 679     clearTarget(ca){
 680         this["computeOverlap"](ca);
 681         ca["overlap"]["draw"] = false;
 682         this["_drawTarget"](ca);
 683     
 684     }
 685 
 686     drawTarget(ca){
 687         this["computeOverlap"](ca);
 688         this["_drawTarget"](ca);
 689 
 690     }
 691 
 692     redraw(){
 693         this.clear();
 694         this.draw();
 695 
 696     }
 697 
 698     static emptyArrA = []
 699     static emptyArrB = []
 700     static paramCon = {alpha: true}
 701 
 702     static defaultStyles = {
 703         filter: "none",
 704         globalAlpha: 1,
 705         globalCompositeOperation: "source-over",
 706         imageSmoothingEnabled: true,
 707         miterLimit: 10,
 708         font: "12px SimSun, Songti SC",
 709         textAlign: "left",
 710         textBaseline: "top",
 711         lineCap: "butt",
 712         lineJoin: "miter",
 713         lineDashOffset: 0,
 714         lineWidth: 1,
 715         shadowColor: "rgba(0, 0, 0, 0)",
 716         shadowBlur: 0,
 717         shadowOffsetX: 0,
 718         shadowOffsetY: 0,
 719         fillStyle: "#000000",
 720         strokeStyle: "#ffffff",
 721     }
 722 
 723     static setDefaultStyles(context){
 724         let k = "", styles = CanvasAnimateRender.defaultStyles;
 725         for(k in styles){
 726             if(context[k] !== styles[k]) context[k] = styles[k];
 727         }
 728 
 729     }
 730 
 731     static getContext(canvas, className, id){
 732         if(CanvasAnimateRender.isCanvas(canvas) === false) canvas = document.createElement("canvas");
 733         const context = canvas.getContext("2d", CanvasAnimateRender.paramCon);
 734 
 735         if(typeof className === "string") canvas.className = className;
 736         if(typeof id === "string") canvas.setAttribute('id', id);
 737         
 738         return context;
 739     }
 740 
 741     static downloadImage(func){
 742         const input = document.createElement("input");
 743         input.type = "file";
 744         input.multiple = "multiple";
 745         input.accept = ".png, .jpg, .jpeg, .bmp, .gif";
 746     
 747         input.onchange = e => {
 748             if(e.target.files.length === 0) return;
 749             const fr = new FileReader();
 750             fr.onloadend = e1 => {
 751                 const img = new Image();
 752                 img.onload = () => func(img);
 753                 img.src = e1.target.result;
 754             }
 755 
 756             fr.readAsDataURL(e.target.files[0]);
 757         }
 758         
 759         input.click();
 760     }
 761 
 762     static isCA(ca){
 763         
 764         return UTILS.isObject(ca) && ca.isCanvasAnimate;
 765 
 766     }
 767 
 768     static isCanvasImage(img){ //OffscreenCanvas:ImageBitmap; CSSImageValue:
 769 
 770         return UTILS.isObject(img) && (ImageBitmap["prototype"]["isPrototypeOf"](img) || HTMLImageElement["prototype"]["isPrototypeOf"](img) || HTMLCanvasElement["prototype"]["isPrototypeOf"](img) || CanvasRenderingContext2D["prototype"]["isPrototypeOf"](img) || HTMLVideoElement["prototype"]["isPrototypeOf"](img));
 771         
 772     }
 773 
 774     static isCanvas(canvas){
 775         
 776         return UTILS.isObject(canvas) && HTMLCanvasElement["prototype"]["isPrototypeOf"](canvas);
 777 
 778     }
 779 
 780     static gradientColor(gradient, colors, close){
 781         if(Array.isArray(colors) === true){
 782             const len = colors.length;
 783             for(let k = 0; k < len; k++) gradient.addColorStop(k / len, colors[k]);
 784             if(close === true) gradient.addColorStop(1, colors[0]);
 785         }
 786 
 787     }
 788 
 789     static gradientColorSymme(gradient, colors){ //长度为2: 0: colors[0] , 0.5: colors[1], 1: colors[0]; (如果长度为2除外的偶数则忽略最后一个颜色)
 790         if(Array.isArray(colors) === true){
 791             const len = Math.round(colors.length/2), count = len * 2;
 792             
 793             for(let k = 0; k < len; k++){
 794                 gradient.addColorStop(k / count, colors[k]);
 795             }
 796 
 797             for(let k = len, i = len; k >= 0; k--, i++){
 798                 gradient.addColorStop(i / count, colors[k]);
 799             }
 800             
 801         }
 802 
 803     }
 804 
 805 }
 806 
 807 
 808 
 809 
 810 /* CanvasAnimateRenderScroll 内置了滚动条的 功能和视图
 811 
 812 parameter:
 813     option: Object{
 814         scrollWidth: Number, //滚动条宽 
 815         scrollColor: String, //滚动条背景色
 816         cursorColor: String, //滚动条游标颜色
 817     }
 818 
 819 attribute:
 820     onscroll: Function(pointer event, scroll type);    //用于监听scroll 默认 null
 821 
 822     //以下属性不建议修改
 823     eventDispatcher: CanvasAnimateEvent;
 824     scrollX: CanvasAnimateCustom;
 825     scrollY: CanvasAnimateCustom;
 826     cursorX: CanvasAnimateCustom;
 827     cursorY: CanvasAnimateCustom;
 828 
 829 method:
 830     addEvent(ca, eventName, callback)
 831     removeEvent(ca, eventName, callback)
 832     getScrollLeft(): Number; //返回滚动轴左边的距离
 833     getScrollTop(): Number; //返回滚动轴上边的距离
 834 
 835     setScroll(type, v, offset): this;
 836         type: "top|left"; //滚动条的类型
 837         v: Number|CanvasAnimate;
 838         offset: Number; //偏移值; 默认: 0;
 839     
 840     setScrollWidth(v): this; 
 841         v: Number; //设置滚动条的宽; v不能小于1
 842     
 843 demo:
 844     
 845     const cars = new CanvasAnimateRenderScroll({
 846         width: 300, 
 847         height: 300,
 848         scrollWidth: 10,
 849     }).pos(50, 50),
 850 
 851     cac = cars.add(new CanvasAnimateCustom()).size(150, 150).rect(4).fill("#fff").stroke("blue", 1).pos(40, 40),
 852     cac1 = cars.add(new CanvasAnimate(cac.image)).pos(40, 230),
 853     cac2 = cars.add(new CanvasAnimate(cac.image)).pos(40, 900),
 854     cac3 = cars.add(new CanvasAnimate(cac.image)).pos(900, 66);
 855 
 856     cars.domElement.style = `
 857         position: absolute;
 858         z-index: 999;
 859         top: 50px;
 860         left: 100px;
 861         background: #fff;
 862     `;
 863     
 864     //自定义 scroll style
 865     cars.scrollY.clear().rect().shadow("#ddd", 1, -1, 0).fill("#eee");
 866 
 867     cars.render();
 868     cars.addEvent(cac, "click", () => {
 869         cars.setScroll("top", cac1, -10).redraw();
 870         console.log(cars.getScrollTop()); //220
 871     });
 872 
 873 */
 874 class CanvasAnimateRenderScroll extends CanvasAnimateRender{
 875 
 876     #maxX = 0;
 877     #maxY = 0;
 878     #scrollWidth = 10;
 879     #scrollColor = "#eee";
 880     #cursorColor = "#aaa";
 881 
 882     constructor(option = {}){
 883         super(option);
 884 
 885         if(typeof option.scrollWidth === "number") this.#scrollWidth = option.scrollWidth;
 886         if(option.scrollColor) this.#scrollColor = option.scrollColor;
 887         if(option.cursorColor) this.#cursorColor = option.cursorColor;
 888 
 889         this.eventDispatcher = new CanvasAnimateEvent(this);
 890         this.scrollX = new CanvasAnimateCustom();
 891         this.scrollY = new CanvasAnimateCustom();
 892         this.cursorX = this._bindScroll(new CanvasAnimateCustom());
 893         this.cursorY = this._bindScroll(new CanvasAnimateCustom());
 894         this.wheelCA = new CanvasAnimate();
 895         this.onscroll = null;
 896         
 897         this.cursorBoxX = this.cursorX.box.set(0, this.box.h - this.#scrollWidth, this.box.w - this.#scrollWidth, this.#scrollWidth);
 898         this.cursorBoxY = this.cursorY.box.set(this.box.w - this.#scrollWidth, 0, this.#scrollWidth, this.box.h - this.#scrollWidth);
 899 
 900         this.scrollX.size(this.cursorBoxX.w, this.cursorBoxX.h).rect().fill(this.#scrollColor).box.pos(0, this.cursorBoxX.y);
 901         this.scrollY.size(this.cursorBoxY.w, this.cursorBoxY.h).rect().fill(this.#scrollColor).box.pos(this.cursorBoxY.x, 0);
 902 
 903         //init scroll event
 904         this.scrollX.index = 
 905         this.scrollY.index = 99999;
 906 
 907         this.cursorX.index = 
 908         this.cursorY.index = Infinity;
 909 
 910         var dPos = 0;
 911 
 912         const setTop = (event, top) => {
 913             if(this.setScrollTop(top) === true){
 914                 if(this.onscroll !== null) this.onscroll(event, "y");
 915                 this.redraw();
 916             }
 917 
 918         },
 919 
 920         setLeft = (event, left) => {
 921             if(this.setScrollLeft(left) === true){
 922                 if(this.onscroll !== null) this.onscroll(event, "x");
 923                 this.redraw();
 924             }
 925 
 926         },
 927         
 928         onMoveTop = event => {
 929             setTop(event, event.pageY - this.domElementRect.y - dPos);
 930 
 931         },
 932 
 933         onMoveLeft = event => {
 934             setLeft(event, event.pageX - this.domElementRect.x - dPos);
 935 
 936         },
 937 
 938         onUpTop = event => {
 939             document.body.removeEventListener('pointermove', onMoveTop);
 940             document.body.removeEventListener('pointerup', onUpTop);
 941 
 942             this.domElement.removeEventListener('pointermove', onMoveTop);
 943             this.domElement.removeEventListener('pointerup', onUpTop);
 944 
 945             onMoveTop(event);
 946 
 947         },
 948 
 949         onUpLeft = event => {
 950             document.body.removeEventListener('pointermove', onMoveLeft);
 951             document.body.removeEventListener('pointerup', onUpLeft);
 952 
 953             this.domElement.removeEventListener('pointermove', onMoveLeft);
 954             this.domElement.removeEventListener('pointerup', onUpLeft);
 955 
 956             onMoveLeft(event);
 957 
 958         }
 959 
 960         this.eventDispatcher.add(this.cursorY, "down", event => {
 961             dPos = event.pageY - this.domElementRect.y - this.cursorBoxY.y;
 962             onUpTop(event);
 963 
 964             document.body.addEventListener("pointermove", onMoveTop);
 965             document.body.addEventListener("pointerup", onUpTop);
 966 
 967             this.domElement.addEventListener("pointermove", onMoveTop);
 968             this.domElement.addEventListener("pointerup", onUpTop);
 969 
 970         });
 971 
 972         this.eventDispatcher.add(this.cursorX, "down", event => {
 973             dPos = event.pageX - this.domElementRect.x - this.cursorBoxX.x;
 974             onUpLeft(event);
 975 
 976             document.body.addEventListener("pointermove", onMoveLeft);
 977             document.body.addEventListener("pointerup", onUpLeft);
 978 
 979             this.domElement.addEventListener("pointermove", onMoveLeft);
 980             this.domElement.addEventListener("pointerup", onUpLeft);
 981 
 982         });
 983 
 984         this.eventDispatcher.add(this.scrollY, "down", event => {
 985             dPos = this.cursorBoxY.h / 2;
 986             onUpTop(event);
 987             
 988         });
 989 
 990         this.eventDispatcher.add(this.scrollX, "down", event => {
 991             dPos = this.cursorBoxX.w / 2;
 992             onUpLeft(event);
 993             
 994         });
 995 
 996         this.wheelCA.box.copy(this.box);
 997         this.eventDispatcher.add(this.wheelCA, "wheel", event => {
 998             dPos = event.wheelDelta === 120 ? -1 : 1; //(0 - event.wheelDelta) * 0.1;
 999             if(this.#maxY > this.box.h && this.cursorY.visible === true) setTop(event, this.cursorBoxY.y + dPos * this.box.h / 10);
1000             else if(this.#maxX > this.box.w && this.cursorX.visible === true) setLeft(event, this.cursorBoxX.x + dPos * this.box.w / 10);
1001             
1002         });
1003 
1004     }
1005 
1006     size(w, h, setElem){
1007         super.size(w, h, setElem);
1008         this.setScrollWidth(this.#scrollWidth);
1009         this.wheelCA.box.copy(this.box);
1010 
1011         return this;
1012     }
1013 
1014     add(ca){
1015         this._bind(ca);
1016         return super.add(ca);
1017     }
1018 
1019     remove(ca){
1020         this._unBind(ca);
1021         return super.remove(ca);
1022     }
1023 
1024     initList(i){
1025         if(i === undefined || i < 0) i = 0;
1026         for(let k = i, len = this.list.length; k < len; k++) this._bind(this.list[k]);
1027 
1028         return super.initList(i);
1029     }
1030 
1031     draw(){
1032         super.draw();
1033         this._drawScroll();
1034 
1035     }
1036 
1037     _drawTarget(ca){
1038         super._drawTarget(ca);
1039         this._drawScroll();
1040 
1041     }
1042 
1043 
1044     //new method
1045     addEvent(ca, eventName, callback){
1046 
1047         return this.eventDispatcher.add(ca, eventName, callback);
1048         
1049     }
1050 
1051     removeEvent(ca, eventName, callback){
1052 
1053         return this.eventDispatcher.remove(ca, eventName, callback);
1054 
1055     }
1056 
1057     //获取scroll的场景位置
1058     getScrollLeft(){
1059         if(this.#maxX <= this.box.w) return 0;
1060         return this.cursorBoxX.x / (this.box.w - this.cursorBoxY.w) * this.#maxX;
1061     }
1062 
1063     getScrollTop(){
1064         if(this.#maxY <= this.box.h) return 0;
1065         return this.cursorBoxY.y / (this.box.h - this.cursorBoxX.h) * this.#maxY;
1066     }
1067 
1068     //通过场景位置(ca.left|ca.top)设置scroll
1069     setScroll(type, v, offset){ //type = "left" | "top"; v = ca | number
1070         if(UTILS.isNumber(offset)  === false) offset = 0;
1071 
1072         if(this.isCA(v) === true) v = v[type];
1073 
1074         else if(UTILS.isNumber(v) === false) return this;
1075 
1076         if(type === "top"){
1077             if(this.#maxY <= this.box.h) this.setScrollTop(0);
1078             else this.setScrollTop((v + offset) / this.#maxY * (this.box.h - this.cursorBoxX.h));
1079             
1080         }
1081 
1082         else if(type === "left"){
1083             if(this.#maxX <= this.box.w) this.setScrollLeft(0);
1084             else this.setScrollLeft((v + offset) / this.#maxX * (this.box.w - this.cursorBoxY.w));
1085             
1086         }
1087 
1088         return this;
1089     }
1090 
1091     setScrollWidth(v){
1092         this.#scrollWidth = 
1093         this.cursorBoxX.h = 
1094         this.cursorBoxY.w = v;
1095 
1096         this.cursorBoxX.y = this.box.h - this.cursorBoxX.h;
1097         this.cursorBoxY.x = this.box.w - this.cursorBoxY.w;
1098 
1099         this.scrollX.size(this.box.w - this.cursorBoxY.w, this.cursorBoxX.h).rect().fill(this.#scrollColor).box.pos(0, this.cursorBoxX.y);
1100         this.scrollY.size(this.cursorBoxY.w, this.box.h - this.cursorBoxX.h).rect().fill(this.#scrollColor).box.pos(this.cursorBoxY.x, 0);
1101 
1102         v = this.#maxX;
1103         this.#maxX = 0;
1104         this._updateMaxX(v);
1105 
1106         v = this.#maxY;
1107         this.#maxY = 0;
1108         this._updateMaxY(v);
1109 
1110         v = this.cursorBoxX.x;
1111         this.cursorBoxX.x = NaN;
1112         this.setScrollLeft(v);
1113 
1114         v = this.cursorBoxY.y;
1115         this.cursorBoxY.y = NaN;
1116         this.setScrollTop(v);
1117 
1118         return this;
1119     }
1120 
1121 
1122     //以下方法限内部使用
1123 
1124     //通过视口位置设置scroll
1125     setScrollLeft(x){
1126         if(x < 0) x = 0;
1127         else{
1128             let _x = this.box.w - this.cursorBoxX.w - this.cursorBoxY.w;
1129             if(x > _x) x = _x;
1130         }
1131 
1132         if(this.cursorBoxX.x !== x){
1133             this.cursorBoxX.x = x;
1134             x = this.getScrollLeft();
1135             for(let k = 0, len = this.list.length; k < len; k++) this.list[k]._upateScrollX(x);
1136             return true;
1137         }
1138 
1139     }
1140 
1141     setScrollTop(y){
1142         if(y < 0) y = 0;
1143         else{
1144             let _y = this.box.h - this.cursorBoxY.h - this.cursorBoxX.h;
1145             if(y > _y) y = _y;
1146         }
1147         
1148         if(this.cursorBoxY.y !== y){
1149             this.cursorBoxY.y = y;
1150             y = this.getScrollTop();
1151             for(let k = 0, len = this.list.length; k < len; k++) this.list[k]._upateScrollY(y);
1152             return true;
1153         }
1154 
1155     }
1156 
1157     _bind(ca){
1158         var _x = ca.box.x, _y = ca.box.y, _w = ca.box.w, _h = ca.box.h, 
1159         _left = ca.box.x, _top = ca.box.y;
1160 
1161         Object.defineProperties(ca.box, {
1162             x: {
1163                 get: () => {return _x;},
1164                 set: v => {
1165                     _left = v;
1166                     _x = v - this.getScrollLeft();
1167                     this._updateMaxX(v + _w);
1168                 }
1169             },
1170             
1171             y: {
1172                 get: () => {return _y;},
1173                 set: v => {
1174                     _top = v;
1175                     _y = v - this.getScrollTop();
1176                     this._updateMaxY(v + _h);
1177                 }
1178             },
1179 
1180             w: {
1181                 get: () => {return _w;},
1182                 set: v => {
1183                     _w = v;
1184                     this._updateMaxX(_left + v);
1185                 }
1186             },
1187             
1188             h: {
1189                 get: () => {return _h;},
1190                 set: v => {
1191                     _h = v;
1192                     this._updateMaxY(_top + v);
1193                 }
1194             },
1195         });
1196 
1197         Object.defineProperties(ca, {
1198             left: {
1199                 get: function (){return _left;}
1200             },
1201             
1202             top: {
1203                 get: function (){return _top;}
1204             },
1205 
1206             width: {
1207                 get: function (){return _w;}
1208             },
1209 
1210             height: {
1211                 get: function (){return _h;}
1212             },
1213 
1214             _upateScrollX: {
1215                 value: scrollLeft => _x = _left - scrollLeft
1216             },
1217 
1218             _upateScrollY: {
1219                 value: scrollTop => _y = _top - scrollTop
1220             },
1221 
1222         });
1223 
1224         this._updateMaxX(_left + _w);
1225         this._updateMaxY(_top + _h);
1226         ca = undefined;
1227 
1228     }
1229 
1230     _bindScroll(cursor){
1231         var _xw = 0, _xh = 0;
1232         Object.defineProperties(cursor.box, {
1233             w: {
1234                 get: () => {return _xw;},
1235                 set: v => {
1236                     if(_xw === v) return;
1237                     _xw = 
1238                     cursor.image.width = v;
1239                     cursor.rect().fill(this.#cursorColor);
1240                     
1241                 }
1242             },
1243             
1244             h: {
1245                 get: () => {return _xh;},
1246                 set: v => {
1247                     if(_xh === v) return;
1248                     _xh = 
1249                     cursor.image.height = v;
1250                     cursor.rect().fill(this.#cursorColor);
1251 
1252                 }
1253             },
1254         });
1255 
1256         return cursor;
1257     }
1258 
1259     _unBind(ca){
1260         var v = ca.left;
1261         delete ca.box.x;
1262         ca.box.x = v;
1263 
1264         v = ca.top;
1265         delete ca.box.y;
1266         ca.box.y = v;
1267 
1268         v = ca.box.w;
1269         delete ca.box.w;
1270         ca.box.w = v;
1271 
1272         v = ca.box.h;
1273         delete ca.box.h;
1274         ca.box.h = v;
1275 
1276         delete ca._upateScrollX;
1277         delete ca._upateScrollY;
1278 
1279         //如果maxBox小于canvasBox则只重置maxBox
1280         if(this.#maxX <= this.box.w && this.#maxY <= this.box.h) return this._resetScroll();
1281         
1282         //当前的ca的box如果小于maxBox则不更新maxBox
1283         if(ca.width + ca.left < this.#maxX && ca.height + ca.top < this.#maxY) return;
1284         
1285         //更新maxBox
1286         this._resetScroll();
1287         for(let k = 0, len = this.list.length, _ca; k < len; k++){
1288             _ca = this.list[k];
1289             if(ca === _ca) continue;
1290 
1291             v = _ca.width + _ca.left;
1292             if(this.#maxX < v) this.#maxX = v;
1293 
1294             v = _ca.height + _ca.top;
1295             if(this.#maxY < v) this.#maxY = v;
1296 
1297         }
1298 
1299         //更新scrollBox
1300         v = this.box.w - this.cursorBoxY.w;
1301         if(this.box.w < this.#maxX) this.cursorBoxX.w = v / this.#maxX * v;
1302         else this.cursorBoxX.w = v;
1303 
1304         v = this.box.h - this.cursorBoxX.h;
1305         if(this.box.h < this.#maxY) this.cursorBoxY.h = v / this.#maxY * v;
1306         else this.cursorBoxY.h = v;
1307 
1308         this.setScrollLeft(this.cursorBoxX.x);
1309         this.setScrollTop(this.cursorBoxY.y);
1310 
1311     }
1312 
1313     _updateMaxX(mx){
1314         if(this.#maxX < mx){
1315             let w = this.box.w - this.cursorBoxY.w;
1316             if(this.box.w < mx) this.cursorBoxX.w = w / mx * w;
1317             else this.cursorBoxX.w = w;
1318             this.#maxX = mx;
1319             
1320         }
1321 
1322     }
1323 
1324     _updateMaxY(my){
1325         if(this.#maxY < my){
1326             let h = this.box.h - this.cursorBoxX.h;
1327             if(this.box.h < my) this.cursorBoxY.h = h / my * h;
1328             else this.cursorBoxY.h = h;
1329             this.#maxY = my;
1330             
1331         }
1332     }
1333 
1334     _resetScroll(){
1335         this.#maxX = 0;
1336         this.#maxY = 0;
1337         this.cursorBoxX.w = this.box.w - this.cursorBoxY.w;
1338         this.cursorBoxY.h = this.box.h - this.cursorBoxX.h;
1339 
1340     }
1341 
1342     _drawScroll(){
1343         if(super.isDraw(this.scrollX) === true) super._draw(this.scrollX);
1344         if(super.isDraw(this.scrollY) === true) super._draw(this.scrollY);
1345         if(super.isDraw(this.cursorX) === true) super._draw(this.cursorX);
1346         if(super.isDraw(this.cursorY) === true) super._draw(this.cursorY);
1347 
1348     }
1349 
1350 }
1351 
1352 
1353 
1354 
1355 /* CanvasAnimateOrdered 
1356 
1357 */
1358 class CanvasAnimateOrdered extends CanvasAnimateRenderScroll{
1359 
1360     constructor(option = {}){
1361         super(option);
1362 
1363     }
1364 
1365 }
1366 
1367 
1368 
1369 
1370 
1371 /* CanvasAnimate 
1372 parameter: 
1373     image
1374 
1375 attribute:
1376     opacity: Float; //透明度; 值0至1之间; 默认1;
1377     visible: Boolean; //默认true; 完全隐藏(既不绘制视图, 也不触发绑定的事件)
1378 
1379     //以下属性不建议直接修改
1380     box: Box; //.x.y是CanvasAnimateRender画布中的位置; .w.h是 this.image 的宽高
1381     circle: Circle; //边界圆, 默认 null;
1382     image: CanvasImageData; //默认 null; this.setImage(img) 修改
1383     overlap: Object; //CanvasAnimateRender.computeOverlaps(ca) 方法更新
1384     index: Integer; //CanvasAnimateRender.index(ca) 修改
1385 
1386     //只读
1387     width, height, top, left: Number; 
1388 
1389 method:
1390     scale(sx, sy: Number, isCenter: Bool): this;    //缩放
1391     sx, sy:        小于1缩放, 大于1放大, 等于1什么都不会做; 默认 1;
1392     isCenter:    缩放中心点是否居中; 默认 true;
1393 
1394     pos(x, y: Number): this;                        //设置this.box的x,y (既root的画布中位置)
1395     load(src: String, func: Function): Image;         //通过src加载图片完成后设置this.box .w.h
1396     setImage(img: ImageData): this;                 //设置this.image; this.box .w.h;
1397     computeCircle(): undefined;                     // 相对于.box计算.circle; 如果为null则创建一个新的 Circle
1398 
1399 demo:
1400     const img = new CanvasAnimateCustom().start(150, 150).rect(5)
1401         .fill("#000000") //背景
1402         .text("value", "red", 18, "center", "center") //文字
1403         .stroke("#ffffff") //边框
1404         .image;
1405         
1406     const ca = root.add(new CanvasAnimate().pos(10, 10).setImage(img));
1407     //root.redraw();
1408     //root.drawTarget(ca); //如果root已经render过了使用此方法绘制单独的ca, 比root.redraw()快2-3倍
1409     root.render();
1410 
1411 */
1412 class CanvasAnimate{
1413 
1414     constructor(image){
1415         this.opacity = 1;
1416         this.visible = true;
1417     
1418         //以下属性不建议直接修改
1419         this.box = new Box();
1420         this.circle = null;
1421         this.overlap = null;
1422         this.index = -1;
1423         
1424         //if(CanvasAnimateRender.isCA(image)) image = image.image;
1425         if(CanvasAnimateRender.isCanvasImage(image)){
1426             this.image = image;
1427             this.box.size(image.width, image.height);
1428         }
1429 
1430         else this.image = null;
1431 
1432     }
1433 
1434     pos(x, y){
1435         this.box.x = x;
1436         this.box.y = y;
1437         return this;
1438     }
1439 
1440     load(src, func){
1441         var img = new Image();
1442         
1443         img.onload = () => {
1444             this.box.size(img.width, img.height);
1445             if(typeof func === "function") func(img);
1446             func = img = null;
1447         }
1448 
1449         img.src = src;
1450         this.image = img;
1451         return img;
1452     }
1453 
1454     setImage(img = null){
1455         if(img !== null) this.box.size(img.width, img.height);
1456         this.image = img;
1457         return this;
1458     }
1459 
1460     computeCircle(inner){
1461         if(this.circle === null) this.circle = new Circle();
1462         this.circle.setFromBox(this.box, inner);
1463         return this;
1464     }
1465 
1466     get left(){return this.box.x;}
1467     get top(){return this.box.y;}
1468     get width(){return this.box.w;}
1469     get height(){return this.box.h;}
1470 
1471 }
1472 
1473 Object.defineProperties(CanvasAnimate.prototype, {
1474 
1475     isCanvasAnimate: {
1476         configurable: false,
1477         enumerable: false,
1478         writable: false,
1479         value: true,
1480     }
1481 
1482 });
1483 
1484 
1485 
1486 
1487 /* CanvasAnimateBox
1488     clear(box: Box): this; //box 如果未定义则清理整个画布
1489     shear(): HTMLCanvasElement; //this.image 拷贝到一个新的画布并返回
1490     size(w, h: Number): this; //设置画布宽高, 并初始化画布上下文
1491     shadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY)
1492     rotateCenter(x, y) //设置旋转中心点
1493     transparentBG(size = 5, r, lineWidth) //默认透明背景
1494 
1495     rotate: Number; //旋转画布;
1496 
1497 
1498 demo:
1499     const image = new CanvasAnimateBox().size(100, 200).rect(10).fill('blue').image,
1500     box = new Box(0, 0, image.width, image.height),
1501     size = new Circle().setFromBox(box, false).r2; //获取旋转占用最大宽高
1502 
1503     const imgRed = new CanvasAnimateBox().size(size, size);
1504     imgRed.drawImage(image, box.center(imgRed.box));
1505     
1506     //render
1507     car.add(imgRed).pos(10, 10);
1508     car.render();
1509 
1510     //rotate
1511     new Timer(()=>{
1512         imgRed.rotate += 0.1;
1513         imgRed.drawImage(image, box);
1514         car.redraw();
1515     }, 1000, 60);
1516 
1517 */
1518 class CanvasAnimateBox extends CanvasAnimate{
1519 
1520     constructor(canvas){
1521         super(canvas);
1522         this._rotate = null;
1523 
1524         if(canvas !== this.image && CanvasAnimateRender.isCanvasImage(canvas)) this.context.drawImage(canvas, 0, 0);
1525 
1526     }
1527 
1528     clear(box){
1529         if(box === undefined) this.context.clearRect(0, 0, this.width, this.height);
1530         else this.context.clearRect(box.x, box.y, box.w, box.h);
1531         return this;
1532     }
1533 
1534     isEmpty(){
1535         return this.box.w < 1 || this.box.h < 1;
1536     }
1537 
1538     shear(box){
1539         const canvas = document.createElement("canvas");
1540 
1541         if(box === undefined){
1542             canvas.width = this.width;
1543             canvas.height = this.height;
1544             canvas.getContext("2d").drawImage(this.image, 0, 0);
1545         }
1546 
1547         else{
1548             canvas.width = box.w;
1549             canvas.height = box.h;
1550             canvas.getContext("2d").drawImage(this.image, box.x, box.y);
1551         }
1552         
1553         return canvas;
1554     }
1555 
1556     size(w, h, defaultStyles = false){
1557         w = (UTILS.isNumber(w) === true && w >= 1) ? w : this.width;
1558         h = (UTILS.isNumber(h) === true && h >= 1) ? h : this.height;
1559         this.image.width = this.box.w = w;
1560         this.image.height = this.box.h = h;
1561         if(defaultStyles === true) CanvasAnimateRender.setDefaultStyles(this.context);
1562         
1563         return this;
1564     }
1565 
1566     shadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY){
1567         shadowColor = shadowColor || "rgba(0,0,0,0)";
1568         const con = this.context;
1569         if(con.shadowColor !== shadowColor) con.shadowColor = shadowColor;
1570         if(con.shadowBlur !== shadowBlur && typeof shadowBlur === "number") con.shadowBlur = shadowBlur;
1571         if(con.shadowOffsetX !== shadowOffsetX && typeof shadowOffsetX === "number") con.shadowOffsetX = shadowOffsetX;
1572         if(con.shadowOffsetY !== shadowOffsetY && typeof shadowOffsetY === "number") con.shadowOffsetY = shadowOffsetY;
1573         return this;
1574     }
1575 
1576     textWidth(text, font){
1577         if(this.#fontSize !== font){
1578             this.#fontSize = font;
1579             this.context.font = font+"px SimSun, Songti SC";
1580         }
1581         
1582         return this.context.measureText(text).width;
1583     }
1584 
1585     rotateCenter(x, y){
1586         if(UTILS.isNumber(x) === false) x = this.width / 2;
1587         if(UTILS.isNumber(y) === false) y = this.height / 2;
1588 
1589         if(this._rotate !== null){
1590             if(this._rotate.x !== x) this._rotate.x = x;
1591             if(this._rotate.y !== y) this._rotate.y = y;
1592             if(this._rotate.a !== 0){
1593                 this.context.translate(this._rotate.x, this._rotate.y);
1594                 this.context.rotate(-this._rotate.a);
1595                 this.context.translate(-this._rotate.x, -this._rotate.y);
1596                 //this.context.resetTransform();
1597                 this._rotate.a = 0;
1598             }
1599         }
1600 
1601         else this._rotate = {a: 0, x: x, y: y}
1602         
1603         return this;
1604     }
1605     
1606 
1607     //定义路径
1608     line(x, y, x1, y1){ //定义线段路径
1609         this.context.beginPath();
1610         this.context.moveTo(x, y);
1611         this.context.lineTo(x1, y1);
1612         
1613         return this;
1614     }
1615 
1616     path(arr, close = false, offsetX = 0, offsetY = 0){ //定义连续线条路径
1617         var len = arr.length;
1618 
1619         if(len >= 2){
1620             if(len % 2 !== 0) len -= 1;
1621             const con = this.context;
1622             con.beginPath();
1623             arr[0] += offsetX;
1624             arr[1] += offsetY;
1625             con.moveTo(arr[0], arr[1]);
1626             for(let k = 2; k < len; k+=2){
1627                 arr[k] += offsetX;
1628                 arr[k+1] += offsetY;
1629                 con.lineTo(arr[k], arr[k+1]);
1630             }
1631             if(close === true){
1632                 con.closePath();
1633                 if(arr[len -2] !== arr[0] || arr[len -1] !== arr[1]) arr.push(arr[0], arr[1]);
1634             }
1635         }
1636 
1637         return this;
1638     }
1639 
1640     _rect(r, box, lineWidth){ //定义边框路径
1641         const con = this.context;
1642         if(con.lineWidth !== lineWidth && lineWidth !== undefined) con.lineWidth = lineWidth;
1643         
1644         const s = con.lineWidth;
1645 
1646         if(UTILS.isObject(box) === false){
1647             var x = s / 2,
1648             y = s / 2,
1649             w = this.width - s,
1650             h = this.height - s;
1651         }
1652         
1653         else{
1654             var x = s / 2 + box.x,
1655             y = s / 2 + box.y,
1656             w = box.w - s,
1657             h = box.h - s;
1658         }
1659 
1660         if(UTILS.isNumber(r) === false || r <= 0){
1661             con.rect(x, y, w, h);
1662             return;
1663         }
1664 
1665         const _x = x + r, 
1666         _y = y + r, 
1667         mx = x + w, 
1668         my = y + h, 
1669         _mx = mx - r, 
1670         _my = my - r;
1671         
1672         //上
1673         con.moveTo(_x, y);
1674         con.lineTo(_mx, y);
1675         con.arcTo(mx, y, mx, _y, r);
1676 
1677         //右
1678         con.lineTo(mx, _y);
1679         con.lineTo(mx, _my);
1680         con.arcTo(mx, my, _x, my, r);
1681 
1682         //下
1683         con.lineTo(_x, my);
1684         con.lineTo(_mx, my);
1685         con.arcTo(x, my, x, _my, r);
1686 
1687         //左
1688         con.lineTo(x, _y);
1689         con.lineTo(x, _my);
1690         con.arcTo(x, y, _x, y, r);
1691 
1692     }
1693 
1694     rect(r, box, lineWidth){ //定义边框路径
1695         this.context.beginPath();
1696         this._rect(r, box, lineWidth);
1697 
1698         return this;
1699     }
1700 
1701     arc(circle){ //定义圆形路径;
1702         if(circle === undefined){
1703             this.computeCircle();
1704             circle = this.circle;
1705 
1706         }
1707         this.context.beginPath();
1708         this.context.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2, false);
1709 
1710         return this;
1711     }
1712 
1713 
1714     //绘制路径
1715     stroke(color, lineWidth){ //绘制路径
1716         if(color && this.#strokeColor !== color){
1717             this.#strokeColor = color;
1718             this.context.strokeStyle = color;
1719         }
1720         if(this.context.lineWidth !== lineWidth && lineWidth !== undefined) this.context.lineWidth = lineWidth;
1721         this.context.stroke();
1722         
1723         return this;
1724     }
1725 
1726     fill(color){ //填充路径 evenodd
1727         if(color && this.#fillColor !== color){
1728             this.#fillColor = color;
1729             this.context.fillStyle = color;
1730         }
1731 
1732         this.context.fill();
1733 
1734         return this;
1735     }
1736 
1737     text(value, color, box){ //填充文字
1738         if(color && this.#fillColor !== color){
1739             this.#fillColor = color;
1740             this.context.fillStyle = color;
1741         }
1742 
1743         if(box === undefined) this.context.fillText(value, (this.width - this.textWidth(value, this.#fontSize))/2, (this.height - this.#fontSize) / 2);
1744         
1745         else{
1746             if(this.#fontSize !== box.h){
1747                 this.#fontSize = box.h;
1748                 this.context.font = box.h+"px SimSun, Songti SC";
1749             }
1750             
1751             this.context.fillText(value, box.x, box.y, box.w);
1752         }
1753         
1754         return this;
1755     }
1756 
1757     drawImage(img, x, y){
1758         if(y !== undefined) this.context.drawImage(img, x, y);
1759         else this.context.drawImage(img, x.x, x.y, x.w, x.h);
1760 
1761     }
1762 
1763     transparentBG(size = 5, r, box, lineWidth){
1764         const con = this.context,
1765         c1 = "rgb(255,255,255)", c2 = "rgb(127,127,127)", 
1766         lenX = Math.ceil(this.width/size), lenY = Math.ceil(this.height/size);
1767         
1768         con.save();
1769         con.beginPath();
1770         this._rect(r, box, lineWidth);
1771         con.clip();
1772 
1773         for(let ix = 0, iy = 0; ix < lenX; ix++){
1774 
1775             for(iy = 0; iy < lenY; iy++){
1776 
1777                 con.fillStyle = (ix + iy) % 2 === 0 ? c1 : c2;
1778                 con.fillRect(ix * size, iy * size, size, size);
1779                 
1780             }
1781 
1782         }
1783 
1784         con.restore();
1785         return this;
1786     }
1787 
1788 
1789     //
1790     get image(){
1791         return this.context.canvas;
1792     }
1793 
1794     set image(v){
1795 
1796         this.context = CanvasAnimateRender.getContext(v);
1797 
1798     }
1799 
1800     get rotate(){
1801         if(this._rotate === null) this._rotate = {a: 0, x: this.width / 2, y: this.height / 2}
1802         return this._rotate.a;
1803     }
1804 
1805     set rotate(a){
1806         if(this._rotate === null) this._rotate = {a: 0, x: this.width / 2, y: this.height / 2}
1807         if(this._rotate.a !== a){
1808             this.context.clearRect(-1, -1, this.width+2, this.height+2);
1809 
1810             this.context.translate(this._rotate.x, this._rotate.y);
1811             this.context.rotate(-this._rotate.a);
1812             this.context.rotate(a);
1813             this.context.translate(-this._rotate.x, -this._rotate.y);
1814             
1815             this._rotate.a = a;
1816 
1817         }
1818 
1819     }
1820 
1821     #fontSize = parseFloat(CanvasAnimateRender.defaultStyles.font);
1822     #fillColor = CanvasAnimateRender.defaultStyles.fillStyle;
1823     #strokeColor = CanvasAnimateRender.defaultStyles.strokeStyle;
1824 
1825 }
1826 
1827 
1828 
1829 
1830 /* CanvasAnimateCustom (this.image总是是一个canvas; 可以使用原生api绘制这个canvas)
1831 
1832 注意: 为了解决线模糊问题, 此类在绘制线时会做一些特殊处理: 线宽四舍五入, 线坐标点向上取整 (bug: 在用线拼凑形状时导致偏差)
1833 
1834 parameter: 
1835     canvas: Canvas; //可选; 默认新的 Canvas
1836 
1837 attribute: 
1838     image: Canvas;
1839     context: CanvasContext;             //你可以在这个 context 中自由绘制
1840     _path: Object{t: String, v: Array}     //当前定义的路径
1841     _rotate: Object{x,y,a}                //当前旋转
1842 
1843 method:
1844     size(w, h): this;                                  //设置画布宽高 (宽高变动时自动清除画布)
1845     clear(): this;                                      //清除画布
1846     unRotate(reset: Bool): this;                    //重置旋转; reset 重置旋转后是否将弧度设为零
1847     rotate(a, x, y, recover, updatePath): this;     //a为弧度, 默认0; 以画布中x,y为中心点旋转定义的路径, 默认.box的中心点;
1848     shadow(color, blur, offsetX, offsetY): this;    //阴影
1849     shear(x, y, w, h, canvas, dx, dy): canvas;         //把this.image的像素克隆到canvas上;
1850         x, y, dx, dy默认0; 
1851         w, h默认this.image 宽高; 
1852         canvas 默认新的canvas
1853     
1854     //定义路径
1855     line(x, y, x1, y1): this;                    //定义线段路径
1856     path(arr: Array[x, y], close: Bool): this;    //定义连续的线路径
1857     rect(r, x, y, w, h): this;                     //参数都是可选的
1858     arc(r, s, e, t): this;                         //定义圆形路径; (自动调用一次.computeCircle())
1859         //r 圆半径 默认this.circle.r, 
1860         //s 起始弧度 默认0, 
1861         //e 结束弧度 默认2PI, 
1862         //t false顺时针, true逆时针 默认false
1863 
1864     //绘制路径
1865     fill(color, lineWidth): this;                        //填充定义的路径
1866     stroke(color): this;                                 //绘制定义的路径
1867     drawImage(img, x, y, w, h, x1, y1, w1, h1): this;    //img 剪裁 到this.iamge上, 除img以外其它参数都是可选的(如果未定义宽高则自动设置为: img的宽高)
1868     img(img, posX, posY): this;                            //img 绘制 到this.iamge上, posX, posY根.text参数名类似
1869     text(value, color, size, posX, posY): this;            //填充文字(如果未定义宽高则自动设置为: 文字的宽高)
1870         value: String;
1871         color: Color;
1872         size: Number|String;
1873         posX: Number|"center|right";
1874         posY: Number|"center|bottom";
1875 
1876     //渐变
1877     linearGradient(x, y, x1, y1, colors, close): CanvasLinearGradient;         //参数都是可选的(起始到结束点)
1878     radialGradient(x, y, r, x1, y1, r1, colors): CanvasRadialGradient;    //参数都是可选的(起始到结束点和半径, 自动调用一次.computeCircle())
1879     gradientColor(gradient, colors, close): this;                         //给渐变对象添加渐变颜色;
1880     gradientColorSymme(gradient, colors): this;                            //长度为2: 0: colors[0] , 0.5: colors[1], 1: colors[0]; (如果长度为2除外的偶数则忽略最后一个颜色)
1881         gradient: CanvasLinearGradient | CanvasLinearGradient;
1882         colors: Array[CanvasColor];
1883 
1884 demo:
1885     const root = new CanvasAnimateRender({width: 750, height: 650, className: ""}), 
1886     cac = root.add(new CanvasAnimateCustom())
1887     .size(300, 300).pos(100, 100)                         //image的宽高 和 root画布中的位置
1888     .rect()                                                //定义矩形路径
1889     .fill("#fff")                                        //填充定义的路径
1890     .text("TEST", "#ff0000", 25, "center", "center")    //填充文字
1891     .stroke("#000");                                    //绘制定义的路径
1892 
1893     //渐变
1894     const colors = ["rgba(0,255,0,0)", "green"],
1895     lg = cac.linearGradient(),                        //创建线性渐变
1896     rg = cac.radialGradient();                         //创建径向渐变
1897     //cac.gradientColor(lg, colors).fill(lg);        //填充线性渐变
1898     cac.gradientColorSymme(rg, colors).fill(rg);    //填充径向渐变
1899 
1900     //阴影 旋转
1901     cac.shadow("#666", 4).path([100, 100, 150, 150, 100, 200]).stroke("red");
1902     //cac.rotate(10*Math.PI/180, 150, 150, true).stroke("#ff0000");
1903     cac.rotate(20*Math.PI/180).stroke("#00ff00");
1904     cac.rotate(40*Math.PI/180).stroke("#0000ff");
1905 
1906     root.render(); 
1907     console.log(root, cac)
1908 
1909 
1910     //自动设置宽高
1911     const img = new CanvasAnimateCustom()
1912     .text("TEST", "#fff", 25, "center", "center")
1913     .rect().stroke().image;
1914 
1915     root.add(new CanvasAnimateCustom()).drawImage(img);
1916     root.redraw();
1917 
1918 */
1919 class CanvasAnimateCustom extends CanvasAnimate{
1920 
1921     constructor(canvas){
1922         super();
1923         this._rotate = {a: 0, x: 0, y: 0}; //记录当前旋转弧度和中心点
1924         this._path = {t: "", v: [], d: true}; //记录外部定义的路径
1925         this.context = CanvasAnimateRender.getContext(canvas);
1926         this.image = this.context.canvas;
1927         
1928         if(this.image === canvas) this.size(this.image.width, this.image.height);
1929 
1930     }
1931 
1932     isEmpty(){
1933         return this.box.w < 1 || this.box.h < 1;
1934     }
1935 
1936     getData(x, y, w, h){
1937 
1938         return this.context.getImageData(x||0, y||0, w||this.box.w, h||this.box.h);
1939     }
1940 
1941     putData(data, x, y){
1942         this.context.putImageData(data, x||0, y||0);
1943         return this;
1944     }
1945 
1946     shear(x, y, w, h, canvas, dx, dy){
1947         if(typeof x !== "number") x = 0;
1948         if(typeof y !== "number") y = 0;
1949         if(typeof w !== "number") w = this.box.w;
1950         if(typeof h !== "number") h = this.box.h;
1951         if(typeof dx !== "number") dx = 0;
1952         if(typeof dy !== "number") dy = 0;
1953         
1954         const context = (canvas || document.createElement("canvas")).getContext("2d");
1955         if(context.canvas !== canvas){
1956             context.canvas.width = w;
1957             context.canvas.height = h;
1958         }
1959 
1960         if(this.isEmpty() === false) context.drawImage(this.image, x, y, w, h, dx, dy, w, h); //context.putImageData(this.context.getImageData(x, y, w, h), dx, dy);
1961 
1962         return context.canvas;
1963         
1964     }
1965 
1966     size(w, h){
1967         w = (typeof w === "number" && w >= 1) ? w : 1;
1968         h = (typeof h === "number" && h >= 1) ? h : 1;
1969         
1970         this.box.size(w, h);
1971         this.image.width = this.box.w;
1972         this.image.height = this.box.h;
1973         CanvasAnimateRender.setDefaultStyles(this.context);
1974         
1975         return this;
1976     }
1977 
1978     clear(x = 0, y = 0, w, h){
1979         this.context.clearRect(x, y, w || this.box.w, h || this.box.h);
1980         return this;
1981     }
1982 
1983     unRotate(setA){
1984         if(this._rotate.a !== 0){
1985             this.context.translate(this._rotate.x, this._rotate.y);
1986             this.context.rotate(-(this._rotate.a));
1987             this.context.translate(-(this._rotate.x), -(this._rotate.y));
1988             if(setA === true) this._rotate.a = 0;
1989         }
1990 
1991         return this;
1992     }
1993 
1994     rotate(a, x, y){
1995         a = typeof a === "number" ? a : 0;
1996         x = typeof x === "number" ? x : this.box.w/2;
1997         y = typeof y === "number" ? y : this.box.h/2;
1998 
1999         if(this._rotate.a !== a) this._rotate.a = a;
2000         if(this._rotate.x !== x) this._rotate.x = x;
2001         if(this._rotate.y !== y) this._rotate.y = y;
2002 
2003         if(this._rotate.a !== 0){
2004             this.context.translate(this._rotate.x, this._rotate.y);
2005             this.context.rotate(this._rotate.a);
2006             this.context.translate(-(this._rotate.x), -(this._rotate.y));
2007             //if(updatePath === true && this._path.t !== "") this[this._path.t]();
2008             
2009         }
2010         
2011         return this;
2012     }
2013 
2014     shadow(shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY){
2015         shadowColor = shadowColor || "rgba(0,0,0,0)";
2016         const con = this.context;
2017         if(con.shadowColor !== shadowColor) con.shadowColor = shadowColor;
2018         if(con.shadowBlur !== shadowBlur && typeof shadowBlur === "number") con.shadowBlur = shadowBlur;
2019         if(con.shadowOffsetX !== shadowOffsetX && typeof shadowOffsetX === "number") con.shadowOffsetX = shadowOffsetX;
2020         if(con.shadowOffsetY !== shadowOffsetY && typeof shadowOffsetY === "number") con.shadowOffsetY = shadowOffsetY;
2021         return this;
2022     }
2023 
2024     linearGradient(x, y, x1, y1, colors, close){
2025         const lg = this.context.createLinearGradient(x || 0, y || 0, x1 || this.box.w, y1 || 0);
2026         this.gradientColor(lg, colors, close);
2027         return lg;
2028     }
2029 
2030     radialGradient(x, y, r, x1, y1, r1, colors){
2031         this.computeCircle();
2032         const rg = this.context.createRadialGradient(x || this.circle.x, y || this.circle.y, r || this.circle.r/2, x1 || this.circle.x, y1 || this.circle.y, r1 || this.circle.r);
2033         this.gradientColor(rg, colors);
2034         return rg;
2035     }
2036 
2037 
2038     //定义路径
2039     line(x, y, x1, y1){ //定义线段路径
2040         this._path.t = "createLine";
2041         this._path.v.length = 0;
2042         this._path.v.push(x, y, x1, y1);
2043         this._path.d = false; //this.createLine();
2044         return this;
2045     }
2046 
2047     path(arr, close){ //定义连续线条路径
2048         this._path.t = "createPath";
2049         this._path.v.length = 0;
2050         this._path.v.push(arr, close);
2051         this._path.d = false; //this.createPath();
2052         return this;
2053     }
2054 
2055     rect(r, x, y, w, h){ //定义边框路径
2056         const s = this.context.lineWidth;
2057         r = r || s * 2;
2058         if(typeof x !== "number") x = 0;
2059         if(typeof y !== "number") y = 0;
2060         if(typeof w !== "number") w = this.box.w;
2061         if(typeof h !== "number") h = this.box.h;
2062         x += s / 2;
2063         y += s / 2;
2064         w -= s;
2065         h -= s;
2066         this._path.t = "createRect";
2067         this._path.v.length = 0;
2068         this._path.v.push(r, x, y, w, h);
2069         this._path.d = false; //this.createRect();
2070 
2071         return this;
2072     }
2073 
2074     arc(circle, s, e, t){ //定义圆形路径;
2075         circle = circle || this.circle;
2076         if(!circle) return this;
2077         //r = (typeof r === "number" && r) > 0 ? r : this.circle.r;
2078         s = typeof s === "number" ? s : 0;
2079         e = typeof e === "number" ? e : Math.PI * 2;
2080         t = typeof t === "boolean" ? t : false;
2081         this._path.t = "createArc";
2082         this._path.v.length = 0;
2083         this._path.v.push(circle, s, e, t);
2084         this._path.d = false; //this.createArc();
2085         
2086         return this;
2087     }
2088 
2089 
2090     //绘制路径
2091     stroke(color, lineWidth){ //绘制路径
2092         if(this._path.t === "" || this.isEmpty() === true) return;
2093         if(this.context.strokeStyle !== color && color) this.context.strokeStyle = color;
2094         if(this.context.lineWidth !== lineWidth && typeof lineWidth === "number") this.context.lineWidth = Math.round(lineWidth);
2095         if(this._path.d === false){
2096             this[this._path.t]();
2097             this._path.d = true;
2098         }
2099         this.context.stroke();
2100         
2101         return this;
2102     }
2103 
2104     fill(color){ //填充路径
2105         if(this._path.t === "" || this.isEmpty() === true) return;
2106         if(this.context.fillStyle !== color && color) this.context.fillStyle = color;
2107         if(this._path.d === false){
2108             this[this._path.t]();
2109             this._path.d = true;
2110         }
2111         this.context.fill();
2112         
2113         return this;
2114     }
2115 
2116     text(value, color, fontSize, posX, posY){ //填充文字
2117         const textBox = this.textWidth(value, fontSize), 
2118         height = Math.abs(textBox.fontBoundingBoxAscent) + Math.abs(textBox.fontBoundingBoxDescent);
2119 
2120         if(this.isEmpty() === true){
2121             const _font = this.context.font;
2122             this.size(textBox.width, height);
2123             if(this.isEmpty() === true) return this;
2124             this.context.font = _font;
2125         }
2126         
2127         if(typeof posX === "string"){
2128             if(textBox.width >= this.box.w) posX = 0;
2129             else{
2130                 switch(posX){
2131                     case "center":
2132                     posX = (this.box.w - textBox.width) / 2;
2133                     break;
2134                     
2135                     case "right":
2136                     posX = this.box.w - textBox.width;
2137                     break;
2138 
2139                     default: posX = 0; break;
2140                 }
2141             }
2142             
2143         }
2144 
2145         else posX = posX || 0;
2146         
2147         
2148         if(typeof posY === "string"){
2149             if(textBox.height >= this.box.h) posY = 0;
2150             else{
2151                 switch(posY){
2152                     case "center":
2153                     posY = (this.box.h - height) / 2;
2154                     break;
2155                     
2156                     case "bottom":
2157                     posY = this.box.h - height;
2158                     break;
2159 
2160                     default: posY = 0; break;
2161                 }
2162             }
2163             
2164         }
2165 
2166         else posY = posY || 0;
2167         
2168         if(this.context.fillStyle !== color && color) this.context.fillStyle = color;
2169         this.context.fillText(value, posX, posY);
2170         
2171         return this;
2172     }
2173 
2174     img(img, posX, posY, w, h){
2175         if(this.isEmpty() === true){
2176             this.size(img.width, img.height);
2177             if(this.isEmpty() === true){
2178                 console.warn("CanvasAnimateCustom: 内容为空");
2179                 return this;
2180             }
2181         }
2182 
2183         w = w || this.width;
2184         h = h || this.height;
2185 
2186         if(typeof posX === "string"){
2187             switch(posX){
2188                 case "center":
2189                 posX = (this.box.w - w) / 2;
2190                 break;
2191                 
2192                 case "right":
2193                 posX = this.box.w - w;
2194                 break;
2195             }
2196         }
2197 
2198         else posX = posX || 0;
2199         
2200         if(typeof posY === "string"){
2201             switch(posY){
2202                 case "center":
2203                 posY = (this.box.h - h) / 2;
2204                 break;
2205                 
2206                 case "bottom":
2207                 posY = this.box.h - h;
2208                 break;
2209             }
2210         }
2211 
2212         else posY = posY || 0;
2213         
2214         this.context.drawImage(img, posX, posY, w, h);
2215 
2216         return this;
2217     }
2218 
2219     drawImage(img, x, y, w, h, x1, y1, w1, h1){
2220         if(!img){
2221             console.warn("CanvasAnimateCustom: 参数错误");
2222             return this;
2223         }
2224 
2225         if(this.isEmpty() === true){
2226             this.size(img.width, img.height);
2227             if(this.isEmpty() === true){
2228                 console.warn("CanvasAnimateCustom: 内容为空");
2229                 return this;
2230             }
2231         }
2232 
2233         this.context.drawImage(img, x || 0, y || 0, w || img.width, h || img.height, x1 || 0, y1 || 0, w1 || this.box.w, h1 || this.box.h);
2234 
2235         return this;
2236     }
2237 
2238     drawTransparentBG(size, x, y, w, h){
2239         size = size || 5;
2240         x = x || 0;
2241         y = y || 0;
2242         w = w || this.box.w;
2243         h = h || this.box.h;
2244 
2245         const con = this.context, c1 = "rgb(255,255,255)", c2 = "rgb(127,127,127)", lenX = Math.ceil(w/size), lenY = Math.ceil(h/size);
2246         
2247         con.save();
2248         con.beginPath();
2249         con.rect(x, y, w, h);
2250         con.clip();
2251 
2252         for(let ix = 0, iy = 0; ix < lenX; ix++){
2253 
2254             for(iy = 0; iy < lenY; iy++){
2255 
2256                 con.fillStyle = (ix + iy) % 2 === 0 ? c1 : c2;
2257                 con.fillRect(ix * size + x, iy * size + y, size, size);
2258                 
2259             }
2260 
2261         }
2262 
2263         con.restore();
2264         return this;
2265     }
2266 
2267 
2268     //以下方法限内部使用
2269     _linePath(x, y, x1, y1, isStart){
2270         if(this.context.lineWidth % 2 === 1){
2271             if(x !== x1 && y !== y1){
2272                 x = Math.floor(x) + 0.5;
2273                 x1 = Math.floor(x1) + 0.5;
2274                 y = Math.floor(y) + 0.5;
2275                 y1 = Math.floor(y1) + 0.5;
2276             }
2277 
2278             else{
2279                 if(x === x1){
2280                     x = Math.floor(x) + 0.5;
2281                     x1 = Math.floor(x1) + 0.5;
2282                 }
2283                 else{
2284                     x = Math.floor(x);
2285                     x1 = Math.floor(x1);
2286                 }
2287 
2288                 if(y === y1){
2289                     y = Math.floor(y) + 0.5;
2290                     y1 = Math.floor(y1) + 0.5;
2291                 }
2292                 else{
2293                     y = Math.floor(y);
2294                     y1 = Math.floor(y1);
2295                 }
2296                 
2297             }
2298             
2299         }
2300 
2301         else{
2302             x = Math.floor(x);
2303             x1 = Math.floor(x1);
2304             y = Math.floor(y);
2305             y1 = Math.floor(y1);
2306         }
2307 
2308         if(isStart !== true) this.context.lineTo(x, y);
2309         else this.context.moveTo(x, y);
2310         this.context.lineTo(x1, y1);
2311 
2312     }
2313 
2314     createLine(){
2315         this.context.beginPath();
2316         this._linePath(this._path.v[0], this._path.v[1], this._path.v[2], this._path.v[3], true);
2317     }
2318 
2319     createPath(){
2320         const con = this.context, arr = this._path.v[0], len = arr.length;
2321         con.beginPath();
2322 
2323         this._linePath(arr[0], arr[1], arr[2], arr[3], true);
2324 
2325         for(let k = 4; k < len; k+=4){
2326             this._linePath(arr[k], arr[k+1], arr[k+2], arr[k+3]);
2327         }
2328 
2329         if(this._path.v[1] === true){
2330             //con.closePath();
2331             this._linePath(arr[len-2], arr[len-1], arr[0], arr[1]);
2332 
2333         }
2334 
2335     }
2336 
2337     createRect(){
2338         const con = this.context, r = Math.round(this._path.v[0]), x = this._path.v[1], y = this._path.v[2], w = this._path.v[3], h = this._path.v[4],
2339         _x = x + r, _y = y + r, mx = x + w, my = y + h, _mx = mx - r, _my = my - r;
2340         con.beginPath();
2341         
2342         //上
2343         con.moveTo(_x, y);
2344         con.lineTo(_mx, y);
2345         con.arcTo(mx, y, mx, _y, r);
2346 
2347         //右
2348         con.lineTo(mx, _y);
2349         con.lineTo(mx, _my);
2350         con.arcTo(mx, my, _x, my, r);
2351 
2352         //下
2353         con.lineTo(_x, my);
2354         con.lineTo(_mx, my);
2355         con.arcTo(x, my, x, _my, r);
2356 
2357         //左
2358         con.lineTo(x, _y);
2359         con.lineTo(x, _my);
2360         con.arcTo(x, y, _x, y, r);
2361 
2362     }
2363 
2364     createArc(){
2365         this.context.beginPath();
2366         this.context.arc(this._path.v[0].x, this._path.v[0].y, this._path.v[0].r, this._path.v[1], this._path.v[2], this._path.v[3]);
2367     }
2368     
2369     textWidth(text, font){
2370         if(typeof font === "number") this.context.font = font+"px SimSun, Songti SC";
2371         else if(font) this.context.font = font;
2372         
2373         const bounding = this.context.measureText(text);
2374 
2375         //兼容火狐 (暂时只用到这两个, 谷歌 和 火狐 获取的box属性名完全不一样)
2376         if(bounding.fontBoundingBoxAscent === undefined) bounding.fontBoundingBoxAscent = bounding.actualBoundingBoxAscent;
2377         if(bounding.fontBoundingBoxDescent === undefined) bounding.fontBoundingBoxDescent = bounding.actualBoundingBoxDescent;
2378 
2379         return bounding;
2380     }
2381 
2382     computeCircle(){
2383         super.computeCircle();
2384         this.circle.pos(this.width/2, this.height/2);
2385         return this;
2386     }
2387 
2388     static getPathTriangle(w, h, result){
2389         if(Array.isArray(result) === false) result = [w/2, 0, w, h, 0, h];
2390         else result.push(w/2, 0, w, h, 0, h);
2391         return result;
2392     }
2393 
2394     get gradientColor(){
2395         return CanvasAnimateRender.gradientColor;
2396     }
2397 
2398     get gradientColorSymme(){
2399         return CanvasAnimateRender.gradientColorSymme;
2400     }
2401 
2402 }
2403 
2404 
2405 
2406 
2407 /* CanvasAnimateImages (可以一次存储多个图像, 然后快速在其之间切换)
2408 parameter: 
2409     images: Array[CanvasImageData];
2410 
2411 attribute:
2412     images: Array[CanvasImageData]; //this.setImage(img) || this.images.push(img, ...);
2413 
2414 method:
2415     set(i: Integer): undefined; //设置当前image; i必须
2416     next(): undefined;
2417     loads(srcs: Array[String || Object{src}], onDone: Function(images, i, srcs), onUpdate: Function(images, i)): this; //通过给的src加载image; 加载完成后push到this.images; srcs 必须, 其它参数可选
2418 
2419 */
2420 class CanvasAnimateImages extends CanvasAnimate{
2421 
2422     #i = -1;
2423 
2424     constructor(images = []){
2425         super();
2426         this.images = images;
2427     
2428     }
2429 
2430     get i(){
2431         return this.#i;
2432     }
2433 
2434     _set(img){
2435         if(img !== null) this.box.size(img.width, img.height);
2436         this.image = img;
2437 
2438     }
2439 
2440     set(i){
2441         this.#i = i;
2442         this._set(this.images[i] || null);
2443         
2444     }
2445 
2446     next(){
2447         const len = this.images.length - 1;
2448         if(len !== -1){
2449             if(this.#i < len) this.#i++;
2450             else this.#i = 0;
2451             
2452             this._set(this.images[this.#i]);
2453 
2454         }
2455         
2456     }
2457 
2458     setImage(img = null){
2459         if(img === null) return;
2460         if(this.#i === -1){
2461             this.#i = this.images.length;
2462             this._set(img);
2463         }
2464 
2465         this.images.push(img);
2466         return this;
2467     }
2468 
2469     loads(srcs, onDone, onUpdate){
2470         onUpdate = typeof onUpdate === "function" ? onUpdate : null;
2471         var i = 0, c = srcs.length, img = null, _i = this.images.length;
2472 
2473         const len = srcs.length, 
2474         func = ()=>{
2475             i++; if(onUpdate !== null) onUpdate(this.images, _i);
2476             if(i === c && typeof onDone === "function"){
2477                 if(this.#i === -1) this.set(0);
2478                 onDone(this.images, _i, srcs);
2479             }
2480             else _i++;
2481         }
2482 
2483         for(let k = 0, ty = ""; k < len; k++){
2484             ty = typeof srcs[k];
2485             if(ty === "string" || ty === "object"){
2486                 ty = ty === "string" ? srcs[k] : srcs[k].src;
2487                 if(ty !== "" && typeof ty === "string"){
2488                     img = new Image();
2489                     img.onload = func;
2490                     this.images.push(img);
2491                     img.src = ty;
2492                 }
2493                 else c--;
2494             }
2495 
2496         }
2497 
2498         return this;
2499     }
2500 
2501 }
2502 
2503 
2504 
2505 
2506 /* CanvasAnimateSprite (把精灵图缓存为多个图像, 然后快速在其之间切换)
2507 parameter: 
2508 attribute:
2509 method:
2510     buffer(image: ImageData, data: Array[Box] || Object{w, h}): this; //把精灵图剪裁为canvas; 然后缓存到images队列;
2511     
2512 */
2513 class CanvasAnimateSprite extends CanvasAnimateImages{
2514 
2515     constructor(images){
2516         super(images);
2517 
2518     }
2519 
2520     buffer(image, data){
2521         if(!image || !data) return console.warn("CanvasAnimateSprite: 参数错误, 缓存失败");
2522         
2523         const paramCon = CanvasAnimateRender.paramCon;
2524         if(Array.isArray(data) === true){
2525             const len = data.length;
2526             for(let k = 0, context, canvas, box; k < len; k++){
2527                 box = data[k];
2528                 if(box.w < 1 || box.h < 1) continue;
2529 
2530                 canvas = document.createElement("canvas");
2531                 canvas.width = box.w;
2532                 canvas.height = box.h;
2533                 context = canvas.getContext("2d", paramCon);
2534                 
2535                 context.drawImage(image, box.x, box.y, box.w, box.h, 0, 0, box.w, box.h);
2536                 this.setImage(canvas);
2537                 
2538             }
2539         }
2540 
2541         else{
2542             if(data.w < 1 || data.h < 1) return console.warn("CanvasAnimateSprite: box小于1");
2543             const mw = image.width, mh = image.height,
2544             lenX = Math.floor(mw / data.w),
2545             lenY = Math.floor(mh / data.h);
2546 
2547             for(let x = 0, y, context, canvas, _x; x < lenX; x++){
2548                 _x = x * data.w;
2549                 for(y = 0; y < lenY; y++){
2550                     canvas = document.createElement("canvas");
2551                     canvas.width = data.w;
2552                     canvas.height = data.h;
2553                     context = canvas.getContext("2d", paramCon);
2554 
2555                     context.drawImage(image, _x, y * data.h, data.w, data.h, 0, 0, data.w, data.h);
2556                     this.setImage(canvas);
2557                 }
2558             
2559             }
2560 
2561         }
2562         
2563         return this;
2564     }
2565 
2566 }
2567 
2568 
2569 
2570 
2571 /* ColorTestViewer 颜色调试器
2572 
2573 attribute:
2574     onchange: Function; //颜色改变回调; 默认null
2575 
2576     //以下属性不建议直接修改
2577     rgb: RGBColor;         //rgb模式颜色
2578     hsv: Object{h,s,v};    //hsv模式颜色
2579     alpha: Number;        //透明度
2580 
2581 method:
2582     update(): undefined;        //更新控件视图 (通常控件被唤出时调用此方法, 前提是在唤出前控件颜色发生了改变)
2583     setValue(r, g, b, a): this; //设置控件的颜色
2584 
2585 demo:
2586     const ctv = new ColorTestViewer({width: 200})
2587     .setValue(0, 0, 255, 1).update(false)
2588     .pos(100, 100).render();
2589 
2590     ctv.onchange = v => console.log(v);
2591 
2592 */
2593 class ColorTestViewer extends CanvasAnimateRender{
2594 
2595     get value(){
2596         return this.rgb.getRGBA(Math.floor(this.alpha * 1000) / 1000);
2597     }
2598 
2599     constructor(option = {}){
2600         super(option);
2601 
2602         this.rgb = new RGBColor(255);
2603         this.hsv = this.rgb.getHSV();
2604         this.alpha = 1;
2605         this.cae = null;
2606         this.onchange = null;
2607 
2608         this.viewSVColor = null;
2609         this.viewSVsv = null;
2610         this.viewSVCursor = null;
2611 
2612         this.viewHValueBG = null;
2613         this.viewHValue = null;
2614         this.viewHScroll = null;
2615         this.viewHCursor = null;
2616 
2617         this.viewAScroll = null;
2618         this.viewACursor = null;
2619 
2620         this.viewColorInfo = null;
2621 
2622         //默认样式
2623         if(option.canvas === undefined){
2624             const width = option.width || 200, height = option.height || (width * 0.8), margin = 2, 
2625             h5 = height * 0.5, h1 = height * 0.1, h3 = height * 0.3;
2626 
2627             this.size(width + margin * 2, height + margin * 5);
2628 
2629             this.initViewSVColor(width, h5);
2630             this.initViewHScroll(width, h1);
2631             this.initViewAScroll(width, h1);
2632             this.initViewHValue(h3, h3);
2633             this.initViewColorInfo(width - h3, h3);
2634 
2635             this.setViewSVPos(margin, margin);
2636             this.setViewHScrollPos(margin, h5 + margin * 2);
2637             this.setViewAScrollPos(margin, h5 + h1 + margin * 3);
2638             this.setViewHValuePos(margin, h5 + h1 * 2 + margin * 4);
2639             this.viewColorInfo.pos(this.viewHValue.box.maxX(), this.viewHValue.box.y);
2640             
2641             this.initList();
2642             this.initEvent();
2643 
2644         }
2645         
2646     }
2647 
2648     update(u){
2649         if(this.viewSVColor !== null){
2650             this.updateViewSVCursor();
2651             this.updateViewSVColor();
2652             this.updateViewHValue();
2653             this.updateViewHCursor();
2654             this.updateViewACursor();
2655             this.updateViewColorInfo();
2656             if(u === true) this.redraw();
2657         }
2658 
2659         return this;
2660     }
2661 
2662     setValue(r, g, b, a){
2663         this.rgb.r = r;
2664         this.rgb.g = g;
2665         this.rgb.b = b;
2666         this.alpha = a;
2667         this.rgb.getHSV(this.hsv);
2668         
2669         return this;
2670     }
2671 
2672     setValueString(color){
2673         if(typeof color !== "string") return this;
2674         var _color = this.getColor(color);
2675         
2676         if(_color[0] === "#"){
2677             const len = _color.length;
2678             if(len === 4){
2679                 _color = _color.slice(1);
2680                 this.rgb.setFormHex(parseInt("0x"+_color + "" + _color));
2681             }
2682             else if(len === 7) this.rgb.setFormHex(parseInt("0x"+_color.slice(1)));
2683             this.alpha = 1;
2684             this.rgb.getHSV(this.hsv);
2685             
2686         }
2687 
2688         else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
2689             const arr = [];
2690             for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
2691                 
2692                 if(is === true){
2693                     if(_color[k] === "," || _color[k] === ")"){
2694                         arr.push(parseFloat(v));
2695                         v = "";
2696                     }
2697                     else v += _color[k];
2698                     
2699                 }
2700 
2701                 else if(_color[k] === "(") is = true;
2702                 
2703             }
2704 
2705             this.setValue(arr[0], arr[1], arr[2], arr[3] === undefined ? 1 : arr[3]);
2706             
2707         }
2708         
2709         return this;
2710     }
2711 
2712     getColor(str){ //检测 str 是否是颜色类型(16进制, rgb, rgba, 英文); 如果是则返回去除掉空格后的字符串颜色(英文则返回对应16进制颜色)
2713         var _color = "";
2714         for(let k = 0, len = str.length; k < len; k++){
2715             if(str[k] === " ") continue;
2716             _color += str[k];
2717         }
2718         
2719         if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
2720         else{
2721             for(let k = 0, len = ColorRefTable.length; k < len; k++){
2722                 str = ColorRefTable[k];
2723                 if(str[0] === _color) return str[1];
2724             }
2725         }
2726 
2727         return "";
2728     }
2729 
2730     exit(){
2731         if(this.cae){
2732             this.onUpSV();
2733             this.onUpH();
2734             this.onUpA();
2735         }
2736 
2737         if(this.domElement.parentElement) this.domElement.parentElement.removeChild(this.domElement);
2738 
2739     }
2740 
2741 
2742     //event
2743     initEvent(){
2744         
2745         const cae = new CanvasAnimateEvent(this);
2746 
2747         //SV
2748         const setSV = (pageX, pageY) => {
2749             pageX = (pageX - this.domElementRect.x - this.viewSVColor.box.x) / this.viewSVColor.box.w * 100;
2750             pageY = (1 - (pageY - this.domElementRect.y - this.viewSVColor.box.y) / this.viewSVColor.box.h) * 100;
2751             if(pageX < 0) pageX = 0;
2752             else if(pageX > 100) pageX = 100;
2753             if(pageY < 0) pageY = 0;
2754             else if(pageY > 100) pageY = 100;
2755             if(this.hsv.s !== pageX || this.hsv.v !== pageY){
2756                 this.hsv.s = pageX;
2757                 this.hsv.v = pageY;
2758                 this.rgb.setFormHSV(this.hsv.h, this.hsv.s, this.hsv.v);
2759                 if(typeof this.onchange === "function") this.onchange(this.value);
2760                 this.updateViewHValue();
2761                 this.updateViewColorInfo();
2762                 this.updateViewSVCursor();
2763                 this.redraw();
2764             }
2765 
2766         },
2767 
2768         onMoveSV = event => {
2769             setSV(event.pageX, event.pageY);
2770         },
2771 
2772         onUpSV = () => {
2773             document.body.removeEventListener("pointerup", onUpSV);
2774             document.body.removeEventListener("pointermove", onMoveSV);
2775             cae.remove(this.viewSVCursor, 'up', onUpSV);
2776             cae.remove(this.viewSVCursor, 'move', onMoveSV);
2777             
2778         },
2779 
2780         onDownSV = event => {
2781             setSV(event.pageX, event.pageY);
2782             cae.add(this.viewSVCursor, "up", onUpSV);
2783             cae.add(this.viewSVCursor, "move", onMoveSV);
2784             document.body.addEventListener("pointerup", onUpSV);
2785             document.body.addEventListener("pointermove", onMoveSV);
2786             
2787         }
2788 
2789         cae.add(this.viewSVColor, "down", onDownSV);
2790         cae.add(this.viewSVCursor, "down", onDownSV);
2791         this.onUpSV = onUpSV;
2792 
2793 
2794         //H
2795         const setH = (pageX) => {
2796             pageX = (pageX - this.domElementRect.x - this.viewHScroll.box.x) / this.viewHScroll.box.w * 360;
2797             if(pageX < 0) pageX = 0;
2798             else if(pageX > 360) pageX = 360;
2799             if(this.hsv.h !== pageX){
2800                 this.hsv.h = pageX;
2801                 this.rgb.setFormHSV(this.hsv.h, this.hsv.s, this.hsv.v);
2802                 if(typeof this.onchange === "function") this.onchange(this.value);
2803                 this.updateViewHValue();
2804                 this.updateViewColorInfo();
2805                 this.updateViewSVColor();
2806                 this.updateViewHCursor();
2807                 this.redraw();
2808             }
2809 
2810         },
2811 
2812         onMoveH = event => {
2813             setH(event.pageX);
2814         },
2815 
2816         onUpH = () => {
2817             document.body.removeEventListener("pointerup", onUpH);
2818             document.body.removeEventListener("pointermove", onMoveH);
2819             cae.remove(this.viewHCursor, 'up', onUpH);
2820             cae.remove(this.viewHCursor, 'move', onMoveH);
2821 
2822         },
2823 
2824         onDownH = event => {
2825             setH(event.pageX);
2826             cae.add(this.viewHCursor, "up", onUpH);
2827             cae.add(this.viewHCursor, "move", onMoveH);
2828             document.body.addEventListener("pointerup", onUpH);
2829             document.body.addEventListener("pointermove", onMoveH);
2830 
2831         }
2832         
2833         cae.add(this.viewHScroll, "down", onDownH);
2834         cae.add(this.viewHCursor, "down", onDownH);
2835         this.onUpH = onUpH;
2836 
2837 
2838 
2839         //A
2840         const setA = (pageX) => {
2841             pageX = (pageX - this.domElementRect.x - this.viewAScroll.box.x) / this.viewAScroll.box.w;
2842             if(pageX < 0) pageX = 0;
2843             else if(pageX > 1) pageX = 1;
2844             if(this.alpha !== pageX){
2845                 this.alpha = pageX;
2846                 if(typeof this.onchange === "function") this.onchange(this.value);
2847                 this.updateViewColorInfo();
2848                 this.updateViewHValue();
2849                 this.updateViewACursor();
2850                 this.redraw();
2851             }
2852 
2853         },
2854 
2855         onMoveA = event => {
2856             setA(event.pageX);
2857         },
2858 
2859         onUpA = () => {
2860             document.body.removeEventListener("pointerup", onUpA);
2861             document.body.removeEventListener("pointermove", onMoveA);
2862             cae.remove(this.viewACursor, 'up', onUpA);
2863             cae.remove(this.viewACursor, 'move', onMoveA);
2864             
2865         },
2866 
2867         onDownA = event => {
2868             setA(event.pageX);
2869             cae.add(this.viewACursor, "up", onUpA);
2870             cae.add(this.viewACursor, "move", onMoveA);
2871             document.body.addEventListener("pointerup", onUpA);
2872             document.body.addEventListener("pointermove", onMoveA);
2873 
2874         }
2875 
2876         cae.add(this.viewAScroll, "down", onDownA);
2877         cae.add(this.viewACursor, "down", onDownA);
2878         this.onUpA = onUpA;
2879 
2880         this.cae = cae;
2881 
2882     }
2883 
2884 
2885     //SV 明度 与 灰度
2886     updateViewSVCursor(){
2887         this.viewSVCursor.pos(this.hsv.s / 100 * this.viewSVColor.box.w + this.viewSVColor.box.x - this.viewSVCursor.circle.r, (1 - this.hsv.v / 100) * this.viewSVColor.box.h + this.viewSVColor.box.y - this.viewSVCursor.circle.r);
2888     }
2889 
2890     updateViewSVColor(){
2891         this.viewSVColor.clear().fill(ColorTestViewer.emptyColor.setFormHSV(this.hsv.h, 100, 100).getHexString());
2892 
2893     }
2894 
2895     setViewSVPos(x, y){
2896         this.viewSVColor.pos(x, y);
2897         this.viewSVsv.pos(x, y);
2898         this.updateViewSVCursor();
2899     }
2900 
2901     initViewSVColor(width, height){ //*3
2902         this.viewSVColor = new CanvasAnimateCustom().size(width, height).rect();
2903 
2904         this.viewSVsv = new CanvasAnimateCustom().size(width, height);
2905         const gradientS = this.viewSVsv.linearGradient(0, height, width, height, ColorTestViewer.colorS, true),
2906         gradientV = this.viewSVsv.linearGradient(width, height, width, 0, ColorTestViewer.colorV, true);
2907         this.viewSVsv.rect().fill(gradientS).fill(gradientV);
2908 
2909         this.viewSVCursor = new CanvasAnimateCustom().size(10, 10);
2910         this.viewSVCursor.computeCircle();
2911         this.viewSVCursor.arc().stroke("#fff");
2912 
2913         this.list.push(this.viewSVColor, this.viewSVsv, this.viewSVCursor);
2914 
2915         this.setViewSVPos(0, 0);
2916         this.updateViewSVColor();
2917 
2918     }
2919 
2920 
2921     //H 颜色
2922     updateViewHValue(){
2923         this.viewHValue.clear().fill(this.rgb.getRGBA(this.alpha));
2924 
2925     }
2926 
2927     setViewHValuePos(x, y){
2928         this.viewHValueBG.pos(x, y);
2929         this.viewHValue.pos(x, y);
2930 
2931     }
2932 
2933     initViewHValue(width, height){ //*2
2934         this.viewHValueBG = new CanvasAnimateCustom().size(width, height)
2935         .drawTransparentBG(5, 0, 0, width, height);
2936 
2937         this.viewHValue = new CanvasAnimateCustom().size(width, height)
2938         .rect();
2939 
2940         this.list.push(this.viewHValueBG, this.viewHValue);
2941         this.updateViewHValue();
2942 
2943     }
2944 
2945     updateViewHCursor(){
2946         this.viewHCursor.pos(this.hsv.h / 360 * this.viewHScroll.box.w + this.viewHScroll.box.x - this.viewHCursor.circle.r, this.viewHScroll.box.y);
2947 
2948     }
2949 
2950     setViewHScrollPos(x, y){
2951         this.viewHScroll.pos(x, y);
2952         this.updateViewHCursor();
2953     }
2954 
2955     initViewHScroll(width, height){ //*2
2956         this.viewHScroll = new CanvasAnimateCustom().size(width, height).rect();
2957         this.viewHScroll.fill(this.viewHScroll.linearGradient(0, height, width, height, ColorTestViewer.colorH, true));
2958 
2959         const size = Math.min(width, height);
2960         this.viewHCursor = new CanvasAnimateCustom().size(size, size);
2961         this.viewHCursor.computeCircle();
2962         this.viewHCursor.arc().stroke("#fff");
2963         
2964         this.list.push(this.viewHScroll, this.viewHCursor);
2965         this.setViewHScrollPos(0, 0);
2966 
2967     }
2968 
2969 
2970     //A 透明度
2971     updateViewACursor(){
2972         this.viewACursor.pos(this.alpha * this.viewAScroll.box.w + this.viewAScroll.box.x - this.viewACursor.circle.r, this.viewAScroll.box.y);
2973 
2974     }
2975 
2976     setViewAScrollPos(x, y){
2977         this.viewAScroll.pos(x, y);
2978         this.updateViewACursor();
2979     }
2980 
2981     initViewAScroll(width, height){ //*2
2982         this.viewAScroll = new CanvasAnimateCustom().size(width, height)
2983         .drawTransparentBG(5, 0, 0, width, height).rect();
2984         this.viewAScroll.fill(this.viewAScroll.linearGradient(0, height, width, height, ColorTestViewer.colorA));
2985 
2986         const size = Math.min(width, height);
2987         this.viewACursor = new CanvasAnimateCustom().size(size, size);
2988         this.viewACursor.computeCircle();
2989         this.viewACursor.arc().stroke("rgb(0,160,255)");
2990 
2991         this.list.push(this.viewAScroll, this.viewACursor);
2992         this.setViewAScrollPos(0, 0);
2993 
2994     }
2995 
2996 
2997     //color text
2998     updateViewColorInfo(){
2999         
3000         this.viewColorInfo.clear().text(this.value, "#000000", 12, "center", "center");
3001 
3002     }
3003 
3004     initViewColorInfo(width, height){ //*1
3005         this.viewColorInfo = new CanvasAnimateCustom().size(width, height);
3006         this.list.push(this.viewColorInfo);
3007         this.updateViewColorInfo();
3008     }
3009 
3010 
3011 
3012     static emptyColor = new RGBColor();
3013 
3014     static colorH = function (){ //颜色渐变
3015         const result = [], color = ColorTestViewer.emptyColor;
3016         for(let h = 0; h < 6; h++){
3017             color.setFormHSV(h/6*360, 100, 100);
3018             result.push(color.getHexString());
3019         }
3020         
3021         return result;
3022     }();
3023 
3024     static colorS = function (){ //饱和度的渐变
3025         const result = [];
3026         for(let s = 0; s < 100; s++) result.push('rgba(255,255,255,' + (1 - s / 100) + ")");
3027         return result;
3028     }();
3029 
3030     static colorV = function (){ //明度渐变
3031         const result = [];
3032         for(let s = 0; s < 100; s++) result.push('rgba(0,0,0,' + (1 - s / 100) + ")");
3033         return result;
3034     }();
3035 
3036     static colorA = function (){ //透明度渐变
3037         const result = [];
3038         for(let a = 0; a <= 10; a++) result.push('rgba(0,0,0,' + (a / 10) + ")");
3039         return result;
3040     }();
3041 
3042 }
3043 
3044 
3045 
3046 
3047 /* MenuView Tree Option Menu 树状选项菜单视图
3048 注意: MenuView 在初始化时用时比较长;  MenuView一旦被实例化后 .visible, .views 除外的属性更新将无效, 因为它们已经被缓存为图像
3049 
3050 parameter:
3051     names: Array[String], funcs, lcons, parentElem(所有后代引用父的.parentElem)
3052     或第一个参数 Array[Object]: [
3053         {
3054             name: String, //对应 .names 必须
3055             lcon: ImageData, //对应 .lcons; (MenuView.lconSize 是每个lcon的宽高) 可选
3056             func: Function //对应 .funcs; 可选
3057         }
3058         ...
3059     ]
3060 
3061 attribute:
3062     parentElem: DomElement; //父容器 默认 null 既body (注意: 你如果要自定义此属性值, 只需要给root(最顶层且没有父的MenuView)传一次就够了,因为root的后代会自动引用自己上级的.parentElem)
3063     visible: Bool; 是否显示视图 默认 false (如果当前MenuView是root(没有父), 则需要手动更新, 事实上root的视图位置(.view.pos) 和 是否显示(.visible) 都需要自己手动更新)
3064 
3065     //以下属性只读; 内部自动定义
3066     top, left, widht, height: Number; //canvas的位置和宽高, 只读
3067     view: CanvasAnimateRender; //用于渲染 CanvasAnimate
3068     events: CanvasAnimateEvent; //CanvasAnimateRender 的事件管理器
3069     views: Array[funcs.callback.v]; //
3070     _eventTargets: Array[CanvasAnimateImages]; //.exit()方法用此属性清理事件
3071     parentElemRect: Box; //.parentElem的位置和宽高(root负责创建和更新此值, 其后代只是引用)
3072     names: Array[String]; //文本内容 默认[];
3073     lcons: Array[][CanvasImageData]; //自定义图标; 对应 names 索引 默认null;
3074     funcs: Array[Function]; //onclick事件回调; 对应 names 索引 默认null;
3075         callback: 
3076             v: Object{
3077                 //以下属性是可选的 默认null
3078                 background: CanvasAnimateImages, //背景, 最底部的cai 
3079                 border: CanvasAnimateImages, //边框 顶部的cai
3080                 lcon_def: CanvasAnimateImages, //系统默认的图标, 0: "✘", 1: "✔", 2: "●", 3: "◆", 4: "■", 5: "★"; (更换对钩✔图标: lcon_def.set(1), 参见: static/defaultLcon)
3081                 lcon: CanvasAnimateImages, //自定义的图标 对应自定义的.lcons (注意: lcon_def 和 lcon 的位置是重叠的, lcon在lcon_def的上层)
3082 
3083                 //以下属性总会被定义 必须
3084                 name: CanvasAnimateImages, //如要获取对应的name值: target.names[key], 回调函数: .funcs[key] 图标: .lcons[key]
3085                 child: CanvasAnimateImages, //右箭头图标 (如果 children[key] 未定义 则.visible = false, 所以可以动态添加,删除,设置,某项的后代)
3086                 disable: Bool, //是否禁用; 默认 false
3087                 key: Number, //对应 .names 的键值
3088                 target: MenuView, //this
3089             },
3090             event: onpointerup Event
3091     
3092 method:
3093     getViewByName(name): Object{funcs.callback.v}; //返回的对象 恒等于 回调返回的参数对象
3094     add(v: MenuView, k: Number): v; //把v添加到自己的子集; k对应.names的键, 如果未定义直接.push(v)
3095     exit(): undefined; //清理自己及自己所有后代的缓存和视图
3096     
3097     //内部自动调用以下方法:
3098     update(): undefined; //更新自己及自己第一层后代位置, .visible 设为true时自动调用一次; 如果更新自己及所有后代: this.traverse(this._update); (如果当前MenuView是root(没有父), 则需要手动更新位置: MenuView.view.pos(left:Number, top:Number));
3099     traverse(callback): undefined; //遍历自己及后代
3100         callback(v: MenuView, k: v在其父的键, p: v的父亲) //如果v存在则返回两个参数, 否则返回三个参数
3101     initView(names);
3102     clearNowView(k)
3103     importData();
3104     _setHiddenOrChild(k);
3105     _update(v, k, p);
3106     _traverse(callback, k);
3107 
3108 static: 
3109     sign: String; //如果定义给后续创建的每个canvas对象添加一条属性(canvas[sign] = true); 默认 "" 既不添加
3110     parentElem: DomElement; //.parentElem 的备胎 默认 null;
3111     className: String; //css类名; 默认 ""
3112     lconSize: Number; //lcon 的宽高 默认 16
3113     textSize: Number; //全局的文字大小 默认12 (注意此值不能超过 lconSize)
3114     borderSize: Number; //如果小于等于0初始化时不创建border; 默认0
3115     textColor: Object; //默认 {down: "#0000ff", up: "#000000", disable: "#666666"};
3116     rowBorderColor: Object; //默认 {down: "#ffffff", up: "#000000", disable: "#666666"};
3117     rowBackgroundColor: Object; //如果为null则不创建背景; 默认 {down: "rgba(0,0,255,0.2)", up: "#ffffff", disable: "#333333"}
3118     rowPadding: Object; //每排的内边距 默认 {top: 2, right: 2, bottom: 2, left: 2}
3119     rowMargin: Object; //每排的外边距 默认 {top: 2, right: 2, bottom: 2, left: 2};
3120     defaultLcon: String; //是否为每排缓存默认的图标(lcon_def); 默认 "min" | "" | "max"; ["✘", "✔", "●", "◆", "■", "★"]; min 缓存0,1,2; ""不缓存; max缓存全部
3121     buttonType: Number; // -1鼠标左右键都可触发回调 | 0只能是左键 | 2只能是右键
3122 
3123     reset(): undefined; //重置上面所有静态属性的值(恢复其默认值, 所有静态属性只对后续创建的 MenuView 有效)
3124     
3125     _cac: CanvasAnimateCustom;
3126     nowView: MenuView;
3127     clearNowView(): undefined;
3128 
3129 private:
3130     length: Number;
3131     visible: Bool;
3132 
3133 demo:
3134     const func = v => {
3135         console.log(v);
3136         v.lcon_def.set(1); //"✔"图标
3137         //v.disable = true; //禁用
3138     },
3139     names = ["AAA", "BBB", "CCC", "DDDDDDDDD"],
3140     funcs = [func, null, func],
3141     lcons = null,
3142 
3143     root = new MenuView(names, null, funcs, lcons);
3144 
3145     //对应 root.names[2]: "CCC"
3146     root.add(new MenuView(names, null, funcs, lcons), 2);
3147 
3148     //对应 root.names[3]: "DDDDDDDDD"
3149     root.add(new MenuView(names, null, funcs, lcons), 3);
3150 
3151     root.view.pos(10, 10); //设置root的位置
3152     root.visible = true; //显示视图
3153 
3154     //
3155     const root = new MenuView([
3156         {name: "AAA", func: func},
3157         {name: "bbb", func: func},
3158         {name: "ccc", func: func},
3159         {name: "ddd", func: func},
3160         {name: "eee", func: func},
3161         {name: "test", func: func},
3162     ]),
3163 
3164     // 添加到 root / name: test 的后代
3165     menuViewA = root.add(new MenuView([
3166         {name: "AAA", func: func},
3167         {name: "bbb", func: func},
3168         {name: "ccc", func: func},
3169         {name: "ddd", func: func},
3170     ]), "test"|5);
3171 
3172     menuViewA.getViewByName("ddd").lcon_def.set(0|1|2); //外部修改样式
3173 
3174 */
3175 class MenuView extends TreeStruct{
3176 
3177     static sign = "";
3178     static parentElem = null;
3179     static className = "";
3180     static textSize = 12;
3181     static lconSize = 16;
3182     static borderSize = 0; //如果小于或等于零不创建边框
3183     static textColor = {down: "#0000ff", up: "#000000", disable: "#666666"};
3184     static rowBorderColor = {down: "#ffffff", up: "#000000", disable: "#666666"};
3185     static rowBackgroundColor = {down: "rgba(0,0,255,0.2)", up: "#ffffff", disable: "#999999"}; //如果为null不创建背景
3186     static rowPadding = {top: 2, right: 2, bottom: 2, left: 2};
3187     static rowMargin = {top: 0, right: 0, bottom: 0, left: 0};
3188     static defaultLcon = "min";
3189     static buttonType = -1;
3190 
3191     static reset(){ //重置上面静态属性的值
3192         MenuView.sign = "";
3193         MenuView.parentElem = null;
3194         MenuView.className = "";
3195         MenuView.textSize = 12;
3196         MenuView.lconSize = 16;
3197         MenuView.borderSize = 0;
3198 
3199         MenuView.textColor.down = "#0000ff";
3200         MenuView.textColor.up = "#000000";
3201         MenuView.textColor.disable = "#666666";
3202 
3203         MenuView.rowBorderColor.down = "#ffffff";
3204         MenuView.rowBorderColor.up = "#000000";
3205         MenuView.rowBorderColor.disable = "#666666";
3206 
3207         MenuView.rowBackgroundColor.down = "rgba(0,0,255,0.2)";
3208         MenuView.rowBackgroundColor.up = "#ffffff";
3209         MenuView.rowBackgroundColor.disable = "#999999";
3210 
3211         MenuView.rowPadding.top = 2;
3212         MenuView.rowPadding.right = 2;
3213         MenuView.rowPadding.bottom = 2;
3214         MenuView.rowPadding.left = 2;
3215 
3216         MenuView.rowMargin.top = 0;
3217         MenuView.rowMargin.right = 0;
3218         MenuView.rowMargin.bottom = 0;
3219         MenuView.rowMargin.left = 0;
3220 
3221         MenuView.defaultLcon = "min";
3222         MenuView.buttonType = -1;
3223     
3224     }
3225 
3226     static _cac = new CanvasAnimateCustom();
3227     static nowView = null;
3228     static clearNowView(){
3229         if(MenuView.nowView === null) return;
3230         MenuView.nowView.traverseUp(v => v.visible = false);
3231         MenuView.nowView = null;
3232 
3233     }
3234 
3235     #length = 0;
3236     #visible = false;
3237 
3238     get top(){
3239         return this.view.domElementRect.y;
3240     }
3241 
3242     get left(){
3243         return this.view.domElementRect.x;
3244     }
3245 
3246     get width(){
3247         return this.view.box.w;
3248     }
3249 
3250     get height(){
3251         return this.view.box.h;
3252     }
3253 
3254     get visible(){
3255         return this.#visible;
3256     }
3257 
3258     set visible(v){
3259         if(v === true){
3260             this.update(); //更新自己及自己第一层后代位置 
3261             this.#visible = true;
3262             this.view.domElement.style.visibility = "visible";
3263         }
3264 
3265         else if(v === false){
3266             this.#visible = false;
3267             this.view.domElement.style.visibility = "hidden";
3268 
3269             //hidden root
3270             if(this.parent === null){
3271                 this.traverse(v => {
3272                     if(v && v.visible !== false) v.visible = false;
3273                     
3274                 });
3275         
3276             }
3277 
3278         }
3279         
3280     }
3281 
3282     constructor(names, funcs, lcons, parentElem){
3283         super();
3284         
3285         this.names = names || [];
3286         this.funcs = funcs || null;
3287         this.lcons = lcons || null;
3288         this.parentElem = parentElem || MenuView.parentElem || document.body;
3289 
3290         this.view = new CanvasAnimateRender({className: MenuView.className});
3291         this.events = new CanvasAnimateEvent(this.view);
3292         this.views = [];
3293         //this._eventTargets = [];
3294         this.parentElemRect = null;
3295         
3296         if(typeof names[0] !== "string") this.importData(names);
3297         this.initView(this.names);
3298 
3299     }
3300 
3301     _traverse(callback, key){
3302         if(callback(this, key) !== "continue"){
3303             for(let k = 0; k < this.#length; k++){
3304                 if(this.children[k]) this.children[k].traverse(callback, k);
3305                 else callback(null, k, this);
3306     
3307             }
3308 
3309         }
3310 
3311     }
3312 
3313     traverse(callback){
3314         this._traverse(callback, this.parent === null ? -1 : this.parent.children.indexOf(this));
3315     }
3316 
3317     getViewByName(name){
3318         const k = this.names.indexOf(name);
3319         if(k !== -1) return this.views[k];
3320     }
3321 
3322     add(v, k){
3323 
3324         v.parent = this;
3325 
3326         if(this.children.includes(v) === false){
3327 
3328             if(k === undefined) this.children.push(v);
3329 
3330             else{
3331 
3332                 if(typeof k === "string") k = this.names.indexOf(k);
3333 
3334                 if(this.children[k]) this.children[k].exit();
3335                 
3336                 this.children[k] = v;
3337                 
3338             }
3339 
3340         }
3341 
3342         if(v.parentElem !== this.parentElem){
3343             v.parentElem = this.parentElem;
3344             v.parentElemRect = this.parentElemRect;
3345         }
3346         
3347         return v;
3348     }
3349 
3350     exit(){
3351 
3352         //清理 MenuView.nowView
3353         MenuView.clearNowView();
3354 
3355         this.traverse(v => {
3356             if(!v) return;
3357 
3358             //清理.view
3359             if(v.view.domElement.parentElement) v.view.domElement.parentElement.removeChild(v.view.domElement);
3360 
3361             //清理.events (over out 事件)
3362             v.events.disposeEvent();
3363             /* for(let k = 0, len = v._eventTargets.length; k < len; k++){
3364                 v.events.clear(v._eventTargets[k], "over");
3365                 v.events.clear(v._eventTargets[k], "out");
3366             } */
3367             
3368         });
3369 
3370         //从树结构中移出
3371         if(this.parent !== null) this.parent.remove(this);
3372 
3373         this.views.length = 0;
3374 
3375     }
3376 
3377     _update(v, k, p){
3378         
3379         //child hidden
3380         if(!v){
3381             const child = p.views[k].child;
3382             if(child.visible !== false){
3383                 child.visible = false;
3384                 p.view.redraw();
3385             }
3386             
3387             return;
3388         }
3389 
3390         if(v.parent === null) return;
3391 
3392         if(v.parent.parentElem !== v.parentElem) v.parentElem = v.parent.parentElem;
3393         if(v.parent.parentElemRect !== v.parentElemRect) v.parentElemRect = v.parent.parentElemRect;
3394 
3395         //child visible
3396         const child = v.parent.views[k].child;
3397         if(child.visible !== true){
3398             child.visible = true;
3399             v.parent.view.redraw();
3400         }
3401         
3402         //canvas position (bug: 不能直接 实时的引用 MenuView 的静态属性, 最好在初始化视图时缓存所需的静态属性)
3403         const x = v.parent.left + v.parent.width - MenuView.rowPadding.left - MenuView.rowMargin.left;
3404         if(x + v.view.box.w > v.parent.parentElemRect.x + v.parent.parentElemRect.width) v.view.pos(v.parent.left - v.width, (MenuView.rowPadding.top + MenuView.rowPadding.bottom + MenuView.lconSize) * k + MenuView.rowMargin.top * k + MenuView.rowMargin.top +  v.parent.top);
3405         else v.view.pos(x, (MenuView.rowPadding.top + MenuView.rowPadding.bottom + MenuView.lconSize) * k + MenuView.rowMargin.top * k + MenuView.rowMargin.top +  v.parent.top);
3406 
3407     }
3408 
3409     update(){ //更新自己第一层后代 (如果更新所有后代: this.traverse(this._update))
3410         if(this.parent === null){
3411             if(this.parentElemRect === null) this.parentElemRect = {x: 0, width: 0};
3412             const rect = this.parentElem.getBoundingClientRect();
3413             this.parentElemRect.x = rect.x;
3414             this.parentElemRect.width = rect.width;
3415         }
3416 
3417         else{
3418             if(this.parent.parentElem !== this.parentElem) this.parentElem = this.parent.parentElem;
3419             if(this.parent.parentElemRect !== this.parentElemRect) this.parentElemRect = this.parent.parentElemRect;
3420             this._update(this, this.parent.children.indexOf(this));
3421         }
3422 
3423         for(let k = 0; k < this.#length; k++) this._update(this.children[k], k, this);
3424         
3425     }
3426 
3427     _setHiddenOrChild(k){
3428         if(k !== -1 && this.parent !== null && this.parent.views[k]){
3429             const obj = this.parent.views[k];
3430             if(obj.disable === false){
3431                 if(obj.child !== null) obj.child.set(1);
3432                 if(obj.background !== null) obj.background.set(1);
3433                 if(obj.border !== null) obj.border.set(1);
3434                 obj.name.set(1);
3435                 this.parent.view.redraw();
3436             }
3437             
3438         }
3439 
3440         if(this.#visible !== false) this.visible = false;
3441 
3442     }
3443 
3444     clearNowView(k){
3445         if(MenuView.nowView !== null && MenuView.nowView !== this && MenuView.nowView !== this.children[k]){
3446 
3447             if(MenuView.nowView.parent === this) MenuView.nowView._setHiddenOrChild(MenuView.nowView.parent === null ? -1 : MenuView.nowView.parent.children.indexOf(MenuView.nowView));
3448 
3449             else{
3450 
3451                 let tar = null;
3452 
3453                 MenuView.nowView.traverse((v, key) => {
3454 
3455                     if(v){
3456                         if(v !== this && v !== this.children[k]) v._setHiddenOrChild(key);
3457                         else tar = v;
3458                     }
3459                     
3460                 });
3461                 
3462                 if(tar === null){
3463 
3464                     MenuView.nowView.traverseUp(v => {
3465 
3466                         if(v !== this && v !== this.children[k]) v._setHiddenOrChild(v.parent === null ? -1 : v.parent.children.indexOf(v));
3467                         else return "break";
3468 
3469                     });
3470 
3471                 }
3472 
3473             }
3474         
3475         }
3476 
3477     }
3478 
3479     importData(data){
3480         data = data || this.names;
3481         if(Array.isArray(data) === false) return console.warn("MenuView: 导入失败");
3482         this.names = [];
3483 
3484         for(let k = 0, len = data.length; k < len; k++){
3485             if(!data[k] || typeof data[k].name !== "string") return console.warn("MenuView: 导入失败");
3486 
3487             this.names.push(data[k].name);
3488 
3489             if(typeof data[k].func === "function"){
3490                 if(this.funcs === null) this.funcs = [];
3491                 this.funcs[k] = data[k].func;
3492             }
3493 
3494             if(data[k].lcon){
3495                 if(this.lcons === null) this.lcons = [];
3496                 this.lcons[k] = data[k].lcon;
3497             }
3498 
3499         }
3500 
3501     }
3502 
3503     initView(names){
3504         names = names || this.names;
3505         this.#length = names.length;
3506         if(Array.isArray(names) === false || this.#length === 0) return console.warn("MenuView: 初始化失败");
3507         
3508         const th = this, size = MenuView.textSize, lconSize = MenuView.lconSize, borSize = MenuView.borderSize, defaultLcon = MenuView.defaultLcon, buttonType = MenuView.buttonType;
3509 
3510         var _cac = MenuView._cac, _path = [1, 1, 1, lconSize-1, lconSize-1, lconSize/2],
3511         color = MenuView.textColor, bgColor = MenuView.rowBackgroundColor, 
3512         borColor = MenuView.rowBorderColor, padding = MenuView.rowPadding, margin = MenuView.rowMargin,
3513         maxTextWidth = 0, maxWidth = 0, maxHeight = 0;
3514 
3515         //set maxTextWidth
3516         for(let k = 0; k < this.#length; k++){
3517             _cac.box.set(0, 0, 0, 0);
3518             _cac.text(names[k], "", size);
3519             if(_cac.box.w > maxTextWidth) maxTextWidth = _cac.box.w;
3520         }
3521 
3522         //initView
3523         for(let k = 0; k < this.#length; k++){
3524             const _h = padding.top + padding.bottom + lconSize, //
3525             _y = _h * k + margin.top * k + margin.top,
3526             bgW = padding.left + padding.right + maxTextWidth + lconSize * 2 + 4;
3527 
3528             //background
3529             let bg = null;
3530             if(bgColor !== null){
3531                 bg = new CanvasAnimateImages().pos(margin.left, _y);
3532                 _cac.size(bgW, _h).rect();
3533                 _cac.context.lineWidth = borSize || 1;
3534                 bg.setImage(_cac.clear().fill(bgColor.disable).shear());
3535                 bg.setImage(_cac.clear().fill(bgColor.up).shear());
3536                 bg.setImage(_cac.clear().fill(bgColor.down).shear());
3537 
3538                 bg.set(1);
3539                 this.view.list.push(bg);
3540             }
3541 
3542             //defined lcons: "✘", "✔", "●", "◆", "■", "★"
3543             let lcon_def = null;
3544             if(defaultLcon !== ""){
3545                 _cac.size(lconSize, lconSize);
3546 
3547                 if(defaultLcon === "max") lcon_def = new CanvasAnimateImages([
3548                     _cac.clear().text("✘", color.disable, size, "center", "center").shear(),
3549                     _cac.clear().text("✔", color.down, size, "center", "center").shear(),
3550                     _cac.clear().text("●", color.down, size, "center", "center").shear(),
3551                     _cac.clear().text("◆", color.down, size, "center", "center").shear(),
3552                     _cac.clear().text("■", color.down, size, "center", "center").shear(),
3553                     _cac.clear().text("★", color.down, size, "center", "center").shear()
3554                 ]);
3555                     
3556                 else lcon_def = new CanvasAnimateImages([
3557                     _cac.clear().text("✘", color.disable, size, "center", "center").shear(),
3558                     _cac.clear().text("✔", color.down, size, "center", "center").shear(),
3559                     _cac.clear().text("●", color.down, size, "center", "center").shear()
3560                 ]);
3561 
3562                 lcon_def.pos(margin.left + padding.left, _y + padding.top);
3563                 this.view.list.push(lcon_def);
3564 
3565             }
3566 
3567             //lcons
3568             let lcon = null;
3569             if(Array.isArray(this.lcons) === true && Array.isArray(this.lcons[k]) === true){
3570                 lcon = new CanvasAnimateImages(this.lcons[k]).pos(margin.left + padding.left, _y + padding.top);
3571                 lcon.set(0);
3572                 this.view.list.push(lcon);
3573 
3574             }
3575 
3576             //names
3577             _cac.size(maxTextWidth, lconSize);
3578             const str = new CanvasAnimateImages([
3579                 _cac.clear().text(names[k], color.disable, size, "", "center").shear(),
3580                 _cac.clear().text(names[k], color.up, size, "", "center").shear(),
3581                 _cac.clear().text(names[k], color.down, size, "", "center").shear(),
3582                 
3583             ]).pos(margin.left + padding.left + lconSize + 2, _y + padding.top);
3584             str.set(1);
3585             this.view.list.push(str);
3586 
3587             //child
3588             _cac.size(lconSize, lconSize).path(_path, true);
3589             const child = new CanvasAnimateImages([
3590                 _cac.clear().stroke(color.disable, 1).shear(),
3591                 _cac.clear().fill(color.up, 1).shear(),
3592                 _cac.clear().stroke(color.down, 1).shear(),
3593                 
3594             ]).pos(str.box.x + maxTextWidth + 2, _y + padding.top);
3595             
3596             child.set(1);
3597             this.view.list.push(child);
3598 
3599             //border
3600             let bor = null;
3601             if(borSize > 0){
3602                 bor = new CanvasAnimateImages().pos(margin.left, _y);
3603                 _cac.size(bgW, _h).rect();
3604                 bor.setImage(_cac.clear().stroke(borColor.disable, borSize).shear());
3605                 bor.setImage(_cac.clear().stroke(borColor.up, borSize).shear());
3606                 bor.setImage(_cac.clear().stroke(borColor.down, borSize).shear());
3607                 bor.set(1);
3608                 this.view.list.push(bor);
3609             }
3610 
3611             //views
3612             let _disable = false, i_lcon = -1, i_lcon_def = -1;
3613             this.views.push({
3614                 background: bg,
3615                 lcon_def: lcon_def,
3616                 lcon: lcon,
3617                 name: str,
3618                 child: child,
3619                 border: bor,
3620                 key: k,
3621                 target: this,
3622                 get disable(){
3623                     return _disable;
3624                 },
3625                 set disable(v){
3626                     if(v === true){
3627                         th.clearNowView(k);
3628                         if(th.children[k]) th.children[k].visible = false;
3629                         if(lcon_def !== null){
3630                             i_lcon_def = lcon_def.i;
3631                             lcon_def.set(0);
3632                         }
3633                         if(lcon !== null){
3634                             i_lcon = lcon.i;
3635                             lcon.set(-1);
3636                         }
3637                         if(bg !== null) bg.set(0);
3638                         if(bor !== null) bor.set(0);
3639                         str.set(0);
3640                         child.set(0);
3641                         _disable = true;
3642                     }
3643 
3644                     else if(v === false){
3645                         if(lcon_def !== null) lcon_def.set(i_lcon_def);
3646                         if(lcon !== null) lcon.set(i_lcon);
3647                         if(bg !== null) bg.set(1);
3648                         if(bor !== null) bor.set(1);
3649                         str.set(1);
3650                         child.set(1);
3651                         _disable = false;
3652                     }
3653                     
3654                 },
3655             });
3656 
3657             //maxWidth, maxHeight
3658             const mw = bgW + margin.left + margin.right;
3659             if(maxWidth < mw) maxWidth = mw;
3660             const mh = _y + _h + margin.bottom;
3661             if(maxHeight < mh) maxHeight = mh;
3662 
3663             //event
3664             const eventTarget = bor || bg || str;
3665             //this._eventTargets.push(eventTarget);
3666 
3667             if(Array.isArray(this.funcs) === true){
3668                 let _isDown = false, pointerId = -1;
3669                 this.events.add(eventTarget, "down", event => {
3670                     if(_disable === true || (buttonType !== -1 && event.button !== buttonType)) return _isDown = false;
3671                     
3672                     if(!this.children[k]){
3673                         if(bg !== null) bg.set(2);
3674                         if(bor !== null) bor.set(2);
3675                         this.view.redraw();
3676                     }
3677                     
3678                     pointerId = event.pointerId;
3679                     _isDown = true;
3680                 });
3681     
3682                 this.events.add(eventTarget, "up", event => {
3683                     if(pointerId !== event.pointerId || _isDown === false || _disable === true || (buttonType !== -1 && event.button !== buttonType)) return;
3684                     pointerId = -1;
3685                     if(!this.children[k]){
3686                         if(bg !== null) bg.set(1);
3687                         if(bor !== null) bor.set(1);
3688                     }
3689                     
3690                     if(typeof this.funcs[k] === "function") this.funcs[k](this.views[k], event);
3691                     this.view.redraw();
3692                 });
3693 
3694             }
3695         
3696             this.events.add(eventTarget, "over", () => {
3697                 if(_disable === true) return;
3698                 if(this.children[k]){
3699                     if(bg !== null) bg.set(2);
3700                     if(bor !== null) bor.set(2);
3701                 }
3702                 str.set(2);
3703                 
3704                 this.clearNowView(k);
3705                 if(this.children[k] !== undefined){
3706                     MenuView.nowView = this.children[k];
3707                     MenuView.nowView.visible = true;
3708                     child.set(2);
3709                 }
3710 
3711                 this.view.redraw();
3712             });
3713 
3714             this.events.add(eventTarget, "out", () => {
3715                 if(_disable === true) return;
3716 
3717                 if(this.children[k]){
3718                     if(bg !== null) bg.set(1);
3719                     if(bor !== null) bor.set(1);
3720                 }
3721                 str.set(1);
3722                 this.view.redraw();
3723                 
3724             });
3725 
3726         }
3727         
3728         //init this.view
3729         this.view.initList();
3730         this.view.size(maxWidth, maxHeight);
3731         this.view.domElement.style.backgroundColor = bgColor.up;
3732         this.view.domElement.style.position = "absolute";
3733         this.view.render(this.parentElem);
3734         this.visible = this.#visible;
3735         if(MenuView.sign !== ""){
3736             if(this.view.domElement[MenuView.sign] === undefined) this.view.domElement[MenuView.sign] = true;
3737             else console.warn("MenuView: 标记失败, 属性名重复 "+MenuView.sign);
3738         }
3739         
3740         _cac = color = bgColor = borColor = padding = margin = null;
3741         
3742     }
3743 
3744 }
3745 
3746 
3747 
3748 
3749 /* ImageViewer 图片查看器
3750 
3751 parameter:
3752     option: {
3753         defaultEvent: Bool; //默认true
3754     };
3755 
3756 attribute:
3757     image: CanvasImage;
3758     scale: Number; //小于1缩小, 大于1放大
3759 
3760 method:
3761     center(): this; //图片在视口居中
3762     setViewportScale: this; //将图片按比例缩放至视口大小
3763     setScale(v): this; //设置.scale
3764     setImage(image): this; //设置.image
3765     drawImage(): this; //更新图像 (之后需要.redraw()更新画布)
3766 
3767 demo:
3768     const imageViewer = new ImageViewer({
3769         width: this.width, 
3770         height: this.width, 
3771         className: 'shadow-convex',
3772     });
3773 
3774     imageViewer.domElement.style = `
3775         background-color: rgb(127,127,127);
3776         border-radius: 4px;
3777         z-index: 99999;
3778         position: absolute;
3779     `;
3780 
3781     imageViewer.pos(100, 100).setImage(image)
3782     .setViewportScale().center()
3783     .drawImage().render();
3784 
3785 */
3786 class ImageViewer extends CanvasAnimateRender{
3787 
3788     //允许图片的最大宽高 (min 不能小于1)
3789     #min = 1;
3790     #max = 4096; 
3791 
3792     constructor(option){
3793         super(option);
3794 
3795         this.eventDispatcher = new CanvasAnimateEvent(this);
3796         this.target = this.add(new CanvasAnimateCustom()).size(this.box.w, this.box.h);
3797 
3798         this.image = null;
3799         this.scale = 1;
3800         
3801         this._rangeMin = 0;
3802         this._rangeMax = 0;
3803         this._box = new Box();
3804         this._oldWidth = this._box.w;
3805 
3806         if(option.defaultEvent === false) return this;
3807 
3808         //拖拽事件
3809         var tzX = 0, tzY = 0;
3810         const onMove = event => {
3811             this._box.pos(event.pageX - this.domElementRect.x - tzX, event.pageY - this.domElementRect.y - tzY);
3812             this.drawImage().redraw();
3813 
3814         },
3815 
3816         onUp = () => {
3817             document.body.removeEventListener("pointerup", onUp);
3818             document.body.removeEventListener("pointermove", onMove);
3819 
3820             this.eventDispatcher.remove(this.target, 'up', onUp);
3821             this.eventDispatcher.remove(this.target, 'move', onMove);
3822             
3823         }
3824 
3825         this.eventDispatcher.add(this.target, "down", event =>{
3826             onUp();
3827 
3828             if(this.image === null) return;
3829 
3830             tzX = event.offsetX - this._box.x;
3831             tzY = event.offsetY - this._box.y;
3832             
3833             this.eventDispatcher.add(this.target, 'up', onUp);
3834             this.eventDispatcher.add(this.target, 'move', onMove);
3835             
3836             document.body.addEventListener("pointerup", onUp);
3837             document.body.addEventListener("pointermove", onMove);
3838 
3839         });
3840 
3841         //缩放事件 (以鼠标为中心点缩放)
3842         this.eventDispatcher.add(this.target, "wheel", event =>{
3843 
3844             const oldWidth = this._box.w;
3845             this.setScale(event.wheelDelta === 120 ? this.scale - this.scale * 0.5 : this.scale + this.scale * 0.5);
3846 
3847             const ratio = this._box.w / oldWidth,
3848             nx = event.offsetX - ((event.offsetX - this._box.x) * ratio + this._box.x) + this._box.x,
3849             ny = event.offsetY - ((event.offsetY - this._box.y) * ratio + this._box.y) + this._box.y;
3850             
3851             this._box.pos(nx, ny);
3852             this.drawImage().redraw();
3853 
3854         });
3855 
3856         //旋转事件
3857 
3858 
3859         this._onUp = onUp;
3860 
3861     }
3862 
3863     exit(){
3864         if(this._onUp) this._onUp();
3865         if(this.domElement.parentElement) this.domElement.parentElement.removeChild(this.domElement);
3866 
3867     }
3868     
3869     size(w, h, setElem){
3870         super.size(w, h, setElem);
3871         this.target.size(this.box.w, this.box.h);
3872 
3873         return this;
3874     }
3875     
3876     center(){
3877         this._box.pos((this.box.w - this._box.w)/2, (this.box.h - this._box.h)/2);
3878 
3879         return this;
3880         
3881     }
3882 
3883     setViewportScale(){
3884         if(this.image === null) return this;
3885         const width = this.image.width, ratio = width / this.image.height;
3886         
3887         return this.setScale(ratio < 1 ? ratio * this.box.w / width : this.box.w / width);
3888     }
3889 
3890     setScaleAt(x, y){ //x, y 是相对于画布位置
3891         const ratio = this._box.w / this._oldWidth,
3892         nx = x - ((x - this._box.x) * ratio + this._box.x) + this._box.x,
3893         ny = y - ((y - this._box.y) * ratio + this._box.y) + this._box.y;
3894         
3895         this._box.pos(nx, ny);
3896 
3897     }
3898 
3899     setScale(v){
3900         if(v === Infinity || isNaN(v) === true || typeof v !== "number") return this;
3901         this.scale = v < this._rangeMin ? this._rangeMin : v > this._rangeMax ? this._rangeMax : v;
3902         if(this.image !== null){
3903             this._box.size(this.image.width * this.scale, this.image.height * this.scale);
3904             this._oldWidth = this._box.w;
3905         }
3906         return this;
3907     }
3908 
3909     setImage(image){
3910 
3911         if(this.isCanvasImage(image) === true){
3912             
3913             if(image.width > image.height){
3914                 this._rangeMin = this.#min / image.width;
3915                 this._rangeMax = this.#max / image.width;
3916 
3917             }
3918 
3919             else{
3920                 this._rangeMin = this.#min / image.height;
3921                 this._rangeMax = this.#max / image.height;
3922 
3923             }
3924 
3925             this.image = image;
3926 
3927         }
3928 
3929         else if(this.image !== null){
3930 
3931             this.image = null;
3932             this.target.clear();
3933             this.redraw();
3934 
3935         }
3936 
3937         return this;
3938     }
3939 
3940     drawImage(){
3941 
3942         if(this.image !== null) this.target.clear().context.drawImage(this.image, this._box.x, this._box.y, this._box.w, this._box.h);
3943 
3944         return this;
3945     }
3946 
3947 }
3948 
3949 
3950 
3951 
3952 /* CanvasAnimateUI UI控件
3953 依赖:
3954     ImageViewer
3955     MenuView
3956     ColorTestViewer
3957 
3958 注意:
3959     传入的 .target 和 .data: CanvasUI不污染其内容(既只读), 所以可以重复利用;
3960     一旦初始化完UI后(.initUI()) 修改.data属性对控件运行没任何影响;
3961     如果想修改 Euler 控件的 range.step 点input框输入: step: 0.01; (两边或冒号两边可以有空格)
3962 
3963 支持的类型: 
3964     string    (文本控件)
3965     color    (颜色控件)
3966     number    (数值控件)
3967     boolean    (复选框控件)
3968     button    (按钮控件)
3969     object    (Point, Vector2, Vector3, Euler, Color, RGBColor, CanvasImageData), //暂不支持: Vector4, Box
3970 
3971     //以下控件其属性值可以是任意类型
3972     select    (单选控件, 定义.selectList 显示声明)
3973     file    (导入控件, .type = json, image 显示声明)
3974 
3975     //特殊
3976     line    (分割线, .type = 'line'; .value = valueUrl|Array[valueUrl])
3977 
3978 parameter(target, data, parentElem, option)
3979     target: Object;    //
3980 
3981     data: Array[
3982         {
3983             //必须:
3984             valueUrl: string, //路径,忽略所有的空格;
3985             例(.链接对象; []链接数组; 不可以混淆): 
3986             target = {name: 'name', arr:[10], obj: null}; 
3987             data = [
3988                 {valueUrl: '.name'},
3989                 {valueUrl: '.arr[0]'},
3990                 {valueUrl: '.obj.name'} //此valueUrl视为 undefined (既忽略此条)
3991             ]
3992             
3993             //可选:
3994             type: String; //可能的值: line, image, json, select
3995             title: String; //标题
3996             explain: String; //详细说明
3997             update: boolean //this.update() 是否可以更新此控件 (注意优先级高于 this.globalUpdate)
3998             disable: boolean //启用或禁用控件(可以通过.onChange 的参数.disable属性随时调整此控件的启用和禁用)
3999             onChange: Function(Object{ //控件使属性值改变时触发
4000                 data:        Object,
4001                 scope:         CanvasAnimateUI,            //
4002                 update:     Function|Array[Function],     //使此控件主动读一次value值,但不更新视图;(配合.redraw 把value传递至控件)
4003                 redraw:     Function,                    //只绘制此控件; obj.target.value = 123; obj.update(); obj.redraw();
4004                 disable:     Boolean,                    //启用或禁用此控件
4005                 target:     Object{value},                //target.object: 是valueUrl的上一层引用, 如果只有一层则等于 CanvasAnimateUI.target
4006                 valueName:    String,                        //此属性只属于数字控件
4007             }), 
4008 
4009             range: object{min, max, step: Number}, //数字控件的范围 默认 this.defiendRange
4010             selectList: Array[Object{name:String, value:任意值}], //显示指定为select 或 type = select;
4011             value: valueUrl || Array[valueUrl], //type 为 line 时才有效; (只要其中某一个 valueUrl 指向的值不等于undefined 则渲染此line; 如果全都等于undefined 或 紧挨的上层也是线 将忽略此line)
4012 
4013         }
4014     ]
4015 
4016     parentElem: DomElement;    //父容器 默认null
4017 
4018     option = {
4019         autoHeight: Bool        //是否自动设置可视高 默认 false
4020         width, height,            //可视宽高
4021         eachHeight,                //每项的高 默认 30
4022         margin,                 //边距 默认 4
4023         titleColor, conColor,    //颜色 默认#000
4024         fontSize,                 //字体大小 默认 12 (建议不超过 .eachHeight)
4025         globalUpdate,             // 默认 false
4026         onChanges,                // 默认 null
4027         defiendRange,            //默认 {min: -999999, max: 999999, step: 1};
4028         numberCursorSize        //数字控件的游标大小比例, 值为 0 - 1 之间; 默认 0.5
4029         style: String            //如果定义则使用.style(style)
4030         
4031     }
4032 
4033 attribute:
4034     target: Object;             //默认null
4035     data: Array;                 //默认null
4036     onChanges: Function(Object); //默认null
4037     defiendRange: Object;         //默认{min: -999999, max: 999999, step: 1};
4038     parent: DomElement;            //默认 body
4039     globalUpdate: Boolean;        //默认false; 如果为true则创建的所有控件都可以在.update() 里面更新; update:false的控件除外
4040 
4041 method:
4042     style(style: String): this;            //添加css样式 (width, height, padding 属性会被覆盖, 可以通过构造器的参数设置这些值)
4043     pos(left, top: Number): this;        //设置位置(前提设定了css定位)
4044     render(parentElem): this;            //添加到dom树
4045     initUI(target, data): this;            //初始化控件
4046     update(redraw: Bool): undefined;     //更新所有已定义为更新(data.update = true)的控件
4047     getView(data): Object;                 //根据所给的data获取 可操控的对象(假设 this.update() 可以更新此data代表的控件); 返回的对象恒等于 .onChange 的参数
4048     getData(valueUrl): Object;             //获取data
4049     
4050 demo:
4051 
4052     const splitLineOfNumber = {type: 'line', value: [".num", ".vector2", '.vector3']},
4053 
4054     selectList = [
4055         {name: "ZeroZeroZeroZeroZero", value: 0},
4056         {name: "OneOneOneOneOne", value: 1},
4057         {name: "TwoTwoTwoTwoTwo", value: 2},
4058         {name: "ThreeThreeThreeThreeThree", value: 3},
4059     ],
4060 
4061     target = {
4062         str: "字符串String",
4063         checkbox: true,
4064         func: v=>console.log(v),
4065         select: 1,
4066         image: null,
4067 
4068         colorHEX: " # f0 0f0 f",
4069         colorRGB: "rgba(11, 22, 255, 1)",
4070         color: "  b l u e ",
4071         colorRGB: new RGBColor(),
4072         colorTHREE: new THREE.Color(),
4073 
4074         num: 0,
4075         vector2: new THREE.Vector2(30, 70),
4076         vector3: new THREE.Vector3(0, 30, 70),
4077 
4078     },
4079 
4080     data = [
4081         {
4082             title: "文本",
4083             valueUrl: ".str",
4084         },
4085 
4086         {
4087             title: "函数",
4088             valueUrl: ".func",
4089         },
4090 
4091         {
4092             title: "颜色",
4093             valueUrl: ".color",
4094         },
4095 
4096         {
4097             title: "复选框",
4098             valueUrl: ".checkbox",
4099         },
4100 
4101         {
4102             title: "单选",
4103             valueUrl: ".select",
4104             selectList: selectList,
4105         },
4106 
4107         //分割线 开始
4108         splitLineOfNumber,
4109         {
4110             title: "数字",
4111             explain: '游标的范围可在range范围内调整',
4112             valueUrl: ".num",
4113             range: {min: -10, max: 10, step: 0.1},
4114             update: true,
4115             onChange: v => console.log(v),
4116         },
4117 
4118         {
4119             title: "坐标2",
4120             valueUrl: ".vector2",
4121         },
4122         splitLineOfNumber,
4123         //分割线 结束
4124 
4125         {
4126             title: "图片",
4127             valueUrl: ".image",
4128             type: "image",
4129         },
4130 
4131     ],
4132 
4133     cau = new CanvasAnimateUI(target, data, null, {
4134         width: 300, 
4135         height: 210,    //可视宽高
4136         eachHeight: 30,    //每项的高
4137         fontSize: 12,     //字体大小
4138         defiendRange: {min: 0, max: 100, step: 1},
4139     })
4140 
4141     .style(`
4142         z-index: 9999;
4143         position: absolute;
4144         left: 200px;
4145         top: 190px;
4146         background: #fff;
4147     `)
4148 
4149     .initUI()
4150     .render();
4151 
4152     //test
4153     new Timer(()=>{
4154         const obj = cau.getView(cau.getData('.num'));
4155         obj.disable = true;
4156         obj.target.value = 0.5;
4157         obj.update();
4158         obj.redraw();
4159         console.log(obj);
4160     }, 5000).start();
4161 
4162 */
4163 class CanvasAnimateUI extends CanvasAnimateRender{
4164 
4165     #i = 0;
4166     #h = 0;
4167     #isLine = false;
4168 
4169     get width(){
4170         return this.box.w;
4171     }
4172 
4173     get height(){
4174         return this.box.h;//return this.domElement.height;
4175     }
4176 
4177     get clientWidth(){
4178         return this.box.w;
4179     }
4180 
4181     get clientHeight(){
4182         return this._ch;
4183     }
4184 
4185     static emptyCAC = new CanvasAnimateCustom();
4186     static emptyTimerA = new Timer(null, 500, Infinity, false);
4187     static emptyFunc(){}
4188     static stopTimers(){
4189         CanvasAnimateUI.emptyTimerA.stop();
4190         CanvasAnimateUI.emptyTimerA.speed = 500;
4191     }
4192 
4193     constructor(target = null, data = null, parentElem = null, option = {}){
4194         super(option);
4195         
4196         this.autoHeight = typeof option.autoHeight === 'boolean' ? option.autoHeight : false;
4197         this.target = target;
4198         this.data = data;
4199         this.globalUpdate = typeof option.globalUpdate === "boolean" ? option.globalUpdate : false;
4200         this.onChanges = option.onChanges || null;
4201         this.defiendRange = option.defiendRange || {min: -999999, max: 999999, step: 1};
4202         //this.readValueUrlType = option.readValueUrlType || "";
4203         
4204         this._margin = option.margin || 4;
4205         this._height = option.eachHeight || 30;
4206         this._titleColor = option.titleColor || "#000";
4207         this._conColor = option.conColor || "#000";
4208         this._titleWidth = this.width * 0.25;
4209         this._conWidth = this.width * 0.75;
4210         this._fontSIze = option.fontSize || 12;
4211         this._stringMaxCount = Math.floor(this._conWidth / this._fontSIze) * 2;
4212         this._funcTitleOut = ()=>this.domElement.title = "";
4213         this._numberInputWidth = 0;
4214         this._ch = this.box.h;
4215         
4216         //this._lines = [];
4217         this._menuViews = [];
4218         this._loopData = [];
4219         this._loopObject = [];
4220         this._globalCA = new CanvasAnimate();
4221 
4222         this.cae = new CanvasAnimateEvent(this);
4223         this.imageViewer = new ImageViewer({width: this.width, height: this.width});
4224         this.colorTestView = new ColorTestViewer({width: this._conWidth});
4225         this.parent = this._getParent();
4226         this._input = this._getInput();
4227         this._inputFunc = null;
4228         
4229         this._initImages(option.numberCursorSize);
4230 
4231         if(parentElem !== null) this.render(parentElem);
4232         this._styleShadowConvex(this.imageViewer.domElement);
4233         this._styleShadowConvex(this.colorTestView.domElement);
4234         if(typeof option.style === 'string') this.style(option.style);
4235         
4236     }
4237 
4238     exit(){
4239         this.cae.disposeEvent(); //event
4240         for(let i = 0, c = this._menuViews.length; i < c; i++) this._menuViews[i].exit(); //this._menuViews
4241         this.imageViewer.exit(); //this.imageViewer
4242         this.colorTestView.exit(); //this.colorTestView
4243         if(this._input.parentElement) this._input.parentElement.removeChild(this._input); //this._input
4244         if(this.parent.parentElement) this.parent.parentElement.removeChild(this.parent); //this.parent
4245 
4246     }
4247 
4248     render(parentElem){
4249         if(!parentElem) parentElem = document.body;
4250         parentElem.appendChild(this.parent);
4251         super.updateCanvas();
4252 
4253         this.imageViewer.domElement.style = `
4254             background-color: rgb(127,127,127);
4255             border-radius: 4px;
4256             z-index: 99999;
4257             display: none;
4258             position: absolute;
4259         `;
4260 
4261         this.colorTestView.domElement.style = `
4262             background-color: #fff;
4263             border-radius: 4px;
4264             z-index: 99999;
4265             display: none;
4266             position: absolute;
4267         `;
4268 
4269         this.imageViewer.render();
4270         this.colorTestView.render();
4271         document.body.appendChild(this._input);
4272 
4273         return this;
4274 
4275     }
4276 
4277     initUI(target, data){
4278         this.imageViewer.domElement.style.display = 
4279         this.colorTestView.domElement.style.display = 
4280         this._input.style.display = "none";
4281 
4282         for(let i = 0, c = this._menuViews.length; i < c; i++) this._menuViews[i].exit();
4283 
4284         this.cae.disposeEvent();
4285         this.cae._eventList = {};
4286         this.#isLine = false;
4287         this.#h = this.#i = 
4288         this._menuViews.length = 
4289         this.list.length = 
4290         //this._lines.length = 
4291         this._loopData.length = 
4292         this._loopObject.length = 0;
4293         
4294         if(target && this.target !== target) this.target = target;
4295         if(data && this.data !== data) this.data = data;
4296         if(!this.target || !this.data) return this;
4297         this.updateCanvas();
4298 
4299         //create view
4300         for(let k = 0, len = this.data.length, data, y; k < len; k++){
4301             data = this.data[k]; 
4302             y = this._height * this.#i + this.#i * this._margin;
4303             if(this._initUI(data, y) !== true) continue;
4304             this.#h += this._height;
4305             this.#i++;
4306             this.#isLine = data.type === 'line';
4307             //nextMake = this._initMake(data, y, nextMake);
4308 
4309             /* //this._lines
4310             c = this._lines.length;
4311             for(i = 0; i < c; i++){
4312                 ca = this._lines[i];
4313                 k = ca._content.indexOf(data.valueUrl);
4314                 if(k !== -1) ca._content[k+1] = true;
4315             } */
4316 
4317         }
4318         
4319         /* //this._lines 是否绘制
4320         for(let i = 0, c = this._lines.length, k, len, ca = null; i < c; i++){
4321             ca = this._lines[i];
4322 
4323             len = ca._content.length;
4324             for(k = 0; k < len; k += 2){
4325                 if(ca._content[k+1] === true){
4326                     ca = null;
4327                     break;
4328                 }
4329             }
4330             
4331             if(ca !== null){
4332                 i = this.list.indexOf(ca);
4333                 this.list.splice(i, 1);
4334                 len = this.list.length;
4335                 for(k = i; k < len; k++) this.list[k].box.y -= this._height;
4336             }
4337 
4338         } */
4339 
4340         //.domElement size
4341         var _h = this.#h + this.#i * this._margin;
4342         this.initList().size(this.clientWidth, _h < this.clientHeight ? this.clientHeight - this._margin*2 : _h).draw();
4343 
4344         //._globalCA event
4345         this._globalCA.box.set(0, 0, this.width, this.height);
4346         this.cae.add(this._globalCA, "down", ()=>{
4347             this._hiddenImageViewer();
4348             this._hiddenColorTestView();
4349         });
4350         this.cae.add(this._globalCA, "up", CanvasAnimateUI.stopTimers);
4351         
4352         //this.autoHeight
4353         if(this.autoHeight === true) this.parent.style.height = `${this.box.h+this._margin*2}px`;
4354 
4355         return this;
4356     }
4357 
4358     update(redraw){
4359         if(this.parent.offsetWidth === 0) return;
4360 
4361         const c = this._loopObject.length;
4362         for(let i = 0, v; i < c; i++){
4363             v = this._loopObject[i];
4364             if(typeof v.update === "function") v.update();
4365             else if(Array.isArray(v.update) === true){
4366                 for(let k = 0, len = v.update.length; k < len; k++) v.update[k]();
4367             }
4368         }
4369 
4370         if(redraw !== false) this.redraw();
4371 
4372     }
4373 
4374     style(style = ''){
4375         style += `
4376             width: ${this.clientWidth}px;
4377             height: ${this.clientHeight}px;
4378             padding: ${this._margin}px;
4379             overflow: hidden auto;
4380         `;
4381 
4382         this.parent.style = style;
4383         super.updateCanvas();
4384         
4385         return this;
4386     }
4387 
4388     pos(left, top){
4389         this.parent.style.left = left+"px";
4390         this.parent.style.top = top+"px";
4391         super.updateCanvas();
4392         return this;
4393     }
4394 
4395     getView(data){
4396         const i = this._loopData.indexOf(data);
4397         if(i !== -1) return this._loopObject[i];
4398         
4399     }
4400 
4401     getData(valueUrl){
4402         const len = this.data.length;
4403         for(let k = 0; k < len; k++){
4404             if(this.data[k].valueUrl === valueUrl) return this.data[k];
4405         }
4406 
4407     }
4408 
4409 
4410     //以下方法限内部使用
4411     _redrawTarget(y){ //只重绘目标(仅一列)
4412         CanvasAnimateUI.emptyCAC.box.set(0, y, this.width, this._height);
4413         this.drawTarget(CanvasAnimateUI.emptyCAC);
4414 
4415     }
4416 
4417     _bindHover(cai, y = 0, title = "", out = 0, over = 1){
4418         this.cae.add(cai, "out", ()=>{
4419             if(title !== "") this.domElement.title = "";
4420             this.domElement.style.cursor = "";
4421             cai.set(out);
4422             this._redrawTarget(y);
4423         });
4424 
4425         this.cae.add(cai, "over", ()=>{
4426             if(title !== "") this.domElement.title = title;
4427             this.domElement.style.cursor = "pointer";
4428             cai.set(over);
4429             this._redrawTarget(y);
4430         });
4431 
4432         cai.set(out);
4433         return cai;
4434     }
4435 
4436     _initMake(data, y, nextMake){
4437 
4438         if(typeof data.make === "object"){
4439             if(nextMake === false) this.list.push(new CanvasAnimate().setImage(this.img_line).pos(0, y - this.img_line.height/2));
4440 
4441             var r = false;
4442             if(Array.isArray(data.make) === true){
4443                 const len = data.make.length;
4444                 for(let k = 0; k < len; k++){
4445                     y = this._height * this.#i + this.#i * this._margin;
4446                     if(this._initUI(data.make[k], y) !== true) continue;
4447                     if(r !== true) r = true;
4448                     this.#h += this._height;
4449                     this.#i++;
4450                     
4451                 }
4452                 
4453             }
4454 
4455             else{
4456                 y = this._height * this.#i + this.#i * this._margin;
4457                 r = this._initUI(data.make, y);
4458                 if(r === true){
4459                     this.#h += this._height;
4460                     this.#i++;
4461                 }
4462             }
4463 
4464             if(r === true) this.list.push(new CanvasAnimate().setImage(this.img_line).pos(0, y + this._height - this.img_line.height/2));
4465 
4466             return r;
4467         }
4468 
4469         return false;
4470     }
4471 
4472     _initImages(numberCursorSize = 0.5){
4473         if(numberCursorSize > 1) numberCursorSize = 1;
4474 
4475         const cac =  CanvasAnimateUI.emptyCAC, mw = this._titleWidth + this._conWidth, f_2 = this._fontSIze / 2, 
4476         m_2 = this._margin/2, m2 = this._margin*2, _h8 = this._height*0.8, mar = (this._height -  this._fontSIze) / 2,
4477         gradient_line = cac.linearGradient(0, this._height, mw, this._height);
4478 
4479         //禁用背景图片
4480         this.img_dis = cac.size(mw, this._height).rect().fill("rgba(0,0,0,0.4)").shear();
4481 
4482         //解释说明图片
4483         this.img_explainA = cac.size(f_2 + m_2 * 2, f_2 + m_2 * 2).text("?", "#000", f_2 + m_2, "center", "center").computeCircle().arc(cac.circle).stroke("#000").shear();
4484         this.img_explainB = cac.clear().text("?", "#0000ff", f_2 + m_2, "center", "center").stroke("#0000ff").shear();
4485 
4486         //make 分割线图片
4487         cac.gradientColorSymme(gradient_line, ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"]);
4488         this.img_line = cac.size(mw, this._height).line(1, this._height/2, mw - 1, this._height/2).stroke(gradient_line, 1).shear();
4489 
4490         //冒号图片
4491         this.img_colon = cac.size(m2, this._height).rect().text(":", "", this._fontSIze, "center", "center").shear();
4492 
4493         //复选框图片
4494         this.img_checkboxA = cac.size(this._fontSIze + this._margin, this._fontSIze + this._margin).rect().fill("#fff").stroke(this._conColor, 1).shear();
4495         this.img_checkboxB = cac.text("✔", "#0000ff", this._fontSIze, "center", "center").shear();
4496         this.img_checkboxC = cac.clear().stroke("#0000ff", 1).shear();
4497 
4498         //颜色控件图片
4499         this.img_colorBG = cac.size(this._height, this._height).rect().drawTransparentBG().shear();
4500         this.img_colorA = cac.clear().stroke(this._conColor).shear();
4501         this.img_colorB = cac.clear().stroke("#0000ff").shear();
4502 
4503         //按钮图片
4504         this.img_buttonEditA = cac.clear().text("▤", "#000000", _h8, "center", "center").shear();
4505         this.img_buttonEditB = cac.clear().text("▤", "#0000ff", _h8, "center", "center").shear();
4506 
4507         cac.rotate(Math.PI/2);
4508         this.img_buttonEditC = cac.clear().text(">", "#000000", _h8, "center", "center").shear();
4509         this.img_buttonEditD = cac.clear().text(">", "#0000ff", _h8, "center", "center").shear();
4510         cac.unRotate(true);
4511 
4512         this.img_buttonResetA = cac.size(this._height, this._height).text("↻", "#000000", this._fontSIze, "center", "center").shear();
4513         this.img_buttonResetB = cac.clear().text("↻", "#0000ff", this._fontSIze, "center", "center").shear();
4514 
4515         cac.box.w = 0;
4516         cac.text("Button", "#000", this._fontSIze, "center", "center");
4517         cac.size(cac.box.w+m2, this._fontSIze+m2).rect().shadow("", 1, 1, 1);
4518         this.img_buttonA = cac.clear().shadow("#666").fill("#eee").shadow().text("Button", "#000", this._fontSIze, "center", "center").shear();
4519         this.img_buttonB = cac.clear().shadow("#666").fill("#fff").shadow().text("Button", "#0000ff", this._fontSIze, "center", "center").shear();
4520         this.img_buttonC = cac.clear().shadow("#666", 0, -1, -1).fill("#fff").shadow().text("Button", "#0000ff", this._fontSIze, "center", "center").shear();
4521 
4522         //数字控件图片
4523         this.img_numButSubA = cac.size(f_2, f_2).path([f_2 / 2, 0, f_2, f_2, 0, f_2], true).fill("#000").shear();
4524         this.img_numButSubB = cac.clear().fill("blue").shear();
4525 
4526         this.img_numButAddA = cac.clear().path([f_2 / 2, f_2, f_2, 0, 0, 0], true).fill("#000").shear();
4527         this.img_numButAddB = cac.clear().fill("blue").shear();
4528 
4529         cac.box.w = 0;
4530         this.img_numButSignA = cac.size(mar*numberCursorSize, mar*numberCursorSize).computeCircle().arc(cac.circle).fill("#000").shear();
4531         this.img_numButSignB = cac.clear().arc(cac.circle).fill("blue").shear();
4532 
4533         this._numberInputWidth = (this._conWidth - m2 - this._height) / 3 - f_2;
4534         this.img_numBoxA = cac.size(this._numberInputWidth, this._fontSIze+2).rect().shadow("#666", 1, 1, 1).stroke("#eee").shear();
4535         this.img_numBoxB = cac.clear().shadow().stroke("blue", 1).shear();
4536 
4537         cac.size(this._height, this._fontSIze).shadow("#666", 1, 1, 1).rect();
4538         this.img_buttonStepA = cac.stroke("#eee", 1).shear();
4539         this.img_buttonStepB = cac.clear().shadow().stroke("blue", 1).shear();
4540 
4541         //进度条图片
4542         this.img_progress = cac.size(this._conWidth - m2 - this._height, 2).line(0, 1, this._conWidth, 1).stroke("blue", 2).shear();
4543 
4544     }
4545 
4546     _isDrawByData(data){
4547         const v = this._getValueByUrl(data.valueUrl);
4548 
4549         if(v === undefined) return;
4550         
4551         switch(data.type){
4552             case "image":
4553             return true;
4554 
4555             case "select":
4556             return true;
4557 
4558             case "json":
4559             return true;
4560         }
4561 
4562         if(Array.isArray(data.selectList) === true) return true;
4563         
4564         const ty = typeof v;
4565         switch(ty){
4566             case "object":
4567             if(v === null || (v.isVector2 !== true && v.isVector3 !== true && v.isEuler !== true && v.isColor !== true && v.isRGBColor !== true && this.isCanvasImage(v) !== true)) return;
4568             break;
4569 
4570             case "string":
4571             break;
4572 
4573             case "number":
4574             break;
4575 
4576             case "boolean":
4577             break;
4578             
4579             case "function":
4580             break;
4581 
4582             default: return;
4583         }
4584 
4585         return true;
4586     }
4587 
4588     _initUI(data, y){
4589 
4590         if(data.type === 'line'){
4591             if(this.#isLine === true) return;
4592             return this.createLine(data, y);
4593         }
4594 
4595         const v = this._getValueByUrl(data.valueUrl);
4596 
4597         if(v === undefined) return;
4598         
4599         switch(data.type){
4600             case "image":
4601             this.createFileImage(data, y);
4602             return true;
4603 
4604             case "select":
4605             this.createSelect(data, v, y);
4606             return true;
4607 
4608             case "json":
4609             this.createFileJSON(data, y);
4610             return;
4611 
4612         }
4613 
4614         if(Array.isArray(data.selectList) === true){
4615             this.createSelect(data, v, y);
4616             return true;
4617         }
4618         
4619         const ty = typeof v;
4620     
4621         switch(ty){
4622             case "object":
4623             if(v === null) return;
4624             if(v.isVector2 === true || v.isPoint === true) this.createNumbers(data, "Vector2", y);
4625             else if(v.isVector3 === true) this.createNumbers(data, "Vector3", y);
4626             else if(v.isEuler === true) this.createNumbers(data, "Euler", y);
4627             else if(v.isColor === true || v.isRGBColor === true) this.createColor(data, v, y);
4628             else if(this.isCanvasImage(v) === true) this.createFileImage(data, y);
4629             else return;
4630             break;
4631 
4632             case "string":
4633             const _v = this.colorTestView.getColor(v);
4634             if(_v !== "") this.createColor(data, _v, y);
4635             else this.createText(data, v, y);
4636             break;
4637 
4638             case "number":
4639             this.createNumbers(data, "number", y);
4640             break;
4641 
4642             case "boolean":
4643             this.createCheckbox(data, v, y);
4644             break;
4645             
4646             case "function":
4647             this.createFunc(data, y);
4648             break;
4649 
4650             default: return;
4651         }
4652 
4653         return true;
4654     }
4655 
4656     _string(str){
4657         if(str.length > this._stringMaxCount) return str.substr(0, this._stringMaxCount);
4658         return str;
4659     }
4660 
4661     _number(num, range){
4662         if(num < range.min) num = range.min;
4663         else if(num > range.max) num = range.max;
4664         
4665         return num;
4666     }
4667 
4668     _numToStr(num){
4669         return this._string(String(Math.floor(num * 1000) / 1000));
4670     }
4671 
4672     _getValueByUrl(valueUrl){
4673         try{
4674 
4675             return eval("this.target" + valueUrl);
4676             
4677         }
4678 
4679         catch(e){
4680 
4681             return;
4682 
4683         }
4684 
4685     }
4686 
4687     _getEventParam(data, y, target, disableCA){
4688         disableCA.visible = typeof data.disable === "boolean" ? data.disable : false;
4689         this.cae.add(disableCA, "click", CanvasAnimateUI.emptyFunc);
4690         this.cae.add(disableCA, "down", CanvasAnimateUI.emptyFunc);
4691         
4692         const result = {
4693             target: target,
4694             data: data,
4695             scope: this,
4696             redraw: ()=>this._redrawTarget(y),
4697             get disable(){
4698                 return disableCA.visible;
4699             },
4700             set disable(v){
4701                 if(typeof v === "boolean") disableCA.visible = v;
4702 
4703             },
4704 
4705         }
4706 
4707         if(data.update === true || (this.globalUpdate === true && data.update !== false)){
4708             this._loopData.push(data);
4709             this._loopObject.push(result);
4710         }
4711 
4712         return result;
4713     }
4714 
4715     _styleShadowConvex(elem){
4716         elem.style.boxShadow = '2px 2px 4px 0px #666666';
4717         return elem;
4718     }
4719 
4720     _getParent(){
4721         const elem = this._styleShadowConvex(document.createElement("div"));//全局 parent
4722         
4723         elem.style = `
4724             width: ${this.clientWidth}px;
4725             height: ${this.clientHeight}px;
4726             padding: ${this._margin}px;
4727             overflow: hidden auto;
4728         `;
4729         
4730         elem.appendChild(this.domElement);
4731         elem.onscroll = () => this.updateCanvas();
4732 
4733         return elem;
4734     }
4735 
4736     _getInput(){
4737         const elem = document.createElement("textarea");
4738         elem.style = `
4739             width: ${this._conWidth*0.9}px;
4740             height: ${this._conWidth*0.45}px;
4741             position: absolute;
4742             left: 0px;
4743             top: 0px;
4744             display: none;
4745             z-index: 99999;
4746             font-size:${this._fontSIze}px;
4747             padding: 2px;
4748         `;
4749 
4750         elem.onchange = e => this._hiddenInput(e.target.value);
4751         elem.onblur = () => elem.style.display = "none";
4752         //this.parent.appendChild(elem);
4753         
4754         return elem;
4755     }
4756 
4757     _getSelectIndex(list, value){
4758         for(let k = 0, len = list.length; k < len; k++){
4759             if(list[k].value === value) return k;
4760         }
4761         return -1;
4762     }
4763 
4764     _getValueUrl(valueUrl){
4765         var urls = [], str = "", tar = this.target;
4766 
4767         for(let k = 0, v, len = valueUrl.length; k < len; k++){
4768             v = valueUrl[k];
4769             if(v === " ") continue;
4770             
4771             if(v === '.' || v === '[' || v === ']'){
4772                 if(str !== ""){
4773                     urls.push(str);
4774                     str = "";
4775                 }
4776                 
4777             }
4778 
4779             else str += v;
4780 
4781         }
4782 
4783         if(str !== "") urls.push(str);
4784         
4785         if(urls.length > 1){
4786             let _len = urls.length - 1;
4787             for(let k = 0; k < _len; k++) tar = tar[urls[k]];
4788             str = urls[_len];
4789         }
4790 
4791         else str = urls[0];
4792         
4793         urls = undefined;
4794         
4795         return {
4796             object: tar,
4797             name: str,
4798             get value(){
4799                 return this.object[this.name];
4800             },
4801             set value(v){
4802                 this.object[this.name] = v;
4803             },
4804 
4805         }
4806         
4807     }
4808 
4809     _showInput(y, func, value){
4810         this._input.style.display = "block";
4811         const rect = this._input.getBoundingClientRect();
4812         
4813         y += this._height+this.domElementRect.y;
4814         if(y + rect.height > window.innerHeight) y -= this._height + rect.height;
4815         this._input.style.top = y + "px";
4816         
4817         var x = this.domElementRect.x + this._titleWidth + this._conWidth - rect.width;
4818         if(x + rect.width > window.innerWidth) x -= x + rect.width - window.innerWidth + this._margin;
4819         this._input.style.left = x + "px";
4820 
4821         this._inputFunc = func;
4822         this._input.value = value;
4823         this._input.select();
4824 
4825     }
4826 
4827     _showColorTestView(y, func, value){
4828         this.colorTestView.onchange = func;
4829         this.colorTestView.setValueString(value).update(true);
4830         this.colorTestView.domElement.style.display = "block";
4831 
4832         y += this.domElementRect.y - this.colorTestView.box.h;
4833         if(y < 0) y += this.colorTestView.box.h + this._height;
4834 
4835         var x = this.domElementRect.x + this._titleWidth + this._conWidth - this.colorTestView.box.w;
4836         if(x + this.colorTestView.box.w > window.innerWidth) x -= x + this.colorTestView.box.w - window.innerWidth + this._margin;
4837 
4838         this.colorTestView.pos(x, y);
4839         
4840     }
4841 
4842     _showImageViewer(y, image){
4843         this.imageViewer.setImage(image)
4844         .setViewportScale()
4845         .center()
4846         .drawImage()
4847         .redraw();
4848         this.imageViewer.domElement.style.display = "block";
4849 
4850         y += this.domElementRect.y - this.imageViewer.box.h;
4851         if(y < 0) y += this.imageViewer.box.h + this._height;
4852 
4853         var x = this.domElementRect.x;
4854         if(x + this.imageViewer.box.w > window.innerWidth) x -= x + this.imageViewer.box.w - window.innerWidth + this._margin;
4855         
4856         this.imageViewer.pos(x, y);
4857 
4858     }
4859 
4860     _hiddenInput(value){
4861         this._input.style.display = "none";
4862         if(this._inputFunc !== null){
4863             this._inputFunc(value);
4864             
4865         }
4866     }
4867 
4868     _hiddenColorTestView(){
4869         if(this.colorTestView.onchange !== null){
4870             this.colorTestView.onchange = null;
4871             this.colorTestView.domElement.style.display = "none";
4872         }
4873     }
4874 
4875     _hiddenImageViewer(){
4876         this.imageViewer.setImage().clear();
4877         this.imageViewer.domElement.style.display = "none";
4878 
4879     }
4880 
4881     _onChanges(data, param){
4882         if(typeof data.onChange === "function") data.onChange(param);
4883         //else{
4884         //    if(type === "func") param.target.value.call(param.target.object, param); //eval('this.target'+data.valueUrl+'(param)'); //param.target.value(param);
4885 
4886         //}
4887 
4888         if(this.onChanges !== null) this.onChanges(param);
4889         
4890     }
4891 
4892     _createMenuView(list){
4893         MenuView.reset();
4894         const menuView = new MenuView(list);
4895         this._styleShadowConvex(menuView.view.domElement);
4896         menuView.view.domElement.style.zIndex = "99999";
4897         this._menuViews.push(menuView);
4898         return menuView;
4899     }
4900 
4901     _cretaeTitle(textTitle, textExplain, y){ //标题
4902         if(typeof textTitle === "string"){
4903             //data.title
4904             const title = new CanvasAnimateCustom().size(this._titleWidth, this._height).text(textTitle, this._titleColor, this._fontSIze, 0, "center").pos(0, y);
4905             this.cae.add(title, "out", this._funcTitleOut);
4906             this.cae.add(title, "over", ()=>this.domElement.title = textTitle);
4907             this.list.push(title);
4908 
4909             //data.explain
4910             if(typeof textExplain === "string"){
4911                 this.list.push(this._bindHover(new CanvasAnimateImages([this.img_explainA, this.img_explainB]), y, textExplain).pos(title.box.maxX() - this.img_explainA.width - 1, title.box.y + 1));
4912                 
4913             }
4914 
4915             return title.box.maxX();
4916         }
4917         
4918         return this._titleWidth;
4919     }
4920 
4921     _createNumber(data, progress, param, x, y, target, name){
4922         var v;
4923         const bor = this._bindHover(new CanvasAnimateImages([this.img_numBoxA, this.img_numBoxB]), y).pos(x, y + (this._height - this.img_numBoxA.height)/2),
4924         con = new CanvasAnimateCustom().size(this._numberInputWidth, this._height).pos(x, y),
4925         but_sub = this._bindHover(new CanvasAnimateImages([this.img_numButSubA, this.img_numButSubB]), y).pos(bor.box.maxX(), y + (this._height - this.img_numButSubA.height * 2) / 2 - 1),
4926         but_add = this._bindHover(new CanvasAnimateImages([this.img_numButAddA, this.img_numButAddB]), y).pos(but_sub.box.x, but_sub.box.maxY() + 1),
4927         but_sign = new CanvasAnimateImages([this.img_numButSignA, this.img_numButSignB]).pos(this._numberInputWidth / 2 + bor.box.x - this.img_numButSignA.width/2, bor.box.y - this.img_numButSignA.height),
4928         
4929         setValue = (value, isUpdate) => {
4930             value = this._number(value, param);
4931             if(v !== value){
4932                 target[name] = v = value;
4933                 param.valueName = name;
4934                 con.clear().text(this._numToStr(v), this._conColor, this._fontSIze, 2, "center");
4935                 if(but_sign.i === 1) progress._updateCursor(v);
4936 
4937                 if(isUpdate !== false){
4938                     this._onChanges(data, param);
4939                     param.redraw();
4940                 }
4941                 
4942                 return true;
4943             }
4944 
4945         },
4946 
4947         toValue = ()=>{
4948             if(v !== target[name]){
4949                 v = this._number(target[name], param);
4950                 con.clear().text(this._numToStr(v), this._conColor, this._fontSIze, 2, "center");
4951                 if(typeof progress._updateCursor === "function") progress._updateCursor(v);
4952             }
4953             
4954         },
4955         
4956         onInputHidden = value => {
4957             value = parseFloat(value);
4958             if(isNaN(value) === true) return;
4959             setValue(value);
4960             
4961         }
4962         
4963         //event
4964         this.cae.add(bor, "click", () => this._showInput(y, onInputHidden, v));
4965         this.cae.add(bor, "out", this._funcTitleOut);
4966         this.cae.add(bor, "over", ()=>this.domElement.title = v);
4967         this.cae.add(but_sign, "click", ()=>{
4968             but_sign.next();
4969             if(but_sign.i === 0) progress._updateCursorAtSign();
4970             else progress._updateCursor(v);
4971             param.redraw();
4972             
4973         });
4974         //this.cae.add(but_sign, "over", ()=>{
4975         //    progress._updateCursor(v);
4976         //    param.redraw();
4977         //});
4978 
4979         this.cae.add(but_sub, "up", CanvasAnimateUI.stopTimers);
4980         this.cae.add(but_add, "up", CanvasAnimateUI.stopTimers);
4981         const _setValueSub = ()=>{
4982             CanvasAnimateUI.emptyTimerA.speed = 150;
4983             setValue(v - (param.type !== 'Euler' ? param.step : param.__step));
4984         },
4985         _setValueAdd = ()=>{
4986             CanvasAnimateUI.emptyTimerA.speed = 150;
4987             setValue(v + (param.type !== 'Euler' ? param.step : param.__step));
4988         }
4989         this.cae.add(but_sub, "down", ()=>{
4990             _setValueSub();
4991             CanvasAnimateUI.emptyTimerA.start(_setValueSub, 500);
4992             
4993         });
4994         this.cae.add(but_add, "down", ()=>{
4995             _setValueAdd();
4996             CanvasAnimateUI.emptyTimerA.start(_setValueAdd, 500);
4997 
4998         });
4999 
5000         this.cae.add(but_sub, "out", this._funcTitleOut);
5001         this.cae.add(but_sub, "over", () => this.domElement.title = "min: "+param.min);
5002 
5003         this.cae.add(but_add, "out", this._funcTitleOut);
5004         this.cae.add(but_add, "over", () => this.domElement.title = "max: "+param.max);
5005 
5006         if(Array.isArray(param.update) === false) param.update = [];
5007         param.update.push(toValue);
5008 
5009         but_sign.set(0);
5010         this.list.push(con, bor, but_sub, but_add, but_sign);
5011 
5012         return {
5013             maxX: but_sub.box.maxX(),
5014             sign: but_sign,
5015             setValue: setValue,
5016             toValue: toValue,
5017         }
5018     }
5019 
5020     createLine(data, y){
5021         var is;
5022 
5023         if(typeof data.value === "string") is = this._getValueByUrl(data.value) !== undefined;
5024 
5025         else if(Array.isArray(data.value) === true){
5026             for(let k = 0, len = data.value.length; k < len; k++){
5027                 if(this._getValueByUrl(data.value[k]) !== undefined){
5028                     is = true;
5029                     break;
5030                 }
5031             }
5032         }
5033 
5034         if(is === true) this.list.push(new CanvasAnimate(this.img_line).pos(0, y));
5035 
5036         return is;
5037     }
5038 
5039     createText(data, v, y){
5040         const target = this._getValueUrl(data.valueUrl),
5041         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5042 
5043         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5044         con = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height, this._height).text(this._string(v), this._conColor, this._fontSIze, 0, "center").pos(colon.box.maxX(), y),
5045         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(con.box.maxX(), y),
5046         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5047 
5048         eventParam = this._getEventParam(data, y, target, disableCA),
5049         onInputHaidden = value => {
5050             target.value = v = value;
5051             con.clear().text(this._string(v), this._conColor, this._fontSIze, 0, "center");
5052             this._onChanges(data, eventParam);
5053             this._redrawTarget(y);
5054         }
5055 
5056         //event
5057         this.cae.add(con, "out", this._funcTitleOut);
5058         this.cae.add(con, "over", ()=>this.domElement.title = v);
5059         this.cae.add(but, "click", () => this._showInput(y, onInputHaidden, v));
5060 
5061         eventParam.update = ()=>{
5062             if(target.value !== v){
5063                 v = target.value;
5064                 con.clear().text(this._string(v), this._conColor, this._fontSIze, 0, "center");
5065             }
5066             
5067         }
5068         
5069         this.list.push(colon, con, but, disableCA);
5070         
5071     }
5072 
5073     createColor(data, v, y){
5074         const isObj = typeof v === "object";
5075         if(isObj === true) v = v.getStyle(); //兼容 class Color
5076         
5077         const target = this._getValueUrl(data.valueUrl),
5078         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5079         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5080         conA = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height * 2, this._height).text(v, this._conColor, this._fontSIze, 0, "center").pos(colon.box.maxX(), y),
5081         conBG = new CanvasAnimate(this.img_colorBG).pos(conA.box.maxX(), y),
5082         conB = new CanvasAnimateCustom().size(this._height, this._height).rect().shadow("#666", 2).fill(v).pos(conBG.box.x, y),
5083         conC = this._bindHover(new CanvasAnimateImages([this.img_colorA, this.img_colorB]), y).pos(conBG.box.x, y),
5084         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(conB.box.maxX(), y), 
5085         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5086         eventParam = this._getEventParam(data, y, target, disableCA),
5087 
5088         setValue = value => {
5089             value = this.colorTestView.getColor(value);
5090             
5091             if(value === "") return;
5092             v = value;
5093             if(isObj === false) target.value = v;
5094             else target.value.set(v);
5095             
5096             conA.clear().text(v, this._conColor, this._fontSIze, 0, "center");
5097             conB.clear().fill(v);
5098             this._onChanges(data, eventParam);
5099             this._redrawTarget(y);
5100         }
5101 
5102         //event
5103         this.cae.add(but, "click", () => this._showInput(y, setValue, v));
5104         this.cae.add(conC, "click", () => this._showColorTestView(y, setValue, v));
5105         
5106         this.cae.add(conA, "out", this._funcTitleOut);
5107         this.cae.add(conA, "over", ()=>this.domElement.title = v);
5108         
5109         eventParam.update = ()=>{
5110             if(target.value !== v){
5111                 v = isObj === false ? target.value : target.value.getStyle();
5112                 conA.clear().text(v, this._conColor, this._fontSIze, 0, "center");
5113                 conB.clear().fill(v);
5114             }
5115             
5116         }
5117 
5118         this.list.push(colon, conA, conBG, conB, conC, but, disableCA);
5119     }
5120 
5121     createCheckbox(data, v, y){
5122         const target = this._getValueUrl(data.valueUrl),
5123         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5124         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5125         con = new CanvasAnimateImages([this.img_checkboxA, this.img_checkboxB]).pos(colon.box.maxX(), y + (this._height - this.img_checkboxA.height) / 2),
5126         con_hover = new CanvasAnimate(this.img_checkboxC).pos(con.box.x, con.box.y),
5127         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5128         eventParam = this._getEventParam(data, y, target, disableCA);
5129         con.set(v === true ? 1 : 0);
5130 
5131         //event
5132         con_hover.visible = false;
5133         this.cae.add(con, "out", ()=>{
5134             this._funcTitleOut();
5135             con_hover.visible = false;
5136             this._redrawTarget(y);
5137         });
5138 
5139         this.cae.add(con, "over", ()=>{
5140             this.domElement.title = String(v);
5141             con_hover.visible = true;
5142             this._redrawTarget(y);
5143         });
5144 
5145         this.cae.add(con, "click", () => {
5146             con.next();
5147             v = con.i === 1 ? true : false;
5148             target.value = v;
5149             this._onChanges(data, eventParam);
5150             this._redrawTarget(y);
5151         });
5152 
5153         //update
5154         eventParam.update = ()=>{
5155             if(target.value !== v){
5156                 v = target.value;
5157                 con.set(v === true ? 1 : 0);
5158             }
5159             
5160         }
5161         
5162         this.list.push(colon, con, con_hover, disableCA);
5163 
5164     }
5165 
5166     createFunc(data, y){
5167         const titleWidth = this._cretaeTitle(data.title, data.explain, y),
5168         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5169         con = new CanvasAnimate(this.img_buttonA).pos(colon.box.maxX(), y + (this._height - this.img_buttonA.height) / 2),
5170         con_hover = new CanvasAnimate(this.img_buttonB).pos(con.box.x, con.box.y),
5171         con_click = new CanvasAnimate(this.img_buttonC).pos(con.box.x, con.box.y),
5172         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5173 
5174         target = this._getValueUrl(data.valueUrl),
5175         param = this._getEventParam(data, y, target, disableCA);
5176 
5177         con_click.visible = false;
5178         con_hover.visible = false;
5179 
5180         //event
5181         this.cae.add(con, "out", ()=>{
5182             this._funcTitleOut();
5183             con_hover.visible = false;
5184             this._redrawTarget(y);
5185         });
5186 
5187         this.cae.add(con, "over", ()=>{
5188             this.domElement.title = target.value.name;
5189             con_hover.visible = true;
5190             this._redrawTarget(y);
5191         });
5192 
5193         this.cae.add(con, "down", () => {
5194             con_hover.visible = false;
5195             con_click.visible = true;
5196             this._redrawTarget(y);
5197         });
5198 
5199         this.cae.add(con, "up", () => {
5200             con_click.visible = false;
5201             //this._onChanges(data, param, "func"); //执行回调1
5202             //param.target.value.call(param.target.object, param);  //执行回调2
5203             //eval('this.target'+data.valueUrl+'(param)'); //执行回调3
5204             //param.target.value(param);
5205             if(this.target){
5206                 if(!data.eventFunc){
5207                     eval('this.target'+data.valueUrl+'(param)');
5208                     if(this.onChanges !== null) this.onChanges(param);
5209                 }
5210                 else this._onChanges(data, param);
5211             }
5212             this._redrawTarget(y);
5213         });
5214 
5215         param.update = CanvasAnimateUI.emptyFunc;
5216 
5217         this.list.push(colon, con, con_hover, con_click, disableCA);
5218         
5219     }
5220 
5221     createSelect(data, v, y){
5222         var k = this._getSelectIndex(data.selectList, v), menuParam = null;
5223 
5224         const target = this._getValueUrl(data.valueUrl), _list = [], list = data.selectList, 
5225         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5226         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5227         con = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height, this._height).text(k !== -1 ? this._string(list[k].name) : "undefined", this._conColor, this._fontSIze, 0, "center").pos(colon.box.maxX(), y),
5228         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditC, this.img_buttonEditD]), y).pos(con.box.maxX(), y),
5229         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5230         eventParam = this._getEventParam(data, y, target, disableCA),
5231 
5232         func = value => {
5233             menuParam.lcon_def.set();
5234             menuParam.background.set();
5235             menuParam = value;
5236             menuParam.lcon_def.set(1);
5237             menuParam.background.set(2);
5238 
5239             v = list[value.key].value;
5240             target.value = v;
5241             con.clear().text(this._string(list[value.key].name), this._conColor, this._fontSIze, 0, "center");
5242             this._onChanges(data, eventParam);
5243             this._redrawTarget(y);
5244             value.target.visible = false;
5245             
5246         }
5247 
5248         for(let i = 0, len = list.length; i < len; i++) _list.push({name: list[i].name, func: func});
5249         this.cae.add(con, "out", this._funcTitleOut);
5250         this.cae.add(con, "over", ()=>this.domElement.title = v);
5251         
5252         eventParam.update = ()=>{
5253             if(target.value !== v){
5254                 v = target.value;
5255                 k = this._getSelectIndex(list, v);
5256                 con.clear().text(k !== -1 ? this._string(list[k].name) : "undefined", this._conColor, this._fontSIze, 0, "center");
5257             }
5258             
5259         }
5260 
5261         //init menuView
5262         var menuView = null;
5263         this.cae.add(this._globalCA, "down", () => {
5264             if(menuView !== null && menuView.visible === true) menuView.visible = false;
5265         });
5266 
5267         this.cae.add(but, "click", () => {
5268 
5269             //这里减轻一下.initUI()的压力: 既在初始化时先不创建 menuView 控件;
5270             if(menuView === null){
5271                 menuView = this._createMenuView(_list)
5272                 if(k !== -1){
5273                     menuParam = menuView.views[k];
5274                     menuParam.lcon_def.set(1);
5275                     menuParam.background.set(2);
5276                     
5277                 }
5278             }
5279 
5280             let top = y + this._height + this.domElementRect.y;
5281             if(top + menuView.height > window.innerHeight) top -= this._height + menuView.height;
5282 
5283             let left = this.domElementRect.x + this._titleWidth + this._conWidth - menuView.width;
5284             if(left + menuView.width > window.innerWidth) left -= left + menuView.width - window.innerWidth + this._margin;
5285 
5286             menuView.view.pos(left, top);
5287             menuView.visible = true;
5288             
5289         });
5290 
5291 
5292         this.list.push(colon, con, but, disableCA);
5293     }
5294 
5295     createNumber(data, y, param, progress, progressCursor){
5296 
5297         //param 特有属性 (min, max, step)
5298         param.min = (typeof data.range === "object" && typeof data.range.min === "number") ? data.range.min : this.defiendRange.min;
5299         param.max = (typeof data.range === "object" && typeof data.range.max === "number") ? data.range.max : this.defiendRange.max;
5300         var _step = (typeof data.range === "object" && typeof data.range.step === "number") ? data.range.step : this.defiendRange.step;
5301         if(param.type === "Euler"){
5302             param.__step = _step;
5303             _step = param.target.value.order;
5304         }
5305         Object.defineProperty(param, 'step', {
5306             get: () => {
5307                 return _step;
5308             },
5309 
5310             set: v => {
5311                 if(param.type === "Euler"){
5312                     //如果想修改 Euler 控件的range.step: 'step: 0.01';
5313                     let i = v.indexOf(':');
5314                     if(i !== -1 && UTILS.removeSpaceSides(v.substr(0, i)) === 'step'){
5315                         let step = parseFloat(v.substr(i+1));
5316                         if(isNaN(step) === false) param.__step = step;
5317                         
5318                     }
5319 
5320                     else{
5321                         param.target.value.order = UTILS.removeSpaceSides(v).toUpperCase(); //去两边空格 并 转为大写字母
5322                         stepVal.clear().text(param.target.value.order, this._conColor, this._fontSIze, "center", "center");
5323                     }
5324                     
5325                 }
5326 
5327                 else{
5328                     v = parseFloat(v);
5329                     if(isNaN(v) === true) return;
5330                     _step = v;
5331                     stepVal.clear().text(this._numToStr(v), this._conColor, this._fontSIze, "center", "center");
5332                     
5333                 }
5334 
5335             }
5336 
5337         });
5338 
5339         progress._min = param.min;
5340         progress._max = param.max;
5341         progress._width = progress.box.w;
5342         progress._updateCursor = value => {
5343             if(value < progress._min) value = progress._min;
5344             else if(value > progress._max) value = progress._max;
5345             progress.box.w = (value - progress._min) / (progress._max - progress._min) * progress._width;
5346             progressCursor.box.x = progress.box.maxX() - progressCursor.box.w/2;
5347 
5348         }
5349 
5350         const titleWidth = this._cretaeTitle(data.title, data.explain, y),
5351         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5352         stepBor = this._bindHover(new CanvasAnimateImages([this.img_buttonStepA, this.img_buttonStepB]), y)
5353         .pos(colon.box.x + this._conWidth - this._height, y + (this._height - this.img_buttonStepA.height) / 2),
5354         stepVal = new CanvasAnimateCustom().size(stepBor.box.w, stepBor.box.h).pos(stepBor.box.x, stepBor.box.y),
5355 
5356         setStepVal = value => {
5357             param.step = value;
5358             param.redraw();
5359         }
5360 
5361         this.cae.add(stepBor, "click", () => this._showInput(y, setStepVal, param.type !== "Euler" ? param.step : param.target.value.order));
5362 
5363         this.list.push(colon, stepVal, stepBor);
5364         param.step = _step;
5365         
5366     }
5367 
5368     createNumbers(data, v, y){
5369         var isDownCursor = false;
5370 
5371         const disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5372         progress = new CanvasAnimate(this.img_progress).pos(this.img_colon.width + this._titleWidth, y + this._height - this.img_progress.height - 1),
5373         progressCursor = new CanvasAnimateImages([this.img_numButSignA, this.img_numButSignB]).pos(progress.box.x, progress.box.y - this.img_numButSignA.height / 2),
5374 
5375         //create number input
5376         target = this._getValueUrl(data.valueUrl), 
5377         param = this._getEventParam(data, y, target, disableCA),
5378         inputX = this._createNumber(data, progress, param, progress.box.x, y, v === "number" ? target.object : target.value, v === "number" ? target.name : "x"),
5379         inputY = (v === "Vector2" || v === "Vector3" || v === "Euler") ? this._createNumber(data, progress, param, inputX.maxX, y, target.value, "y") : null,
5380         inputZ = (v === "Vector3" || v === "Euler") ? this._createNumber(data, progress, param, inputY.maxX, y, target.value, "z") : null,
5381 
5382         //event
5383         onmove = event => {
5384             let is = false;
5385             v = (event.pageX - this.domElementRect.x - progress.box.x) / progress._width * (progress._max - progress._min) + progress._min;
5386 
5387             //setValue 本来就根据param.min.max限制值, 这里是根据 progress.min.max 限制值
5388             if(v < progress._min) v = progress._min;
5389             else if(v > progress._max) v = progress._max;
5390 
5391             if(inputX.sign.i === 1){
5392                 if(inputX.setValue(v, false) === true && is !== true) is = true;
5393                 
5394             }
5395 
5396             if(inputY !== null && inputY.sign.i === 1){
5397                 if(inputY.setValue(v, false) === true && is !== true) is = true;
5398 
5399             }
5400 
5401             if(inputZ !== null && inputZ.sign.i === 1){
5402                 if(inputZ.setValue(v, false) === true && is !== true) is = true;
5403 
5404             }
5405 
5406             if(is === true){
5407                 this._onChanges(data, param);
5408                 param.redraw();
5409             }
5410 
5411         },
5412 
5413         onup = ()=>{
5414             this.cae.remove(this._globalCA, "move", onmove);
5415             this.cae.remove(this._globalCA, "up", onup);
5416             this.cae.remove(progressCursor, "move", onmove);
5417             this.cae.remove(progressCursor, "up", onup);
5418             isDownCursor = false;
5419         };
5420 
5421         //bug: 划入时 点击游标会触发move事件(猜测: 可能是在down里面绑定事件带来的延迟)
5422         this.cae.add(progressCursor, "down", () => {
5423             if(param.disable === true) return;
5424             isDownCursor = true;
5425             //onmove(event);
5426             this.cae.add(this._globalCA, "move", onmove);
5427             this.cae.add(this._globalCA, "up", onup);
5428             this.cae.add(progressCursor, "move", onmove);
5429             this.cae.add(progressCursor, "up", onup);
5430 
5431         });
5432 
5433         this.cae.add(progressCursor, "out", ()=>{
5434             this.domElement.title = "";
5435             this.domElement.style.cursor = "";
5436             progressCursor.set(0);
5437             param.redraw();
5438         });
5439 
5440         this.cae.add(progressCursor, "over", ()=>{
5441             this.domElement.title = progress._min + " <-点进度条调整此范围-> " + progress._max;
5442             this.domElement.style.cursor = "pointer";
5443             progressCursor.set(1);
5444             param.redraw();
5445         });
5446 
5447         this.cae.add(progress, 'up', ()=>{
5448             if(isDownCursor !== false) return;
5449             this._showInput(y, v=>{
5450                 let i = v.indexOf(',');
5451                 if(i !== -1){
5452                     let a = parseFloat(v.substr(0, i)), b = parseFloat(v.substr(i+1));
5453                     if(isNaN(a) === false && isNaN(b) === false && a >= param.min && a < param.max && b > param.min && b <= param.max && a < b){
5454                         progress._min = a;
5455                         progress._max = b;
5456                     }
5457                     else{
5458                         progress._min = param.min;
5459                         progress._max = param.max;
5460                     }
5461 
5462                     progress._updateCursorAtSign();
5463                     param.redraw();
5464                 }
5465             }, progress._min+","+progress._max);
5466         });
5467 
5468         //param 特有属性 (type: "number" || "Vector2" || "Vector3" || "Euler")
5469         param.type = v;
5470 
5471         //create number scene
5472         this.createNumber(data, y, param, progress, progressCursor);
5473 
5474         //init
5475         progress._updateCursorAtSign = () => {
5476             if(inputZ !== null && inputZ.sign.i === 1) progress._updateCursor(target.value.z);
5477             else if(inputY !== null && inputY.sign.i === 1) progress._updateCursor(target.value.y);
5478             else if(inputX.sign.i === 1) progress._updateCursor(typeof target.value === "number" ? target.value : target.value.x);
5479 
5480         }
5481         
5482         progressCursor.set(0);
5483         inputX.sign.set(1);
5484         inputX.toValue(); //inputX.setValue(v === "number" ? target.value : target.value.x, false);
5485         if(inputY !== null) inputY.toValue(); //if(inputY !== null) inputY.setValue(target.value.y);
5486         if(inputZ !== null) inputZ.toValue(); //if(inputZ !== null) inputZ.setValue(target.value.z);
5487         this.list.push(progress, progressCursor, disableCA);
5488         
5489     }
5490 
5491     createFileImage(data, y){
5492         var imageInfo = "";
5493         const target = this._getValueUrl(data.valueUrl),
5494         titleWidth = this._cretaeTitle(data.title, data.explain, y),
5495         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
5496         conA = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height * 3, this._height).pos(colon.box.maxX(), y),
5497         butReset = this._bindHover(new CanvasAnimateImages([this.img_buttonResetA, this.img_buttonResetB]), y, "reset").pos(conA.box.maxX(), y),
5498         conBG = new CanvasAnimate(this.img_colorBG).pos(butReset.box.maxX(), y),
5499         conB = new CanvasAnimateCustom().size(this._height, this._height).pos(conBG.box.x, y),
5500         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(conB.box.maxX(), y), 
5501         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
5502         eventParam = this._getEventParam(data, y, target, disableCA),
5503         
5504         setValue = value => {
5505             if(this.isCanvasImage(value) === true){
5506                 let p = value.width / value.height, _w = 0, _h = 0;
5507                 if(p < 1){
5508                     _w = p * conB.box.w; 
5509                     _h = conB.box.h;
5510                 }
5511                 else{
5512                     _w = conB.box.w;
5513                     _h = conB.box.w / p;
5514                 }
5515 
5516                 conB.clear().drawImage(value, 0, 0, value.width, value.height, (conB.box.w - _w) / 2, (conB.box.h - _h) / 2, _w, _h);
5517 
5518                 imageInfo = value.width + " * " + value.height + " | " + this._numToStr(_w / value.width);
5519                 conA.clear().text(imageInfo, this._conColor, this._fontSIze, 0, "center");
5520 
5521             }
5522 
5523             else{
5524                 imageInfo = "";
5525                 conA.clear();
5526                 conB.clear();
5527                 value = null;
5528             }
5529             
5530             if(target.value !== value){
5531                 target.value = value;
5532                 this._onChanges(data, eventParam);
5533             }
5534 
5535             this._redrawTarget(y);
5536         }
5537 
5538         //event
5539         this.cae.add(conB, "click", () => CanvasAnimateUI.downloadImage(setValue));
5540         this.cae.add(butReset, "click", setValue);
5541         this.cae.add(but, "click", () => this._showImageViewer(y, target.value));
5542         
5543         this.cae.add(conA, "out", this._funcTitleOut);
5544         this.cae.add(conA, "over", ()=>this.domElement.title = imageInfo);
5545         
5546         //update
5547         eventParam.update = ()=> setValue(target.value);
5548 
5549         //init
5550         if(this.isCanvasImage(target.value) === false && target.value !== null) target.value = null;
5551         setValue(target.value);
5552         this.list.push(colon, conA, butReset, conBG, conB, but, disableCA);
5553         
5554     }
5555 
5556     createFileJSON(data, y){
5557 
5558         console.log("CanvasAnimateUI: 暂不支持 type = json");
5559 
5560     }
5561 
5562 }
5563 
5564 
5565 
5566 
5567 /** ProgressView 进度条视图
5568 parameter: 
5569     option = {
5570     
5571         bgColor: Color; //背景色
5572         scrollColor: Color; //滚动条颜色
5573 
5574         textBar: Bool; //进度条是否展示文本
5575         textColor: Color; //字体颜色(.textBar 为true才有效)
5576         textSize: Number; //字体大小(.textBar 为true才有效)
5577 
5578     }
5579 
5580 attribute:
5581 method:
5582     set(p: Number, u: Bool): undefined; //设置进度条的进度 p: now / all, u: 是否更新视图 默认false;
5583     setWidth(v: Number, u: Bool): undefined; //
5584     exit(): undefined;
5585 
5586 demo:
5587 
5588 */
5589 class ProgressView extends CanvasAnimateRender{
5590 
5591     constructor(option){
5592         super(option);
5593         
5594         const width = this.domElement.width, height = this.domElement.height,
5595         cac = new CanvasAnimateCustom().size(width, height).rect().fill(option.bgColor),
5596         scroll = new CanvasAnimateCustom().size(width, height).rect().fill(option.scrollColor);
5597         this.list.push(cac, scroll);
5598 
5599         option.textBar = typeof option.textBar === "boolean" ? option.textBar : false;
5600         if(option.textBar === true){
5601             this._textSize = option.textSize || 12;
5602             this._textColor = option.textColor || "#000000";
5603             this._textBar = new CanvasAnimateCustom().size(width, height);
5604             this.list.push(this._textBar);
5605             
5606         }
5607 
5608         else this._textBar = null;
5609         
5610         this._scroll = scroll;
5611         this._width = width;
5612 
5613         this.initList();
5614         this.set(0);
5615         
5616     }
5617 
5618     set(p, u){
5619         p = this._width * p;
5620         p = p < 1 ? 1 : p > this._width ? this._width : p;
5621 
5622         if(this._scroll.box.w !== p){
5623             this._scroll.box.w = p;
5624             if(this._textBar !== null){
5625                 this._textBar.clear().text((p/this._width*100).toFixed(2) + "%", this._textColor, this._textSize, "center", "center");
5626             }
5627             if(u === true) this.redraw();
5628         }
5629         
5630     }
5631 
5632     setWidth(v, u){
5633         this._scroll.box.w = v;
5634         if(this._textBar !== null){
5635             this._textBar.clear().text((v/this._width*100).toFixed(2) + "%", this._textColor, this._textSize, "center", "center");
5636         }
5637         if(u === true) this.redraw();
5638 
5639     }
5640 
5641     exit(){
5642         if(this.domElement.parentElement) this.domElement.parentElement.removeChild(this.domElement);
5643         
5644     }
5645 
5646 }
5647 
5648 
5649 
5650 
5651 /* PolygonTest 调试类 Polygon (可视化调试: 多边形之合并)
5652 
5653     无任何属性, new就完事了!
5654 
5655 */
5656 class PolygonTest{
5657 
5658     constructor(){
5659         const path = [], box = new Box(),
5660 
5661         car = new CanvasAnimateRender({
5662             width: window.innerWidth, 
5663             height: window.innerHeight,
5664         }).render(), 
5665     
5666         cab = car.add(new CanvasAnimateBox()).size(car.box.w, car.box.h, true);
5667         
5668         function draw(obj){
5669             console.log(obj);
5670     
5671             target._redraw();
5672     
5673             obj.forEach((v, i) => {
5674                 const str = String(i), 
5675                 size = cab.textWidth(str, 20), 
5676                 width = size < 20 ? 20 : size;
5677                 
5678                 cab.rect(2, box.set(v.x, v.y, width, width), 2).fill('#fff');
5679                 cab.text(str, '#0000ff', box.pos(v.x + (width - size)/2, v.y));
5680     
5681                 if(v.x1 === undefined) cab.stroke('#ffff00');
5682                 
5683             });
5684     
5685             car.redraw();
5686         }
5687     
5688     
5689         const target = {
5690             title: '测试多边形之合并(此类并不完美)',
5691             polygonA: null,
5692             polygonB: null,
5693             centerA: new Box(),
5694             centerB: new Box(),
5695             length: 0,
5696             mergeType: 0,
5697     
5698             merge(){
5699                 if(this.polygonA === null || this.polygonB === null) return console.warn('必须已生成两个多边形');
5700                 if(this.mergeType === 0) draw(this.polygonA.merge(this.polygonB));
5701                 else draw(this.polygonB.merge(this.polygonA));
5702                 
5703             },
5704     
5705             setPolygonA(){
5706                 this.polygonA = new Polygon(path.concat());
5707                 this.centerA.setFromPolygon(this.polygonA, false);
5708                 update_offsetNum();
5709             },
5710     
5711             setPolygonB(){
5712                 this.polygonB = new Polygon(path.concat());
5713                 this.centerB.setFromPolygon(this.polygonB, false);
5714                 update_offsetNum();
5715             },
5716     
5717             generate(){
5718                 if(path.length < 6) return console.warn('路径至少需要3个点');
5719     
5720                 if(this.polygonA !== null && this.polygonB !== null){
5721                     const _path = path.concat();
5722                     this.clear();
5723                     _path.forEach(v => path.push(v));
5724                     this.generate();
5725                     return;
5726                 }
5727     
5728                 else{
5729                     cab.path(path, true);
5730     
5731                     if(this.polygonA === null) this.setPolygonA();
5732                     else this.setPolygonB();
5733                     path.length = 0;
5734                     
5735                 }
5736                 
5737                 this._redraw();
5738                 car.redraw();
5739             },
5740     
5741             clear(){
5742                 path.length = 0;
5743                 this.polygonA = this.polygonB = null;
5744                 this.centerA.set(0,0,0,0);
5745                 this.centerB.set(0,0,0,0);
5746                 cab.clear();
5747                 car.clear();
5748             },
5749     
5750             clearPath(){
5751                 path.length = 0;
5752                 cab.clear();
5753                 this._redraw();
5754                 car.redraw();
5755             },
5756     
5757             _redraw(){
5758                 if(this.polygonA !== null){
5759                     cab.clear().path(this.polygonA.path).stroke('#00ff00', 1);
5760                     this.sign(this.polygonA.path, 'rgba(0,255,0,1)', 'rgba(0,255,0,0.6)');
5761                     if(this.polygonB !== null){
5762                         cab.path(this.polygonB.path).stroke('#ff0000', 1);
5763                         this.sign(this.polygonB.path, 'rgba(255,0,0,1)', 'rgba(255,0,0,0.6)');
5764                     }
5765                     
5766                 }
5767                 
5768             },
5769     
5770             //标记线的第一和第二个点
5771             sign(path, color1, color2){
5772                 cab.rect(5, box.set(path[0]-5, path[1]-5, 10, 10), 2).fill(color1);
5773                 cab.rect(5, box.set(path[2]-5, path[3]-5, 10, 10), 2).fill(color2);
5774             },
5775     
5776             //偏移路径
5777             offsetTimer: new Timer(()=>target.offset(), 300, 1, false),
5778             offsetNum: new Point(0, 0),
5779             offsetTarget: 'polygonA',
5780             offsetVN: 'x',
5781             offsetPoint: new Point(),
5782             offset(){
5783                 if(this[this.offsetTarget] === null) return;
5784                 path.length = 0;
5785                 
5786                 const pathNew = path, pathOld = this[this.offsetTarget].path, point = this.offsetPoint, 
5787                 center = this.offsetTarget === 'polygonA' ? 'centerA' : 'centerB',
5788                 x = this.offsetNum[this.offsetVN] * car.box[this.offsetVN === 'x' ? 'w' : 'h'];
5789                 
5790                 for(let k = 0, len = pathOld.length; k < len; k+=2){
5791                     point.set(pathOld[k], pathOld[k+1]);
5792                     point[this.offsetVN] = point[this.offsetVN] - this[center][this.offsetVN] + x;
5793                     pathNew.push(point.x, point.y);
5794                 }
5795     
5796                 this[this.offsetTarget === 'polygonA' ? 'setPolygonA' : 'setPolygonB']();
5797                 path.length = 0;
5798                 this._redraw();
5799                 car.redraw();
5800             },
5801     
5802             //克隆
5803             clone(){
5804                 if(this[this.offsetTarget] === null) return;
5805                 path.length = 0;
5806                 this[this.offsetTarget].path.forEach(v => path.push(v));
5807                 this[this.offsetTarget === 'polygonA' ? 'setPolygonB' : 'setPolygonA']();
5808                 
5809                 path.length = 0;
5810                 this._redraw();
5811                 car.redraw();
5812                 
5813             },
5814     
5815         },
5816     
5817         lineData = {type: 'line', value: '.title'},
5818     
5819         data = [
5820             {
5821                 title: '项目',
5822                 valueUrl: '.title',
5823             },
5824             {
5825                 explain: '路径长度',
5826                 title: 'length',
5827                 valueUrl: '.length',
5828                 disable: true,
5829             },
5830     
5831             {
5832                 explain: '生成多边形',
5833                 title: 'generate',
5834                 valueUrl: '.generate',
5835             },
5836     
5837             lineData,
5838             {
5839                 title: 'mergeType',
5840                 valueUrl: '.mergeType',
5841                 selectList: [{name: 'lineB to lineA', value: 0}, {name: 'lineA to lineB', value: 1}]
5842             },
5843     
5844             {
5845                 explain: '合并多边形',
5846                 title: 'merge',
5847                 valueUrl: '.merge',
5848             },
5849             lineData,
5850     
5851             {
5852                 title: 'line',
5853                 valueUrl: '.offsetTarget',
5854                 selectList: [{name: 'lineA', value: 'polygonA'}, {name: 'lineB', value: 'polygonB'}],
5855                 onChange: ()=> update_offsetNum(),
5856             },
5857     
5858             {
5859                 title: 'clone',
5860                 valueUrl: '.clone',
5861                 explain: '克隆形状后它们会重叠, 可以通过offset控件移动它们',
5862             },
5863     
5864             {
5865                 title: 'offset',
5866                 valueUrl: '.offsetNum',
5867                 range: {min: 0, max: 1, step: 0.01},
5868                 onChange: v => {
5869                     v.scope.target.offsetVN = v.valueName;
5870                     v.scope.target.offsetTimer.start();
5871                 },
5872             },
5873             lineData,
5874     
5875             {
5876                 explain: '清理路径和多边形',
5877                 title: 'clearAll',
5878                 valueUrl: '.clear',
5879             },
5880     
5881             {
5882                 explain: '清理路径',
5883                 title: 'clearPath',
5884                 valueUrl: '.clearPath',
5885             },
5886     
5887         ],
5888     
5889         ui = new CanvasAnimateUI(target, data, document.body, {
5890             autoHeight: true,
5891             width: 250,
5892             globalUpdate: true,
5893             defiendRange: {min: 0, max: car.box.w, step: 1},
5894             style: `
5895                 position: absolute;
5896                 right: 2px;
5897                 top: 2px;
5898                 background: #fff;
5899             `,
5900         }).initUI(),
5901     
5902     
5903         ui_length = ui.getView(ui.getData('.length')),
5904         update_length = function (){
5905             target.length = path.length; //更新值
5906             ui_length.update[0](); //更新ca
5907             ui_length.redraw(); //更新画布
5908         },
5909         
5910         ui_offsetNum = ui.getView(ui.getData('.offsetNum')),
5911         update_offsetNum = function (){
5912             const center = target.offsetTarget === 'polygonA' ? 'centerA' : 'centerB';
5913             target.offsetNum.set(target[center].x / car.box.w, target[center].y / car.box.h);  //更新值
5914             ui_offsetNum.update[0](); //更新ca
5915             ui_offsetNum.update[1](); //更新ca
5916             ui_offsetNum.redraw(); //更新画布
5917         };
5918     
5919     
5920         //event
5921         const cae = new CanvasAnimateEvent(car), 
5922         eventBox = new CanvasAnimate();
5923         eventBox.box.copy(car.box);
5924     
5925         cae.add(eventBox, 'click', event => {
5926             if(path.length >= 2) cab.line(path[path.length - 2], path[path.length - 1], event.pageX, event.pageY).stroke('#ffff00', 1);
5927             else cab.rect(20/2, box.size(20, 20).pos(event.pageX - 10, event.pageY - 10), 2).fill('#ffff00');
5928     
5929             path.push(event.pageX, event.pageY);
5930             car.redraw();
5931             update_length();
5932         });
5933     
5934     
5935     }
5936 
5937 }

 

标签:box,Canvas,return,ca,js,画布,._,null,data
来源: https://www.cnblogs.com/weihexinCode/p/16368449.html