theme

package
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: AGPL-3.0 Imports: 20 Imported by: 0

README

ui/theme

全局颜色、字号、字体。默认浅色;组件在每帧读取语义色。完整用法见主题。

// 开窗前或 UI 回调中同步切换。
theme.Apply(theme.Dark())
theme.Apply(theme.Light())

// 后台 goroutine 中排队到帧锁内。
core.Update(func() { theme.Apply(theme.Dark()) })

// 自定义完整调色板,不修改预设。
p := theme.Light()
p.Primary = theme.RGB(0x15803d)
theme.Apply(p)

Palette 包含全部颜色,Light()、Dark()、Current() 返回副本。Apply 替换全部颜色(零值也会应用),同步 Material.Palette,递增 Revision(),请求所有窗口重绘;保留 Material 指针、字体和排版器。Apply 不自己取得帧锁:回调本来已持锁,后台必须使用 core.Update。Current、Revision 和颜色变量的读取也遵守帧锁规则。

Success 表示成功,Warning 表示警告,Info 表示提示;可用作正文或图标颜色。OnColor 用于 Primary/Danger 实心背景上的文字,不保证适合所有语义色背景。

cx.Cache 随 Apply 自动失效;应用自己的渲染缓存需将 theme.Revision() 纳入键。Render 时重新读取颜色;已构造的静态元素、自定义固定颜色和 Markdown 独立配色不会被自动重写。局部主题用 Scope(见下文),不自动跟随系统外观。

直接修改颜色变量不会同步 Material、刷新缓存或请求重绘;运行时请用 Apply。

  • 依赖:Gio 和 ui/internal/loop(仅通知全局重绘)。
  • 被依赖:el、kit、window、markdown。

验证入口:go run ./examples/components -section theme -theme dark。

颜色 浅色默认值 用在哪里
Bg #f5f6f8 窗口背景
Surface #ffffff 卡片、输入框底色
Border #e3e5e8 边框、分隔线
Text / Muted #1f2328 / #6b7280 正文 / 次要文字、占位文字
Primary / PrimaryHover / PrimaryText #2563eb / #1d4ed8 / #1d4ed8 主按钮、焦点边框 / 悬停 / 链接等蓝色文字
Danger / DangerHover / DangerText #dc2626 / #b91c1c / #b91c1c 危险按钮 / 悬停 / 错误文字
Success / Warning / Info #15803d / #a16207 / #0369a1 状态正文、图标
Subtle / SubtleHover #eceef1 / #e2e5e9 次要按钮、悬停底色
OnColor #ffffff 实心主色、危险色背景上的文字
Highlight #dbeafe 选中行、选中项
Scrim 40% 黑 模态浮层后面的遮罩
CodeBg / CodeText #f0f1f3 / #1f2328 代码块
Chart 8 个分类色 图表系列颜色,按顺序使用;浅色和深色各一套,均通过色觉缺陷校验

多主题:Register(name, p) 登记调色板,Named(name) 取副本,Names() 列出全部(内置 light、dark、nord、paper、solarized-dark、high-contrast,以及 aurora,除 light、dark 外都是 themes/ 里的主题文件),应用拿它做主题选择器,选中后 Use(name)(应用并记住名字,CurrentName() 读回)。ParseTheme(json) 读主题文件:base 指定继承 light、dark 或已登记的主题,colors 只写要改的颜色,键是 Palette 字段名(大小写不限),值是 #rgb、#rgba、#rrggbb 或 #rrggbbaa,chart 是最多 8 个颜色的数组,bgGradient、primaryGradient 是 {"from", "to", "angle"} 渐变(内置 aurora 用了它们)。WatchThemes(dir, interval, onChange) 加载目录里的主题文件,并在文件变化时下一帧重新注册,正在用的主题会重新应用。Scope(p) 给窗口的一部分换调色板并返回恢复函数,供 el.Themed 使用;它不重绘,也不改 Revision。

LoadFonts(files...) 加载字体文件(TTF、OTF、TTC)并重绘所有窗口,Web 版必须用它提供中文字体,见在浏览器里运行。

尺寸刻度在 scale.go:间距 SpaceXxs 2 / SpaceXs 4 / SpaceSm 6 / SpaceMd 8 / SpaceLg 12 / SpaceXl 16 / Space2xl 24 / Space3xl 32,圆角 RadiusSm 4 / RadiusMd 6 / RadiusLg 8 / RadiusXl 12 / RadiusFull(药丸和圆),字号 TextXs 11 / TextSm 12 / TextMd 13 / TextControl 14 / TextBody 15 / TextLg 17 / TextXl 20 / TextHeading 22,阴影层级 ElevationSm(提示)/ ElevationMd(菜单、弹层、下拉、通知)/ ElevationLg(对话框、侧滑面板、命令面板),阴影颜色 Shadow 随浅深色切换。MonoFace 是等宽字体优先级。

字号常量 BodySize / SmallSize / HeadingSize 为 15 / 13 / 22 sp;ControlHeight 为 36dp,是所有单行字段(输入框、下拉框、日期时间、搜索框)的高度;Face 是字体优先级(苹方 → 冬青黑体 → 微软雅黑 → Noto Sans CJK → Go),逐字形回退。Material 是底层的 Gio material.Theme,提供字形排版器,自己写 Gio 代码时用它。

Documentation

Overview

Package theme holds the colors, text sizes and fonts every UI module reads. Apply changes the global palette under the UI frame lock.

Index

Constants

View Source
const (
	RadiusSm   = 4  // small marks: links, keycaps, compact chips
	RadiusMd   = 6  // controls: buttons, fields, menu items
	RadiusLg   = 8  // cards, popovers, menus, navigation rows
	RadiusXl   = 12 // dialogs, sheets, chat bubbles
	RadiusFull = 9999
)

Radius steps, in dp. RadiusFull makes a pill or a circle: el clamps a radius to half the shorter side.

View Source
const (
	TextXs      = 11 // axis labels, badges
	TextSm      = 12 // captions, errors, group titles
	TextMd      = 13 // secondary text
	TextControl = 14 // buttons, tabs, toggles
	TextBody    = 15 // body text
	TextLg      = 17 // panel and dialog titles
	TextXl      = 20 // page titles
	TextHeading = 22
)

Text size steps, in sp. BodySize, SmallSize and HeadingSize are TextBody, TextMd and TextHeading.

View Source
const (
	SpaceXxs = 2  // hairline gaps: stacked labels, tight icon pairs
	SpaceXs  = 4  // inside compact controls, between a label and its hint
	SpaceSm  = 6  // between an icon and its text
	SpaceMd  = 8  // between controls in a row, list rows
	SpaceLg  = 12 // control padding, between form fields
	SpaceXl  = 16 // card padding, between groups
	Space2xl = 24 // dialog and page padding, between sections
	Space3xl = 32 // between page regions
)

Spacing steps, in dp, for gaps, padding and margins. Most layouts need only Xs to Xl; off-scale values are for optical adjustments such as centering an icon.

View Source
const (
	BodySize    unit.Sp = 15
	SmallSize   unit.Sp = 13
	HeadingSize unit.Sp = 22

	// ControlHeight is the height of every single-line field: inputs,
	// selects, pickers and search boxes, so fields side by side line up.
	ControlHeight unit.Dp = 36
)

Text sizes.

View Source
const Face font.Typeface = "PingFang SC, Hiragino Sans GB, Microsoft YaHei, Noto Sans CJK SC, Noto Sans SC, Go"

Face lists font families in priority order. Pinning a CJK family avoids tofu from a system fallback font that lacks some simplified Chinese glyphs.

View Source
const MonoFace font.Typeface = "SF Mono, Menlo, Cascadia Mono, Consolas, DejaVu Sans Mono, Go Mono, PingFang SC, Microsoft YaHei, Noto Sans CJK SC"

MonoFace lists monospaced families for code, numbers in columns and keycaps; CJK falls back to Face's fonts.

Variables

View Source
var (
	ElevationSm = Elevation{Offset: 1, Blur: 3}   // cards that lift on hover
	ElevationMd = Elevation{Offset: 4, Blur: 12}  // popovers, menus, dropdowns, toasts
	ElevationLg = Elevation{Offset: 12, Blur: 32} // dialogs, sheets, command palette
)

Elevation steps: menus and popovers float a little, dialogs more.

View Source
var (
	Bg           = RGB(0xf5f6f8) // window background
	Surface      = RGB(0xffffff) // cards and fields
	Border       = RGB(0xe3e5e8) // borders and dividers
	Text         = RGB(0x1f2328) // body text
	Muted        = RGB(0x6b7280) // secondary text, hints, unchecked icons
	Primary      = RGB(0x2563eb) // primary buttons, links, focus, checked icons
	DangerText   = RGB(0xb91c1c) // danger text on surfaces
	PrimaryText  = RGB(0x1d4ed8) // text on selected surfaces
	CodeBg       = RGB(0xf0f1f3)
	CodeText     = RGB(0x1f2328)
	PrimaryHover = RGB(0x1d4ed8)
	DangerHover  = RGB(0xb91c1c)
	SubtleHover  = RGB(0xe2e5e9)
	Success      = RGB(0x15803d)                                   // positive status
	Warning      = RGB(0xa16207)                                   // caution status
	Info         = RGB(0x0369a1)                                   // informational status
	Danger       = RGB(0xdc2626)                                   // danger buttons
	Subtle       = RGB(0xeceef1)                                   // secondary buttons
	OnColor      = RGB(0xffffff)                                   // text on Primary and Danger
	Highlight    = RGB(0xdbeafe)                                   // selected rows and options
	Scrim        = color.NRGBA{A: 0x66}                            // dims the window behind a dialog
	Shadow       = color.NRGBA{R: 0x10, G: 0x18, B: 0x28, A: 0x2e} // tints raised surfaces' shadows
	// Chart is the categorical order for data series; see Palette.Chart.
	Chart = Light().Chart
	// BgGradient and PrimaryGradient are optional; see Palette.
	BgGradient, PrimaryGradient Gradient
)

Colors. Components read them at layout time.

View Source
var Material = newMaterial()

Material is the underlying Gio theme: text shaper and icons.

View Source
var ReducedMotion bool

ReducedMotion is the effective preference. It follows the system by default; SetReducedMotion gives the application an explicit override.

Functions

func Apply

func Apply(p Palette)

Apply synchronously replaces the global palette and redraws every window. Call before opening windows or from a UI callback under the frame lock. Other goroutines must use core.Update(func() { theme.Apply(p) }). Apply never acquires the frame lock itself, so callbacks cannot deadlock on it. Fonts, the text shaper and the Material pointer remain unchanged.

func CurrentName

func CurrentName() string

CurrentName is the theme last chosen with Use; "light" by default. Apply with an unnamed palette does not change it.

func FetchFonts

func FetchFonts(urls ...string) error

FetchFonts is for WebAssembly builds, where it downloads font files with the browser. Elsewhere it returns an error: read the files and LoadFonts.

func FollowSystemMotion

func FollowSystemMotion()

FollowSystemMotion removes the application override and applies the latest system value. Unsupported platforms default to allowing motion.

func LoadFonts

func LoadFonts(files ...[]byte) error

LoadFonts adds font files (TTF, OTF or TTC collections) to the text shaper and redraws every window. Text picks them by family name from Face, so load a family listed there, such as Noto Sans SC. A web build needs this: the browser gives a WebAssembly app no system fonts, and without a CJK font Chinese shows as boxes. Desktop apps can use it to ship a font.

Call it before window.Main or from a callback; from another goroutine wrap it in core.Update, like Apply.

func Names

func Names() []string

Names lists the registered palettes in registration order.

func NewShaper

func NewShaper(extra ...font.FontFace) *text.Shaper

NewShaper builds an independent shaper with extra faces ahead of the fonts loaded through LoadFonts and the Go fallback fonts. System font fallback follows the same platform defaults as Material.Shaper.

func RGB

func RGB(c uint32) color.NRGBA

RGB converts 0xRRGGBB to an opaque color.

func Register

func Register(name string, p Palette)

Register adds a named palette, or replaces one, for Named and Names; an app offers them in its theme picker. Built in: light, dark, nord, paper, solarized-dark, high-contrast and aurora (with gradients).

func Revision

func Revision() uint64

Revision changes whenever Apply replaces the palette. Use it in custom render cache keys. Read under the UI lock, like Current.

func Scope

func Scope(p Palette) (restore func())

Scope sets the palette for a part of the window and returns the function that puts the previous one back. el.Themed uses it around a subtree's Render and paint, so the rest of the window keeps its colors; it does not redraw anything. Call it under the frame lock and always restore.

func SetReducedMotion

func SetReducedMotion(reduce bool)

SetReducedMotion overrides the system preference. Call under the UI frame lock.

func SetSystemReducedMotion

func SetSystemReducedMotion(reduce bool)

SetSystemReducedMotion is the window backend's preference bridge. Native changes are remembered while an application override is active.

func Use

func Use(name string) error

Use applies the registered palette name and remembers it, so CurrentName reports it and a ThemeWatcher reapplies it when its file changes. Call it like Apply: before opening windows or under the frame lock.

Types

type Elevation

type Elevation struct{ Offset, Blur float32 }

Elevation is a soft shadow under a raised surface, in dp: Offset moves it down, Blur is how far it fades out. Its color is Shadow.

type Gradient

type Gradient struct {
	From, To color.NRGBA
	Angle    float32
}

Gradient is a linear blend from From to To. Angle is in degrees: 0 runs left to right, 90 top to bottom. The zero Gradient means none.

func (Gradient) IsZero

func (g Gradient) IsZero() bool

IsZero reports whether g is unset.

type Palette

type Palette struct {
	PrimaryText, DangerText, CodeBg, CodeText      color.NRGBA
	Bg, Surface, Border, Text, Muted               color.NRGBA
	Primary, PrimaryHover, Danger, DangerHover     color.NRGBA
	Success, Warning, Info                         color.NRGBA
	Subtle, SubtleHover, OnColor, Highlight, Scrim color.NRGBA
	// Shadow tints the shadows of raised surfaces (Elevation); dark themes
	// need a stronger one to read against a dark background.
	Shadow color.NRGBA
	// Chart is the categorical order for data series: slot i always means
	// series i. Validated for color-vision deficiency against Surface.
	Chart [8]color.NRGBA
	// BgGradient, when set, paints the window background instead of Bg;
	// PrimaryGradient paints primary buttons, progress bars and the user's
	// chat bubbles instead of Primary. Bg and Primary stay the solid colors
	// for everything else, so set them to a color from the gradient.
	BgGradient, PrimaryGradient Gradient
}

Palette contains every global color token. Start from Light or Dark when customizing: Apply replaces all colors, including zero (transparent) values.

func Current

func Current() Palette

Current returns a copy of the current colors. Read it under the UI lock, like the public color variables, or before opening the first window.

func Dark

func Dark() Palette

Dark returns an independent copy of the dark palette.

func Light

func Light() Palette

Light returns an independent copy of the default light palette.

func Named

func Named(name string) (Palette, bool)

Named returns a copy of a registered palette.

func ParseTheme

func ParseTheme(data []byte) (name string, p Palette, err error)

ParseTheme reads a theme file: a base palette ("light" or "dark", or any registered name) and the colors it changes, by Palette field name in any case, as #rgb, #rgba, #rrggbb or #rrggbbaa. Chart takes up to eight colors; bgGradient and primaryGradient take {"from", "to", "angle"}.

{"name": "Nord", "base": "dark",
 "colors": {"bg": "#2e3440", "surface": "#3b4252", "primary": "#88c0d0",
            "bgGradient": {"from": "#2e3440", "to": "#3b4252", "angle": 90}}}

type ThemeWatcher

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

ThemeWatcher keeps the themes in a directory registered while their files change, e.g. so a designer sees a theme file update the running app.

func WatchThemes

func WatchThemes(dir string, interval time.Duration, onChange func(names []string, err error)) (*ThemeWatcher, error)

WatchThemes registers every *.json theme file in dir now, then checks the directory every interval (default one second) for added, changed or removed files. Changes are registered on the next frame, under the frame lock; if the theme in use (CurrentName) changed, it is applied again. onChange, if set, runs then too with the changed theme names, or with the error of a file that failed to parse; the other files still load.

It polls instead of using file system events, so it needs nothing but the standard library and works the same on every platform.

func (*ThemeWatcher) Stop

func (w *ThemeWatcher) Stop()

Stop ends the watching; registered themes stay.

Jump to

Keyboard shortcuts

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