theme

package
v0.1.9 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 38 Imported by: 0

README

ui/theme

English | 简体中文

Color, size scale, font and system dynamics preferences. For user usage and parameter list, see Topic.

Documentation Responsibilities
theme.go, gofonts.go Default text style, Gio Material theme and pocket font
palette.go Palette copy, Apply / Scope, version number and semantic color
registry.go, themes/ Built-in theme, registry, JSON parsing
watch.go Theme directory polling and hot reloading of the current theme
scale.go Spacing, corner rounded corners, font size and shading scale
fonts.go, fonts_*.go, fetch_*.go Font loading, Android system CJK fonts and browser font download
text_paint.go GlyphPainter reuses vector fragments for caller-shaped single-line text; bounded caches, whole-run fallback for complex glyphs
glyph_atlas.go Opt-in glyph image pages with bounded masks and page storage; prepare before painting and release resources on close
motion.go The system reduces animation and application overlay values, scroll bar preferences

Depends on Gio and ui/internal/loop. Apply retains the Material pointer, font, and typesetter, synchronizes the palette, increments Revision(), and requests a redraw of all windows; it does not acquire the frame lock itself. Scope temporarily switches the color without redrawing or incrementing the version number.

el, kit, window, markdown Read topics. Colors and version numbers are read and written according to threading rule, component caching uses cx.Cache or the theme version is included in the key.

Verification entrance: go run ./examples/components -section theme -theme dark.

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 EmojiFace font.Typeface = "Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, Noto Emoji"

EmojiFace lists platform emoji families for use at the end of a custom typeface list. Naming these explicitly lets common-script emoji reach the system's emoji font instead of an arbitrary missing-glyph fallback. Actual color rendering depends on the font format supported by Gio.

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

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, " + Face

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 LoadFontFilesWhere added in v0.1.6

func LoadFontFilesWhere(keep func(font.Font) bool, paths ...string) error

LoadFontFilesWhere is LoadFontsWhere for font files by path. A file is mapped into memory rather than read where the system allows, and stays mapped for the process lifetime. Upstream typesetting v0.3.5 still copies font tables and eagerly parses outlines; mapping the source does not eliminate those heap allocations.

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 LoadFontsWhere added in v0.1.6

func LoadFontsWhere(keep func(font.Font) bool, files ...[]byte) error

LoadFontsWhere is LoadFonts for the faces keep accepts, and parses only those. The faces may keep referring to files, which must not change. A face parsed holds its tables in memory, megabytes for a CJK one, and a collection such as PingFang.ttc holds two dozen faces of which an app draws one or two: loading all of it costs hundreds of megabytes.

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 SetSystemScrollbarsAutoHide added in v0.0.5

func SetSystemScrollbarsAutoHide(hide bool)

SetSystemScrollbarsAutoHide is the window backend's bridge for the platform's "hide scroll bars at rest" setting; el's ScrollbarSystem mode follows it. Safe from any goroutine.

func SystemScrollbarsAutoHide added in v0.0.5

func SystemScrollbarsAutoHide() bool

SystemScrollbarsAutoHide reports the platform's last known setting.

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 FontSet added in v0.1.6

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

FontSet is fonts parsed, with a text shaper made for them, ready to put to use. Parsing fonts and making a shaper, which reads the system's font index, takes tens of milliseconds: prepare the set on another goroutine while the window opens, and Use it before the first frame.

func PrepareFontFiles added in v0.1.6

func PrepareFontFiles(keep func(font.Font) bool, paths ...string) (*FontSet, error)

PrepareFontFiles parses the faces keep accepts of the font files at paths, mapped as LoadFontFilesWhere maps them, and makes a shaper for them and the Go fonts. It touches no shared state: call it from any goroutine.

func (*FontSet) Use added in v0.1.6

func (s *FontSet) Use()

Use adds the set's fonts, as LoadFonts would, and redraws every window. Call it before window.Main or from a callback; from another goroutine wrap it in core.Update. Its shaper serves when no fonts were loaded before it; otherwise a new one is made with them all.

type GlyphAtlas added in v0.1.6

type GlyphAtlas struct {
	// SubpixelPhases has the same meaning as GlyphPainter.SubpixelPhases.
	// Zero preserves the exact horizontal positions.
	SubpixelPhases int
	// contains filtered or unexported fields
}

GlyphAtlas reuses rasterized vector glyphs in immutable image pages. It is opt-in for single-line text drawn at integral pixel translations, without extra scale or rotation. Complex runs use the normal vector drawing path.

Each frame: BeginFrame, Prepare all runs, Commit, then Paint those runs. Preparing all runs before painting lets each changed page be uploaded once. Cached page pixels are bounded to 8 MiB, masks to 2 MiB and 4096 entries. GPU textures and recent immutable page snapshots add to that memory. Use serially, keep it across frames, and call Release when no longer needed. Do not copy a GlyphAtlas after first use.

func (*GlyphAtlas) BeginFrame added in v0.1.6

func (a *GlyphAtlas) BeginFrame(sh *text.Shaper)

BeginFrame starts preparing text for a frame. Changing the shaper drops all caches so font glyph IDs cannot be confused with those of the previous one.

func (*GlyphAtlas) Commit added in v0.1.6

func (a *GlyphAtlas) Commit()

Commit freezes changed image pages. Prepare must not be called again until the next BeginFrame. Existing ImageOps always keep their pixels immutable.

func (*GlyphAtlas) Paint added in v0.1.6

func (a *GlyphAtlas) Paint(ops *op.Ops, params text.Parameters, gs []text.Glyph, col color.NRGBA)

Paint draws a previously prepared run at the same origin as Shaper.Shape. The caller owns clipping, baseline translation and semantic operations.

func (*GlyphAtlas) Prepare added in v0.1.6

func (a *GlyphAtlas) Prepare(params text.Parameters, gs []text.Glyph, col color.NRGBA)

Prepare caches the vector glyphs and colors required by a run. params must match the parameters used to shape gs. It may replace the shaper's iterator. Cache limits or an unavailable offscreen GPU leave a run on the vector path.

func (*GlyphAtlas) Release added in v0.1.6

func (a *GlyphAtlas) Release()

Release frees the scratch GPU and drops cached masks and image pages. The atlas may be used again by calling BeginFrame.

func (*GlyphAtlas) ReleaseScratch added in v0.1.6

func (a *GlyphAtlas) ReleaseScratch()

ReleaseScratch frees temporary rasterization resources while preserving cached masks and image pages. Call serially after Commit, for example when the UI becomes idle. Future new glyphs recreate the renderer as needed.

func (*GlyphAtlas) Stats added in v0.1.6

func (a *GlyphAtlas) Stats() GlyphAtlasStats

type GlyphAtlasStats added in v0.1.6

type GlyphAtlasStats struct {
	Masks, MaskBytes, Pages, PageBytes int
	RasterDraws, VectorDraws           uint64
	// MaskBatches counts successful offscreen submissions and readbacks.
	MaskBatches uint64
}

GlyphAtlasStats reports retained CPU cache storage and cumulative draws. PageBytes excludes GPU textures and older snapshots referenced by Gio.

type GlyphPainter added in v0.1.6

type GlyphPainter struct {
	// FragmentSize is the maximum number of glyphs per fragment. Zero means two.
	// Values are limited to 1–8; smaller fragments reuse more paths but draw more.
	FragmentSize int
	// SubpixelPhases rounds each glyph's horizontal position inside a
	// fragment to 1/SubpixelPhases of a pixel. Zero keeps exact positions.
	// Text whose advances are not whole pixels, such as a monospace grid at
	// fractional scale, otherwise yields up to 64 phases per glyph: more
	// fragments than the cache holds, so paths and their GPU buffers are
	// rebuilt every frame. Four phases are visually indistinguishable at
	// text sizes. Values are limited to 1–64.
	SubpixelPhases int
	// contains filtered or unexported fields
}

GlyphPainter paints shaped single-line text using reusable vector fragments. Its zero value is ready to use. Like text.Shaper, it must be used serially. Keep it across frames; caches retain at most 128 styles and 4096 fragments. Do not copy a GlyphPainter after its first use. Complex or overlapping glyphs and translucent colors use a whole-run path.

func (*GlyphPainter) Paint added in v0.1.6

func (p *GlyphPainter) Paint(ops *op.Ops, sh *text.Shaper, params text.Parameters, gs []text.Glyph, col color.NRGBA)

Paint draws glyphs at the same origin as Shaper.Shape and Shaper.Bitmaps: X is relative to the first glyph and Y is the caller's baseline, not Glyph.Y. params must describe the font and size used to shape glyphs. The caller owns clipping and semantic operations. Paint may replace the shaper's iterator.

type GlyphRenderer added in v0.1.9

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

GlyphRenderer shares bounded glyph caches across changing custom text runs. Keep one per view and use serially: BeginFrame, Prepare all visible runs, Commit, then Paint. Release it when the view closes. Do not copy after use. Integral vertical origins use GlyphAtlas. Horizontal fractions are baked into glyph masks so cached pixels are never resampled. Other origins use vector drawing. Complex runs retain the atlas/painter fallbacks for overlapping ink, combining marks, bidi and translucent colors. Extra caller-applied scale or rotation requires GlyphPainter directly.

func (*GlyphRenderer) BeginFrame added in v0.1.9

func (r *GlyphRenderer) BeginFrame(sh *text.Shaper)

func (*GlyphRenderer) Commit added in v0.1.9

func (r *GlyphRenderer) Commit()

func (*GlyphRenderer) Paint added in v0.1.9

func (r *GlyphRenderer) Paint(ops *op.Ops, run GlyphRun)

func (*GlyphRenderer) Prepare added in v0.1.9

func (r *GlyphRenderer) Prepare(run GlyphRun)

func (*GlyphRenderer) Release added in v0.1.9

func (r *GlyphRenderer) Release()

func (*GlyphRenderer) ReleaseScratch added in v0.1.9

func (r *GlyphRenderer) ReleaseScratch()

ReleaseScratch drops temporary rasterization resources when a view is idle. It preserves prepared masks and image pages. Call after Commit, serially.

func (*GlyphRenderer) Stats added in v0.1.9

func (r *GlyphRenderer) Stats() GlyphAtlasStats

type GlyphRun added in v0.1.9

type GlyphRun struct {
	Params   text.Parameters
	Glyphs   []text.Glyph
	Color    color.NRGBA
	Position f32.Point
}

GlyphRun describes already-shaped, caller-positioned single-line text. Position is its baseline origin in physical pixels. The caller retains the glyph slice until Paint; shaping, clipping and semantic text remain its job.

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