xui

package module
v0.8.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 22 Imported by: 0

README

xui — shadcn 风格的 xcgui 自绘组件

release golang GoDoc License

介绍

为 github.com/twgh/xcgui (炫彩界面库)提供一套 shadcn-ui 风格的自绘组件: 主题令牌、变体系统、copy-not-install 分发。

┌─────────────────────────────────────────────┐
│  你的应用                                    │
│  ┌───────────────────────────────────────┐  │
│  │ ui/  ← 组件源码归你所有, 随意修改       │  │
│  │   button.go  checkbox.go  input.go …  │  │
│  │   theme.go   ← 你的主题, 改这里换肤    │  │
│  └───────────────────────────────────────┘  │
│              ↑ 依赖              ↑ 拷贝      │
│  github.com/twgh/xui (运行时)   xui CLI     │
│              ↑                              │
│  github.com/twgh/xcgui (行为层)             │
└─────────────────────────────────────────────┘
示例图

xui

快速开始

直接运行示例(想先看效果)

不想先建项目时, 可以直接运行仓库里的 demo(全部组件 + 主题切换):

go run github.com/twgh/xui/demo@latest
新建项目(推荐)

先装一次 CLI (之后直接敲 xui; 安装位置与 PATH 见下方 CLI 章节):

go install github.com/twgh/xui/cmd/xui@latest
xui init -n myapp
cd myapp
go run .

-n 会创建一个默认的项目, 目录已存在且非空时需加 -f/--force。

想一直用最新的 CLI 可用以下命令:

go run github.com/twgh/xui/cmd/xui@latest init -n myapp
已有项目

在项目根目录执行。组件目录默认 ui/, 想换名字: xui init components (或 -p components)

xui init
示例源码
package main

import (
    "github.com/twgh/xcgui/app"
    ui "yourmodule/ui" // 换成你的模块路径
)

func main() {
    app.InitOrExit()
    a := app.New(true)                    // Direct2D 渲染
    a.EnableAutoDPI(true).EnableDPI(true) // 跟随系统 DPI
    a.SetPaintFrequency(10)               // hover 过渡 ~60fps

    // 窗口: 圆角阴影 + 自绘标题栏(最小化/最大化/关闭) + 主题化底色, 全部默认配好
    w := ui.NewAppWindow("我的应用", 640, 480)
    content := w.Content()
    content.SetPadding(24, 20, 24, 24) // 内容区留白

    ui.NewButton(0, 0, 0, 0, "确定", content.Handle)
    ui.NewCheckbox(0, 0, 0, 0, "记住我", content.Handle)
    ui.NewInput(0, 0, 0, 0, content.Handle, ui.WithPlaceholder("用户名"))

    w.Show(true)
    a.Run()
    a.Exit()
}

标题栏要加自己的按钮(如切换主题)用 w.AddCaptionButton(icons.IconMoon, func() { ... }), 排在最小化按钮左边; 内容组件纵向排列, 间距由内容容器的 SetSpace 控制。

换肤

组件在绘制时实时读取主题令牌(等价于 CSS 变量), 切换主题零成本:

xui.SetTheme(xui.Dark())          // 一行全局换肤

主题文件 ui/theme.go 默认是内置主题的副本(逐字段改即可换肤), 也可以照 shadcn 的 :root / .dark 自己写一份 —— 令牌与 CSS 变量一一对应(字段表见 DESIGN.md 3.1)。

想运行时换"主题色"时, 克隆当前主题并只替换主色组即可(不必重建窗口, 下一帧全窗口自动用新色):

t := *xui.Light()                 // 暗色用 *xui.Dark()
t.Primary = xui.Hex("#2563eb")
t.PrimaryForeground = xui.Hex("#fafafa")
t.Ring = t.Primary                // 焦点环
t.SidebarPrimary = t.Primary      // 侧边栏主色(如用 Sidebar)
t.SidebarPrimaryForeground = t.PrimaryForeground
xui.SetTheme(&t)

demo 的标题栏调色板与「基础1 → 主题色」卡片演示了这套做法(go run github.com/twgh/xui/demo@latest 可直接跑)。

组件

组件 说明
Button 6 变体(default/secondary/outline/ghost/destructive/link)× 8 尺寸档;图标按钮 / 图标+文字 / 内容对齐 / 胶囊 / 加载态;cx 传 0 按文字自动量宽
ButtonGroup 按钮组:横向/纵向、首尾外角 + 相邻边直角、宽高按内容自撑;可混放 Button / Input / ComboBox
Alert 提示条:图标 + 标题 + 描述 + 右上动作槽;两变体(default/destructive),高度按内容自撑、文字自动折行
Empty 空状态容器:图标底板 + 标题 + 描述 + 动作区;三个槽位 Header() / Content() / Body(),不画边框
Field 表单字段布局:Label / Title / Description / Content / 校验错误区,垂直或水平取向;横向时内容默认 240 宽(嵌套布局只认 Fixed 宽)
Item 列表项:媒体 + 内容 + 动作区 + 头/尾槽位,三变体两尺寸;配 ItemGroup / ItemSeparator / ItemTitle / ItemDescription
Breadcrumb 面包屑:链接(hover 过渡 + OnClick)/ 当前页 / 分隔符 / 省略号
Kbd 键帽:宽度按文字自适应;NewKbdGroup 排组合键
Skeleton 骨架屏:主题圆角 + 2s 脉冲;WithSkeletonCircle 做头像占位
Spinner 加载指示器:图标 1s/圈旋转,尺寸/配色可调
Toggle / ToggleGroup 双状态按钮与按钮组:default / outline 两变体、三档尺寸;鼠标/空格/回车切换;单选组默认互斥且可全部取消,spacing=0 做成分段控件
NativeSelect 原生下拉框:<option value> 字符串语义,最省事的选择框;富条目(图标/禁用/分组)用 Select
Select 自绘下拉(radix item-aligned 弹层):条目可带图标/禁用/分组;弹层盖住触发区、选中项正对触发区,键盘 ↑↓/Home/End/Enter/ESC
InputOTP 验证码输入:逐格输入、打进已填格替换并前进,Ctrl+V 分发粘贴;可限字符模式与分隔位,全满触发 OnComplete
Accordion 手风琴:single 单开互斥(可全收)或多开;200ms 高度过渡。注意内容里的子项要 xui.ItemFill 声明宽度
Collapsible 可折叠面板:任意控件绑作触发区,内容放进 ContentStack();收起后不占布局
Popover 弹出卡片:点击触发、点外部/ESC 关闭;内容里嵌了会自己弹窗的控件(如 ColorPicker)时要 WithPopoverKeepOnBlur(true),否则本卡片会先失焦关掉
HoverCard 悬停卡片:悬停 700ms 打开、移出 300ms 关闭,可交互且不抢焦点;适合用户资料预览
Avatar / AvatarGroup 圆形头像:图片居中裁圆、无图回落首字母、右下徽标;组做负间距叠放
Sheet 侧边面板:标题/描述/内容/页脚与 Dialog 同一套;ESC / 点遮罩 / X 关闭,Tab 焦点陷阱,从贴边侧滑入,四边可选
Drawer 底部抽屉:顶部抓手按住下拖过面板高 1/3 关闭、否则回弹;方向可改 top/left/right
Dialog 模态对话框:全窗遮罩 + 居中面板,标题/描述折行 + 任意内容 + 页脚按钮;WithDialogScroll 内容区滚动而头部/页脚固定,BindClose 把任意控件变成关闭钮
AlertDialog 警示对话框:必须明确选择(无 X、点遮罩不关,ESC 关,焦点落在 Cancel);可与 Dialog 共用遮罩/模糊/焦点陷阱,支持再叠一层
Menu 下拉菜单 / 右键菜单(原生菜单承载行为 + 自绘皮肤):条目/勾选/单选/子菜单/快捷键/禁用/破坏性/前置图标;NewContextMenu(元素, build) 右键触发,每次重建
Menubar 菜单栏:条目 API 与 Menu 共用;打开任一菜单后鼠标移到其它触发区即切换,全套键盘导航,子菜单悬停展开
NavigationMenu 导航菜单:所有菜单共用一个视口,悬停开、切换时视口随内容收放、新页面从切换方向滑入;内容里可 LinkIn 进 xui.HStack 做多列网格
Sidebar 侧边栏:返回"面板 + 主内容区",分组/子菜单/徽章/动作钮/折叠(offcanvas/icon/none, Ctrl+B)/左右贴边;三种外观 sidebar/floating/inset
Command 命令面板:输入即过滤、↑↓ 循环跳过禁用、Enter/ESC;可放进 ui.NewDialog 组合成 ⌘K 弹窗
Combobox "输入即过滤"的建议列表(= Popover + Command):单选选中即关,WithComboboxMultiple 多选保持打开;条目模型与 Select 共用
ComboBox 注意: 这是原生 ComboBox 载体(对应 shadcn 的 select),不是上面那个 Combobox;要"输入即过滤"用 Combobox
Input 输入框:原生 Edit(光标/输入法/选择)+ 自绘壳体与焦点环;校验态、占位符、前缀/后缀/清除/密码切换/计数,以及步进器 WithStepper(−/+ 与 ↑/↓,失焦自动夹取吸附)
Textarea 文本域:Input 的多行预设 + 随内容自动长高;要固定高度/滚动条用 Input + WithMultiline
InputGroup 输入组:输入框与附加件共用一层边框/焦点环,内层是"裸输入"(不画壳体);四向 addon 可放文本/图标/按钮/Kbd/下拉触发,一个 addon 放多个控件只整体外扩一次;block-* addon 存在时自动切成三段式
Checkbox / Radio 自绘指示器 + 原生状态管理:Checkbox 三态(不确定态画减号、点击落到选中),两者都有校验态、空格切换与键盘焦点环;"整块区域可点"时用 Checkbox.Toggle() / Radio.Select()(走与点击指示器相同的路径,SetSelected 是不触发回调的静默版),放进非 Card 底色容器时用 SetBackdrop 跟随宿主底色
RadioGroup 单选组:AddItem(value, text) 或 Attach(radio, value) 收编放在 Field 等子容器里的条目(跨父级互斥);↑↓←→ 循环移动并选中、Home/End 首末
Switch / Progress / Slider 开关 / 进度条 / 滑块:都有禁用与校验态;Progress 可 indeterminate 短条往返与 ui.NewProgressField(标题 + 百分比 + 条);Slider 支持值域/步进、范围与多滑块、垂直、反向
Calendar 月历(全自绘):6×7 网格、范围选择(两次点击落区间)、多月并排、ISO 周数列、禁用与预订划线、年月可下拉、页脚/预设;尺寸有 helper 算宽
DatePicker 日期框:outline 触发区 + 弹层内放 Calendar,下方放不下自动翻到上方;范围模式并排两个月、预设按钮、禁用日期、可输入形态(键入 2026-10-22 / 今天 / 3天后)
TimePicker 时间框:弹层内"时/分"两列滚动列表,选择即生效、弹层保持打开
ColorPicker 颜色选择器:触发区(色块 + #rrggbb + chevron)+ 弹层(SV 方块 / 色相条 / 预设色 / Hex 输入);颜色以 #rrggbb 进出
Table 数据表格(原生 List 载体 + 自绘皮肤):表头/行边框/悬浮/选中/主题化细滚动条;列宽可"占满剩余宽度"
DataTable 在 Table 之上加排序 / 筛选 / 分页 / 勾选 / 列显隐 / 双击原地编辑;表头菜单排序(Shift+点多列排序)、表头可拖拽换列顺序、分面筛选、跨页全选(Shift+点表头复选框);列的行为由 DataColumn 的字段描述(排序/对齐/富渲染/隐藏/筛选项/只读)
Tree 树形视图(原生 Tree 载体 + 自绘皮肤):缩进参考线 / 旋转 chevron / 行悬浮选中;叶节点留空槽位使文字全树对齐
Chart 图表(纯自绘):柱/折/面积/饼/圆环/径向/雷达,堆叠、平滑、数据点、图例临时隐藏、悬停指示线与提示卡、Y 轴刻度按数据自动取整齐值、点击数据点回调
Pagination 分页:上/下一页 + 页码 + 省略号折叠(>7 页),当前页描边态
Carousel 轮播:整页滑动(300ms、循环首尾相接)、←/→ 键盘翻页、鼠标拖拽松手吸附;上/下张是内容外侧 48px 的圆形描边按钮,到边界禁用;幻灯片是纵向流容器,内容超出由视口裁剪;可自动播放与指定初始页
Resizable 可调整分栏:面板按百分比分主轴空间、窗口缩放保持比例,拖动分隔条伸缩两侧(相邻到下限后挤压更远的);方向键每次 5%、Home/End 到边界、双击分隔条复位,分隔条可 Tab 聚焦
ScrollArea 滚动容器(原生 LayoutFrame 载体):内容比容器高时滚动、内容大小按子项自动撑开、滚动条自动显隐;SetContentFill() 切"铺满视口 + 面板吃剩余高度"形态
AspectRatio 固定宽高比容器:高度 = 宽度 / ratio,宽度变化时自动重算并逐级重排
Tabs 标签页(default / line 两变体):行内图标、单标签禁用、value 语义、BindPanel 绑面板;支持纵向堆叠与自动换行;键盘 = 整块一个 Tab 站点,←/→/↑/↓ 移动、Home/End 首末、Enter/空格切换
Tooltip 提示气泡(独立透明弹出窗口,不抢焦点):四方向 + 对齐/间距、显示延迟与跳过延迟、键盘焦点也触发、内容可带快捷键块
Toast 通知(窗口内覆盖元素):默认右下角堆叠,四角与上下居中可选;4 种语义类型 + 动作按钮 + 可选关闭钮,自动关闭、悬停暂停、滑入淡入
ScreenToast 屏幕通知:独立置顶小窗,固定在屏幕右下角,应用最小化/隐藏也能看到、不抢焦点;卡片绘制与自动关闭与 Toast 同款
ScreenPopup 屏幕弹窗:固定在屏幕工作区右下角的常驻面板 —— 不是通知(默认不自动关闭、不堆叠),内容任意 xui 控件、高度自撑;默认不抢焦点,WithScreenPopupFocusable(true) 改为点击后键盘可用
Tray 系统托盘:左键还原/前台化绑定窗口(可覆盖),右键弹 themed Menu,资源图标可直接用 .syso 里的 HICON;监听 TaskbarCreated,Explorer 重启后自动恢复图标。⚠️ 同一进程只应创建一个托盘图标(xcgui 的托盘 API 是全局状态)
AppWindow 主窗口封装:圆角阴影 + 自绘标题栏(最小化/最大化/关闭、双击最大化/还原、最大化图标自动切换)+ 主题化底色;NewAppWindow("标题", 640, 480) 后往 Content() 放内容,AddCaptionButton 加标题栏按钮
Label / Card / Badge / Separator 静态展示组件:Badge 6 变体(含 ghost/link)、行内图标、加载指示器、自定义配色与点击回调,cx 传 0 按内容量宽、SetText 后自动重量;Card 有 sm 尺寸与卡片头(标题 + 说明 + 右上动作区)/ 说明 / 页脚;Separator 支持垂直

交互类组件默认带齐 4 态: 正常 / hover / 按下 / 禁用, 外加键盘焦点环(静态展示与容器类组件没有按下态)。

hover 带过渡动画(默认 150ms + cubic-bezier(0.4,0,0.2,1), 与 tailwind / shadcn 的 transition-* 默认值一致; 表格行/树行/菜单条目同样有), 过渡由运行时管理(静止时零开销)。两件事你自己要做:

a := app.New(true)
a.EnableAutoDPI(true).EnableDPI(true)
a.SetPaintFrequency(10)                  // 放开 xcgui 默认的 30ms 重绘节流: 实测 30.9 → 59.0 fps
xui.SetHoverDuration(200 * time.Millisecond) // 改过渡时长;传 0 = 立即到位(关掉过渡)

对话框与提示气泡自带进出场动画(dialog 淡入淡出 100ms、tooltip 淡入 + 8px 滑入 150ms)。自己的弹层 要用同款动画就交给运行时的 xui.Presence(不要自己起定时器), 用法与坑见 DESIGN.md。

图标

运行时自带一份预处理好的 lucide 图标资源(2130 个, 当前版本见 icons.Version), 描边用直通 D2D 几何填充出图(自带抗锯齿)。每个图标一个常量, 名字是 lucide 名的驼峰形式:

import "github.com/twgh/xui/icons"

p.Icon(icons.IconEye, &rc, xui.Current().Foreground, 2)   // 常量: 编译期内联, 零体积成本
p.Icon(icons.IconChartScatter, &rc, color, 2)             // 含"填充点"的图标也照渲染

// 名字来自用户输入/配置时按名字取 —— 会把整张资源表链进二进制(实测 +426KB):
p.Icon(icons.Path("eye"), &rc, color, 2)
icons.Has("star"); icons.Names(); icons.Count()           // 查询

也可以直接把 path 数据贴进 Painter.Icon(24 网格,strokeWidth 用 24 网格单位,lucide 默认 2); 手里是整段 SVG(从 heroicons / feather / iconfont 复制来的 <svg viewBox="…"><path d="…"/></svg>) 也交给同一个 Painter.Icon —— 它会自动识别(看首字符是不是 <), 提取 path、按 viewBox 归一到 24 网格、按 fill/stroke 判定填充:

p.Icon(svgText, &rc, color, 0)                // 整段 SVG: 0 = 用 SVG 自己的 stroke-width(heroicons 1.5)
paths, sw, err := xui.ParseIconSVG(svgText)   // 想先校验 / 自己缓存归一化结果

组件内部用到的图标同样取 icons.IconXxx: 实测"只用常量"的程序体积增量 0 字节(2130 条数据里 只进用到的那条), 所以组件、业务代码都推荐用常量。

Lucide 官网 可以搜索/浏览整份资源。

CLI

开关清单与改动判定以 xui --help 为准(它随 CLI 版本走); 下面只列影响用法的几个, -j 的字段表只在本文档里。

安装 / 升级
go install github.com/twgh/xui/cmd/xui@latest

二进制在 $(go env GOPATH)/bin (Windows 默认 %USERPROFILE%\go\bin\xui.exe), 该目录不在 PATH 时把它加进去。

升级重跑同一条命令, 或者用 CLI 自己: xui self-update (加 --check 只比较当前版本与 @latest)。@latest 偶尔被 模块代理缓存拖旧, 想立刻用某个版本就直接写 tag (xui self-update v0.8.2, 也支持 main / commit 短哈希)。

命令
xui init -n myapp       # 新建完整项目(自动 go mod tidy; go.mod + main.go + README + AGENTS.md + 组件目录(默认 ui/) + winres/)
xui init -n myapp -p components # 同上, 但组件目录叫 components/(import 路径与下发文档都跟着这个名字)
xui init -n myapp --dry-run # 预览整个生成过程: 不建目录、不写文件、不联网
xui init -n myapp --force # 目录已存在且非空时放开这道闸门(你的改动仍然保留; 要丢弃再加 -o)
xui init -n .           # 在当前空目录直接生成项目(项目名取目录名)
xui init [组件目录]     # 已有项目: 只写组件目录(theme.go + 基础组件 + DESIGN.md + AGENTS.md; 已存在则跳过)
xui add button card     # 追加拷贝指定组件(自动带上依赖)
xui add --all           # 拷贝全部组件
xui add card --dry-run  # 只显示将创建/覆盖/跳过的文件, 不写本地
xui add card --view     # 打印组件源码(可加 =card.go 或 =card 只看某个文件/某个组件), 不写本地
xui add card --diff     # 与本地文件做逐行 diff(过滤值同上), 不写本地
xui update              # 升级已装组件(省略组件名 = 组件目录里已装的全部, 外加已在的两份文档; 不接 --all)
xui update button       # 只升级指定组件(含依赖; 没装过的只提示, 不会替你装 —— 那是 add)
xui update -o           # 连本地改过的文件一起覆盖(改动会丢, 先备份)
xui list                # 列出可用组件
xui search button       # 搜索: 位置参数就是搜索词(等价于 -q button)
xui view button         # 打印组件源码
xui info                # 显示 CLI / 项目环境信息(组件目录不是 ui 时: xui info -p <目录>)
xui version             # 只打印版本(同 -v / --version; 离线可用, 不拉数据源)
xui self-update [版本]  # 升级 CLI 自己(默认 @latest; 也可 tag / main / commit 短哈希)
xui self-update --check # 只比较当前与 @latest, 不安装(不接受版本参数)

组件名统一是 shadcn 文档的 kebab-case(navigation-menu / date-picker / scroll-area), 但不区分大小写、也可省略连字符与下划线: xui add navigationmenu、xui add datepicker 与 xui add navigation-menu、xui add date-picker 等价(只有唯一的归一化结果才接受, 不会按前缀猜; 输错时会提示最接近的组件名)。

选项:

选项 用于 作用
-n, --name <项目名> init 新建完整项目(-n . = 铺在当前空目录)
-f, --force init -n 放开"目标目录已存在且非空"这道闸门; 不隐含丢弃你的改动
-o, --overwrite init / add / update 丢弃本地改动(默认保留, 见下方判定); 管模板文件与组件文件
-p, --path <目录> init / add / update / info 组件目录, 默认 ui。init -n 用它在新项目里定名字(import 路径与下发文档跟着走); 只许项目内的相对目录(绝对路径或 .. 跳出会被拒)
--no-install init / add / update 组件文件照写, 但不联网动依赖(init -n 还跳过 go mod tidy 与 xc 安装)
--dry-run init / add / update 只预览, 没有任何副作用: 不建目录、不写文件、不写清单、不联网、不生成 .syso
-a, --all add 全部组件(update 不接: 省略组件名就已经是"已装的全部", 要连没装的一起装用 add --all)
--view[=组件或文件] / --diff[=组件或文件] add / update 打印源码 / 与本地对比, 不写本地; 过滤值给组件名(card)或文件基名(card.go)都行, 谁都没命中就按失败退出(码 1) —— 别把它当成"没有差异"
[词] / -q, --query / -l, --limit / -o, --offset list / search 搜索词(位置参数与 -q 等价)、限制数量、跳过前 n 个
-j, --json list / search / info / view / --view / --diff / self-update --check 结果出结构化 JSON(字段表见下)
--strict add / update 有文件被跳过时用退出码 2 失败(默认只汇总一句就正常退出)
-c, --cwd <目录> 全部 先切到该目录, 后续相对路径都以它为准
-s, --silent 全部 只压进度输出: 命令结果(list 的表、view/--diff 的正文、info、version、--check 的对比)照常, 警告与错误照常;xui list -s 就是一份没有表头、可直接接管道的清单
-v, --version / -h, --help 全部 版本 / 帮助(-h 任何位置都生效)

三条约束:

  • 带值的选项不给空值 —— -p "" 与 -p= 一样当场报错(空目录会把组件拷进当前目录根, 空的 --view= 会从"只看一个文件"变成"全部打印")。
  • --view / --diff 不与 --dry-run / -o / --no-install / --strict 同用 —— 它们本来就不写文件, 一起给会直接报错; -v 是版本号, 看源码写 --view; self-update --check 不接受版本参数。
  • 短选项也接受 -x=值, 与长选项一致: -p=src、-l=5、-o=2。

退出码: 0 = 跑通(有跳过时 stderr 给一句"共 N 个文件被跳过"的汇总); 1 = 命令失败(源拉不到、权限、参数错、--view/--diff 的过滤值没命中); 2 = 加了 --strict 且有文件被跳过。CI 想"没更新干净就失败"就跑 xui update --strict(配 --dry-run 可先空跑一遍)。

结构化输出(-j)的字段:

命令 JSON 形状(字段)
list / search source cli runtime(数据源声明的配套运行时) query offset limit total(匹配数) shown(本次显示数) components[]{name files[] deps[]}
info cli cwd go componentsDir componentsInitialized goFiles source updateCheckAt updateCheckLatest
view / --view source filter matched files[]{name component content}
--diff dir filter matched files[]{path file state diff}, state = missing / upgrade / edited / unknown
self-update --check current latest localBuild upgradable command(下一步该跑什么, 已最新时为空串)

空的 files / components / deps 一律是 [] 而不是 null; 过滤值没命中时靠 matched=false 区分(这时 files 也是空, 但不是"没有差异")。

组件源码拷进项目后归你所有; add / update / init 都不会覆盖你本地改过的文件, 确认要丢弃本地改动时加 -o/--overwrite。

更新时的判定: CLI 在组件目录写一份 .xui-manifest.json, 记下每个文件安装那一刻的 sha256(init -n 生成的 go.mod / main.go / README.md / AGENTS.md / .gitignore 也一起记)。之后逐文件比对 —— 组件文件与模板文件共用这一套判定:

本地文件 xui update 的动作
不存在 创建
与新版完全一致 跳过(“已一致”)
内容不同,但该文件的清单哈希与本地一致(装完之后没动过) 直接替换(“更新: 版本升级”;加 -o 也是这句)
内容不同,且清单哈希对不上(你改过) 警告并跳过,要 -o 才覆盖(“本地修改已丢弃”)
内容不同,但清单里没有这个文件(老项目 / 手动删了清单) 警告并跳过,要 -o 才覆盖(只说“无安装清单基线, 无法判断是否改过”, 不断言你改过)

也就是说: 自己没动过的文件会跟着版本正常升级, 只有你手改过的那些才需要 -o; --diff 的每个文件头也标的是同一个来源判定。清单缺失(老项目 / 手动删了它)时退回保守模式 —— 一律"无法判断来源"并跳过, 加 -o 之后清单会重新生成。

运行时版本: 组件源码跟 main(永远最新), 而运行时 github.com/twgh/xui 是 Go 模块依赖、只能按 tag 取; registry.json 的 runtime 字段声明与这批组件配套的运行时版本(每次发布都抬成刚发的 tag)。add / update / init 装完组件会顺手把项目 go.mod 的该依赖抬到它(等价 go get github.com/twgh/xui@<tag>, go.sum 一起更新), 升级完直接 go build 就行, 不用你再 go get -u。

这五种情况不动你的依赖: --dry-run 与 --no-install 只打印命令; go.mod 里把该模块 replace 到本地工作副本时以你的 replace 为准; 项目里找不到 go.mod 时只提示; 找到的那份 go.mod 既在当前目录之外、又没要求 github.com/twgh/xui 时也只提示。想自己锁版本: 先 go get github.com/twgh/xui@<你选的 tag> 再跑 xui update。

数据源: 默认从仓库 main 分支现拉组件(组件永远最新); CLI 本体的版本由 go install / go run 时的 @latest 或 tag 决定, 组件版本用下面的 XUI_REGISTRY 锁。默认源自动选路, 第一个通的用它跑完本次命令, 全都不通才降级到 CLI 内嵌源码; 拷到一半某个文件拉不到(镜像限速、临时挂起)会自动换下一个源接着拷, 不会留下半套组件目录。选路过程逐行打在 stderr 上。

直连 https://raw.githubusercontent.com/twgh/xui/main
  → Gitee 镜像 https://gitee.com/twgh/xui/raw/main        (国内稳定)
  → GitCode 镜像 https://api.gitcode.com/api/v5/repos/twgh/xui/contents (国内备用)
  → 加速代理 ghfast / gh-proxy                             (兜底)

环境变量 XUI_REGISTRY 覆盖数据源(写什么就用什么,不再套代理):

XUI_REGISTRY=D:\path\to\xui xui add dialog                    # 本地目录(离线)
XUI_REGISTRY=https://raw.githubusercontent.com/twgh/xui/v0.8.2 xui add dialog   # 锁组件版本(换成实际 tag)
XUI_REGISTRY=https://raw.githubusercontent.com/twgh/xui/main xui add dialog     # 只走直连(跳过镜像与代理)
XUI_REGISTRY=https://gitee.com/twgh/xui/raw/main xui add dialog                # 只走 Gitee 镜像
XUI_REGISTRY=https://api.gitcode.com/api/v5/repos/twgh/xui/contents xui add dialog  # 只走 GitCode 镜像

显式指定 XUI_REGISTRY 时拉取失败会直接报错(便于排查), 不静默降级。

已知约束

  • 渲染固定按 Direct2D 设计(app.New(true))。

  • 组件坐标使用逻辑像素(XEle_GetClientRect 返回未缩放矩形), 框架渲染时统一按窗口 DPI 变换; 自绘不要再乘 DPI(开 EnableAutoDPI + EnableDPI 即可)。

  • 本模块的 go 指令是 1.18: 即使你用 Go 1.22+ 的工具链, for range 的循环变量也按旧语义共享 —— 在循环里给控件注册回调(或起 goroutine)时, 循环变量必须先拷一份, 否则所有回调都取到最后一次的值:

    for _, item := range items {
        item := item // ← 必须有(本模块 go 指令 < 1.22)
        btn.AddEvent_MouseStay(func(hEle int, pbHandled *bool) int { use(item); return 0 })
    }
    

详见 DESIGN.md。

项目文档

交互语义与视觉逐条对照 shadcn-ui 官方源码实现; 汇总在 DESIGN.md 第四节末。

文档 内容
README.md 本文: 安装 / 用法 / 组件一览
docs/DESIGN.md 自定义指南(随 xui init 安装到组件目录, 默认 ui/DESIGN.md): 架构原理、主题系统、组件解剖、与 shadcn 的语义对照、xcgui 踩坑全集(第八节)、本库依赖的 xcgui API 清单(第九节)
docs/HANDOFF.md 仓库维护者用: 当前进度 / 待办 / 仓库结构 / 常用命令 / 视觉验证工作流
AGENTS.md 仓库维护者用: AI 会话入口硬规则与文档导航

组件自己的完整选项与方法清单在 pkg.go.dev 和拷进你项目的源码注释里(源码就在组件目录, 每个 With* 都带说明)。

License

MIT

Documentation

Overview

Package xui 为 github.com/twgh/xcgui 提供一套 shadcn 风格的自绘控件运行时: 设计令牌(主题)管理、绘制助手与状态跟踪。

组件源码不由本包提供, 而是通过 xui CLI 拷贝到用户项目的组件目录(默认 ui/), 组件在绘制时通过 Current() 实时解析主题令牌, 因此切换主题只需 SetTheme + 重绘。

图标: 内置 lucide 资源在 github.com/twgh/xui/icons 子包(每图标一个 IconXxx 常量, 另有 Path(name)); 图标数据统一交给 Painter.Icon —— path 数据与整段 SVG 都吃(自动识别); 想先校验 SVG 用 ParseIconSVG。

注意: 本模块的 go 指令是 1.18 —— 即使在 Go 1.22+ 工具链下, for range 的循环变量也按旧语义**共享**: 在循环里注册回调 / 起 goroutine 时必须先 `v := v` 拷一份, 否则所有回调都取到最后一个值。

Index

Constants

View Source
const (
	// PopupDuration 弹层 / 对话框进出场时长(shadcn duration-100)。
	PopupDuration = 100 * time.Millisecond
	// TooltipDuration 提示气泡进出场时长(tw-animate-css 的 animate-in 默认 150ms)。
	TooltipDuration = 150 * time.Millisecond
)

===== 弹层 / 对话框的进出场过渡(presence) =====

与 hover 过渡共用同一套驱动器(见 anim.go): 同一个目标上的多个过渡共用一个定时器, 全部到位自动关掉。区别在于 presence 面向"出现/消失":

  • 值仍是 0..1 的进度, 但由 apply 回调每帧落到具体表现上(元素透明度 / 窗口位置 / 窗口 alpha);
  • 到位时回调 done(每个条目只调一次), 退场到位后条目被回收;
  • 键空间与 hover 隔离(presence 的键内部映射到负数)。

时长与缓动对齐 shadcn / tailwind:

  • dialog / dropdown-menu / select 都写的是 duration-100 → PopupDuration(100ms);
  • tooltip 没写时长 → tw-animate-css 的 animate-in 默认 150ms;
  • 缓动取 CSS 默认的 `ease` = cubic-bezier(0.25,0.1,0.25,1)(animate-in 是 keyframes, 用的是 animation 的默认缓动, 与 transition-* 的 tailwind 默认值 cubic-bezier(0.4,0,0.2,1) 不是同一条)。
View Source
const ItemKeep int32 = -1

ItemKeep 是 ItemSize 里表示"这条轴不覆盖"的哨兵(用元素自身尺寸)。

用 -1 而不是 0: 0 是**合法尺寸**(0 宽/高的元素会被排成不可见), 用户可能真要设 0。

Variables

This section is empty.

Functions

func AlphaFloor

func AlphaFloor() float64

AlphaFloor 返回窗口透明度的下限(1/255, 见 SetWindowAlpha 的说明)。 进场前把弹层窗口压到这个值可消除首帧闪现, 又不会触发 alpha=0 的合成停摆。

func AnimationScale

func AnimationScale() float64

AnimationScale 返回当前过渡时长倍率。

func AttachWindow

func AttachWindow(hEle int)

AttachWindow 组件构造时调用, 为组件所在窗口挂接全局服务:

  • 登记窗口随主题切换重绘;
  • 挂 Tab 键钩子, Tab 后获得的焦点标记为键盘来源。

每个窗口只挂一次, 幂等。

func BindWindowBackground

func BindWindowBackground(hWindow int)

BindWindowBackground 把窗口底色绑定到主题 Background 令牌, 主题切换时自动更新。必须用 Leave 状态注册填充——窗口绘制时处于 Window_State_Flag_Leave, 用 Nothing(0) 状态注册的填充不会被选中; 且必须先 Clear 清掉默认的白色填充。 必须在 UI 线程调用。窗口关闭时调 UnregisterWindow 一并解绑。

func DropPresence added in v0.8.0

func DropPresence(hOwner int, key int32)

DropPresence 丢弃 (hOwner, key) 上的进出场过渡条目。 供通知/弹层在"没播完退场就被直接销毁"时清场: 条目里捏着 apply/done 闭包, 宿主窗口长命(主窗口)时漏一次就多一份, 只增不减。

func DropTransitions

func DropTransitions(hOwner int)

DropTransitions 丢弃 hOwner 上**全部**过渡条目(元素级与窗口级都适用)。

供"宿主生命周期结束 / 弹层被隐藏"的调用方清场: 淡入中途隐藏时, 未到位条目会留在 表里(未 settled), 它们的 apply 会随残留定时器继续执行, 与下一次进场的 apply 打架 (时间框弹层会概率性停在半透明: 淡入中途 hide 后快速重开, fast 路径的 终态与旧条目的 apply 互相覆盖)。调用方负责把相关元素的 alpha 归位到期望的静止态。

func FadeWindowAlpha

func FadeWindowAlpha(hWindow int) func(v float64)

FadeWindowAlpha 返回一个给 Presence 用的 apply 回调: 按进度设置窗口透明度, 并立即重绘该窗口(透明度要绘制才可见)。

func FocusedByKeyboard

func FocusedByKeyboard() bool

FocusedByKeyboard 报告当前焦点是否来自键盘 Tab。 组件绘制时据此决定是否绘制焦点框(文本输入类无需判断)。

func FontHandle

func FontHandle(spec FontSpec) int

FontHandle 返回主题字体对应的炫彩字体句柄, 相同规格全局复用。 组件在构造或主题回调中调用 SetFont(xui.FontHandle(t.FontDefault))。

单位换算: xcgui 字体的 size 单位是 pt(磅, 见 font.NewEX 文档), 渲染尺寸 = pt×DPI/72 物理像素; 主题令牌的 Size 是逻辑像素(= px×DPI/96 物理)。联立得 pt = px×72/96(= ×0.75, 与 DPI 无关)。此前把 14 直接当 pt 传入, 144 DPI 下 渲染出 18.7 逻辑像素的文字(墨迹实测 26-27 物理像素), 全组件文字放大了 1.33×。 pt 只能取整, 14px→11pt=14.67px(+0.67), 12/16px 等恰好取整。

func HStack

func HStack(parent int, ht, space int32) *widget.LayoutEle

HStack 建一个横向行容器(作为 parent 布局的布局项): 宽度铺满, 高度固定 ht, 子项间距 space, 交叉轴(垂直)居中 —— 行内混合高度的控件靠它对齐。

行内的项按需要声明宽度: Fill 会独占整行, 等分剩余宽度用 xui.ItemWeight。

func HostWindowOf added in v0.5.0

func HostWindowOf(hEle int) int

HostWindowOf 返回元素所属的 **xcgui 窗口**句柄(找不到返回 0)。

不能一步 `XC_hWindowFromHWnd(XWidget_GetHWND(ele))`: 元素在 ScrollArea(LayoutFrame)、 弹层容器这类自带 HWND 的容器内部时, 取到的 HWND 不是 xcgui 窗口, 映射出来是个无效 小值(实测 7) —— 拿它去 `XWnd_*`、presence、主题重绘都会静默失败。沿 HWND 父链往上找 第一个"映射成功且确实是窗口"的句柄。

func Hover

func Hover(hOwner int, active bool) float64

Hover 取 hOwner 上"元素级"hover 的过渡值(0..1), 并把目标设为 active。 hOwner 既是过渡状态的归属(按句柄登记), 也是动画期间的重绘目标。

func HoverDuration

func HoverDuration() time.Duration

HoverDuration 返回当前 hover 过渡时长(0 表示已关闭过渡)。

func HoverItem

func HoverItem(hOwner int, index int32, active bool) float64

HoverItem 取 hOwner 上第 index 项的 hover 过渡值(0..1), 用于"逐项悬停"的 载体(原生 List / Tree / ListBox 的行与条目)。active 一般直接来自绘制回调里 观察到的项状态(如 List_Item_State_Stay)。

func ItemFill

func ItemFill(item Item)

ItemFill 让布局项宽度占满剩余空间(主轴 Fill), 高度不覆盖(用元素自身尺寸)。

注意 Fill 在主轴上会**独占一整行/列**, 兄弟项被挤到下一行; 行内等分剩余空间要用 ItemWeight。

func ItemFillHeight

func ItemFillHeight(item Item)

ItemFillHeight 让布局项高度占满父级的**剩余空间**(纵向按权重分配), 宽度不覆盖。

注意与宽度方向的 ItemFill 语义不同(实测, 见 DESIGN 第八节 55):

  • 宽度 `Fill` = "铺满父级整宽", 竖向链里这样用没问题;
  • 高度若也用 `Fill`, 拿到的是**父级整高**(减自身边距), 不是剩余空间 —— 前面还有兄弟项(标题/说明)时就会溢出, 所以这里用 `Weight 1`。
  • `Weight` 只在**父级高度确定**时才算得出剩余空间(窗口直属 / Fixed / Weight 父级都行); 父级是 Fill 或 Auto 时会被算成 0 —— 卡片高度是 Auto 时不要用本函数。

func ItemFloat

func ItemFloat(item Item)

ItemFloat 让布局项"浮动": 在布局方向上**向反方向对齐**(行布局里靠尾, 即 CSS 的 margin-left:auto 效果)。

⚠️ 语义取自 xcgui 文档 "启用浮动, 向反方向对齐", 本项目未实测(demo 里靠 Weight 撑开实现同行靠尾)。

func ItemHeight

func ItemHeight(item Item, h int32)

ItemHeight 锁定布局项高度为固定像素, 宽度不覆盖(用元素自身尺寸)。

func ItemMargin

func ItemMargin(item Item, left, top, right, bottom int32)

ItemMargin 设置布局项四边外边距(与 CSS margin 同义)。

func ItemMarginBottom

func ItemMarginBottom(item Item, m int32)

ItemMarginBottom 设置布局项下边距, 其余三边保持不变。

func ItemMarginLeft

func ItemMarginLeft(item Item, m int32)

ItemMarginLeft 设置布局项左边距, 其余三边保持不变。

func ItemMarginRight

func ItemMarginRight(item Item, m int32)

ItemMarginRight 设置布局项右边距, 其余三边保持不变。

func ItemMarginTop

func ItemMarginTop(item Item, m int32)

ItemMarginTop 设置布局项上边距, 其余三边保持不变。

func ItemMinSize

func ItemMinSize(item Item, w, h int32)

ItemMinSize 限制布局项在**缩放类尺寸**(Auto/Fill/Weight/Percent)下的最小宽高, 0 = 不限制。

Fixed 轴不受它影响(固定值就是固定值); 典型用法: Fill 铺满但有下限。

func ItemPosition

func ItemPosition(item Item, left, top, right, bottom int32)

ItemPosition 设置布局项位置偏移(四边)。

⚠️ 语义取自 xcgui 文档 "窗口组件_布局项_置位置", 本项目未实测 —— 用前先写探针确认相对谁偏移。

func ItemSize

func ItemSize(item Item, w, h int32)

ItemSize 锁定布局项的宽/高为固定像素; 某一轴传 ItemKeep 表示该轴不覆盖(用元素自身尺寸)。

xui.ItemSize(box, 300, 200)           // 两条轴都锁
xui.ItemSize(box, 300, xui.ItemKeep)  // 只锁宽(等价于 ItemWidth)

只锁一条轴时用 ItemWidth / ItemHeight 更清楚; ItemSize 是"两条轴一起传"的场合(值可能是 算出来的, 其中一条按条件才锁)。

func ItemSwap

func ItemSwap(item Item)

ItemSwap 让布局项在水平/垂直布局切换时交换宽高(及最小宽高)。

⚠️ 语义取自 xcgui 文档 "根据水平垂直布局变换, 交换属性", 本项目未实测。

func ItemWeight

func ItemWeight(item Item, weight int32)

ItemWeight 让布局项宽度按权重分配剩余空间, 高度不覆盖(用元素自身尺寸)。

func ItemWidth

func ItemWidth(item Item, w int32)

ItemWidth 锁定布局项宽度为固定像素, 高度不覆盖(用元素自身尺寸)。

用于设计稿常量宽: 等宽按钮行(`xui.ItemWidth(btn, 96)`)、定宽下拉框/搜索框、定宽表格。 常量要来自设计稿, 不要写成"元素当前多宽"。

func ItemWrap

func ItemWrap(item Item)

ItemWrap 让布局项强制换行(从该项开始折到下一行/列)。

⚠️ 语义取自 xcgui 文档 "启用换行, 强制换行", 本项目未实测。

func MonitorWorkArea added in v0.4.1

func MonitorWorkArea(hWnd uintptr) (xc.RECT, bool)

MonitorWorkArea 返回窗口所在显示器的工作区(物理像素); 取不到时 ok=false。

用工作区而不是整屏: 工作区已经扣掉任务栏 / 停靠栏。弹层夹在整屏高度里时, 只要 触发区靠近屏幕底部(例如页面已滚到底), 卡片下半截就会被任务栏盖住 —— 任务栏永远 在最上层, 弹层是 topmost 也盖不过它。

⚠️ hWnd 必须是 **Win32 HWND**(xc.XWnd_GetHWND 转出来的), 不能喂 xcgui 的窗口句柄: MonitorFromWindow 对无效句柄返回 0, 调用方会静默退回"整屏"兜底分支, 任务栏又会盖上来。

"上方放不下翻到下方"、贴边夹紧这类判断都必须按它算, 不能拿 (0,0) 当屏幕边缘: 显示器原点不一定是 (0,0)(显示器摆在主屏正上方/右侧时坐标是负的或偏移的), 拿 0 当边界会把明明放得下的弹层/提示翻走。

func NextPresenceKey

func NextPresenceKey() int32

NextPresenceKey 分配一个全局唯一的 presence 键。

键在"同一个 hOwner"内必须互不相同: Presence 是幂等的 —— 目标值没变时它不会重启过渡, 也不再调用 apply(过渡由定时器逐帧驱动, 到位后条目就静默了)。若两个不同的弹层复用同一个 键, 第二个弹层的新窗口会**停留在初始状态**(例如 alpha 压在下限 = 完全看不见, 但窗口已经 存在并吞掉点击), 表现为"点了没反应 / 第二次才生效"。用本函数取键即可避免手工计数器撞车 (实测: menu 与 combobox 各自的计数器都从 1 开始, 先开下拉再开菜单 → 菜单全透明)。

func NoteKeyboard

func NoteKeyboard()

NoteKeyboard 由组件在键盘导航时调用(例如对话框内的 Tab 焦点循环在自行搬焦点前): 随后获得的焦点视为键盘焦点, 组件会绘制焦点框。

func NotePointerPress

func NotePointerPress()

NotePointerPress 由组件在鼠标按下时调用: 随后获得的焦点视为鼠标焦点。

func OnThemeChange

func OnThemeChange(fn func(*Theme)) (unsub func())

OnThemeChange 订阅主题切换。需要把颜色同步给原生 API 的组件 (如 Edit.SetTextColor)用它; 只在绘制回调里取色的组件无需订阅。 返回反注册函数(幂等, 重复调用无副作用)。

func OnThemeChangeFor

func OnThemeChangeFor(hEle int, fn func(*Theme))

OnThemeChangeFor 订阅主题切换, 并在 hEle 销毁时**自动反注册**。 组件构造函数用它替代裸 OnThemeChange —— 元素/窗口反复创建时, 裸订阅会在全局 订阅表里无限累积, 句柄被复用后还可能把回调刷到无关元素上。 实测: 元素显式 Destroy、随父窗口 CloseWindow 连带销毁、 以及嵌套在子容器里的元素, 都会触发 XE_DESTROY, 所以这条绑定是可靠的。

func ParseIconSVG

func ParseIconSVG(svg string) (paths string, strokeWidth float32, err error)

ParseIconSVG 把一整段 SVG 文本转成图标 path 数据。

返回:

  • paths: 归一化后的 path(绝对坐标、缩放到 **24x24 网格**、填充子路径带 `!` 前缀、闭合子路径带 `Z`), 可直接喂给 Painter.Icon, 也可以自己缓存起来复用;
  • strokeWidth: 建议线宽(**24 网格单位**): 取 SVG 里的 stroke-width(按 viewBox 折算), 没有则 2 (lucide 惯例)。想让调用方决定线宽就忽略它。

支持哪些元素/属性见 svgp 包文档(认 svg/g/path, 继承 fill/stroke/stroke-width; 忽略形状元素与 transform 属性)。

func Presence

func Presence(hOwner int, key int32, visible bool, dur time.Duration,
	apply, done func(v float64)) float64

Presence 驱动 (hOwner, key) 上的一个进出场过渡并返回当前进度(0..1)。

  • visible = true: 进度 0→1(进场); false: 1→0(退场)。
  • apply 每帧被调用(UI 线程内、定时器回调里), 用它把进度落到元素透明度 / 窗口位置上;
  • done 到位时回调一次(退场到位后条目自动回收, 不需要自己清状态);
  • 幂等: 目标不变时只返回当前进度, 可以像 Hover 一样在事件里无脑调用;
  • **键在同一 hOwner 内必须唯一**(每个逻辑弹层一个键): 已完成的条目会一直留在表里 (target=1 不回收), 复用键不会重启过渡、也不会再调 apply, 新窗口就停在自己设的初始 状态上。键用 NextPresenceKey() 分配, 不要各组件自己起计数器(见该函数的说明);
  • apply 在调用时**立即以当前进度执行一次**, 之后每帧再由定时器驱动 —— 这样即使键撞上 一条已到位的过渡(过渡不会跑), 目标状态也一定会落地(不会出现全透明窗口);
  • hOwner 既是过渡值的归属, 也是动画期间的重绘目标(元素或窗口句柄), 每次 tick 都会立即重绘它 —— 改动落在**别的窗口**上时也一样(实测: 弹层窗口的透明度要靠 宿主窗口的重绘带动合成, 见 DESIGN 第八节 47)。

dur 传 0 时用 PopupDuration; 过渡被 SetHoverDuration(0) 全局关掉时立即到位。

func PresenceOnce added in v0.8.0

func PresenceOnce(hOwner int, visible bool, dur time.Duration, apply, done func(v float64)) float64

PresenceOnce 是 Presence 的一次性版本: 键由本函数自己分配, 过渡到位后条目立即回收。

适用于"每次交互都开始一段新过渡"的场景(轮播滑动 / 导航菜单换页 / 抽屉拖拽回弹): 这类过渡每次都必须用新键 —— 复用键会命中已到位的条目, Presence 不再重启过渡, 动画只跑第一次。而常规 Presence 的已到位条目按设计常驻表里(供同键复用), 键一多 就只增不减: 内存随交互次数线性增长, 每帧还要遍历这些早已到位的条目。

func Pulse added in v0.3.3

func Pulse(hOwner int, active bool) float64

Pulse 返回 hOwner 上脉冲动画的相位(0..1), 周期 2s(对齐 tailwind 的 animate-pulse)。active=false 时停止并返回 0。与 Spin 共用同一个重绘定时器, 键空间互不影响(同一个元素可以同时旋转 + 脉冲)。

组件自行把相位映射成透明度: tailwind 的关键帧是 1 → 0.5 → 1, 例如

phase := xui.Pulse(hEle, true)
alpha := 0.75 + 0.25*math.Cos(2*math.Pi*phase)

func RegisterWindow

func RegisterWindow(hWindow int)

RegisterWindow 登记一个窗口, 主题切换时自动重绘它。 窗口关闭后应调用 UnregisterWindow, 句柄失效会导致 XWnd_Redraw 静默无效(无害但应避免)。

func SetAnimationScale

func SetAnimationScale(f float64)

SetAnimationScale 按倍率缩放**全部**过渡时长(hover 与弹层进出场都受影响): 1 = 原速, 0 = 立即到位(等于关掉全部过渡), 10 = 慢十倍。

除了"低配机器 / 无障碍关动画", 它主要用于调试: 把 100~150ms 的动画放慢后, 外部截图脚本才能稳定抓到中间帧做像素验收(见 DESIGN 第八节 47)。 必须在 UI 线程调用(与其它 UI 操作同一约束)。

func SetElementsAlpha

func SetElementsAlpha(v float64, hEles ...int)

SetElementsAlpha 按进度 v(0..1)设置一组元素的透明度, 供对话框 / 弹层做整组淡入淡出。

元素级透明度是框架在合成阶段乘上去的, 对自绘内容同样有效(实测), 因此这是"让一组自绘元素一起淡入"的最省事办法; 注意它不影响窗口本身的不透明度。

⚠️ 元素透明度**不会级联到子元素**(XEle_SetAlpha 只作用于元素自身的绘制输出, 子元素是独立元素、有自己的 alpha, 实测: 对话框只设 overlay 时, 遮罩和面板底淡了, 标题/输入框等子元素仍全不透明, 前几帧像"文字浮在弹窗上")。整组进出请用 SetSubtreeAlpha。

func SetHoverDuration

func SetHoverDuration(d time.Duration)

SetHoverDuration 覆盖 hover 过渡时长(默认 150ms)。 传 0 表示关闭过渡: 状态变化立即到位, 不启动定时器(低配机器 / 自动化对比可用)。 必须在 UI 线程调用(与其它 UI 操作同一约束)。

func SetSubtreeAlpha

func SetSubtreeAlpha(v float64, hRoots ...int)

SetSubtreeAlpha 按进度 v(0..1)设置一棵元素子树(含根)的透明度。 元素透明度不级联(见 SetElementsAlpha 的说明), "整组一起淡"必须显式逐个设置。

func SetSubtreeAlphaSkipScrollBars added in v0.3.2

func SetSubtreeAlphaSkipScrollBars(v float64, hRoots ...int)

SetSubtreeAlphaSkipScrollBars 同 SetSubtreeAlpha, 但跳过滚动条及其子树。

滚动条的 alpha 是它自己的"悬停显隐"状态(见 components/scrollbar.go 的 showScrollBarOnHover), 不是内容淡入的一部分。弹窗/对话框的整组淡入若把它 一起设成 1, 滚动条会在打开瞬间亮出来, 直到下一次鼠标移动才被显隐逻辑藏掉 —— 表现为"有时候打开就显示滚动条"。滚动条需要淡出时用普通 SetSubtreeAlpha (例如关闭弹窗), 打开时的淡入用本函数。

func SetTheme

func SetTheme(t *Theme)

SetTheme 切换当前主题并重绘所有已注册窗口。 必须在 UI 线程调用(与任何 UI 操作的线程约束一致)。

func SetWindowAlpha

func SetWindowAlpha(hWindow int, v float64)

SetWindowAlpha 按进度 v(0..1)设置窗口整体透明度。

只对创建时带透明类型的窗口有效(异型 / 阴影 / 圆角); 普通窗口 XWnd_SetTransparentAlpha 无效(实测: 只设置 alpha 不改变窗口外观, 也不会自动加上 WS_EX_LAYERED), 这类窗口只能拿位置做动画。

注意: 透明度在窗口**绘制时**才落地 —— 改完不重绘该窗口, 画面会一直停在 上一次绘制时的透明度上(实测踩过)。用 FadeWindowAlpha / SlideWindowAlpha 就带重绘。

func SlideWindowAlpha

func SlideWindowAlpha(hWindow int, x, y, dx, dy int32, alpha byte) func(v float64)

SlideWindowAlpha 返回一个给 Presence 用的 apply 回调: 按进度把窗口从 (x+dx, y+dy) 滑到 (x, y), 同时把窗口透明度设为 alpha×v(见 SetWindowAlpha 的限制)。

坐标是**物理像素**(XWnd_SetWindowPos 直通 Win32, 与 Tooltip 的定位一致)。

func Spin added in v0.3.0

func Spin(hOwner int, active bool) float64

Spin 返回 hOwner 上持续旋转动画的相位(0..1), active=true 时启动/继续, active=false 时停止并返回 0。周期固定 1s(与 tailwind 的 animate-spin 一致), 由运行时同一个重绘定时器约 60fps 驱动 —— 组件在绘制回调里每帧调用即可, **不要自己起定时器或缓存相位**。

hOwner 是元素时, 动画帧挂到**宿主窗口**上: 同一窗口里的所有 Spin/Pulse 共用 一个定时器, 每帧只重绘动画元素所在的矩形分组(相近/重叠的合并; 逐个元素 XEle_Redraw 会反复触达窗口合成, 更贵, 见 DESIGN 第八节 106); 元素被隐藏 (含祖先页签隐藏/滚出视口/窗口最小化)后自动停表。 hOwner 是窗口时保持旧行为(整窗重绘)。

相位按墙钟时间算(不是逐帧累加), 掉帧时不会走慢; SetAnimationScale(0) 会 冻结旋转(便于截图对比), 恢复后从当前相位继续。

func SpinDetached added in v0.7.0

func SpinDetached(hEle, hOwner int, active bool) float64

SpinDetached 用于**元素与驱动定时器的窗口不在同一个窗口**的旋转动画: 定时器仍挂在 hOwner(宿主窗口)上, 但每帧额外重绘元素所在的小窗。

典型场景是屏幕通知这类独立异型置顶小窗: 异型小窗自己的定时器只有约 10fps (见 DESIGN 47), 无法驱动转圈; 把定时器挂到宿主窗口后, 元素所在的小窗不会 跟着宿主重绘, 所以要由这里补一次。普通窗口内的元素直接用 Spin。

func TextHeight

func TextHeight(text string, hFont int) int32

TextHeight 量测单行文本的行高(逻辑像素)。同 TextWidth 的坑, 必须给足宽度 + NoWrap。

func TextWidth

func TextWidth(text string, hFont int) int32

TextWidth 量测**单行**文本的逻辑像素宽度(与绘制同字体; hFont 用 FontHandle 取)。

坑(探针实测, 见 DESIGN.md 第八节 48): `app.GetTextShowRect` 的 width 参数传 0 **不是"不限宽"**, 而是"按一个字符宽折行"——此时返回的 CX 只是一列宽, CY 变成"行数×行高" (3 个汉字会得到 CX=15 / CY=59)。量宽必须给足 width 并加 NoWrap, 此时 CX 就是真实单行宽度 (14px 字号下汉字恰好 15/字, 与绘制结果一致), **不需要任何倍率校正**。

func UnregisterWindow

func UnregisterWindow(hWindow int)

UnregisterWindow 取消登记窗口: 同时清掉它注册的重绘与底色绑定, 避免窗口销毁后残留失效句柄(主题切换时对失效句柄调 XWnd_* 会弹模态错误框)。

func VStackWeight

func VStackWeight(parent int, weight, space int32) *widget.LayoutEle

VStackWeight 建一个纵向流容器, 宽度按权重等分**横向父布局**的剩余宽度(做等宽列用, 传 1 = 等分), 高度占满父级, 子项间距 space。

注意高度是 `Fill`(父级整高): 列里装的是"高度自撑(Auto)"的卡片时**别用它**, 要自己拼一个 "Weight 宽 + Auto 高"的列 —— 否则卡片内容区里的整宽控件(原生 Edit/ComboBox/分隔线)会按 卡片宽排版、右边缘压到卡片边框上(实测, 见 DESIGN 第八节 59)。它适合列内是"吃剩余高度"的面板。

Types

type Color

type Color uint32

Color 是炫彩界面库使用的 ABGR 颜色(底层 uint32, 由 xc.RGBA 生成)。

const None Color = 0

None 表示"不绘制"。组件解析主题时用它表达透明/缺省, 例如 Ghost 按钮的背景。

func Fade

func Fade(a, b Color, t float64) Color

Fade 按 t(0~1)在 a(静止态)与 b(悬停态)之间插值, 供 hover 过渡使用。

与 Mix 的差别是**在预乘 alpha 空间插值**(与浏览器 CSS transition 的做法一致): a=None(全透明, RGB 记作 0) → b=Muted 这类"淡入", 直接线性插值会先经历"半透明的 黑灰"(先变脏再变亮), 预乘后是干净的透明度渐变。两端素都是不透明时两者等价。

func Hex

func Hex(s string) Color

Hex 解析 Web/CSS 十六进制颜色("#fafafa"、"fafafa"、"#fff"), 透明度默认不透明, 失败时 panic(主题文件是静态数据, 尽早暴露拼写错误)。

func HexE

func HexE(s string) (Color, error)

HexE 是 Hex 的错误返回版本。

func RGBA

func RGBA(r, g, b, a byte) Color

RGBA 按炫彩颜色布局(r 低位)构造颜色。

func (Color) Darken

func (c Color) Darken(amount float64) Color

Darken 返回按比例加暗的颜色, amount 取值 0~1。

func (Color) IsNone

func (c Color) IsNone() bool

IsNone 报告是否为 None(不绘制)。

func (Color) Lighten

func (c Color) Lighten(amount float64) Color

Lighten 返回按比例提亮的颜色, amount 取值 0~1。

func (Color) Mix

func (c Color) Mix(other Color, t float64) Color

Mix 返回与 other 按 t(0~1)插值的颜色, t=0 即原色。 对应 CSS color-mix(in oklch, a, b t%) 的近似(线性 sRGB 插值)。

func (Color) RGBA

func (c Color) RGBA() (r, g, b, a byte)

RGBA 分量, 注意炫彩布局是 r 在最低字节。

func (Color) ScaleAlpha

func (c Color) ScaleAlpha(frac float64) Color

WithAlpha 返回同色但指定透明度的副本。 ScaleAlpha 在现有透明度上再乘一个系数, 对应 CSS 的"半透明色再加 /30"这类写法: 例如暗色令牌 input = 白 15%, `bg-input/30` 的最终 alpha = 15% × 30% = 4.5%。 (用 WithAlpha 会直接覆盖成 30%, 实测会明显偏亮)

func (Color) String

func (c Color) String() string

String 格式化为 "#rrggbb"。

func (Color) WithAlpha

func (c Color) WithAlpha(a byte) Color

type Corners added in v0.3.0

type Corners struct {
	TopLeft     int32
	TopRight    int32
	BottomRight int32
	BottomLeft  int32
}

Corners 是四角独立的圆角半径(逻辑像素), 用于按钮组这类"外角圆、内角方"的壳。

func UniformCorners added in v0.3.0

func UniformCorners(radius int32) Corners

UniformCorners 返回四角相同的圆角。

func (Corners) Inset added in v0.3.0

func (c Corners) Inset(d int32) Corners

Inset 把四角半径同时收缩 d(不小于 0), 供焦点环内芯与描边内缩使用。

type FontSpec

type FontSpec struct {
	Family string // 字体名, 如 "微软雅黑"、"Segoe UI"
	Size   int32  // 字号(逻辑像素; xcgui 字体单位是 pt, FontHandle 内部按 px×0.75 换算)
	Bold   bool
	Italic bool
}

FontSpec 描述主题中的一套字体方案。组件按需从中选取字段。

没有下划线字段: `FontStyle_Underline` 在本框架的 D2D 文本路径下不渲染(见 DESIGN 第八节 110), hover:underline 一律手绘 1px 线(Button 的 link 变体、Accordion 触发区)。

type HoverTracker

type HoverTracker struct {
	// contains filtered or unexported fields
}

HoverTracker 为没有原生 hover 状态反馈的元素(纯 Element 载体的组件) 跟踪鼠标停留状态。基于 AddEvent_MouseStay/MouseLeave 实现。

用法: 组件构造时 tracker := xui.NewHoverTracker(btn.Element), 绘制时读 tracker.Hover()。回调复用(EventBus), 不占 2000 上限。

func NewHoverTracker

func NewHoverTracker(ele *widget.Element) *HoverTracker

NewHoverTracker 开始跟踪 ele 的鼠标进出。

func (*HoverTracker) Hover

func (t *HoverTracker) Hover() bool

Hover 返回当前是否鼠标停留。

type Item

type Item interface {
	LayoutItem_SetWidth(nType xcc.Layout_Size_, nWidth int32) *objectbase.Widget
	LayoutItem_SetHeight(nType xcc.Layout_Size_, nHeight int32) *objectbase.Widget
	LayoutItem_GetMargin(pMargin *xc.RECT) *objectbase.Widget
	LayoutItem_SetMargin(left, top, right, bottom int32) *objectbase.Widget
	LayoutItem_SetMinSize(width, height int32) *objectbase.Widget
	LayoutItem_SetPosition(left, top, right, bottom int32) *objectbase.Widget
	LayoutItem_EnableFloat(bFloat ...bool) *objectbase.Widget
	LayoutItem_EnableWrap(bWrap ...bool) *objectbase.Widget
	LayoutItem_EnableSwap(bEnable ...bool) *objectbase.Widget
}

Item 是"能当布局项用"的元素: 所有 widget 都提升自 objectbase.Widget, 自带 LayoutItem_* 包装。

type Painter

type Painter struct {
	Draw *drawx.Draw

	Rc    xc.RECT // 元素客户区(逻辑坐标)
	Scale float64 // 物理/逻辑像素比(高 DPI 下 >1)
	// contains filtered or unexported fields
}

Painter 封装绘制回调中的常用操作: 圆角壳、焦点环、边框、状态取色。 组件的 onPaint 一般以 BeginPaint 开头, 以它返回的 Painter 操作。

重要: 绘制坐标系是逻辑像素(DPI=96 基准)。XEle_GetClientRect 返回的 是未缩放的逻辑矩形, 框架在渲染时统一做逻辑→物理变换, 因此这里 不要对尺寸做任何 DPI 预缩放(实测 DPI=144 时缩放会导致双重放大, 指示器撑满元素客户区而被上下裁剪)。

func BeginPaint

func BeginPaint(hEle, hDraw int) *Painter

BeginPaint 从元素绘制回调的 hDraw 构造 Painter, 并启用抗锯齿。

func BeginPaintWindow

func BeginPaintWindow(hWindow, hDraw int) *Painter

BeginPaintWindow 从窗口绘制回调的 hDraw 构造 Painter(填窗口客户区)。

func (*Painter) FillRound

func (p *Painter) FillRound(rc *xc.RECT, color Color, radius int32)

FillRound 填充圆角矩形。

func (*Painter) FillRoundCorners added in v0.3.0

func (p *Painter) FillRoundCorners(rc *xc.RECT, color Color, c Corners)

FillRoundCorners 填充四角半径可不同的圆角矩形。

func (*Painter) FocusFrame

func (p *Painter) FocusFrame(ring Color, fill Color, radius int32)

FocusFrame 绘制 shadcn v4 风格的聚焦态(focus-visible:ring-3 ring-ring/50): 先把元素边缘 [0,3] 区域填 50% 透明焦点色, 调用方随后把正常填充画在 内缩 3px 的矩形上——焦点环完全替代边框。

为什么不用"边框 + 描边环"两层: 元素绘制被裁剪在自身客户区内, 外扩式 圆环不可见; 而内侧描边环与边框叠放会在对边产生不均匀的视觉宽度 (环与边框重合的一侧显窄), 半透明环下还会透出边框黑线。 填充式单层结构没有描边居中/裁剪问题, 四边宽度天然一致。

func (*Painter) FocusFrameCorners added in v0.3.0

func (p *Painter) FocusFrameCorners(ring Color, fill Color, c Corners)

FocusFrameCorners 是四角独立的 FocusFrame(按钮组的外角圆/内角方)。

func (*Painter) H

func (p *Painter) H() int32

H 是客户区高度。

func (*Painter) Icon

func (p *Painter) Icon(data string, rc *xc.RECT, color Color, strokeWidth float32, scale ...float32)

Icon 在 rc 内绘制一枚**描边图标**(等比铺满)。

data 两种形态, **自动识别**(跳过前导空白看首字符是不是 `<`):

  • **path 数据**: **24x24 网格**的描边型 path, 例如 icons.IconXxx / icons.Path("eye") / 自己拼的 path。子路径想按**填充**画时用 SubPath.Filled 标注(整段 SVG 会自动判定); 非 24 网格的请先把 坐标缩放过去(整段 SVG 则不用, 见下)。
  • **整段 SVG**: 从图标站复制来的 `<svg viewBox="…"><path d="…"/></svg>`(heroicons / feather / iconfont…)—— 自动提取 `<path>`、按 viewBox **归一到 24 网格**(任意网格)、按 fill/stroke 判定 填充子路径。归一化结果**按 SVG 原文缓存**(最多 512 条), 每帧只多一次首字符判断。

strokeWidth 用 24 网格单位; 传 <= 0 时: SVG 用它自己的 stroke-width(里面也没有则 2), path 数据按 2(lucide 惯例)。想先校验 SVG 或自己缓存归一化结果, 用 ParseIconSVG。

出图优先走 D2D 几何管线(有抗锯齿, 见 d2dicon.go); 只有 GDI 渲染模式(app.New(false))或 D2D 资源建不出来时才回落到折线描边。

scale(可选, 默认 1)是以绘制方块中心为基准的缩放, 用于"按下变小"这类按压反馈 —— 缩放走渲染目标变换矩阵, 不会击穿几何缓存。折线回落路径忽略它(GDI 侧没有变换)。

func (*Painter) IconRotated added in v0.3.0

func (p *Painter) IconRotated(data string, rc *xc.RECT, color Color, strokeWidth float32,
	angle float64, scale ...float32)

IconRotated 是 Icon 的旋转版本: angle 为弧度, 以绘制方块中心为基准顺时针旋转 (屏幕坐标 y 向下, 正角即顺时针), 适合加载 spinner。scale 语义同 Icon。 D2D 与 GDI 折线回落都支持旋转。

func (*Painter) IconRotatedOffset added in v0.3.0

func (p *Painter) IconRotatedOffset(data string, rc *xc.RECT, color Color, strokeWidth float32,
	angle float64, dx, dy int32, scale ...float32)

IconRotatedOffset 在 IconRotated 基础上把图标在**画布物理像素**上再平移 (dx,dy)。 用于高 DPI 下把图标与文字的按下位移对齐到同一物理像素取整(见 DESIGN 第八节 88); GDI 折线回落忽略这两个偏移。

func (*Painter) Line

func (p *Painter) Line(x1, y1, x2, y2 int32, color Color, lineWidth int32)

Line 绘制一条线段。

func (*Painter) RingFrame

func (p *Painter) RingFrame(ring Color, fill Color, radius int32)

RingFrame 与 FocusFrame 同构, 但环带色**按传入值原样**使用(不再统一乘 50%)。 shadcn 的 aria-invalid 环改成 destructive/20(暗色 /40), 与 focus-visible 的 ring-ring/50 不是同一个透明度, 所以需要一个能自定义环带色的入口。

func (*Painter) RingFrameCorners added in v0.3.0

func (p *Painter) RingFrameCorners(ring Color, fill Color, c Corners)

RingFrameCorners 是四角独立的 RingFrame。

func (*Painter) StrokeRound

func (p *Painter) StrokeRound(rc *xc.RECT, color Color, radius int32, lineWidth int32)

StrokeRound 绘制圆角矩形边框。注意线宽沿路径居中, 外扩约 lineWidth/2, 贴边的描边会被元素边界裁掉, 需要完整可见时请内缩。

func (*Painter) StrokeRoundCorners added in v0.3.0

func (p *Painter) StrokeRoundCorners(rc *xc.RECT, color Color, c Corners, lineWidth int32)

StrokeRoundCorners 绘制四角半径可不同的圆角矩形边框(裁剪/线宽语义同 StrokeRound)。

func (*Painter) Text

func (p *Painter) Text(str string, rc *xc.RECT, color Color, align xcc.TextFormatFlag_)

Text 在指定矩形绘制文字, align 为 xcc.TextAlignFlag_* 组合。 颜色为 None 时跳过。

func (*Painter) TextCenter

func (p *Painter) TextCenter(str string, color Color)

TextCenter 在元素中央绘制文字。

func (*Painter) TextF added in v0.3.0

func (p *Painter) TextF(str string, rc *xc.RECTF, color Color, align xcc.TextFormatFlag_)

TextF 是 Text 的浮点矩形版本, 用于高 DPI 下需要亚逻辑像素位移的场合 (例如按钮按压时让文字与图标对齐到同一个物理像素, 见 DESIGN 第八节 88)。

func (*Painter) W

func (p *Painter) W() int32

W, H 返回客户区宽高(逻辑像素)。

type State

type State struct {
	Disabled bool
	Hover    bool
	Pressed  bool
	Focused  bool
}

State 是组件绘制的统一状态输入。

func StateOf

func StateOf(hEle int) State

StateOf 从元素状态标志解析出组件关心的四态。 底层: xc.XEle_GetStateFlags 返回 xcc.CombinedState, 各标志位定义见 xcc/combinedstate.go。

type Theme

type Theme struct {
	Name string // 主题名, 用于调试与主题切换菜单

	// 表面
	Background Color // 窗口底色
	Foreground Color // 默认前景(正文文字)

	// 卡片 / 弹层
	Card              Color
	CardForeground    Color
	Popover           Color // 弹出层(菜单、下拉)底色
	PopoverForeground Color

	// 语义色
	Primary               Color // 主操作色
	PrimaryForeground     Color // 主操作色上的文字
	Secondary             Color // 次要填充
	SecondaryForeground   Color
	Muted                 Color // 弱化填充(输入框底、禁用底)
	MutedForeground       Color // 弱化文字(说明、占位符)
	Accent                Color // 悬停高亮
	AccentForeground      Color
	Destructive           Color // 破坏性操作
	DestructiveForeground Color

	// 边框与焦点环
	Border Color
	Input  Color // 输入控件边框, shadcn 中通常与 Border 相同
	Ring   Color // 焦点环

	// 图表色板(shadcn 的 --chart-1..5)。
	// 组件库本身不画图表, 这两组令牌是给**用户应用**取用的: 主题从 shadcn 机械转译时
	// 一并带过来, 才不会在"换个主题后自己的图表配色不跟着变"。
	// 值与原版一样是灰阶(neutral 预设的 chart 色板无彩度), 顺序 = chart-1..5。
	Chart1 Color
	Chart2 Color
	Chart3 Color
	Chart4 Color
	Chart5 Color

	// 侧边栏(shadcn 的 --sidebar-*): ui.Sidebar 分组/条目/描边/焦点环的取色来源。
	// 亮色 = 比页面略亮的中性面, 暗色 = 比页面略亮一档的面; Border 等语义与主色板平行。
	Sidebar                  Color
	SidebarForeground        Color
	SidebarPrimary           Color
	SidebarPrimaryForeground Color
	SidebarAccent            Color
	SidebarAccentForeground  Color
	SidebarBorder            Color
	SidebarRing              Color

	// 圆角(逻辑像素)。shadcn 用 --radius 派生 sm/md/lg/xl, 这里由助手函数计算。
	Radius int32

	// 字体方案
	FontDefault FontSpec
	FontMono    FontSpec
}

Theme 是一套完整的设计令牌, 字段与 shadcn-ui 的 CSS 变量一一对应。 任何 shadcn 社区主题都可以机械转译成本结构体。

组件绘制时必须通过 Current() 实时取令牌, 禁止把颜色缓存到组件字段, 这是主题系统零成本切换的前提。

func Current

func Current() *Theme

Current 返回当前主题。组件在绘制回调中调用, 无需加锁: 事件回调与 SetTheme 都在 UI 线程, 不会并发竞争。 若在非 UI 线程需要读取, 使用 CurrentSafe。

func CurrentSafe

func CurrentSafe() *Theme

CurrentSafe 是 Current 的加锁版本, 供非 UI 线程读取。

func Dark

func Dark() *Theme

Dark 暗色主题。Border/Input 是半透明白(与 shadcn dark 定义一致), 可自适应叠放的表面。

func Light

func Light() *Theme

Light 亮色主题。

func (*Theme) Disabled

func (t *Theme) Disabled() Color

Disabled 返回禁用态下的文字色(约 50% 透明度的前景色)。

func (*Theme) IsDark

func (t *Theme) IsDark() bool

IsDark 报告主题底色是否偏暗。 组件需要按 shadcn 的 dark:* 规则换色时用它——shadcn 允许对同一个部件在 亮/暗主题下指定不同令牌(如 switch 的滑块), 而主题结构体里没有"亮暗"标记, 只能按底色亮度推断。

func (*Theme) Overlay

func (t *Theme) Overlay() Color

Overlay 返回弹层遮罩色(黑色 55% 透明)。

func (*Theme) Pill

func (t *Theme) Pill(h int32) int32

Pill 返回高度 h 的胶囊圆角(徽章 rounded-4xl)。

func (*Theme) RadiusLg

func (t *Theme) RadiusLg() int32

func (*Theme) RadiusMd

func (t *Theme) RadiusMd() int32

func (*Theme) RadiusSm

func (t *Theme) RadiusSm() int32

RadiusSm/Md/Lg/Xl 按 shadcn 的比例从基准 Radius 派生各级圆角。 圆角比例与 shadcn/tailwind v4 的 radius 派生一致(基准 --radius, nova 预设为 10): sm=0.6x, md=0.8x, lg=1x, xl=1.4x。

func (*Theme) RadiusXl

func (t *Theme) RadiusXl() int32

Directories

Path Synopsis
cmd
xui command
xui CLI: 把组件源码拷贝进用户项目(shadcn 的 copy-not-install 模式)。
xui CLI: 把组件源码拷贝进用户项目(shadcn 的 copy-not-install 模式)。
Package ui 提供 shadcn 风格的 xcgui 自绘组件。
Package ui 提供 shadcn 风格的 xcgui 自绘组件。
xui 组件演示: light/dark 一键换肤 + 全部组件。
xui 组件演示: light/dark 一键换肤 + 全部组件。
Package icons 是 xui 的 lucide 图标资源: 把 lucide-static 的 SVG 预处理成 **一条 path 数据**(24 网格), 直接喂给 xui.Painter.Icon 即可出图。
Package icons 是 xui 的 lucide 图标资源: 把 lucide-static 的 SVG 预处理成 **一条 path 数据**(24 网格), 直接喂给 xui.Painter.Icon 即可出图。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL