js UI控件

   1 /* CanvasAnimateUI UI控件
   2 依赖:
   3     ImageViewer
   4     MenuView
   5     ColorTestViewer
   6 
   7 注意:
   8     传入的 .target 和 .data: CanvasUI不污染其内容(既只读), 所以可以重复利用;
   9     一旦初始化完UI后(.initUI()) 修改.data属性对控件运行没任何影响;
  10     如果想修改 Euler 控件的 range.step 点input框输入: step: 0.01; (两边或冒号两边可以有空格)
  11 
  12 支持的类型: 
  13     string    (文本控件)
  14     color    (颜色控件)
  15     number    (数值控件)
  16     boolean    (复选框控件)
  17     button    (按钮控件)
  18     object    (Vector2, Vector3, Euler, Color, RGBColor, CanvasImageData), //暂不支持: Vector4, Box
  19 
  20     //以下控件其属性值可以是任意类型
  21     select    (单选控件, 定义.selectList 显示声明)
  22     file    (导入控件, .type = json, image 显示声明)
  23 
  24     //特殊
  25     line    (分割线, .type = 'line'; .value = valueUrl|Array[valueUrl])
  26 
  27 parameter(target, data, parentElem, option)
  28     target: Object;    //
  29     data: Array[
  30         {
  31             //必须:
  32             valueUrl: string, //路径,忽略所有的空格;
  33             例(.链接对象; []链接数组; 不可以混淆): 
  34             target = {name: 'name', arr:[10], obj: null}; 
  35             data = [
  36                 {valueUrl: '.name'},
  37                 {valueUrl: '.arr[0]'},
  38                 {valueUrl: '.obj.name'} //此valueUrl视为 undefined (既忽略此条)
  39             ]
  40             
  41             //可选:
  42             type: String; //可能的值: line, image, json, select
  43             title: String; //标题
  44             explain: String; //详细说明
  45             update: boolean //this.update() 是否可以更新此控件 (注意优先级高于 this.globalUpdate)
  46             disable: boolean //启用或禁用控件(可以通过.onChange 的参数.disable属性随时调整此控件的启用和禁用)
  47             onChange: Function(Object{ //控件使属性值改变时触发
  48                 data:        Object,
  49                 scope:         CanvasAnimateUI,            //
  50                 update:     Function|Array[Function],     //使此控件主动读一次value值,但不更新视图;(配合.redraw 把value传递至控件)
  51                 redraw:     Function,                    //只绘制此控件; obj.target.value = 123; obj.update(); obj.redraw();
  52                 disable:     Boolean,                    //启用或禁用此控件
  53                 target:     Object{value},                //target.object: 是valueUrl的上一层引用, 如果只有一层则等于 CanvasAnimateUI.target
  54                 
  55             }), 
  56 
  57             range: object{min, max, step: Number}, //数字控件的范围 默认 this.defiendRange
  58             selectList: Array[Object{name:String, value:任意值}], //显示指定为select 或 type = select;
  59             value: valueUrl || Array[valueUrl], //type 为 line 时才有效; (只要其中某一个 valueUrl 指向的值不等于undefined 则渲染此line; 如果全都等于undefined 或 紧挨的上层也是线 将忽略此line)
  60 
  61         }
  62     ]
  63     parentElem: DomElement;    //父容器 默认null
  64     option = {
  65         width, height,            //可视宽高
  66         eachHeight,                //每项的高 默认 30
  67         margin,                 //边距 默认 4
  68         titleColor, conColor,    //颜色 默认#000
  69         fontSize,                 //字体大小 默认 12 (建议不超过 .eachHeight)
  70         globalUpdate,             // 默认 false
  71         onChanges,                // 默认 null
  72         defiendRange,            //默认 {min: -999999, max: 999999, step: 1};
  73         numberCursorSize        //数字控件的游标大小比例 值 0 - 1 之间; 默认 0.5
  74     }
  75 
  76 attribute:
  77     target: Object;             //默认null
  78     data: Array;                 //默认null
  79     onChanges: Function(Object); //默认null
  80     defiendRange: Object;         //默认{min: -999999, max: 999999, step: 1};
  81     parent: DomElement;            //默认 body
  82     globalUpdate: Boolean;        //默认false; 如果为true则创建的所有控件都可以在.update() 里面更新; update:false的控件除外
  83 
  84 method:
  85     style(style: String): this;            //添加css样式 (width, height, padding 属性会被覆盖, 可以通过构造器的参数设置这些值)
  86     pos(left, top: Number): this;        //设置位置(前提设定了css定位)
  87     render(parentElem): this;            //添加到dom树
  88     initUI(target, data): this;            //初始化控件
  89     update(redraw: Bool): undefined;     //更新所有已定义为更新(data.update = true)的控件
  90     getView(data): Object;                 //根据所给的data获取 可操控的对象(假设 this.update() 可以更新此data代表的控件); 返回的对象恒等于 .onChange 的参数
  91     getData(valueUrl): Object;             //获取data
  92     
  93 demo:
  94 
  95     const splitLineOfNumber = {type: 'line', value: [".num", ".vector2", '.vector3']},
  96 
  97     selectList = [
  98         {name: "ZeroZeroZeroZeroZero", value: 0},
  99         {name: "OneOneOneOneOne", value: 1},
 100         {name: "TwoTwoTwoTwoTwo", value: 2},
 101         {name: "ThreeThreeThreeThreeThree", value: 3},
 102     ],
 103 
 104     target = {
 105         str: "字符串String",
 106         checkbox: true,
 107         func: v=>console.log(v),
 108         select: 1,
 109         image: null,
 110 
 111         colorHEX: " # f0 0f0 f",
 112         colorRGB: "rgba(11, 22, 255, 1)",
 113         color: "  b l u e ",
 114         colorRGB: new RGBColor(),
 115         colorTHREE: new THREE.Color(),
 116 
 117         num: 0,
 118         vector2: new THREE.Vector2(30, 70),
 119         vector3: new THREE.Vector3(0, 30, 70),
 120 
 121     },
 122 
 123     data = [
 124         {
 125             title: "文本",
 126             valueUrl: ".str",
 127         },
 128 
 129         {
 130             title: "函数",
 131             valueUrl: ".func",
 132         },
 133 
 134         {
 135             title: "颜色",
 136             valueUrl: ".color",
 137         },
 138 
 139         {
 140             title: "复选框",
 141             valueUrl: ".checkbox",
 142         },
 143 
 144         {
 145             title: "单选",
 146             valueUrl: ".select",
 147             selectList: selectList,
 148         },
 149 
 150         //分割线 开始
 151         splitLineOfNumber,
 152         {
 153             title: "数字",
 154             explain: '游标的范围可在range范围内调整',
 155             valueUrl: ".num",
 156             range: {min: -10, max: 10, step: 0.1},
 157             update: true,
 158             onChange: v => console.log(v),
 159         },
 160 
 161         {
 162             title: "坐标2",
 163             valueUrl: ".vector2",
 164         },
 165         splitLineOfNumber,
 166         //分割线 结束
 167 
 168         {
 169             title: "图片",
 170             valueUrl: ".image",
 171             type: "image",
 172         },
 173 
 174     ],
 175 
 176     cau = new CanvasAnimateUI(target, data, null, {
 177         width: 300, 
 178         height: 210,    //可视宽高
 179         eachHeight: 30,    //每项的高
 180         fontSize: 12,     //字体大小
 181         defiendRange: {min: 0, max: 100, step: 1},
 182     })
 183 
 184     .style(`
 185         z-index: 9999;
 186         position: absolute;
 187         left: 200px;
 188         top: 190px;
 189         background: #fff;
 190     `)
 191 
 192     .initUI()
 193     .render();
 194 
 195     //test
 196     new Timer(()=>{
 197         const obj = cau.getView(cau.getData('.num'));
 198         obj.disable = true;
 199         obj.target.value = 0.5;
 200         obj.update();
 201         obj.redraw();
 202         console.log(obj);
 203     }, 5000).start();
 204 
 205 */
 206 class CanvasAnimateUI extends CanvasAnimateRender{
 207 
 208     #i = 0;
 209     #h = 0;
 210     #isLine = false;
 211 
 212     get width(){
 213         return this.box.w;
 214     }
 215 
 216     get height(){
 217         return this.box.h;//return this.domElement.height;
 218     }
 219 
 220     get clientWidth(){
 221         return this.box.w;
 222     }
 223 
 224     get clientHeight(){
 225         return this._ch;
 226     }
 227 
 228     static emptyCAC = new CanvasAnimateCustom();
 229     static emptyTimerA = new Timer(null, 500, Infinity, false);
 230     static emptyFunc(){}
 231     static stopTimers(){
 232         CanvasAnimateUI.emptyTimerA.stop();
 233         CanvasAnimateUI.emptyTimerA.speed = 500;
 234     }
 235 
 236     constructor(target = null, data = null, parentElem = null, option = {}){
 237         super(option);
 238         
 239         this.target = target;
 240         this.data = data;
 241         this.globalUpdate = typeof option.globalUpdate === "boolean" ? option.globalUpdate : false;
 242         this.onChanges = option.onChanges || null;
 243         this.defiendRange = option.defiendRange || {min: -999999, max: 999999, step: 1};
 244         //this.readValueUrlType = option.readValueUrlType || "";
 245         
 246         this._margin = option.margin || 4;
 247         this._height = option.eachHeight || 30;
 248         this._titleColor = option.titleColor || "#000";
 249         this._conColor = option.conColor || "#000";
 250         this._titleWidth = this.width * 0.25;
 251         this._conWidth = this.width * 0.75;
 252         this._fontSIze = option.fontSize || 12;
 253         this._stringMaxCount = Math.floor(this._conWidth / this._fontSIze) * 2;
 254         this._funcTitleOut = ()=>this.domElement.title = "";
 255         this._numberInputWidth = 0;
 256         this._ch = this.box.h;
 257         
 258         //this._lines = [];
 259         this._menuViews = [];
 260         this._loopData = [];
 261         this._loopObject = [];
 262         this._globalCA = new CanvasAnimate();
 263 
 264         this.cae = new CanvasAnimateEvent(this);
 265         this.imageViewer = new ImageViewer({width: this.width, height: this.width, className: 'shadow-convex'});
 266         this.colorTestView = new ColorTestViewer({width: this._conWidth, className: 'shadow-convex'});
 267         this.parent = this._getParent();
 268         this._input = this._getInput();
 269         this._inputFunc = null;
 270         
 271         this._initImages(option.numberCursorSize);
 272 
 273         if(parentElem !== null) this.render(parentElem);
 274         
 275     }
 276 
 277     exit(){
 278         this.cae.disposeEvent(); //event
 279         for(let i = 0, c = this._menuViews.length; i < c; i++) this._menuViews[i].exit(); //this._menuViews
 280         this.imageViewer.exit(); //this.imageViewer
 281         this.colorTestView.exit(); //this.colorTestView
 282         if(this._input.parentElement) this._input.parentElement.removeChild(this._input); //this._input
 283         if(this.parent.parentElement) this.parent.parentElement.removeChild(this.parent); //this.parent
 284 
 285     }
 286 
 287     render(parentElem){
 288         if(!parentElem) parentElem = document.body;
 289         parentElem.appendChild(this.parent);
 290         super.updateCanvas();
 291 
 292         this.imageViewer.domElement.style = `
 293             background-color: rgb(127,127,127);
 294             border-radius: 4px;
 295             z-index: 99999;
 296             display: none;
 297             position: absolute;
 298         `;
 299 
 300         this.colorTestView.domElement.style = `
 301             background-color: #fff;
 302             border-radius: 4px;
 303             z-index: 99999;
 304             display: none;
 305             position: absolute;
 306         `;
 307 
 308         this.imageViewer.render();
 309         this.colorTestView.render();
 310         document.body.appendChild(this._input);
 311 
 312         return this;
 313 
 314     }
 315 
 316     initUI(target, data){
 317         this.imageViewer.domElement.style.display = 
 318         this.colorTestView.domElement.style.display = 
 319         this._input.style.display = "none";
 320 
 321         for(let i = 0, c = this._menuViews.length; i < c; i++) this._menuViews[i].exit();
 322 
 323         this.cae.disposeEvent();
 324         this.cae._eventList = {};
 325         this.#isLine = false;
 326         this.#h = this.#i = 
 327         this._menuViews.length = 
 328         this.list.length = 
 329         //this._lines.length = 
 330         this._loopData.length = 
 331         this._loopObject.length = 0;
 332         
 333         if(target && this.target !== target) this.target = target;
 334         if(data && this.data !== data) this.data = data;
 335         if(!this.target || !this.data) return this;
 336         this.updateCanvas();
 337 
 338         //create view
 339         for(let k = 0, len = this.data.length, data, y; k < len; k++){
 340             data = this.data[k]; 
 341             y = this._height * this.#i + this.#i * this._margin;
 342             if(this._initUI(data, y) !== true) continue;
 343             this.#h += this._height;
 344             this.#i++;
 345             this.#isLine = data.type === 'line';
 346             //nextMake = this._initMake(data, y, nextMake);
 347 
 348             /* //this._lines
 349             c = this._lines.length;
 350             for(i = 0; i < c; i++){
 351                 ca = this._lines[i];
 352                 k = ca._content.indexOf(data.valueUrl);
 353                 if(k !== -1) ca._content[k+1] = true;
 354             } */
 355 
 356         }
 357         
 358         /* //this._lines 是否绘制
 359         for(let i = 0, c = this._lines.length, k, len, ca = null; i < c; i++){
 360             ca = this._lines[i];
 361 
 362             len = ca._content.length;
 363             for(k = 0; k < len; k += 2){
 364                 if(ca._content[k+1] === true){
 365                     ca = null;
 366                     break;
 367                 }
 368             }
 369             
 370             if(ca !== null){
 371                 i = this.list.indexOf(ca);
 372                 this.list.splice(i, 1);
 373                 len = this.list.length;
 374                 for(k = i; k < len; k++) this.list[k].box.y -= this._height;
 375             }
 376 
 377         } */
 378 
 379         //.domElement size
 380         var _h = this.#h + this.#i * this._margin;
 381         this.initList().size(this.clientWidth, _h < this.clientHeight ? this.clientHeight - this._margin*2 : _h).draw();
 382 
 383         //._globalCA event
 384         this._globalCA.box.set(0, 0, this.width, this.height);
 385         this.cae.add(this._globalCA, "down", ()=>{
 386             this._hiddenImageViewer();
 387             this._hiddenColorTestView();
 388         });
 389         this.cae.add(this._globalCA, "up", CanvasAnimateUI.stopTimers);
 390         
 391         return this;
 392     }
 393 
 394     update(redraw){
 395         if(this.parent.offsetWidth === 0) return;
 396 
 397         const c = this._loopObject.length;
 398         for(let i = 0, v; i < c; i++){
 399             v = this._loopObject[i];
 400             if(typeof v.update === "function") v.update();
 401             else if(Array.isArray(v.update) === true){
 402                 for(let k = 0, len = v.update.length; k < len; k++) v.update[k]();
 403             }
 404         }
 405 
 406         if(redraw !== false) this.redraw();
 407 
 408     }
 409 
 410     style(style = ''){
 411         style += `
 412             width: ${this.clientWidth}px;
 413             height: ${this.clientHeight}px;
 414             padding: ${this._margin}px;
 415         `;
 416 
 417         this.parent.style = style;
 418         super.updateCanvas();
 419         
 420         return this;
 421     }
 422 
 423     pos(left, top){
 424         this.parent.style.left = left+"px";
 425         this.parent.style.top = top+"px";
 426         super.updateCanvas();
 427         return this;
 428     }
 429 
 430     getView(data){
 431         const i = this._loopData.indexOf(data);
 432         if(i !== -1) return this._loopObject[i];
 433         
 434     }
 435 
 436     getData(valueUrl){
 437         const len = this.data.length;
 438         for(let k = 0; k < len; k++){
 439             if(this.data[k].valueUrl === valueUrl) return this.data[k];
 440         }
 441 
 442     }
 443 
 444 
 445     //以下方法限内部使用
 446     _redrawTarget(y){ //只重绘目标(仅一列)
 447         CanvasAnimateUI.emptyCAC.box.set(0, y, this.width, this._height);
 448         this.drawTarget(CanvasAnimateUI.emptyCAC);
 449 
 450     }
 451 
 452     _bindHover(cai, y = 0, title = "", out = 0, over = 1){
 453         this.cae.add(cai, "out", ()=>{
 454             if(title !== "") this.domElement.title = "";
 455             this.domElement.style.cursor = "";
 456             cai.set(out);
 457             this._redrawTarget(y);
 458         });
 459 
 460         this.cae.add(cai, "over", ()=>{
 461             if(title !== "") this.domElement.title = title;
 462             this.domElement.style.cursor = "pointer";
 463             cai.set(over);
 464             this._redrawTarget(y);
 465         });
 466 
 467         cai.set(out);
 468         return cai;
 469     }
 470 
 471     _initMake(data, y, nextMake){
 472 
 473         if(typeof data.make === "object"){
 474             if(nextMake === false) this.list.push(new CanvasAnimate().setImage(this.img_line).pos(0, y - this.img_line.height/2));
 475 
 476             var r = false;
 477             if(Array.isArray(data.make) === true){
 478                 const len = data.make.length;
 479                 for(let k = 0; k < len; k++){
 480                     y = this._height * this.#i + this.#i * this._margin;
 481                     if(this._initUI(data.make[k], y) !== true) continue;
 482                     if(r !== true) r = true;
 483                     this.#h += this._height;
 484                     this.#i++;
 485                     
 486                 }
 487                 
 488             }
 489 
 490             else{
 491                 y = this._height * this.#i + this.#i * this._margin;
 492                 r = this._initUI(data.make, y);
 493                 if(r === true){
 494                     this.#h += this._height;
 495                     this.#i++;
 496                 }
 497             }
 498 
 499             if(r === true) this.list.push(new CanvasAnimate().setImage(this.img_line).pos(0, y + this._height - this.img_line.height/2));
 500 
 501             return r;
 502         }
 503 
 504         return false;
 505     }
 506 
 507     _initImages(numberCursorSize = 0.5){
 508         if(numberCursorSize > 1) numberCursorSize = 1;
 509 
 510         const cac =  CanvasAnimateUI.emptyCAC, mw = this._titleWidth + this._conWidth, f_2 = this._fontSIze / 2, 
 511         m_2 = this._margin/2, m2 = this._margin*2, _h8 = this._height*0.8, mar = (this._height -  this._fontSIze) / 2,
 512         gradient_line = cac.linearGradient(0, this._height, mw, this._height);
 513 
 514         //禁用背景图片
 515         this.img_dis = cac.size(mw, this._height).rect().fill("rgba(0,0,0,0.4)").shear();
 516 
 517         //解释说明图片
 518         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();
 519         this.img_explainB = cac.clear().text("?", "#0000ff", f_2 + m_2, "center", "center").stroke("#0000ff").shear();
 520 
 521         //make 分割线图片
 522         cac.gradientColorSymme(gradient_line, ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"]);
 523         this.img_line = cac.size(mw, this._height).line(1, this._height/2, mw - 1, this._height/2).stroke(gradient_line, 1).shear();
 524 
 525         //冒号图片
 526         this.img_colon = cac.size(m2, this._height).rect().text(":", "", this._fontSIze, "center", "center").shear();
 527 
 528         //复选框图片
 529         this.img_checkboxA = cac.size(this._fontSIze + this._margin, this._fontSIze + this._margin).rect().fill("#fff").stroke(this._conColor, 1).shear();
 530         this.img_checkboxB = cac.text("✔", "#0000ff", this._fontSIze, "center", "center").shear();
 531         this.img_checkboxC = cac.clear().stroke("#0000ff", 1).shear();
 532 
 533         //颜色控件图片
 534         this.img_colorBG = cac.size(this._height, this._height).rect().drawTransparentBG().shear();
 535         this.img_colorA = cac.clear().stroke(this._conColor).shear();
 536         this.img_colorB = cac.clear().stroke("#0000ff").shear();
 537 
 538         //按钮图片
 539         this.img_buttonEditA = cac.clear().text("▤", "#000000", _h8, "center", "center").shear();
 540         this.img_buttonEditB = cac.clear().text("▤", "#0000ff", _h8, "center", "center").shear();
 541 
 542         cac.rotate(Math.PI/2);
 543         this.img_buttonEditC = cac.clear().text(">", "#000000", _h8, "center", "center").shear();
 544         this.img_buttonEditD = cac.clear().text(">", "#0000ff", _h8, "center", "center").shear();
 545         cac.unRotate(true);
 546 
 547         this.img_buttonResetA = cac.size(this._height, this._height).text("↻", "#000000", this._fontSIze, "center", "center").shear();
 548         this.img_buttonResetB = cac.clear().text("↻", "#0000ff", this._fontSIze, "center", "center").shear();
 549 
 550         cac.box.w = 0;
 551         cac.text("Button", "#000", this._fontSIze, "center", "center");
 552         cac.size(cac.box.w+m2, this._fontSIze+m2).rect().shadow("", 1, 1, 1);
 553         this.img_buttonA = cac.clear().shadow("#666").fill("#eee").shadow().text("Button", "#000", this._fontSIze, "center", "center").shear();
 554         this.img_buttonB = cac.clear().shadow("#666").fill("#fff").shadow().text("Button", "#0000ff", this._fontSIze, "center", "center").shear();
 555         this.img_buttonC = cac.clear().shadow("#666", 0, -1, -1).fill("#fff").shadow().text("Button", "#0000ff", this._fontSIze, "center", "center").shear();
 556 
 557         //数字控件图片
 558         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();
 559         this.img_numButSubB = cac.clear().fill("blue").shear();
 560 
 561         this.img_numButAddA = cac.clear().path([f_2 / 2, f_2, f_2, 0, 0, 0], true).fill("#000").shear();
 562         this.img_numButAddB = cac.clear().fill("blue").shear();
 563 
 564         cac.box.w = 0;
 565         this.img_numButSignA = cac.size(mar*numberCursorSize, mar*numberCursorSize).computeCircle().arc(cac.circle).fill("#000").shear();
 566         this.img_numButSignB = cac.clear().arc(cac.circle).fill("blue").shear();
 567 
 568         this._numberInputWidth = (this._conWidth - m2 - this._height) / 3 - f_2;
 569         this.img_numBoxA = cac.size(this._numberInputWidth, this._fontSIze+2).rect().shadow("#666", 1, 1, 1).stroke("#eee").shear();
 570         this.img_numBoxB = cac.clear().shadow().stroke("blue", 1).shear();
 571 
 572         cac.size(this._height, this._fontSIze).shadow("#666", 1, 1, 1).rect();
 573         this.img_buttonStepA = cac.stroke("#eee", 1).shear();
 574         this.img_buttonStepB = cac.clear().shadow().stroke("blue", 1).shear();
 575 
 576         //进度条图片
 577         this.img_progress = cac.size(this._conWidth - m2 - this._height, 2).line(0, 1, this._conWidth, 1).stroke("blue", 2).shear();
 578 
 579     }
 580 
 581     _isDrawByData(data){
 582         const v = this._getValueByUrl(data.valueUrl);
 583 
 584         if(v === undefined) return;
 585         
 586         switch(data.type){
 587             case "image":
 588             return true;
 589 
 590             case "select":
 591             return true;
 592 
 593             case "json":
 594             return true;
 595         }
 596 
 597         if(Array.isArray(data.selectList) === true) return true;
 598         
 599         const ty = typeof v;
 600         switch(ty){
 601             case "object":
 602             if(v === null || (v.isVector2 !== true && v.isVector3 !== true && v.isEuler !== true && v.isColor !== true && v.isRGBColor !== true && this.isCanvasImage(v) !== true)) return;
 603             break;
 604 
 605             case "string":
 606             break;
 607 
 608             case "number":
 609             break;
 610 
 611             case "boolean":
 612             break;
 613             
 614             case "function":
 615             break;
 616 
 617             default: return;
 618         }
 619 
 620         return true;
 621     }
 622 
 623     _initUI(data, y){
 624 
 625         if(data.type === 'line'){
 626             if(this.#isLine === true) return;
 627             return this.createLine(data, y);
 628         }
 629 
 630         const v = this._getValueByUrl(data.valueUrl);
 631 
 632         if(v === undefined) return;
 633         
 634         switch(data.type){
 635             case "image":
 636             this.createFileImage(data, y);
 637             return true;
 638 
 639             case "select":
 640             this.createSelect(data, v, y);
 641             return true;
 642 
 643             case "json":
 644             this.createFileJSON(data, y);
 645             return;
 646 
 647         }
 648 
 649         if(Array.isArray(data.selectList) === true){
 650             this.createSelect(data, v, y);
 651             return true;
 652         }
 653         
 654         const ty = typeof v;
 655     
 656         switch(ty){
 657             case "object":
 658             if(v === null) return;
 659             if(v.isVector2 === true) this.createNumbers(data, "Vector2", y);
 660             else if(v.isVector3 === true) this.createNumbers(data, "Vector3", y);
 661             else if(v.isEuler === true) this.createNumbers(data, "Euler", y);
 662             else if(v.isColor === true || v.isRGBColor === true) this.createColor(data, v, y);
 663             else if(this.isCanvasImage(v) === true) this.createFileImage(data, y);
 664             else return;
 665             break;
 666 
 667             case "string":
 668             const _v = this.colorTestView.getColor(v);
 669             if(_v !== "") this.createColor(data, _v, y);
 670             else this.createText(data, v, y);
 671             break;
 672 
 673             case "number":
 674             this.createNumbers(data, "number", y);
 675             break;
 676 
 677             case "boolean":
 678             this.createCheckbox(data, v, y);
 679             break;
 680             
 681             case "function":
 682             this.createFunc(data, y);
 683             break;
 684 
 685             default: return;
 686         }
 687 
 688         return true;
 689     }
 690 
 691     _string(str){
 692         if(str.length > this._stringMaxCount) return str.substr(0, this._stringMaxCount);
 693         return str;
 694     }
 695 
 696     _number(num, range){
 697         if(num < range.min) num = range.min;
 698         else if(num > range.max) num = range.max;
 699         
 700         return num;
 701     }
 702 
 703     _numToStr(num){
 704         return this._string(String(Math.floor(num * 1000) / 1000));
 705     }
 706 
 707     _getValueByUrl(valueUrl){
 708         try{
 709 
 710             return eval("this.target" + valueUrl);
 711             
 712         }
 713 
 714         catch(e){
 715 
 716             return;
 717 
 718         }
 719 
 720     }
 721 
 722     _getEventParam(data, y, target, disableCA){
 723         disableCA.visible = typeof data.disable === "boolean" ? data.disable : false;
 724         this.cae.add(disableCA, "click", CanvasAnimateUI.emptyFunc);
 725         this.cae.add(disableCA, "down", CanvasAnimateUI.emptyFunc);
 726         
 727         const result = {
 728             target: target,
 729             data: data,
 730             scope: this,
 731             redraw: ()=>this._redrawTarget(y),
 732             get disable(){
 733                 return disableCA.visible;
 734             },
 735             set disable(v){
 736                 if(typeof v === "boolean") disableCA.visible = v;
 737 
 738             },
 739 
 740         }
 741 
 742         if(data.update === true || (this.globalUpdate === true && data.update !== false)){
 743             this._loopData.push(data);
 744             this._loopObject.push(result);
 745         }
 746 
 747         return result;
 748     }
 749 
 750     _getParent(){
 751         const elem = document.createElement("div");//全局 parent
 752         //elem.className = "scroll-block-y shadow-convex";
 753         
 754         elem.style = `
 755             width: ${this.clientWidth}px;
 756             height: ${this.clientHeight}px;
 757             padding: ${this._margin}px;
 758         `;
 759         
 760         elem.appendChild(this.domElement);
 761         elem.onscroll = () => this.updateCanvas();
 762 
 763         return elem;
 764     }
 765 
 766     _getInput(){
 767         const elem = document.createElement("textarea");
 768         elem.style = `
 769             width: ${this._conWidth*0.9}px;
 770             height: ${this._conWidth*0.45}px;
 771             position: absolute;
 772             left: 0px;
 773             top: 0px;
 774             display: none;
 775             z-index: 99999;
 776             font-size:${this._fontSIze}px;
 777             padding: 2px;
 778         `;
 779 
 780         elem.onchange = e => this._hiddenInput(e.target.value);
 781         elem.onblur = () => elem.style.display = "none";
 782         //this.parent.appendChild(elem);
 783         
 784         return elem;
 785     }
 786 
 787     _getSelectIndex(list, value){
 788         for(let k = 0, len = list.length; k < len; k++){
 789             if(list[k].value === value) return k;
 790         }
 791         return -1;
 792     }
 793 
 794     _getValueUrl(valueUrl){
 795         var urls = [], str = "", tar = this.target;
 796 
 797         for(let k = 0, v, len = valueUrl.length; k < len; k++){
 798             v = valueUrl[k];
 799             if(v === " ") continue;
 800             
 801             if(v === '.' || v === '[' || v === ']'){
 802                 if(str !== ""){
 803                     urls.push(str);
 804                     str = "";
 805                 }
 806                 
 807             }
 808 
 809             else str += v;
 810 
 811         }
 812 
 813         if(str !== "") urls.push(str);
 814         
 815         if(urls.length > 1){
 816             let _len = urls.length - 1;
 817             for(let k = 0; k < _len; k++) tar = tar[urls[k]];
 818             str = urls[_len];
 819         }
 820 
 821         else str = urls[0];
 822         
 823         urls = undefined;
 824         
 825         return {
 826             object: tar,
 827             name: str,
 828             get value(){
 829                 return this.object[this.name];
 830             },
 831             set value(v){
 832                 this.object[this.name] = v;
 833             },
 834 
 835         }
 836         
 837     }
 838 
 839     _showInput(y, func, value){
 840         this._input.style.display = "block";
 841         const rect = this._input.getBoundingClientRect();
 842         
 843         y += this._height+this.domElementRect.y;
 844         if(y + rect.height > window.innerHeight) y -= this._height + rect.height;
 845         this._input.style.top = y + "px";
 846         
 847         var x = this.domElementRect.x + this._titleWidth + this._conWidth - rect.width;
 848         if(x + rect.width > window.innerWidth) x -= x + rect.width - window.innerWidth + this._margin;
 849         this._input.style.left = x + "px";
 850 
 851         this._inputFunc = func;
 852         this._input.value = value;
 853         this._input.select();
 854 
 855     }
 856 
 857     _showColorTestView(y, func, value){
 858         this.colorTestView.onchange = func;
 859         this.colorTestView.setValueString(value).update(true);
 860         this.colorTestView.domElement.style.display = "block";
 861 
 862         y += this.domElementRect.y - this.colorTestView.box.h;
 863         if(y < 0) y += this.colorTestView.box.h + this._height;
 864 
 865         var x = this.domElementRect.x + this._titleWidth + this._conWidth - this.colorTestView.box.w;
 866         if(x + this.colorTestView.box.w > window.innerWidth) x -= x + this.colorTestView.box.w - window.innerWidth + this._margin;
 867 
 868         this.colorTestView.pos(x, y);
 869         
 870     }
 871 
 872     _showImageViewer(y, image){
 873         this.imageViewer.setImage(image)
 874         .setViewportScale()
 875         .center()
 876         .drawImage()
 877         .redraw();
 878         this.imageViewer.domElement.style.display = "block";
 879 
 880         y += this.domElementRect.y - this.imageViewer.box.h;
 881         if(y < 0) y += this.imageViewer.box.h + this._height;
 882 
 883         var x = this.domElementRect.x;
 884         if(x + this.imageViewer.box.w > window.innerWidth) x -= x + this.imageViewer.box.w - window.innerWidth + this._margin;
 885         
 886         this.imageViewer.pos(x, y);
 887 
 888     }
 889 
 890     _hiddenInput(value){
 891         this._input.style.display = "none";
 892         if(this._inputFunc !== null){
 893             this._inputFunc(value);
 894             
 895         }
 896     }
 897 
 898     _hiddenColorTestView(){
 899         if(this.colorTestView.onchange !== null){
 900             this.colorTestView.onchange = null;
 901             this.colorTestView.domElement.style.display = "none";
 902         }
 903     }
 904 
 905     _hiddenImageViewer(){
 906         this.imageViewer.setImage().clear();
 907         this.imageViewer.domElement.style.display = "none";
 908 
 909     }
 910 
 911     _onChanges(data, param){
 912         if(typeof data.onChange === "function") data.onChange(param);
 913         //else{
 914         //    if(type === "func") param.target.value.call(param.target.object, param); //eval('this.target'+data.valueUrl+'(param)'); //param.target.value(param);
 915 
 916         //}
 917 
 918         if(this.onChanges !== null) this.onChanges(param);
 919         
 920     }
 921 
 922     _createMenuView(list){
 923         MenuView.reset();
 924         const menuView = new MenuView(list);
 925         menuView.view.domElement.className = "shadow-convex";
 926         menuView.view.domElement.style.zIndex = "99999";
 927         this._menuViews.push(menuView);
 928         return menuView;
 929     }
 930 
 931     _cretaeTitle(textTitle, textExplain, y){ //标题
 932         if(typeof textTitle === "string"){
 933             //data.title
 934             const title = new CanvasAnimateCustom().size(this._titleWidth, this._height).text(textTitle, this._titleColor, this._fontSIze, 0, "center").pos(0, y);
 935             this.cae.add(title, "out", this._funcTitleOut);
 936             this.cae.add(title, "over", ()=>this.domElement.title = textTitle);
 937             this.list.push(title);
 938 
 939             //data.explain
 940             if(typeof textExplain === "string"){
 941                 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));
 942                 
 943             }
 944 
 945             return title.box.maxX();
 946         }
 947         
 948         return this._titleWidth;
 949     }
 950 
 951     _createNumber(data, progress, param, x, y, target, name){
 952         var v;
 953         const bor = this._bindHover(new CanvasAnimateImages([this.img_numBoxA, this.img_numBoxB]), y).pos(x, y + (this._height - this.img_numBoxA.height)/2),
 954         con = new CanvasAnimateCustom().size(this._numberInputWidth, this._height).pos(x, y),
 955         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),
 956         but_add = this._bindHover(new CanvasAnimateImages([this.img_numButAddA, this.img_numButAddB]), y).pos(but_sub.box.x, but_sub.box.maxY() + 1),
 957         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),
 958         
 959         setValue = (value, isUpdate) => {
 960             value = this._number(value, param);
 961             if(v !== value){
 962                 target[name] = v = value;
 963                 con.clear().text(this._numToStr(v), this._conColor, this._fontSIze, 2, "center");
 964                 if(but_sign.i === 1) progress._updateCursor(v);
 965 
 966                 if(isUpdate !== false){
 967                     this._onChanges(data, param);
 968                     param.redraw();
 969                 }
 970                 
 971                 return true;
 972             }
 973 
 974         },
 975 
 976         toValue = ()=>{
 977             if(v !== target[name]){
 978                 v = this._number(target[name], param);
 979                 con.clear().text(this._numToStr(v), this._conColor, this._fontSIze, 2, "center");
 980                 if(typeof progress._updateCursor === "function") progress._updateCursor(v);
 981             }
 982             
 983         },
 984         
 985         onInputHidden = value => {
 986             value = parseFloat(value);
 987             if(isNaN(value) === true) return;
 988             setValue(value);
 989             
 990         }
 991         
 992         //event
 993         this.cae.add(bor, "click", () => this._showInput(y, onInputHidden, v));
 994         this.cae.add(bor, "out", this._funcTitleOut);
 995         this.cae.add(bor, "over", ()=>this.domElement.title = v);
 996         this.cae.add(but_sign, "click", ()=>{
 997             but_sign.next();
 998             if(but_sign.i === 0) progress._updateCursorAtSign();
 999             else progress._updateCursor(v);
1000             param.redraw();
1001             
1002         });
1003         //this.cae.add(but_sign, "over", ()=>{
1004         //    progress._updateCursor(v);
1005         //    param.redraw();
1006         //});
1007 
1008         this.cae.add(but_sub, "up", CanvasAnimateUI.stopTimers);
1009         this.cae.add(but_add, "up", CanvasAnimateUI.stopTimers);
1010         const _setValueSub = ()=>{
1011             CanvasAnimateUI.emptyTimerA.speed = 150;
1012             setValue(v - (param.type !== 'Euler' ? param.step : param.__step));
1013         },
1014         _setValueAdd = ()=>{
1015             CanvasAnimateUI.emptyTimerA.speed = 150;
1016             setValue(v + (param.type !== 'Euler' ? param.step : param.__step));
1017         }
1018         this.cae.add(but_sub, "down", ()=>{
1019             _setValueSub();
1020             CanvasAnimateUI.emptyTimerA.start(_setValueSub, 500);
1021             
1022         });
1023         this.cae.add(but_add, "down", ()=>{
1024             _setValueAdd();
1025             CanvasAnimateUI.emptyTimerA.start(_setValueAdd, 500);
1026 
1027         });
1028 
1029         this.cae.add(but_sub, "out", this._funcTitleOut);
1030         this.cae.add(but_sub, "over", () => this.domElement.title = "min: "+param.min);
1031 
1032         this.cae.add(but_add, "out", this._funcTitleOut);
1033         this.cae.add(but_add, "over", () => this.domElement.title = "max: "+param.max);
1034 
1035         if(Array.isArray(param.update) === false) param.update = [];
1036         param.update.push(toValue);
1037 
1038         but_sign.set(0);
1039         this.list.push(con, bor, but_sub, but_add, but_sign);
1040 
1041         return {
1042             maxX: but_sub.box.maxX(),
1043             sign: but_sign,
1044             setValue: setValue,
1045             toValue: toValue,
1046         }
1047     }
1048 
1049     createLine(data, y){
1050         var is;
1051 
1052         if(typeof data.value === "string") is = this._getValueByUrl(data.value) !== undefined;
1053 
1054         else if(Array.isArray(data.value) === true){
1055             for(let k = 0, len = data.value.length; k < len; k++){
1056                 if(this._getValueByUrl(data.value[k]) !== undefined){
1057                     is = true;
1058                     break;
1059                 }
1060             }
1061         }
1062 
1063         if(is === true) this.list.push(new CanvasAnimate(this.img_line).pos(0, y));
1064 
1065         return is;
1066     }
1067 
1068     createText(data, v, y){
1069         const target = this._getValueUrl(data.valueUrl),
1070         titleWidth = this._cretaeTitle(data.title, data.explain, y),
1071 
1072         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
1073         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),
1074         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(con.box.maxX(), y),
1075         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
1076 
1077         eventParam = this._getEventParam(data, y, target, disableCA),
1078         onInputHaidden = value => {
1079             target.value = v = value;
1080             con.clear().text(this._string(v), this._conColor, this._fontSIze, 0, "center");
1081             this._onChanges(data, eventParam);
1082             this._redrawTarget(y);
1083         }
1084 
1085         //event
1086         this.cae.add(con, "out", this._funcTitleOut);
1087         this.cae.add(con, "over", ()=>this.domElement.title = v);
1088         this.cae.add(but, "click", () => this._showInput(y, onInputHaidden, v));
1089 
1090         eventParam.update = ()=>{
1091             if(target.value !== v){
1092                 v = target.value;
1093                 con.clear().text(this._string(v), this._conColor, this._fontSIze, 0, "center");
1094             }
1095             
1096         }
1097         
1098         this.list.push(colon, con, but, disableCA);
1099         
1100     }
1101 
1102     createColor(data, v, y){
1103         const isObj = typeof v === "object";
1104         if(isObj === true) v = v.getStyle(); //兼容 class Color
1105         
1106         const target = this._getValueUrl(data.valueUrl),
1107         titleWidth = this._cretaeTitle(data.title, data.explain, y),
1108         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
1109         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),
1110         conBG = new CanvasAnimate(this.img_colorBG).pos(conA.box.maxX(), y),
1111         conB = new CanvasAnimateCustom().size(this._height, this._height).rect().shadow("#666", 2).fill(v).pos(conBG.box.x, y),
1112         conC = this._bindHover(new CanvasAnimateImages([this.img_colorA, this.img_colorB]), y).pos(conBG.box.x, y),
1113         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(conB.box.maxX(), y), 
1114         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
1115         eventParam = this._getEventParam(data, y, target, disableCA),
1116 
1117         setValue = value => {
1118             value = this.colorTestView.getColor(value);
1119             
1120             if(value === "") return;
1121             v = value;
1122             if(isObj === false) target.value = v;
1123             else target.value.set(v);
1124             
1125             conA.clear().text(v, this._conColor, this._fontSIze, 0, "center");
1126             conB.clear().fill(v);
1127             this._onChanges(data, eventParam);
1128             this._redrawTarget(y);
1129         }
1130 
1131         //event
1132         this.cae.add(but, "click", () => this._showInput(y, setValue, v));
1133         this.cae.add(conC, "click", () => this._showColorTestView(y, setValue, v));
1134         
1135         this.cae.add(conA, "out", this._funcTitleOut);
1136         this.cae.add(conA, "over", ()=>this.domElement.title = v);
1137         
1138         eventParam.update = ()=>{
1139             if(target.value !== v){
1140                 v = isObj === false ? target.value : target.value.getStyle();
1141                 conA.clear().text(v, this._conColor, this._fontSIze, 0, "center");
1142                 conB.clear().fill(v);
1143             }
1144             
1145         }
1146 
1147         this.list.push(colon, conA, conBG, conB, conC, but, disableCA);
1148     }
1149 
1150     createCheckbox(data, v, y){
1151         const target = this._getValueUrl(data.valueUrl),
1152         titleWidth = this._cretaeTitle(data.title, data.explain, y),
1153         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
1154         con = new CanvasAnimateImages([this.img_checkboxA, this.img_checkboxB]).pos(colon.box.maxX(), y + (this._height - this.img_checkboxA.height) / 2),
1155         con_hover = new CanvasAnimate(this.img_checkboxC).pos(con.box.x, con.box.y),
1156         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
1157         eventParam = this._getEventParam(data, y, target, disableCA);
1158         con.set(v === true ? 1 : 0);
1159 
1160         //event
1161         con_hover.visible = false;
1162         this.cae.add(con, "out", ()=>{
1163             this._funcTitleOut();
1164             con_hover.visible = false;
1165             this._redrawTarget(y);
1166         });
1167 
1168         this.cae.add(con, "over", ()=>{
1169             this.domElement.title = String(v);
1170             con_hover.visible = true;
1171             this._redrawTarget(y);
1172         });
1173 
1174         this.cae.add(con, "click", () => {
1175             con.next();
1176             v = con.i === 1 ? true : false;
1177             target.value = v;
1178             this._onChanges(data, eventParam);
1179             this._redrawTarget(y);
1180         });
1181 
1182         //update
1183         eventParam.update = ()=>{
1184             if(target.value !== v){
1185                 v = target.value;
1186                 con.set(v === true ? 1 : 0);
1187             }
1188             
1189         }
1190         
1191         this.list.push(colon, con, con_hover, disableCA);
1192 
1193     }
1194 
1195     createFunc(data, y){
1196         const titleWidth = this._cretaeTitle(data.title, data.explain, y),
1197         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
1198         con = new CanvasAnimate(this.img_buttonA).pos(colon.box.maxX(), y + (this._height - this.img_buttonA.height) / 2),
1199         con_hover = new CanvasAnimate(this.img_buttonB).pos(con.box.x, con.box.y),
1200         con_click = new CanvasAnimate(this.img_buttonC).pos(con.box.x, con.box.y),
1201         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
1202 
1203         target = this._getValueUrl(data.valueUrl),
1204         param = this._getEventParam(data, y, target, disableCA);
1205 
1206         con_click.visible = false;
1207         con_hover.visible = false;
1208 
1209         //event
1210         this.cae.add(con, "out", ()=>{
1211             this._funcTitleOut();
1212             con_hover.visible = false;
1213             this._redrawTarget(y);
1214         });
1215 
1216         this.cae.add(con, "over", ()=>{
1217             this.domElement.title = target.value.name;
1218             con_hover.visible = true;
1219             this._redrawTarget(y);
1220         });
1221 
1222         this.cae.add(con, "down", () => {
1223             con_hover.visible = false;
1224             con_click.visible = true;
1225             this._redrawTarget(y);
1226         });
1227 
1228         this.cae.add(con, "up", () => {
1229             con_click.visible = false;
1230             //this._onChanges(data, param, "func"); //执行回调1
1231             //param.target.value.call(param.target.object, param);  //执行回调2
1232             //eval('this.target'+data.valueUrl+'(param)'); //执行回调3
1233             //param.target.value(param);
1234             if(this.target){
1235                 if(!data.eventFunc){
1236                     eval('this.target'+data.valueUrl+'(param)');
1237                     if(this.onChanges !== null) this.onChanges(param);
1238                 }
1239                 else this._onChanges(data, param);
1240             }
1241             this._redrawTarget(y);
1242         });
1243 
1244         param.update = CanvasAnimateUI.emptyFunc;
1245 
1246         this.list.push(colon, con, con_hover, con_click, disableCA);
1247         
1248     }
1249 
1250     createSelect(data, v, y){
1251         var k = this._getSelectIndex(data.selectList, v), menuParam = null;
1252 
1253         const target = this._getValueUrl(data.valueUrl), _list = [], list = data.selectList, 
1254         titleWidth = this._cretaeTitle(data.title, data.explain, y),
1255         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
1256         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),
1257         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditC, this.img_buttonEditD]), y).pos(con.box.maxX(), y),
1258         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
1259         eventParam = this._getEventParam(data, y, target, disableCA),
1260 
1261         func = value => {
1262             menuParam.lcon_def.set();
1263             menuParam.background.set();
1264             menuParam = value;
1265             menuParam.lcon_def.set(1);
1266             menuParam.background.set(2);
1267 
1268             v = list[value.key].value;
1269             target.value = v;
1270             con.clear().text(this._string(list[value.key].name), this._conColor, this._fontSIze, 0, "center");
1271             this._onChanges(data, eventParam);
1272             this._redrawTarget(y);
1273             value.target.visible = false;
1274             
1275         }
1276 
1277         for(let i = 0, len = list.length; i < len; i++) _list.push({name: list[i].name, func: func});
1278         this.cae.add(con, "out", this._funcTitleOut);
1279         this.cae.add(con, "over", ()=>this.domElement.title = v);
1280         
1281         eventParam.update = ()=>{
1282             if(target.value !== v){
1283                 v = target.value;
1284                 k = this._getSelectIndex(list, v);
1285                 con.clear().text(k !== -1 ? this._string(list[k].name) : "undefined", this._conColor, this._fontSIze, 0, "center");
1286             }
1287             
1288         }
1289 
1290         //init menuView
1291         var menuView = null;
1292         this.cae.add(this._globalCA, "down", () => {
1293             if(menuView !== null && menuView.visible === true) menuView.visible = false;
1294         });
1295 
1296         this.cae.add(but, "click", () => {
1297 
1298             //这里减轻一下.initUI()的压力: 既在初始化时先不创建 menuView 控件;
1299             if(menuView === null){
1300                 menuView = this._createMenuView(_list)
1301                 if(k !== -1){
1302                     menuParam = menuView.views[k];
1303                     menuParam.lcon_def.set(1);
1304                     menuParam.background.set(2);
1305                     
1306                 }
1307             }
1308 
1309             let top = y + this._height + this.domElementRect.y;
1310             if(top + menuView.height > window.innerHeight) top -= this._height + menuView.height;
1311 
1312             let left = this.domElementRect.x + this._titleWidth + this._conWidth - menuView.width;
1313             if(left + menuView.width > window.innerWidth) left -= left + menuView.width - window.innerWidth + this._margin;
1314 
1315             menuView.view.pos(left, top);
1316             menuView.visible = true;
1317             
1318         });
1319 
1320 
1321         this.list.push(colon, con, but, disableCA);
1322     }
1323 
1324     createNumber(data, y, param, progress, progressCursor){
1325 
1326         //param 特有属性 (min, max, step)
1327         param.min = (typeof data.range === "object" && typeof data.range.min === "number") ? data.range.min : this.defiendRange.min;
1328         param.max = (typeof data.range === "object" && typeof data.range.max === "number") ? data.range.max : this.defiendRange.max;
1329         var _step = (typeof data.range === "object" && typeof data.range.step === "number") ? data.range.step : this.defiendRange.step;
1330         if(param.type === "Euler"){
1331             param.__step = _step;
1332             _step = param.target.value.order;
1333         }
1334         Object.defineProperty(param, 'step', {
1335             get: () => {
1336                 return _step;
1337             },
1338 
1339             set: v => {
1340                 if(param.type === "Euler"){
1341                     //如果想修改 Euler 控件的range.step: 'step: 0.01';
1342                     let i = v.indexOf(':');
1343                     if(i !== -1 && UTILS.removeSpaceSides(v.substr(0, i)) === 'step'){
1344                         let step = parseFloat(v.substr(i+1));
1345                         if(isNaN(step) === false) param.__step = step;
1346                         
1347                     }
1348 
1349                     else{
1350                         param.target.value.order = UTILS.removeSpaceSides(v).toUpperCase(); //去两边空格 并 转为大写字母
1351                         stepVal.clear().text(param.target.value.order, this._conColor, this._fontSIze, "center", "center");
1352                     }
1353                     
1354                 }
1355 
1356                 else{
1357                     v = parseFloat(v);
1358                     if(isNaN(v) === true) return;
1359                     _step = v;
1360                     stepVal.clear().text(this._numToStr(v), this._conColor, this._fontSIze, "center", "center");
1361                     
1362                 }
1363 
1364             }
1365 
1366         });
1367 
1368         progress._min = param.min;
1369         progress._max = param.max;
1370         progress._width = progress.box.w;
1371         progress._updateCursor = value => {
1372             if(value < progress._min) value = progress._min;
1373             else if(value > progress._max) value = progress._max;
1374             progress.box.w = (value - progress._min) / (progress._max - progress._min) * progress._width;
1375             progressCursor.box.x = progress.box.maxX() - progressCursor.box.w/2;
1376 
1377         }
1378 
1379         const titleWidth = this._cretaeTitle(data.title, data.explain, y),
1380         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
1381         stepBor = this._bindHover(new CanvasAnimateImages([this.img_buttonStepA, this.img_buttonStepB]), y)
1382         .pos(colon.box.x + this._conWidth - this._height, y + (this._height - this.img_buttonStepA.height) / 2),
1383         stepVal = new CanvasAnimateCustom().size(stepBor.box.w, stepBor.box.h).pos(stepBor.box.x, stepBor.box.y),
1384 
1385         setStepVal = value => {
1386             param.step = value;
1387             param.redraw();
1388         }
1389 
1390         this.cae.add(stepBor, "click", () => this._showInput(y, setStepVal, param.type !== "Euler" ? param.step : param.target.value.order));
1391 
1392         this.list.push(colon, stepVal, stepBor);
1393         param.step = _step;
1394         
1395     }
1396 
1397     createNumbers(data, v, y){
1398         var isDownCursor = false;
1399 
1400         const disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
1401         progress = new CanvasAnimate(this.img_progress).pos(this.img_colon.width + this._titleWidth, y + this._height - this.img_progress.height - 1),
1402         progressCursor = new CanvasAnimateImages([this.img_numButSignA, this.img_numButSignB]).pos(progress.box.x, progress.box.y - this.img_numButSignA.height / 2),
1403 
1404         //create number input
1405         target = this._getValueUrl(data.valueUrl), 
1406         param = this._getEventParam(data, y, target, disableCA),
1407         inputX = this._createNumber(data, progress, param, progress.box.x, y, v === "number" ? target.object : target.value, v === "number" ? target.name : "x"),
1408         inputY = (v === "Vector2" || v === "Vector3" || v === "Euler") ? this._createNumber(data, progress, param, inputX.maxX, y, target.value, "y") : null,
1409         inputZ = (v === "Vector3" || v === "Euler") ? this._createNumber(data, progress, param, inputY.maxX, y, target.value, "z") : null,
1410 
1411         //event
1412         onmove = event => {
1413             let is = false;
1414             v = (event.pageX - this.domElementRect.x - progress.box.x) / progress._width * (progress._max - progress._min) + progress._min;
1415 
1416             //setValue 本来就根据param.min.max限制值, 这里是根据 progress.min.max 限制值
1417             if(v < progress._min) v = progress._min;
1418             else if(v > progress._max) v = progress._max;
1419 
1420             if(inputX.sign.i === 1){
1421                 if(inputX.setValue(v, false) === true && is !== true) is = true;
1422                 
1423             }
1424 
1425             if(inputY !== null && inputY.sign.i === 1){
1426                 if(inputY.setValue(v, false) === true && is !== true) is = true;
1427 
1428             }
1429 
1430             if(inputZ !== null && inputZ.sign.i === 1){
1431                 if(inputZ.setValue(v, false) === true && is !== true) is = true;
1432 
1433             }
1434 
1435             if(is === true){
1436                 this._onChanges(data, param);
1437                 param.redraw();
1438             }
1439 
1440         },
1441 
1442         onup = ()=>{
1443             this.cae.remove(this._globalCA, "move", onmove);
1444             this.cae.remove(this._globalCA, "up", onup);
1445             this.cae.remove(progressCursor, "move", onmove);
1446             this.cae.remove(progressCursor, "up", onup);
1447             isDownCursor = false;
1448         };
1449 
1450         //bug: 划入时 点击游标会触发move事件(猜测: 可能是在down里面绑定事件带来的延迟)
1451         this.cae.add(progressCursor, "down", () => {
1452             if(param.disable === true) return;
1453             isDownCursor = true;
1454             //onmove(event);
1455             this.cae.add(this._globalCA, "move", onmove);
1456             this.cae.add(this._globalCA, "up", onup);
1457             this.cae.add(progressCursor, "move", onmove);
1458             this.cae.add(progressCursor, "up", onup);
1459 
1460         });
1461 
1462         this.cae.add(progressCursor, "out", ()=>{
1463             this.domElement.title = "";
1464             this.domElement.style.cursor = "";
1465             progressCursor.set(0);
1466             param.redraw();
1467         });
1468 
1469         this.cae.add(progressCursor, "over", ()=>{
1470             this.domElement.title = progress._min + " <-点进度条调整此范围-> " + progress._max;
1471             this.domElement.style.cursor = "pointer";
1472             progressCursor.set(1);
1473             param.redraw();
1474         });
1475 
1476         this.cae.add(progress, 'up', ()=>{
1477             if(isDownCursor !== false) return;
1478             this._showInput(y, v=>{
1479                 let i = v.indexOf(',');
1480                 if(i !== -1){
1481                     let a = parseFloat(v.substr(0, i)), b = parseFloat(v.substr(i+1));
1482                     if(isNaN(a) === false && isNaN(b) === false && a >= param.min && a < param.max && b > param.min && b <= param.max && a < b){
1483                         progress._min = a;
1484                         progress._max = b;
1485                     }
1486                     else{
1487                         progress._min = param.min;
1488                         progress._max = param.max;
1489                     }
1490 
1491                     progress._updateCursorAtSign();
1492                     param.redraw();
1493                 }
1494             }, progress._min+","+progress._max);
1495         });
1496 
1497         //param 特有属性 (type: "number" || "Vector2" || "Vector3" || "Euler")
1498         param.type = v;
1499 
1500         //create number scene
1501         this.createNumber(data, y, param, progress, progressCursor);
1502 
1503         //init
1504         progress._updateCursorAtSign = () => {
1505             if(inputZ !== null && inputZ.sign.i === 1) progress._updateCursor(target.value.z);
1506             else if(inputY !== null && inputY.sign.i === 1) progress._updateCursor(target.value.y);
1507             else if(inputX.sign.i === 1) progress._updateCursor(typeof target.value === "number" ? target.value : target.value.x);
1508 
1509         }
1510         
1511         progressCursor.set(0);
1512         inputX.sign.set(1);
1513         inputX.toValue(); //inputX.setValue(v === "number" ? target.value : target.value.x, false);
1514         if(inputY !== null) inputY.toValue(); //if(inputY !== null) inputY.setValue(target.value.y);
1515         if(inputZ !== null) inputZ.toValue(); //if(inputZ !== null) inputZ.setValue(target.value.z);
1516         this.list.push(progress, progressCursor, disableCA);
1517         
1518     }
1519 
1520     createFileImage(data, y){
1521         var imageInfo = "";
1522         const target = this._getValueUrl(data.valueUrl),
1523         titleWidth = this._cretaeTitle(data.title, data.explain, y),
1524         colon = new CanvasAnimate(this.img_colon).pos(titleWidth, y),
1525         conA = new CanvasAnimateCustom().size(this._conWidth - colon.box.w - this._height * 3, this._height).pos(colon.box.maxX(), y),
1526         butReset = this._bindHover(new CanvasAnimateImages([this.img_buttonResetA, this.img_buttonResetB]), y, "reset").pos(conA.box.maxX(), y),
1527         conBG = new CanvasAnimate(this.img_colorBG).pos(butReset.box.maxX(), y),
1528         conB = new CanvasAnimateCustom().size(this._height, this._height).pos(conBG.box.x, y),
1529         but = this._bindHover(new CanvasAnimateImages([this.img_buttonEditA, this.img_buttonEditB]), y).pos(conB.box.maxX(), y), 
1530         disableCA = new CanvasAnimate(this.img_dis).pos(0, y),
1531         eventParam = this._getEventParam(data, y, target, disableCA),
1532         
1533         setValue = value => {
1534             if(this.isCanvasImage(value) === true){
1535                 let p = value.width / value.height, _w = 0, _h = 0;
1536                 if(p < 1){
1537                     _w = p * conB.box.w; 
1538                     _h = conB.box.h;
1539                 }
1540                 else{
1541                     _w = conB.box.w;
1542                     _h = conB.box.w / p;
1543                 }
1544 
1545                 conB.clear().drawImage(value, 0, 0, value.width, value.height, (conB.box.w - _w) / 2, (conB.box.h - _h) / 2, _w, _h);
1546 
1547                 imageInfo = value.width + " * " + value.height + " | " + this._numToStr(_w / value.width);
1548                 conA.clear().text(imageInfo, this._conColor, this._fontSIze, 0, "center");
1549 
1550             }
1551 
1552             else{
1553                 imageInfo = "";
1554                 conA.clear();
1555                 conB.clear();
1556                 value = null;
1557             }
1558             
1559             if(target.value !== value){
1560                 target.value = value;
1561                 this._onChanges(data, eventParam);
1562             }
1563 
1564             this._redrawTarget(y);
1565         }
1566 
1567         //event
1568         this.cae.add(conB, "click", () => CanvasAnimateUI.downloadImage(setValue));
1569         this.cae.add(butReset, "click", setValue);
1570         this.cae.add(but, "click", () => this._showImageViewer(y, target.value));
1571         
1572         this.cae.add(conA, "out", this._funcTitleOut);
1573         this.cae.add(conA, "over", ()=>this.domElement.title = imageInfo);
1574         
1575         //update
1576         eventParam.update = ()=> setValue(target.value);
1577 
1578         //init
1579         if(this.isCanvasImage(target.value) === false && target.value !== null) target.value = null;
1580         setValue(target.value);
1581         this.list.push(colon, conA, butReset, conBG, conB, but, disableCA);
1582         
1583     }
1584 
1585     createFileJSON(data, y){
1586         console.log("CanvasAnimateUI: 暂不支持 type = json");
1587     }
1588 
1589 }
部分代码

 

API说明:

 

依赖:
    ImageViewer
    MenuView
    ColorTestViewer

注意:
    传入的 .target 和 .data: CanvasUI不污染其内容(既只读), 所以可以重复利用;
    一旦初始化完UI后(.initUI()) 修改.data属性对控件运行没任何影响;
    如果想修改 Euler 控件的 range.step 点input框输入: step: 0.01; (两边或冒号两边可以有空格)

支持的类型:
    string  (文本控件)
    color   (颜色控件)
    number  (数值控件)
    boolean (复选框控件)
    button  (按钮控件)
    object  (Vector2, Vector3, Euler, Color, RGBColor, CanvasImageData), //暂不支持: Vector4, Box

    //以下控件其属性值可以是任意类型
    select  (单选控件, 定义.selectList 显示声明)
    file    (导入控件, .type = json, image 显示声明)

    //特殊
    line    (分割线, .type = 'line'; .value = valueUrl|Array[valueUrl])

parameter(target, data, parentElem, option)
    target: Object; //
    data: Array[
        {
            //必须:
            valueUrl: string, //路径,忽略所有的空格;
            例(.链接对象; []链接数组; 不可以混淆):
            target = {name: 'name', arr:[10], obj: null};
            data = [
                {valueUrl: '.name'},
                {valueUrl: '.arr[0]'},
                {valueUrl: '.obj.name'} //此valueUrl视为 undefined (既忽略此条)
            ]
           
            //可选:
            type: String; //可能的值: line, image, json, select
            title: String; //标题
            explain: String; //详细说明
            update: boolean //this.update() 是否可以更新此控件 (注意优先级高于 this.globalUpdate)
            disable: boolean //启用或禁用控件(可以通过.onChange 的参数.disable属性随时调整此控件的启用和禁用)
            onChange: Function(Object{ //控件使属性值改变时触发
                data:       Object,
                scope:      CanvasAnimateUI,            //
                update:     Function|Array[Function],   //使此控件主动读一次value值,但不更新视图;(配合.redraw 把value传递至控件)
                redraw:     Function,                   //只绘制此控件; obj.target.value = 123; obj.update(); obj.redraw();
                disable:    Boolean,                    //启用或禁用此控件
                target:     Object{value},              //target.object: 是valueUrl的上一层引用, 如果只有一层则等于 CanvasAnimateUI.target
               
            }),

            range: object{min, max, step: Number}, //数字控件的范围 默认 this.defiendRange
            selectList: Array[Object{name:String, value:任意值}], //显示指定为select 或 type = select;
            value: valueUrl || Array[valueUrl], //type 为 line 时才有效; (只要其中某一个 valueUrl 指向的值不等于undefined 则渲染此line; 如果全都等于undefined 或 紧挨的上层也是线 将忽略此line)

        }
    ]
    parentElem: DomElement; //父容器 默认null
    option = {
        width, height,          //可视宽高
        eachHeight,             //每项的高 默认 30
        margin,                 //边距 默认 4
        titleColor, conColor,   //颜色 默认#000
        fontSize,               //字体大小 默认 12 (建议不超过 .eachHeight)
        globalUpdate,           // 默认 false
        onChanges,              // 默认 null
        defiendRange,           //默认 {min: -999999, max: 999999, step: 1};
        numberCursorSize        //数字控件的游标大小比例 值 0 - 1 之间; 默认 0.5
    }

attribute:
    target: Object;             //默认null
    data: Array;                //默认null
    onChanges: Function(Object); //默认null
    defiendRange: Object;       //默认{min: -999999, max: 999999, step: 1};
    parent: DomElement;         //默认 body
    globalUpdate: Boolean;      //默认false; 如果为true则创建的所有控件都可以在.update() 里面更新; update:false的控件除外

method:
    style(style: String): this;         //添加css样式 (width, height, padding 属性会被覆盖, 可以通过构造器的参数设置这些值)
    pos(left, top: Number): this;       //设置位置(前提设定了css定位)
    render(parentElem): this;           //添加到dom树
    initUI(target, data): this;         //初始化控件
    update(redraw: Bool): undefined;    //更新所有已定义为更新(data.update = true)的控件
    getView(data): Object;              //根据所给的data获取 可操控的对象(假设 this.update() 可以更新此data代表的控件); 返回的对象恒等于 .onChange 的参数
    getData(valueUrl): Object;          //获取data
   
demo:

 

  1 <!DOCTYPE html>
  2 <html lang = "zh-cmn-Hans">
  3     
  4     <head>
  5         <title>DEMO</title>
  6         <meta charset = "utf-8" />
  7         <script src = "./js/Utils.js" type = "text/javascript"></script>
  8         <link rel = "stylesheet" charset = "utf-8" href = "./css/utils.css" />
  9     </head>
 10 
 11     <body>
 12 
 13         <script type = "text/javascript">
 14 
 15             document.body.style = `
 16                 margin: 0;
 17                 width: ${window.innerWidth}px;
 18                 height: ${window.innerHeight}px;
 19                 background-color: #000;
 20             `;
 21 
 22             //ui控件
 23             const splitLineOfNumber = {type: 'line', value: [".num", ".vector2", '.vector3']},
 24 
 25             selectList = [
 26                 {name: "ZeroZeroZeroZeroZero", value: 0},
 27                 {name: "OneOneOneOneOne", value: 1},
 28                 {name: "TwoTwoTwoTwoTwo", value: 2},
 29                 {name: "ThreeThreeThreeThreeThree", value: 3},
 30             ],
 31 
 32             target = {
 33                 str: "字符串String",
 34                 checkbox: true,
 35                 func: v=>console.log(v),
 36                 select: 1,
 37                 image: null,
 38 
 39                 colorHEX: " # f0 0f0 f",
 40                 colorRGB: "rgba(11, 22, 255, 1)",
 41                 color: "  b l u e ",
 42                 colorRGB: new RGBColor(),
 43                 //colorTHREE: new THREE.Color(),
 44 
 45                 num: 0,
 46                 //vector2: new THREE.Vector2(30, 70),
 47                 //vector3: new THREE.Vector3(0, 30, 70),
 48 
 49             },
 50 
 51             data = [
 52                 {
 53                     title: "文本",
 54                     valueUrl: ".str",
 55                 },
 56 
 57                 {
 58                     title: "函数",
 59                     valueUrl: ".func",
 60                 },
 61 
 62                 {
 63                     title: "颜色",
 64                     valueUrl: ".color",
 65                 },
 66 
 67                 {
 68                     title: "复选框",
 69                     valueUrl: ".checkbox",
 70                 },
 71 
 72                 {
 73                     title: "单选",
 74                     valueUrl: ".select",
 75                     selectList: selectList,
 76                 },
 77 
 78                 //分割线 开始
 79                 splitLineOfNumber,
 80                 {
 81                     title: "数字",
 82                     explain: '游标的范围可在range范围内调整',
 83                     valueUrl: ".num",
 84                     range: {min: -10, max: 10, step: 0.1},
 85                     update: true,
 86                     onChange: v => console.log(v),
 87                 },
 88 
 89                 {
 90                     title: "坐标2",
 91                     valueUrl: ".vector2",
 92                 },
 93                 splitLineOfNumber,
 94                 //分割线 结束
 95 
 96                 {
 97                     title: "图片",
 98                     valueUrl: ".image",
 99                     type: "image",
100                 },
101 
102             ],
103 
104             cau = new CanvasAnimateUI(target, data, null, {
105                 width: 300, 
106                 height: 210,    //可视宽高
107                 eachHeight: 30,    //每项的高
108                 fontSize: 12,     //字体大小
109                 defiendRange: {min: 0, max: 100, step: 1},
110 
111             })
112 
113             .style(`
114                 z-index: 9999;
115                 position: absolute;
116                 left: 200px;
117                 top: 190px;
118                 background: #fff;
119             `)
120 
121             .initUI()
122             .render();
123 
124             cau.parent.className = 'scroll-block-y';
125 
126         </script>
127 
128     </body>
129 
130 </html>

 

提取地址: https://pan.baidu.com/s/1TV1j5BeZ7ZhidCq7aQXePA

提取码: 1111

 

posted @ 2022-05-28 14:01  鸡儿er  阅读(167)  评论(0编辑  收藏  举报