主题与字体
浅色与深色主题、覆盖令牌、控件密度、减弱动效和等宽字体。
主题是值
ui.Theme 是一个由语义化颜色令牌和尺寸度量组成的结构体。没有样式表,运行时也不解析任何东西。每个组件都以参数形式接收你给它的主题,所以改变外观就是用另一个值来绘制:
package main
import (
"log"
"github.com/vivaui/viva/app"
"github.com/vivaui/viva/flex"
"github.com/vivaui/viva/ui"
)
// Identities are names in a tree, not numbers: the application declares the
// tree once and every control it paints is a name inside a feature of it.
var (
tree = ui.Tree{}
page = tree.Feature("theming")
toggle = tree.ID(page, "toggle")
)
type model struct {
dark bool
view flex.View
}
// theme is the active theme: a value, so changing appearance is changing which
// value Paint reads.
func (m *model) theme() ui.Theme {
theme := ui.LightTheme()
if m.dark {
theme = ui.DarkTheme()
}
// Tokens are plain fields. Override one to brand the application.
theme.Primary = ui.Color{Red: 0.9, Green: 0.45, Blue: 0.1, Alpha: 1}
theme.PrimaryForeground = ui.Color{Red: 1, Green: 1, Blue: 1, Alpha: 1}
return theme
}
func newConfig(m *model) app.Config {
config := app.Config{Tree: &tree, Title: "Theme", Width: 480, Height: 240}
config.Paint = func(canvas *ui.Canvas, size ui.Size) {
theme := m.theme()
bounds := ui.Rect{Width: size.Width, Height: size.Height}
canvas.Fill(bounds, theme.Background)
m.view.Draw(canvas, theme, bounds, func(f *flex.Frame) {
f.Column().Fill().Pad(24).Gap(12).Items(flex.Start).Children(func() {
if f.Button(toggle, "Toggle theme").Clicked() {
m.dark = !m.dark
}
f.Text("Hello")
})
})
}
return config
}
func main() {
var canvas ui.Canvas
if err := app.RunCanvas(newConfig(&model{}), &canvas); err != nil {
log.Fatal(err)
}
}
ui.LightTheme() 和 ui.DarkTheme() 返回完整的主题。令牌是普通字段:页面用 Background 和 Foreground,表面用 Card 和 CardForeground,菜单和对话框用 Popover,还有带对应前景色的 Primary、Secondary、Accent 和 Muted,以及 Border、Input、用于焦点环的 Ring、Success 和 Danger,外加五种 Chart 颜色。覆盖某个字段就能为应用定制品牌色,示例就是这样改 Primary 的。
用一个函数返回当前主题,像上面的 theme 方法那样,并在 Paint 开头读取它。其他地方都不需要知道当前用的是哪个主题。
跟随系统
Viva 目前没有暴露操作系统的浅色或深色设置,也从不替你选择主题。应用把用户的选择保存在自己的状态里,示例里的切换按钮就是这样做的。运行时会报告的外观偏好只有减弱动效。
密度
Theme.Metrics 携带组件使用的尺寸令牌:控件高度、圆角、字号和内边距。Metrics.WithDensity(ui.DensityCompact)(或 DensityLarge)可以派生出另一种密度的度量,而不改变颜色:
theme := ui.DarkTheme()
theme.Metrics = theme.Metrics.WithDensity(ui.DensityCompact)
组件从度量中读取几何尺寸而不是写死,所以窗口里的每个控件都会跟着变化。
动效
Theme.Transition 是控件默认的状态变化动效;零值会关闭动画,手写的主题就是这样。app.ReduceMotion() 在 macOS 和 Windows 上报告操作系统的减弱动效偏好,其他平台返回 false。Canvas.Animate 已经遵守它,会原样返回目标值,所以通过画布做动画的组件不需要额外处理。
文字
ui.TextStyle 接受 Size、Weight 和 Color。文字用系统字体绘制,所以每个平台显示自己的字体,中日韩文字不需要任何设置就会回退到系统的中日韩字体。Viva 不捆绑字体。
用于代码和终端时,设置 Mono: true,默认使用系统的等宽字体。MonoFace 可以选择别的:ui.MonoClassic 请求传统的终端字体,ui.MonoFamily(name) 选择任何已安装的字体族。app.MonospaceFamilies() 列出它们,供设置页使用;它每次都会读取系统,可能要几十毫秒,所以在页面打开时调用,不要在绘制过程中调用。等宽行里每个 ASCII 字符的前进宽度都是 Canvas.CellSize(size),这正是字符网格需要的。