跳转至

05 · 样式与主题

Style 是什么

ui.Style不可变的外观与盒模型声明。用 free function 起头,再链式方法叠加:

style := ui.Background(ui.TokenAccent).
    TextColor(ui.TokenAccentForeground).
    Radius(8).
    PaddingX(12).PaddingY(6).
    Cursor(ui.CursorPointer)

挂到控件:

ui.Button("ok", ui.Text("确定")).Style(style)
// 或
ui.Box(child).Style(ui.Padding(24))

没有单独的 Builder / Build 步骤;每次方法返回新的 Style 值。

级联顺序(固定)

组件 defaults
  → 继承文本(父级 Text 声明)
  → variant
  → size
  → StyleScope 祖先
  → 实例 Style
  → ExpandTokens → Cascade(When) → ResolveColors → Transitions

后写的同名属性在同一层内覆盖先写的When 在运行时按状态叠加。

When:状态样式

primary := ui.Background(ui.TokenAccent).
    TextColor(ui.TokenAccentForeground).
    Radius(8).
    Cursor(ui.CursorPointer).
    When(ui.Hovered, ui.Background(ui.TokenAccentHover)).
    When(ui.Pressed, ui.Background(ui.TokenAccentPressed).Scale(0.96, 0.96)).
    When(ui.Disabled, ui.Opacity(0.5))

常见条件:HoveredPressedFocusVisibleDisabledCheckedSelectedInvalidLoading 等。

Model 布尔值 接进同一条路径:

ui.When(ui.If(model.Highlighted), ui.Background(ui.TokenAccent))

StyleScope:子树默认

ui.StyleScope(
    ui.FontSize(14).TextColor(ui.TokenForeground),
    ui.Column(
        ui.Text("继承字号与颜色"),
        ui.Button("save", ui.Text("保存")).Style(primary),
    ).Gap(8),
)

Scope 向下传递;实例 Style 仍优先。Scope 不会改全局 Theme。

Part:复合控件零件

根 Style 作用在外层盒子;内部用 Part

barStyle := ui.Background(ui.RGBA(0x111827cc)).
    Part(ui.PartTrack, ui.Height(6).Background(ui.TokenSurfaceRaised)).
    Part(ui.PartFill, ui.Background(ui.TokenAccent)).
    Part(ui.PartLabel, ui.TextColor(ui.TokenMutedForeground))

ui.ProgressBar("upload", 42).Label("上传").Style(barStyle)

常见 Part:PartContentPartLabelPartIconPartTrackPartFillPartThumbPartPanelPartItemPartBackdropPartPlaceholderPartPrefixPartSuffix 等。

字段类控件(Select、Input 组)常用 PartContent 表示字段表面。

颜色与 Token

优先用主题 Token,便于明暗主题切换:

ui.Background(ui.TokenSurface)
ui.TextColor(ui.TokenForeground)
ui.Background(ui.TokenAccent)

字面色:

ui.RGB(0x0078d4)           // 0xRRGGBB
ui.RGBA(0x00000030)        // 0xRRGGBBAA
ui.WithAlpha(ui.TokenFocus, 0.5)

渐变与自定义绘制

自定义组件需要绘制渐变时,可以先用主题解析画刷,再交给公共绘制函数:

brush, ok := ui.ResolveBrush(ctx, ui.LinearGradient(
    ui.ColorStop(0, ui.TokenAccent),
    ui.ColorStop(1, ui.TokenDanger),
))
if ok {
    ui.DrawBrush(gtx, image.Rect(0, 0, 160, 32), 6, brush)
}

ResolveColor 用于取得单个主题解析色;DrawBrushRRect 用于已有 clip.RRect 的形状。组件不应在每个绘制路径中重新解析 Token,也不要把 渐变绘制扩展成浮层系统。

主题

启动时

ui.Run(ui.NewProgram(Model{}, Update, View),
    ui.WithTheme(ui.DarkTheme()), // 或 DefaultTheme()
    // 或局部改:
    ui.CustomizeTheme(func(theme *ui.Theme) {
        theme.Palette.Accent = color.NRGBA{R: 0x00, G: 0x78, B: 0xd4, A: 0xff}
        theme.Components.Button.Radius = 8
    }),
    ui.Locale(ui.LanguageChinese),
)

运行时(多窗口 Application)

application.SetTheme("main", ui.DarkTheme())
application.SetLanguage("main", ui.LanguageChinese)

必须在目标窗口事件循环路径上生效(见 examples/multi_windows)。

字体

默认主题使用 sans-serifmonospace 字体族,并允许 Gio 使用系统字体 回退。这样不需要额外资源就能显示不同语言;但不同系统的字形可能略有差异。 系统字体回退会建立完整的系统字体索引,启动和内存开销都可能明显增加;只需要 固定字体时,建议关闭它并提供完整的字体回退集合。

需要固定字体时,可以把字体文件随程序打包。ParseFontCollection 支持 TTF、OTF 和 TTC:

package main

import (
    _ "embed"

    "github.com/qianniancn/flowui/ui"
)

//go:embed fonts/Inter-Regular.ttf
var interRegular []byte

func theme() ui.Theme {
    faces, err := ui.ParseFontCollection(interRegular)
    if err != nil {
        panic(err)
    }
    theme := ui.DefaultTheme()
    theme.Typography.Typeface = "Inter, sans-serif"
    theme.Fonts.Collection = faces
    theme.Fonts.SystemFonts = false
    return theme
}

Theme.Fonts.Collection 应在应用启动前准备好,多个窗口可以共享解析后的字体 数据;每个窗口仍会创建自己的 Shaper。关闭系统字体后,请确保集合包含应用需要 的字符范围,必要时将多个字体集合并到同一个 Collection

完整示例:

go run ./examples/fonts

如果需要检查绘制和内存开销,可以运行 profiling 示例直接生成性能文件, 不需要启动 HTTP 服务:

go run ./examples/profiling -memprofile="profiling.heap.pprof" -cpuprofile="profiling.cpu.pprof" -duration=30
go tool pprof -top profiling.heap.pprof
go tool pprof -top profiling.cpu.pprof

示例会在指定时长后写出文件,窗口可以在这段时间内正常调整和操作。 如果要专门分析字体示例,请在 examples/fonts 中使用系统级进程分析器, 或将相同的字体配置复制到 profiling 示例中。

几何常用项

ui.Width(200).Height(40)
ui.MinWidth(100).MaxWidth(400)
ui.Padding(12)           // 或 PaddingX / PaddingY
ui.Margin(8)
ui.Radius(8)             // 或四角分别设置(API 以 go doc 为准)
ui.BorderWidth(1).BorderColor(ui.TokenBorder)
ui.BorderBottomWidth(1).BorderBottomColor(ui.TokenBorder)
ui.BoxShadow(0, 6, 18, 0, ui.RGBA(0x00000030))
ui.Opacity(0.9)
ui.Cursor(ui.CursorPointer)
ui.Overflow(ui.StyleOverflowHidden) // 裁剪溢出

Transition(声明式属性动画)

ui.Background(ui.TokenSurface).
    Transition(ui.PropBackgroundColor, 120*time.Millisecond).
    When(ui.Hovered, ui.Background(ui.TokenSurfaceRaised))

需要稳定 Key;详见 12-动画

原则小结

  1. 外观与盒模型 → Style
  2. 排列策略 → 布局容器
  3. 状态外观 → When,不要为 hover 另建一棵 UI 树
  4. 主题色 → Token,少写死 RGB

下一步