EUI-NEO

组件

EUI-NEO 技术文档。

组件

组件层位于 components/。它不是另一套渲染系统,也不直接持有 OpenGL primitive;组件只负责把常用 DSL 结构封装成更短、更稳定的 builder 调用。

页面仍然由 core::dsl::Ui 里的 rowcolumnstackrecttextimagepolygon 等图元组成。组件内部也只声明 DSL 树,事件、hover、pressed、transition、dirty rect 和 framebuffer cache 都继续交给 core::dsl::Runtime

引入

通常只需要包含聚合头:

#include "components/components.h"

这个头当前导出:

组件用途
button按钮,支持文字、图标、主题、禁用、右键回调
checkbox受控复选框
radio受控单选项
toggleSwitch受控开关
progress进度条,value 范围是 0.0f - 1.0f
slider滑块,value 范围是 0.0f - 1.0f
input文本输入框,支持单行/多行、选择、剪贴板快捷键
segmented分段选择
tabs标签页切换
scroll滚动条/offset 控制器
dropdown下拉选择
datepickerdialog 式日期选择器,受控 open/date
timepickerdialog 式时间选择器,受控 open/time
colorpickerdialog 式颜色选择器,受控 open/value
dataTable / datatable简单数据表格
dialog模态对话框
toast右上角 toast,支持自动消失
contextMenu右键菜单
linechart / lineChart折线图
barchart / barChart柱状图
piechart / pieChart饼图
panel套用主题的 RectBuilder
text / label套用主题文本色的 TextBuilder
image套用主题图片样式的 ImageBuilder
theme颜色和视觉 token 辅助函数

基本约定

Theme

主题定义在 components/theme.h,常用入口:

const auto dark = components::theme::DarkThemeColors();
const auto light = components::theme::LightThemeColors();

const auto page = components::theme::pageVisuals(dark);
const auto field = components::theme::fieldVisuals(dark);

当前提供:

API说明
color(r, g, b, a)创建 core::Color
withAlpha(color, alpha)替换 alpha
withOpacity(color, opacity)按比例调整 alpha
light() / dark()基础明暗主题
LightThemeColors() / DarkThemeColors()兼容参考项目命名
pageVisuals(tokens)页面标题、正文、卡片、间距等 token
fieldVisuals(tokens)输入控件圆角、padding、focus、弹层阴影等 token
CurrentPageVisuals(tokens)pageVisuals 别名
CurrentFieldVisuals(tokens)fieldVisuals 别名
resolveFieldFill(...)按 hover / active 计算字段背景
ResolveFieldFill(...)resolveFieldFill 别名
buttonHover(...)按主题计算按钮 hover 色
buttonPressed(...)按主题计算按钮 pressed 色
border(...)常用边框
buttonBorder(...)按钮边框
shadow(...)通用阴影
buttonShadow(...)按钮阴影
panelShadow(...)面板阴影
popupShadow(...)弹层阴影

基础包装

paneltextlabelimage 返回的仍然是 core DSL builder,所以可以继续链式调用底层能力。

components::panel(ui, "card", tokens)
    .size(360.0f, 220.0f)
    .transition(0.18f)
    .build();

components::text(ui, "title", tokens)
    .size(420.0f, 48.0f)
    .text("EUI Gallery")
    .fontSize(38.0f)
    .horizontalAlign(core::HorizontalAlign::Center)
    .verticalAlign(core::VerticalAlign::Center)
    .build();

components::image(ui, "cover", tokens)
    .size(320.0f, 180.0f)
    .source("assets/icon.png")
    .cover()
    .build();

text.h 还提供 titleTextStyle(tokens)subtitleTextStyle(tokens)bodyTextStyle(tokens),用于快速生成 core::TextStyle

控件

button

components::button(ui, "save")
    .size(180.0f, 54.0f)
    .theme(tokens)
    .icon(0xF0C7)
    .text("Save")
    .transition(0.18f)
    .onClick([] {
        // update page state
    })
    .build();

常用 API:sizescaletexticonfontSizeiconSizetextColoriconColorstylethemeprimaryThemesecondaryThemeradiusroundingopacitydisabledenabledtranslatetranslateXtranslateYpressScalebordershadowcolorstransitiononClickonContextMenu

按钮内部是 Stack + Rect + Row + Text。按压缩放作用在外层 stack,背景、图标和文字会一起动。

checkbox / radio / toggleSwitch

这些都是受控组件,页面传当前值,回调里写回状态。

components::checkbox(ui, "remember")
    .theme(tokens)
    .checked(remember)
    .text("Remember me")
    .onChange([&](bool value) {
        remember = value;
    })
    .build();

components::radio(ui, "mode.a")
    .selected(mode == 0)
    .text("Mode A")
    .onSelect([&] {
        mode = 0;
    })
    .build();

components::toggleSwitch(ui, "motion")
    .checked(enableMotion)
    .label("Motion")
    .onChange([&](bool value) {
        enableMotion = value;
    })
    .build();

checkbox 支持 sizecheckedtextfontSizeboxSizestylethemetransitiononChange

radio 支持 sizeselected / checkedtextfontSizedotSizestylethemetransitiononSelectonChange

toggleSwitch 支持 sizecheckedlabel / textfontSizetrackSizestylethemetransitiononChange

progress / slider

components::progress(ui, "loading")
    .theme(tokens)
    .size(320.0f, 14.0f)
    .value(progress)
    .build();

components::slider(ui, "volume")
    .theme(tokens)
    .size(320.0f, 34.0f)
    .value(volume)
    .onChange([&](float value) {
        volume = value;
    })
    .build();

progress 支持 sizevaluestylethemetransition

slider 支持 sizevaluestylethemetransitiononChangevalue 会 clamp 到 0.0f - 1.0f。滑块用透明 hit rect 处理点击和拖拽,拖拽时根据缓存 bounds 计算 next value。

input

components::input(ui, "name")
    .theme(tokens)
    .size(280.0f, 42.0f)
    .value(name)
    .placeholder("Name")
    .onChange([&](const std::string& value) {
        name = value;
    })
    .onEnter([] {
        // submit
    })
    .build();

支持 sizetext / valueplaceholdermultilinefontSizeinsetstylethemetransitiononChangeonEnteronFocus

当前 input 支持 focus、点击定位光标、拖动选择、Shift+方向键选择、Ctrl+A/C/X/V、Backspace、Delete、Enter、单行和多行模式。暂时没有 IME 组合态显示和撤销栈。

segmented / tabs

components::segmented(ui, "density")
    .items({"Compact", "Normal", "Comfort"})
    .selected(density)
    .onChange([&](int index) {
        density = index;
    })
    .build();

components::tabs(ui, "main.tabs")
    .items({"Style", "Bing", "Settings"})
    .selected(tab)
    .onChange([&](int index) {
        tab = index;
    })
    .build();

两者都支持 sizeitemsselectedfontSizestylethemetransitiononChange

scroll

scroll 只是滚动条和 offset 控制器,不直接持有内容。内容裁剪由外层 .clip() 完成,内容位移由页面按 offset 写到子树。

ui.stack("list.viewport")
    .size(360.0f, 260.0f)
    .clip()
    .content([&] {
        ui.column("list.content")
            .y(-scrollY)
            .size(360.0f, contentHeight)
            .build();
    })
    .build();

components::scroll(ui, "list.scroll")
    .size(8.0f, 260.0f)
    .viewport(260.0f)
    .content(contentHeight)
    .offset(scrollY)
    .onChange([&](float value) {
        scrollY = value;
    })
    .build();

支持 xypositionsizeoffset / valueviewport / viewportHeightcontent / contentHeightstepzIndex / zstylethemetransitiononChange

弹层和反馈

dialog

components::dialog(ui, "confirm")
    .open(showDialog)
    .screen(screen.width, screen.height)
    .title("Confirm")
    .message("Apply these changes?")
    .primaryText("Apply")
    .secondaryText("Cancel")
    .onPrimary([&] {
        showDialog = false;
    })
    .onSecondary([&] {
        showDialog = false;
    })
    .onClose([&] {
        showDialog = false;
    })
    .build();

支持 openscreensizetitlemessageprimaryTextsecondaryTextstylethemetransitionzIndex / zonPrimaryonSecondaryonClose

dialog 会绘制 backdrop,并用透明 hit rect 吃掉后面内容的 hover、click 和 scroll。打开/关闭动画作用在整体面板上,内容跟着外层一起动。

toast

components::toast(ui, "saved.toast")
    .visible(showToast)
    .screen(screen.width, screen.height)
    .title("Saved")
    .message("Your changes were saved.")
    .icon(0xF00C)
    .duration(2.4f)
    .onAutoDismiss([&] {
        showToast = false;
    })
    .onDismiss([&] {
        showToast = false;
    })
    .build();

支持 visiblescreensizetitlemessageiconstylethemetransitionzIndex / zduration / autoDismissonAutoDismissonDismiss

duration(0.0f) 表示不自动消失。关闭按钮是内部命中区,点击触发 onDismiss

contextMenu

components::contextMenu(ui, "item.menu")
    .open(menuOpen)
    .screen(screen.width, screen.height)
    .position(menuX, menuY)
    .items({"Rename", "Duplicate", "Delete"})
    .onSelect([&](int index) {
        menuOpen = false;
        selectedAction = index;
    })
    .onDismiss([&] {
        menuOpen = false;
    })
    .build();

支持 openscreenpositionsize(width, itemHeight)itemsstylethemetransitionzIndex / zonSelectonDismiss

右键菜单会根据 screen 把弹层限制在屏幕内,并绘制全屏透明 dismiss 层。菜单项 hover 使用即时状态,快速 hover 不会等待颜色动画拖尾。

components::dropdown(ui, "theme.dropdown")
    .items({"Dark", "Light", "System"})
    .selected(themeIndex)
    .open(dropdownOpen)
    .placeholder("Theme")
    .onOpenChange([&](bool value) {
        dropdownOpen = value;
    })
    .onChange([&](int index) {
        themeIndex = index;
    })
    .build();

支持 sizeitemsselectedplaceholderopenitemHeightstylethemetransitionzIndex / zonChangeonOpenChange

下拉面板会随外层 stack 展开,选项 hover 和右键菜单一致,关闭时选项会禁用命中,避免点到隐藏面板。

datepicker / timepicker / colorpicker

这三个 picker 都是受控组件,命名不带下划线。页面传入当前值和 open,组件通过 onChange / onOpenChange 回写 next state。

picker 现在和 dialog 一样绘制全屏 overlay + 居中面板,适合放在页面根级 compose 末尾,避免被滚动容器或 .clip() 裁剪。触发入口可以用 button 或自定义字段单独绘制。

面板打开后会维护组件内部 draft:日期/时间轮盘、滚轮、颜色 slider、预设色块都只更新面板内预览,不会立即改变页面状态。点击 Done 才触发 onChange 提交 draft;点击遮罩只关闭,不提交。

components::datepicker(ui, "profile.date")
    .theme(tokens)
    .screen(screen.width, screen.height)
    .size(420.0f, 270.0f)
    .date(year, month, day)
    .open(dateOpen)
    .onOpenChange([&](bool open) {
        dateOpen = open;
    })
    .onChange([&](int nextYear, int nextMonth, int nextDay) {
        year = nextYear;
        month = nextMonth;
        day = nextDay;
    })
    .build();

components::timepicker(ui, "profile.time")
    .theme(tokens)
    .screen(screen.width, screen.height)
    .size(330.0f, 264.0f)
    .time(hour, minute)
    .minuteStep(5)
    .open(timeOpen)
    .onOpenChange([&](bool open) {
        timeOpen = open;
    })
    .onChange([&](int nextHour, int nextMinute) {
        hour = nextHour;
        minute = nextMinute;
    })
    .build();

components::colorpicker(ui, "profile.color")
    .theme(tokens)
    .screen(screen.width, screen.height)
    .size(420.0f, 320.0f)
    .value(accent)
    .open(colorOpen)
    .onOpenChange([&](bool open) {
        colorOpen = open;
    })
    .onChange([&](core::Color value) {
        accent = value;
    })
    .build();

datepicker 支持 openscreensize(panelWidth, panelHeight)date(year, month, day)stylethemetransitionzIndex / zonChangeonOpenChange。年份范围是 1900 - 2200,月份和日期会按闰年和当月天数修正,onChange 只在 Done 时触发。

timepicker 支持 openscreensize(panelWidth, panelHeight)time(hour, minute)minuteStepstylethemetransitionzIndex / zonChangeonOpenChange。内部显示 12 小时轮盘,回调仍返回 0 - 23 的小时和 0 - 59 的分钟,onChange 只在 Done 时触发。

colorpicker 支持 openscreensize(panelWidth, panelHeight)value(core::Color)colors(std::vector<core::Color>)stylethemetransitionzIndex / zonChangeonOpenChange。颜色 alpha 会按 1.0f 处理,面板里提供 RGB slider 和预设色块,onChange 只在 Done 时触发。

picker 的拖动、滚轮和 slider 交互只捕获静态 draft / drag state,不捕获临时 builder 指针;外部 onChange 只在 Done 的命中区触发。

数据展示

dataTable

components::dataTable(ui, "metrics.table")
    .size(420.0f, 174.0f)
    .columns({"Name", "Status", "Value"})
    .rows({
        {"Cache", "Ready", "94%"},
        {"Render", "Idle", "12ms"},
        {"Network", "Online", "32ms"},
    })
    .theme(tokens)
    .build();

支持 sizecolumnsrowsstylethemetransitiondatatable 是同一个 builder 的别名。

表格内部会裁剪内容区,表头和末行圆角用 patch rect 衔接,避免圆角和分割线断开。

图表

三个图表都是通用组件,使用 DSL 图元绘制,不使用 SVG 图片。tooltip 通过 hoverOpacityFrom(sourceId) 绑定到对应数据点、柱或扇区。

linechart

components::linechart(ui, "usage.line")
    .size(206.0f, 236.0f)
    .title("Usage")
    .values({0.18f, 0.32f, 0.48f, 0.42f, 0.72f, 0.64f})
    .labels({"Jan", "Feb", "Mar", "Apr", "May", "Jun"})
    .theme(tokens)
    .build();

支持 sizetitlevalueslabelsstylethemetransitionlineChart 是别名。折线由旋转 rect 线段绘制,点是单层交互圆,hover 时显示对应 label 和百分比。

barchart

components::barchart(ui, "usage.bar")
    .size(206.0f, 236.0f)
    .title("Volume")
    .values({0.92f, 0.36f, 0.68f, 0.52f})
    .labels({"D1", "D2", "D3", "D4"})
    .theme(tokens)
    .build();

支持 sizetitlevalueslabelscolorsstylethemetransitionbarChart 是别名。每根柱子独立 hover,tooltip 锚点位于柱顶。

piechart

components::piechart(ui, "usage.pie")
    .size(206.0f, 236.0f)
    .title("Share")
    .values({42.0f, 24.0f, 18.0f, 16.0f})
    .labels({"Blue", "Green", "Orange", "Pink"})
    .theme(tokens)
    .build();

支持 sizetitlevalueslabelscolorsstylethemetransitionpieChart 是别名。扇区使用 core 的 polygon 图元绘制,负数按 0 处理,显示值按总和归一化为百分比。

当前限制

写新组件时的底线

class ExampleBuilder {
public:
    ExampleBuilder(core::dsl::Ui& ui, std::string id)
        : ui_(ui), id_(std::move(id)) {}

    ExampleBuilder& value(bool value) {
        value_ = value;
        return *this;
    }

    ExampleBuilder& onChange(std::function<void(bool)> callback) {
        onChange_ = std::move(callback);
        return *this;
    }

    void build() {
        ui_.stack(id_)
            .size(160.0f, 42.0f)
            .visualStateFrom(id_ + ".hit", 0.965f)
            .content([&] {
                ui_.rect(id_ + ".hit")
                    .size(160.0f, 42.0f)
                    .states(normal_, hover_, pressed_)
                    .onClick([callback = onChange_, value = value_] {
                        if (callback) {
                            callback(!value);
                        }
                    })
                    .build();

                ui_.text(id_ + ".label")
                    .size(160.0f, 42.0f)
                    .text(value_ ? "On" : "Off")
                    .horizontalAlign(core::HorizontalAlign::Center)
                    .verticalAlign(core::VerticalAlign::Center)
                    .build();
            })
            .build();
    }

private:
    core::dsl::Ui& ui_;
    std::string id_;
    bool value_ = false;
    std::function<void(bool)> onChange_;
    core::Color normal_{0.12f, 0.14f, 0.18f, 1.0f};
    core::Color hover_{0.16f, 0.18f, 0.24f, 1.0f};
    core::Color pressed_{0.10f, 0.12f, 0.16f, 1.0f};
};

重点:组件只是 DSL 组合。视觉、交互和动画都声明到 DSL 树上,由 Runtime 统一执行。