HT for Web 交互式图元手册

索引


概述

交互式图元是普通图元(ht.Node)的扩展,包含按钮(ht.ButtonNode)、切换按钮(ht.ToggleButtonNode)、复选框(ht.CheckboxNode)、 单选按钮(ht.RadioButtonNode)、开关(ht.SwitchNode)、进度条(ht.ProgressBarNode)、滑动条(ht.SliderNode)、 微调按钮(ht.SpinnerNode)、组合框(ht.ComboboxNode)等。

var liveNodes = [
    'Button',
    'ToggleButton',
    'Checkbox',
    'Switch',
    'RadioButton',
    'ProgressBar',
    'Slider',
    'Spinner',
    'Combobox',
    'RadioButton'
];

function init () {
    liveNodes.forEach(function (type, i) {
        var node = addNode(type, 120 + (i % 5) * 150, 50 + Math.floor(i / 5) * 50);
        if (i % 5 === 4) buttonGroup.add(node);
    });

    initBody();
}

function addNode (type, x, y) {
    // Create live node
    var node = new ht[type + 'Node']();
    node.setPosition(x, y);
    dataModel.add(node);

    // Set button label
    node.s('live.label', type);

    // Set toggle button selected
    if (node.setSelected) node.setSelected(true);

    // Set value for ProgressBar, Slider and Spinner
    if (node.setValue) node.setValue(50);

    // Setup Combobox
    if (node.setItems) {
        node.setItems(liveNodes);
        node.setSelectedItem(type);
    }
}

用途

交互式图元不同于一般的图元,一般的图元仅用于图形化展示信息,但交互式图元可响应用户鼠标、键盘和触摸屏输入,和普通的用户界面组件功能相当, 可用在监控领域中呈现图形用户界面,给被监控对象发送指令,更改被监控对象的指标。比如用开关(ht.SwitchNode)控制照明灯的打开与关闭, 用滑动条(ht.SliderNode)控制照明灯的明暗等等。

特点

图元

交互式图元

交互式图元(ht.LiveNode)是所有交互式图元的基类,默认宽width为100像素,高height为35像素,包含如下状态,可以根据这些状态定制交互式图元外观:

可以通过如下Style属性设置交互式图元的样式:

交互式图元的背景颜色为LiveNode#getBackground(),文字颜色为LiveNode#getForeground(),这些颜色会根据交互式图元的 hoverpressedenabled等状态改变。

LiveNode#getBackground()
LiveNode#getForeground()

按钮

按钮(ht.ButtonNode)可以响应用户的鼠标点击和键盘SpaceEnter事件,执行onClicked方法指定的动作。 按钮需要先获得焦点(通过鼠标点击,或者Tab键切换焦点)才能响应键盘事件。按钮的图片名称为button_image,矢量描述如下:

ht.Default.setImage('button_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        {
            type: { func: 'style@live.shape' },
            borderWidth: { func: 'style@live.border.width' },
            borderColor: { func: 'style@live.border.color' },
            gradient: { func: 'style@live.gradient' },
            gradientColor: { func: 'style@live.gradient.color' },
            background: { func: 'getBackground' },
            rect: [0, 0, 1, 1],
            relative: true
        },
        {
            type: 'text',
            text: { func: 'style@live.label' },
            align: { func: 'style@live.label.align' },
            color: { func: 'getForeground' },
            font: { func: 'style@live.label.font' },
            rect: [0, 0, 1, 1],
            relative: true,
            offsetX: { func: 'style@live.label.offset.x' },
            offsetY: { func: 'style@live.label.offset.y' }
        }
    ]
});

下面的例子演示了普通按钮、圆角按钮、不可编辑按钮和不可用按钮:

addButton(200, 50, 'Normal');

// Set button shape to 'roundRect'
addButton(350, 50, 'Round rect').s('live.shape', 'roundRect');

addButton(500, 50, 'Uneditable', false);
addButton(650, 50, 'Disabled', true, false);

function addButton(x, y, label, editable, enable) {
    var button = new ht.ButtonNode();
    button.setPosition(x, y);

    // Set button label
    button.s('live.label', label);

    // Set button editable
    if (editable !== undefined) button.setEditable(editable);

    // Set button enabled
    if (enable !== undefined) button.setEnabled(enable);

    // Add click action
    var clicked = false;
    button.onClicked = function () {
        if (clicked) return;
        clicked = true;
        alert(button.s('live.label') + ' is clicked');
        setTimeout(function () {
            clicked = false;
        }, 100);
    };

    dataModel.add(button);
    return button;
}

下面的例子演示了如何用按钮模拟iOS7的拨号键盘:

其中数字按钮图片描述如下:

var image = clone(ht.Default.getImage('button_image'));
image.comps.push({
    type: 'text',
    text: { func: 'attr@label2' },
    rect: [0, 0, 1, 1],
    relative: true,
    offsetY: 8,
    color: {
        func: function (data) {
            return data.isPressed() ? 'white' : 'black';
        }
    },
    font: '12px arial, sans-serif',
    align: 'center'
});
ht.Default.setImage('DialButton', image);

先复制了默认实现,再添加了第二个标签label2

function clone (o) {
    var result;
    if(o instanceof Array){
        result = [];
        o.forEach(function (e) {
            result.push(clone(e, true));
        });
        return result;
    }
    if(typeof o === 'object'){
        var name;
        result = {};
        for (name in o) {
            result[name] = clone(o[name]);
        }
        return result;
    }
    return o;
}

删除按钮图片描述如下:

ht.Default.setImage('ClearButton', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        {
            type: 'circle',
            background: '#0B65F6',
            rect: [0.15, 0.15, 0.7, 0.7],
            relative: true
        },
        {
            type: 'shape',
            background: '#0B65F6',
            points: {
                func: function (data) {
                    var width = data.getWidth(),
                        height = data.getHeight();
                    return [
                        width * 0.03, height * 0.5, width * 0.3, height * 0.25, width * 0.3, height * 0.75
                    ];
                }
            }
        },
        {
            type: 'shape',
            borderColor: '#FFF',
            borderWidth: {
                func: function (data) {
                    return data.getWidth() * 0.1;
                }
            },
            points: {
                func: function (data) {
                    var width = data.getWidth(),
                        height = data.getHeight();
                    return [
                        width * 0.35, height * 0.35, width * 0.65, height * 0.65,
                        width * 0.65, height * 0.35, width * 0.35, height * 0.65
                    ];
                }
            },
            segments: [1, 2, 1, 2]
        }
    ]
});
ButtonNode#onClicked

设置按钮被点击后执行的动作

button.onClicked = function (e) {
    inputNode.setName(inputNode.getName() + this.s('live.label'));
};

切换按钮

切换按钮(ht.ToggleButtonNode)继承于按钮(ht.ButtonNode),增加了isSelectedsetSelected方法,用于获取和设置是否被按下或选中的状态, 并在选中状态变化时回调onChanged函数。

ToggleButtonNode#isSelected()
ToggleButtonNode#setSelected(value)
ToggleButtonNode#onChanged

下面的例子演示了如何定制切换按钮:

定制切换按钮的图片如下:

function setRecentsImage () {
    ht.Default.setImage('Recents', {
        width: { func: 'getWidth' },
        height: { func: 'getHeight' },
        comps: [
            {
                type: 'circle',
                borderColor: {
                    func: function (data) {
                        return data.isSelected() ? '#0B6AFF' : '#929292';
                    }
                },
                borderWidth: 3,
                rect: [0.1, 0.1, 0.8, 0.8],
                relative: true
            },
            {
                type: 'shape',
                borderColor: {
                    func: function (data) {
                        return data.isSelected() ? '#0B6AFF' : '#929292';
                    }
                },
                borderWidth: 2,
                points: {
                    func: function (data) {
                        var width = data.getWidth(),
                            height = data.getHeight();
                        return [0.5 * width, 0.15 * height, 0.5 * width, 0.5 * height, 0.2 * width, 0.5 * height];
                    }
                },
                segments: [1, 2, 2]
            }
        ]
    });
}

function setKeypadImage () {
    var comps = [{
        type: 'rect',
        rect: [0, 0, 1, 1],
        relative: true,
        background: 'white'
    }];
    var getRect = function (i, j) {
        var width = 1 / 4,
            height = 1 / 4,
            radius = Math.min(width, height)/2,
            xgap = width/4,
            ygap = height/4;
        return [width * i - radius + xgap * (i - 2), height * j - radius + ygap * (j - 2), radius * 2, radius * 2];
    };
    for (var i=1; i<4; i++) {
        for (var j=1; j<4; j++) {
            comps.push({
                type: 'circle',
                rect: getRect(i, j),
                relative: true,
                background: {
                    func: function (data) {
                        return data.isSelected() ? '#0B6AFF' : '#929292';
                    }
                }
            });
        }
    }
    ht.Default.setImage('Keypad', {
        width: { func: 'getWidth' },
        height: { func: 'getHeight' },
        comps: comps
    });
}

切换按钮选中状态变化时,更改标签颜色:

toggleButton.onChanged = function (value) {
    this.s('label.color', value ? '#0B6AFF' : '#929292');
};

复选框

复选框(ht.CheckboxNode)继承于切换按钮(ht.ToggleButton),功能和切换按钮一样,仅仅外观不一样,图片名称为checkbox_image,矢量描述如下:

ht.Default.setImage('checkbox_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        {
            type: { func: 'style@live.shape' },
            background: { func: 'getBackground' },
            rect: {
                func: function (data) {
                    var width = data._width, height = data._height;
                    return {x: height * 0.1, y: height * 0.2, width: height * 0.6, height: height * 0.6};
                }
            }
        },
        {
            type: 'shape',
            points: {
                func: function (data) {
                    var width = data._width, height = data._height;
                    return [0.3 * height, 0.5 * height, 0.4 * height, 0.6 * height, 0.55 * height, 0.35 * height];
                }
            },
            borderWidth: {
                func: function (data) {
                    return data._height * 0.05;
                }
            },
            borderColor: '#FFFFFF',
            visible: {
                func: function (data) {
                    return data._selected || data._hover;
                }
            }
        },
        {
            type: 'text',
            text: { func: 'style@live.label' },
            align: { func: 'style@live.label.align' },
            color: { func: 'getForeground' },
            font: { func: 'style@live.label.font' },
            rect: {
                func: function (data) {
                    var width = data._width, height = data._height;
                    return {x: height * 0.8, y: 0, width: width - height * 0.8, height: height};
                }
            },
            offsetX: { func: 'style@live.label.offset.x' },
            offsetY: { func: 'style@live.label.offset.y' }
        }
    ]
});

单选按钮

单选按钮(ht.RadioButtonNode)继承于切换按钮(ht.ToggleButton),功能和切换按钮一样,仅仅外观不一样,图片名称为radioButton_image,矢量描述如下:

ht.Default.setImage('radioButton_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        {
            type: 'circle',
            borderWidth: {
                func: function (data) {
                    return data._height * 0.1;
                }
            },
            borderColor: { func: 'getBackground' },
            rect: {
                func: function (data) {
                    var width = data._width, height = data._height;
                    return {x: height * 0.1, y: height * 0.2, width: height * 0.6, height: height * 0.6};
                }
            }
        },
        {
            type: 'circle',
            background: { func: 'getBackground' },
            rect: {
                func: function (data) {
                    var width = data._width, height = data._height;
                    return {x: height * 0.3, y: height * 0.4, width: height * 0.2, height: height * 0.2};
                }
            },
            visible: {
                func: function (data) {
                    return data._selected || data._hover;
                }
            }
        },
        {
            type: 'text',
            text: { func: 'style@live.label' },
            align: { func: 'style@live.label.align' },
            color: { func: 'getBackground' },
            font: { func: 'style@live.label.font' },
            rect: {
                func: function (data) {
                    var width = data._width, height = data._height;
                    return {x: height * 0.8, y: 0, width: width - height * 0.8, height: height};
                }
            },
            offsetX: { func: 'style@live.label.offset.x' },
            offsetY: { func: 'style@live.label.offset.y' }
        }
    ]
});

开关

开关(ht.SwitchNode)继承于切换按钮(ht.ToggleButton),功能和切换按钮一样,仅仅外观不一样,图片名称为switch_image,矢量描述如下:

ht.Default.setImage('switch_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        {
            type: { func: 'style@live.shape' },
            borderWidth: { func: 'style@live.border.width' },
            borderColor: { func: 'style@live.border.color' },
            background: { func: 'style@switch.background' },
            opacity: {
                func: function (data) {
                    return data._enabled ? 1 : 0.5;
                }
            },
            rect: [0, 0, 1, 1],
            relative: true
        },
        {
            type: 'circle',
            background: { func: 'getBackground' },
            opacity: {
                func: function (data) {
                    return data._enabled ? 1 : 0.5;
                }
            },
            rect: {
                func: function (data) {
                    var size = 30, gap = 10, selected = data._selected;
                    return {x: selected ? data._width - gap - size : gap, y: (data._height - size) / 2, width: size, height: size};
                }
            }
        },
        {
            type: 'text',
            text: {
                func: function (data) {
                    return data.s(data._selected ? 'switch.text.on' : 'switch.text.off');
                }
            },
            rect: [17, 1, 1],
            relative: true,
            offsetX: {
                func: function (data) {
                    return data._selected ? -10 : 10;
                }
            },
            color: { func: 'getForeground' },
            font: { func: 'style@live.label.font' },
            align: { func: 'style@live.label.align' }
        }
    ]
});

可以通过如下Style属性设置开关的样式:

下面是定制开关的例子:

按钮组

按钮组(ht.ButtonGroup)可以包含切换按钮、复选框、单选按钮和开关等交互式图元,当一个图元被选中时,其他图元自动取消选中,参考定制切换按钮例子

ButtonGroup(items)

按钮组构造函数

ButtonGroup#add(item)

添加切换按钮

ButtonGroup#addAll(items)

添加切换按钮集合

ButtonGroup#remove(item)

删除切换按钮

ButtonGroup#clear()

删除所有切换按钮

ButtonGroup#getItems()
ButtonGroup#getSelected()
ButtonGroup#onChanged

进度条

进度条(ht.ProgressBarNode)用于显示进度百分比,图片名称为progressBar_image,矢量描述如下:

ht.Default.setImage('progressBar_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        // Background
        {
            type: 'rect',
            background: { func: 'style@live.background' },
            rect: [0, 0, 1, 1],
            relative: true
        },
        // Top
        {
            type: 'rect',
            background: { func: 'style@live.background.active' },
            rect: {
                func: function (data) {
                    return [0, 0, data._value / 100, 0.5];
                }
            },
            relative: true
        },
        // Bottom
        {
            type: 'rect',
            background: {
                func: function (data) {
                    return ht.Default.darker(data.s('live.background.active'));
                }
            },
            rect: {
                func: function (data) {
                    return [0, 0.5, data._value / 100, 0.5];
                }
            },
            relative: true
        }
    ]
});
ProgressBarNode#getValue()
ProgressBarNode#setValue(value)

设置进度条的值,取值范围为0-100

滑动条

滑动条(ht.SliderNode)可以设置最大值max、最小值min和当前值value,通过orientation设置显示方向(可选值为horizontalhverticalv), 通过滑动滑块更改当前值。

SliderNode#getValue()
SliderNode#setValue(value)

设置滑动条当前值

SliderNode#getMin()
SliderNode#setMin(value)

设置滑动条最小值

SliderNode#getMax()
SliderNode#setMax(value)

设置滑动条最大值

SliderNode#getOrientation()
SliderNode#setOrientation(value)

设置滑动条显示方向

SliderNode#isHorizontal()
SliderNode#getStep()
SliderNode#setStep(value)

设置滑动条步进

SliderNode#onChanged

可以通过如下Style属性设置滑动条的样式:

滑动条图片名称为slider_image,矢量描述如下:

ht.Default.setImage('slider_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        {
            type: 'rect',
            background: { func: 'style@live.background.active' },
            rect: {
                func: function (data) {
                    var size = data.s('slider.bar.size'),
                        radius = data.s('slider.thumb.size') + data.s('live.border.width'),
                        ratio = data._value / (data._max - data._min),
                        isHorizontal = data.isHorizontal(),
                        width = data._width,
                        height = data._height;
                    return {
                        x: isHorizontal ? radius : (width - size) / 2,
                        y: isHorizontal ? (height - size) / 2 : height - radius - (height - radius * 2) * ratio,
                        width: isHorizontal ? (width - radius * 2) * ratio : size,
                        height: isHorizontal ? size : (height - radius * 2) * ratio
                    };
                }
            }
        },
        {
            type: 'rect',
            background: { func: 'style@live.background' },
            rect: {
                func: function (data) {
                    var size = data.s('slider.bar.size'),
                        radius = data.s('slider.thumb.size') + data.s('live.border.width'),
                        ratio = data._value / (data._max - data._min),
                        isHorizontal = data.isHorizontal(),
                        width = data._width,
                        height = data._height;
                    return {
                        x: isHorizontal ? radius + (width - radius * 2) * ratio : (width - size) / 2,
                        y: isHorizontal ? (height - size) / 2 : radius,
                        width: isHorizontal ? (width - radius * 2) * (1 - ratio) : size,
                        height: isHorizontal ? size : (height - radius * 2) * (1 - ratio)
                    };
                }
            }
        },
        {
            type: 'circle',
            borderWidth: { func: 'style@live.border.width' },
            borderColor: { func: 'style@live.border.color' },
            background: { func: 'style@slider.thumb.background' },
            rect: {
                func: function (data) {
                    var radius = data.s('slider.thumb.size'),
                        ratio = data._value / (data._max - data._min),
                        isHorizontal = data.isHorizontal(),
                        width = data._width,
                        height = data._height;
                    return {
                        x: isHorizontal ? ratio * (width - radius * 2) : width / 2 - radius,
                        y: isHorizontal ? height / 2 - radius : (1 - ratio) * (height - radius * 2),
                        width: radius * 2,
                        height: radius * 2
                    };
                }
            }
        }
    ]
});

微调按钮

微调按钮(ht.SpinnerNode)可以设置最大值max、最小值min和当前值value,通过点击上下按钮更改当前值,图片名称为spinner_image,矢量描述如下:

ht.Default.setImage('spinner_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        // Border
        {
            type: { func: 'style@live.shape' },
            borderWidth: { func: 'style@live.border.width' },
            borderColor: { func: 'style@live.border.color' },
            gradient: { func: 'style@live.gradient' },
            gradientColor: { func: 'style@live.gradient.color' },
            background: {
                func: function (data) {
                    return data.s(data._enabled ? 'spinner.background' : 'live.background.disabled');
                }
            },
            rect: [0, 0, 1, 1],
            relative: true
        },
        // Right Top rect
        {
            type: 'rect',
            background: {
                func: function (data) {
                    var style;
                    if (data._topPressed) {
                        style = 'live.background.active';
                    } else if (data._topHover) {
                        style = 'live.background.hover';
                    } else {
                        style = 'live.background';
                    }
                    return data.s(style);
                }
            },
            rect: {
                func: function (data) {
                    var buttonWidth = data.s('spinner.button.width'),
                        borderWidth = data.s('live.border.width');
                    return {
                        x: data._width - buttonWidth,
                        y: borderWidth,
                        width: buttonWidth - borderWidth,
                        height: data._height / 2 - borderWidth
                    };
                }
            }
        },
        // Right Top Arrow
        {
            type: 'shape',
            points: {
                func: function (data) {
                    var buttonWidth = data.s('spinner.button.width'),
                        width = data._width,
                        height = data._height;
                    return [
                        width - buttonWidth + 0.5 * buttonWidth, 0.15 * height,
                        width - buttonWidth + 0.75 * buttonWidth, 0.4 * height,
                        width - buttonWidth + 0.25 * buttonWidth, 0.4 * height
                    ];
                }
            },
            background: {
                func: function (data) {
                    if (data._topHover) {
                        return '#000000';
                    }
                    return '#FFFFFF';
                }
            }
        },
        // Right Bottom rect
        {
            type: 'rect',
            background: {
                func: function (data) {
                    var style;
                    if (data._bottomPressed) {
                        style = 'live.background.active';
                    } else if (data._bottomHover) {
                        style = 'live.background.hover';
                    } else {
                        style = 'live.background';
                    }
                    return data.s(style);
                }
            },
            rect: {
                func: function (data) {
                    var buttonWidth = data.s('spinner.button.width'),
                        borderWidth = data.s('live.border.width'),
                        width = data._width,
                        height = data._height;
                    return {
                        x: width - buttonWidth,
                        y: height / 2,
                        width: buttonWidth - borderWidth,
                        height: height / 2 - borderWidth
                    };
                }
            }
        },
        // Right Bottom Arrow
        {
            type: 'shape',
            points: {
                func: function (data) {
                    var buttonWidth = data.s('spinner.button.width'),
                        width = data._width,
                        height = data._height;
                    return [
                        width - buttonWidth + 0.5 * buttonWidth, 0.9 * height,
                        width - buttonWidth + 0.75 * buttonWidth, 0.65 * height,
                        width - buttonWidth + 0.25 * buttonWidth, 0.65 * height
                    ];
                }
            },
            background: {
                func: function (data) {
                    if (data._bottomHover) {
                        return '#000000';
                    }
                    return '#FFFFFF';
                }
            }
        }
    ]
});
SpinnerNode#getValue()
SpinnerNode#setValue(value)

设置微调按钮当前值

SpinnerNode#getMin()
SpinnerNode#setMin(value)

设置微调按钮最小值

SpinnerNode#getMax()
SpinnerNode#setMax(value)

设置微调按钮最大值

SpinnerNode#getStep()
SpinnerNode#setStep(value)

设置微调按钮步进

SpinnerNode#onChanged

可以通过如下Style属性设置滑动条的样式:

组合框

组合框(ht.ComboboxNode)用于从集合中选择一项,图片名称为combobox_image,矢量描述如下:

ht.Default.setImage('combobox_image', {
    width: { func: 'getWidth' },
    height: { func: 'getHeight' },
    comps: [
        {
            type: { func: 'style@live.shape' },
            borderWidth: { func: 'style@live.border.width' },
            borderColor: { func: 'style@live.border.color' },
            gradient: { func: 'style@live.gradient' },
            gradientColor: { func: 'style@live.gradient.color' },
            background: { func: 'getBackground' },
            rect: [0, 0, 1, 1],
            relative: true
        },
        {
            type: 'shape',
            points: {
                func: function (data) {
                    var buttonWidth = data._buttonWidth,
                        width = data._width,
                        height = data._height;
                    return [
                        width - buttonWidth + 0.5 * buttonWidth, 0.6 * height,
                        width - buttonWidth + 0.75 * buttonWidth, 0.4 * height,
                        width - buttonWidth + 0.25 * buttonWidth, 0.4 * height
                    ];
                }
            },
            background: {
                func: function (data) {
                    if (data._pressed) {
                        return '#000000';
                    }
                    return '#FFFFFF';
                }
            }
        },
        {
            type: 'text',
            text: { func: 'style@live.label' },
            align: { func: 'style@live.label.align' },
            color: { func: 'getForeground' },
            font: { func: 'style@live.label.font' },
            rect: [0, 0, 1, 1],
            relative: true,
            offsetX: { func: 'style@live.label.offset.x' },
            offsetY: { func: 'style@live.label.offset.y' }
        }
    ]
});
ComboboxNode#getItems()
ComboboxNode#setItems(value)

设置可选项集合

ComboboxNode#getSelectedItem()
ComboboxNode#setSelectedItem(value)

设置当前选中项

ComboboxNode#getSelectedIndex()
ComboboxNode#setSelectedIndex(value)

设置当前选中项索引

ComboboxNode#getItemName()
ComboboxNode#onChanged

欢迎交流 service@hightopo.com