跳到主要内容
版本:1.10.19_build_2024.5.22.17.56

自定义控件

为了进一步提供控件自由度,AHMI IDE提供了自定义控件的功能。

  1. 打开帮助,点击自定控件,打开自定义控件配置面板。

  2. 点击新建,出现空白的自定义控件选项。分别输入自定义控件配置的名称、图标-选中、图标未选中。

  3. 在代码编辑器中编写自定义控件对象,采用JavaScript语言。

  4. 自定义控件对象编写:

    4.1 基本结构:

    var widget = {
    name:'GIF',
    type:'MyGIF',
    group:'显示类',
    info:{
    width:100,
    height:100,
    left:0,
    top:0
    },
    tagType:'array',
    actionTriggers:'',
    texList:function(widget){

    },
    texConfig:function(widgetInfo){

    },
    triggers:{

    },
    draw:function(widget, options){

    },
    paint:function(widget, ctx, options){

    },
    render:function (widget,srcRootDir,dstDir,imgUrlPrefix,resolve, reject){

    },
    transWidget:function(targetWidget, wIdx){

    }
    }
    属性含义附加
    name控件显示名称
    type控件类型
    group控件组类型显示类、触控类、其他类
    info控件属性可配置的属性列表
    tagType变量类型number、string、array
    actionTriggers动作触发条件Press、LongPress、
    Release、EnterLowAlarm、
    LeaveLowAlarm、EnterHighAlarm、
    LeaveHighAlarm、TagChange、
    SwipeLeft、SwipeRight、
    SwipeTop、SwipeBottom等,用|分割
    texList纹理列表控件默认纹理
    draw数据更新根据变量或者其他条件更新数据
    paint绘制逻辑根据数据绘制控件样式
    render预处理逻辑生成时预处理图片等资源
    transWidget控件转换逻辑编辑到仿真时可以修改一些控件参数

    4.2 控件属性

    info:{
    width:400,
    height:400,
    left:0,
    top:0,
    offset:{
    default:0,
    style:'none'
    },
    arrange:{
    type:'selector',
    title:{en:'Direction', zh: '方向'},
    options:[{name:{en:'Vertical', zh: '垂直'},value:0},{name:{en:'Horizontal', zh: '水平'},value:1}],
    default:0,
    style:'short'
    },
    showNum:{
    type:'number',
    title:{en:'Show Num',zh:'显示个数'}[window.lang],
    default:1,
    style:'short',
    limitFunc:function(newValue){
    if(newValue < 1){
    return {en:'Show num cannot be less than 1',zh:'显示个数至少是1'}[window.lang]
    }
    }
    }
    },

    控件的可配置属性可以是一个对象或者是单纯的数值或者字符串。如果是对象,可以精细地控制该属性。所有控件都应该有width、height、left、top属性。 当属性是对象时,其参数含义如下: | 属性 | 含义 | 附加 | | :----:| :----: | :----: | | type | 属性类型 | number、string、selector,selector需要配合options| | title | 属性显示名称 | 字符串/对象,对象时支持多语言 | | options | 选项 | 包含{name, value}的数组 | | default | 属性默认值 | | | style | 属性显示样式 | short占1/2行,long占一行,none不显示 | | limitFunc | 约束条件 | 判断当前值是否合法,不合法给出错误提示 |

4.3 纹理列表
```
texList:function(widget){
// return [{},{}]
var bgTex = {
name:'GIF纹理',
currentSliceIdx:0,
slices:[
{
name:'GIF图片',
imgSrc:'',
color:'rgba(255,255,255,1)'

}
]
}

return [bgTex]
},
```

每个纹理包含名称和slices属性,slices中包含了纹理切片。每个纹理切片都有name、imgSrc、color属性。

4.4 纹理配置
```
texConfig:function(widgetInfo){
switch(widgetInfo.tex.name){
case '元素':
return {
canAddNewSlice:true,
disableEditName:false
}
default:
return {
canAddNewSlice:false,
disableEditName:true
}
}
},
```
有时要根据控件信息来调整纹理配置,目前可以配置canAddNewSlice和disableEditName。其中canAddNewSlice决定是否可以添加新的纹理切片,disableEditName决定是否可以修改纹理名称。

4.5 triggers
```
triggers:{
changeTex:function(layer, widget,arg){
},
changeGeneralAttrs:function(layer, widget,arg){
function _getRandomColor(){
var r = _.random(64, 255);
var g = _.random(64, 255);
var b = _.random(64, 255);
return 'rgba(' + r + ',' + g + ',' + b + ',1.0)';
}
for(var key in arg.attrs){
var newValue = arg.attrs[key]
if(key == 'count'){
widget.info.nums = widget.info.nums.slice(0,newValue)
for(let i=0;i<newValue;i++){
if(widget.info.nums[i] === undefined){
widget.info.nums[i] = 1
}
}
widget.texList[1].slices = widget.texList[1].slices.slice(0,newValue)
for(let i=0;i<newValue;i++){
if(widget.texList[1].slices[i] === undefined){
widget.texList[1].slices[i] = {
name:{en:'Image',zh:'图片'}[window.lang],
imgSrc:'',
color:_getRandomColor()
}
}
}
}
}



},
changeWidgetSize:function(layer, widget,arg) {
var widgetWidth=arg.widgetWidth;
var widgetHeight=arg.WidgetHeight;
layer.set({scaleX:1,scaleY:1,width:widgetWidth,height:widgetHeight});
}

},
```
目前开放changeWidgetSize、changeTex、changeGeneralAttrs三个修改控件的回调函数。changeWidgetSize在修改控件尺寸时触发,用默认代码即可。changeTex在修改控件纹理时触发。changeGeneralAttrs则是修改info中控件属性时触发。

4.6 draw
```
draw:function(widget, options){
//tag to values
if(options && options.isEditing){
widget.curValue = widget.info.nums||[]
}else{
var tag = this.findTagByName(widget.tag)
if(tag){
var tagType = tag.elemType
var curValue = this.getValueByTagName(widget.tag, []);

widget.curValue = curValue
}else{
widget.curValue = []
}
}
var total = 0
for(var i=0;i<widget.curValue.length;i++){
total+= Math.max(0, widget.curValue[i])
}
widget.curValueArcs = []
if(total){
widget.curValue.forEach(function(v,i){
widget.curValueArcs[i] = v/total * Math.PI * 2
})
}


},
```
根据控件的变量或者其他条件,来更新数据。options有一个属性isEditing,表示当前是编辑状态还是仿真状态。一般编辑状态展示一些默认的数据。

4.7 paint
```
paint:function(widget, ctx, options){

var bgTex = {
image:this.getResourceByUrl(widget.texList[0].slices[0].imgSrc),
color:widget.texList[0].slices[0].color
}



if(bgTex.color){
ctx.fillStyle = bgTex.color
ctx.fillRect(0, 0 ,widget.info.width, widget.info.height)
}
if(bgTex.image){
ctx.drawImage(bgTex.image,0,0 ,widget.info.width,widget.info.height)
}

//paint cylinders
var radius = Math.min(widget.info.width, widget.info.height)/2
var startArc = ((widget.info.baseOffset||0)/180*Math.PI)
widget.curValueArcs = widget.curValueArcs || []
var minLen = Math.min(widget.info.width, widget.info.height)
widget.curValueArcs.forEach(function(arc, i){
ctx.save()
ctx.scale(widget.info.width/minLen, widget.info.height/minLen)
ctx.beginPath()
ctx.moveTo(minLen/2, minLen/2)
ctx.arc(minLen/2, minLen/2, radius, startArc, startArc + arc)
ctx.closePath()
ctx.clip()
var curColor = widget.texList[1].slices[i].color
var curImgSrc = widget.texList[1].slices[i].imgSrc
if(curColor){
ctx.fillStyle = curColor
ctx.fillRect(0, 0 , minLen, minLen)
}
var curImg = this.getResourceByUrl(curImgSrc)
if(curImg){
ctx.drawImage(curImg,0,0 ,minLen, minLen)
}
ctx.restore()
startArc += arc

}.bind(this))


},
```
从当前控件数据,采用Canvas来绘制控件。坐标原点为控件左上角。注意
 var bgTex = {
image:this.getResourceByUrl(widget.texList[0].slices[0].imgSrc),
color:widget.texList[0].slices[0].color
}
其中getResourceByUrl为内置方法,从指定URL获取图片。

4.8 render
```
render:function (widget,srcRootDir,dstDir,imgUrlPrefix,resolve, reject){
var info = widget.info;
if (!!info){
//font
//trans each slide
var width = info.width;
var height = info.height;


var slices = []
widget.texList.forEach(function(tex){
slices = slices.concat(tex.slices)
})
var totalSlices = slices.length;
slices.forEach(function (curSlice,i) {
var canvas = new Canvas(width,height);
var ctx = canvas.getContext('2d');


ctx.clearRect(0,0,width,height);
ctx.save();
//render color
ctx.fillStyle = curSlice.color
ctx.fillRect(0,0, width, height)
//render image;
var imgSrc = curSlice.imgSrc;

if (imgSrc !== '' && typeof(imgSrc) !== 'undefined'){

var targetImageObj = this.getResourceByUrl(imgSrc)

ctx.drawImage(targetImageObj,0,0,width, height)
}
var imgName = widget.id.split('.').join('-');
var outputFilename = 'r-'+imgName +'-'+ i+'.png';
var outpath = dstDir +'/'+outputFilename;
canvas.output(outpath,function (err) {
if (err){
reject(err);
}else{
this.trackedRes.push(new ResTrack(imgSrc,curSlice.color,null,outputFilename,width,height,curSlice))
// console.log(_.cloneDeep(this.trackedRes))
//write widget
curSlice.originSrc = curSlice.imgSrc;
curSlice.imgSrc = (imgUrlPrefix||'')+outputFilename;
curSlice.onlyColor = onlyColor(imgSrc, curSlice.color, curSlice.text)
//if last trigger cb
totalSlices -= 1;
if (totalSlices<=0){
resolve()
}
}
}.bind(this));

ctx.restore();
}.bind(this));

}else{
resolve()
}
},
```
为了优化性能,生成时可能会预处理图片纹理,使得其符合实际显示大小。
```
this.trackedRes.push(new ResTrack(imgSrc,curSlice.color,null,outputFilename,width,height,curSlice))
```
这里根据图片的一些参数,来决定这张图是否被复用,以减少存储体积。

4.9 transWidget
```
transWidget:function(targetWidget, wIdx){
targetWidget.subType = Type.MyGIF
return targetWidget
},
```
从编辑状态到仿真状态可能有一些转换操作,可以放在transWidget中进行,例如控件的映射等。
  1. 使用自定义控件 保存好自定义控件配置后,刷新页面,会显示自定义控件选项对话框。选择需要加载的自定义控件就可以生效。对于不想生效的自定义控件,也可以在配置中禁用。