Skip to main content

Customize Controls

To provide further control freedom, the AHMI IDE provides the ability to customize controls.

  1. Open Help and click Customize Controls to open the Custom Controls configuration panel. !

  2. Click New and a blank Custom Control option appears. Enter the name of the custom control configuration, icon - checked, and icon unchecked respectively.

  3. Write the custom control object in the code editor, using the JavaScript language.

  4. Custom control object writing:

    4.1 Basic structure:

    var widget = {
    name:'GIF',

    group:'display class', var
    info:{
    width:100, height:100, {
    height:100,
    left:0,
    top:0
    },
    tagType:'array',
    actionTriggers:'', texList:function(widget){
    texList:function(widget){

    },
    texConfig:function(widgetInfo){

    }, texConfig:function(widgetInfo){
    triggers:{

    },
    draw:function(widget, options){

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

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

    },
    transWidget:function(targetWidget, wIdx){

    }
    }
    PropertyMeaningAdditional
    nameDisplay name of the widget
    typeWidget type
    groupWidget group typeDisplay, Touch, Other
    infoWidget propertiesConfigurable property list
    tagTypeVariable typenumber, string, array
    actionTriggersAction trigger conditionsPress, LongPress,
    Release, EnterLowAlarm,
    LeaveLowAlarm, EnterHighAlarm,
    LeaveHighAlarm, TagChange,
    SwipeLeft, SwipeRight,
    SwipeTop, SwipeBottom, etc. (separated by |)
    texListTexture listDefault texture for the widget
    drawData updateUpdate data based on variables or other conditions
    paintDrawing logicDraw widget style based on data
    renderPreprocessing logicPreprocess images and other resources during generation
    transWidgetWidget conversion logicModify widget parameters during editing to simulation transition
     4.2 Control Properties
    info:{
    width:400,
    height:400,
    left:0,
    top:0,
    offset:{
    default:0,
    style:'none'
    },
    arrange:{
    type:'selector', title:{en:'Direction', zh: 'Direction'}, arrange:{

    title:{en:'Direction', zh: 'Direction'}, options:[{name:{en:'Vertical', zh: 'Vertical'},value:0},{name:{en:'Horizontal', zh: 'horizontal'},value:1}],
    default:0,
    style:'short'
    }, showNum:{{Horizontal's, zh: 'horizontal'},value:1}], default:0, style:'short'
    showNum:{
    type:'number', title:{en:'Show Num' }, showNum:{
    title:{en:'Show Num',zh:'Show number'}[window.lang],
    default:1,
    style:'short', limitFunc:function(newValue)
    limitFunc:function(newValue){
    if(newValue < 1){
    return {en:'Show num cannot be less than 1',zh:'Show number is at least 1'}[window.lang]
    }
    }
    }
    },
 The configurable property of a control can be an object or simply a value or string. If it is an object, the property can be finely controlled. All controls should have width, height, left, and top properties.
When the property is an object, the parameters have the following meanings:
| properties | meaning | additional |
| :----:| :----: | :----: |
| type | The type of the attribute | number, string, selector, selector needs to be combined with options|
| title | attribute display name | string/object, multi-language support for object |
| options | Options | Array containing {name, value} |
| default | property default | | style | property display name | | style | property display name
| style | attribute display style | short takes 1/2 line, long takes one line, none is not displayed |
| limitFunc | constraints | Determine if the current value is legal, give an error if it is not legal |

4.3 Texture List
```
texList:function(widget){
// return [{},{}]
var bgTex = {
name:'GIF Texture',
currentSliceIdx:0,
slices:[
{
name:'GIF Image',
imgSrc:'',
color:'rgba(255,255,255,1)'

}

] }

return [bgTex]
},
```

Each texture contains the name and slices attributes, and the slices contain the texture slices. Each texture slice has name, imgSrc, and color attributes.

4.4 Texture Configuration
```
texConfig:function(widgetInfo){
switch(widgetInfo.tex.name){
case 'element':
Returns {
canAddNewSlice:true,
disableEditName:false
}
default:
Returns {
canAddNewSlice:false,
disableEditName:true
}
}
}, }
```
Sometimes you have to adjust the texture configuration based on the control information, currently you can configure canAddNewSlice and disableEditName. canAddNewSlice determines if you can add a new texture slice or not, and disableEditName determines if you can change the name of the texture or not.


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});
}

},
```
Currently there are three callback functions for modifying controls: changeWidgetSize, changeTex and changeGeneralAttrs. changeWidgetSize is triggered when modifying the size of a control, and it can be triggered by the default code. changeTex is triggered when modifying the texture of a control. changeGeneralAttrs is triggered when modifying control attributes in 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
})
}


},
```
The options have an attribute isEditing, which indicates whether the current state is editing or emulating. Generally the editing state displays some default data.

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))


},
```
From the current control data, a Canvas is used to draw the control. The coordinate origin is the upper left corner of the control. Note that
 var bgTex = {
image:this.getResourceByUrl(widget.texList[0].slices[0].imgSrc),
color:widget.texList[0].slices[0].color
}
Where getResourceByUrl is a built-in method to get the image from the specified 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()
}
},
```
To optimize performance, the image texture may be preprocessed during generation to make it match the actual display size.
```
this.trackedRes.push(new ResTrack(imgSrc,curSlice.color,null,outputFilename,width,height,curSlice))
```
Here based on some parameters of the image, it is decided whether this image is reused or not to reduce the storage size.

4.9 transWidget
```
transWidget:function(targetWidget, wIdx){
targetWidget.subType = Type.MyGIF
return targetWidget
},
```
There may be some transition operations from the editing state to the simulation state that can be placed in the transWidget, such as the mapping of controls etc.
  1. Using Custom Controls After saving the custom control configuration, refresh the page and the Custom Control Options dialog box will be displayed. Select the custom control you want to load to take effect. You can also disable the custom controls that you don't want to take effect in the configuration.