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