File: theme.go

package info (click to toggle)
golang-golang-x-exp 0.0~git20230522.2e198f4-1~bpo12%2B1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-backports
  • size: 6,404 kB
  • sloc: ansic: 1,900; objc: 276; sh: 272; asm: 48; makefile: 26
file content (246 lines) | stat: -rw-r--r-- 7,959 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package theme provides widget themes.
package theme

import (
	"image"
	"image/color"

	"golang.org/x/exp/shiny/unit"
	"golang.org/x/image/font"
	"golang.org/x/image/font/inconsolata"
	"golang.org/x/image/math/fixed"
)

// FontFaceOptions allows asking for font face variants, such as style (e.g.
// italic) or weight (e.g. bold).
//
// TODO: include font.Hinting and font.Stretch typed fields?
//
// TODO: include font size? If so, directly as "12pt" or indirectly as an enum
// (Heading1, Heading2, Body, etc)?
type FontFaceOptions struct {
	Style  font.Style
	Weight font.Weight
}

// FontFaceCatalog provides a theme's font faces.
//
// AcquireFontFace returns a font.Face. ReleaseFontFace should be called, with
// the same options, once a widget's measure, layout or paint is done with the
// font.Face returned.
//
// A FontFaceCatalog is safe for use by multiple goroutines simultaneously, but
// in general, a font.Face is not safe for concurrent use, as its methods may
// re-use implementation-specific caches and mask image buffers.
type FontFaceCatalog interface {
	AcquireFontFace(FontFaceOptions) font.Face
	ReleaseFontFace(FontFaceOptions, font.Face)

	// TODO: add a "Metrics(FontFaceOptions) font.Metrics" method?
}

// Color is a theme-dependent color, such as "the foreground color". Combining
// a Color with a Theme results in a color.Color in the sense of the standard
// library's image/color package. It can also result in an *image.Uniform,
// suitable for passing as the src argument to image/draw functions.
type Color interface {
	Color(*Theme) color.Color
	Uniform(*Theme) *image.Uniform
}

// StaticColor adapts a color.Color to a theme Color.
func StaticColor(c color.Color) Color { return staticColor{image.Uniform{c}} }

type staticColor struct {
	u image.Uniform
}

func (s staticColor) Color(*Theme) color.Color      { return s.u.C }
func (s staticColor) Uniform(*Theme) *image.Uniform { return &s.u }

// Palette provides a theme's color palette. The array is indexed by
// PaletteIndex constants such as Accent and Foreground.
//
// The colors are expressed as image.Uniform values so that they can be easily
// passed as the src argument to image/draw functions.
type Palette [PaletteLen]image.Uniform

func (p *Palette) Light() *image.Uniform      { return &p[Light] }
func (p *Palette) Neutral() *image.Uniform    { return &p[Neutral] }
func (p *Palette) Dark() *image.Uniform       { return &p[Dark] }
func (p *Palette) Accent() *image.Uniform     { return &p[Accent] }
func (p *Palette) Foreground() *image.Uniform { return &p[Foreground] }
func (p *Palette) Background() *image.Uniform { return &p[Background] }

// PaletteIndex is both an integer index into a Palette array and a Color.
type PaletteIndex int

func (i PaletteIndex) Color(t *Theme) color.Color      { return t.GetPalette()[i].C }
func (i PaletteIndex) Uniform(t *Theme) *image.Uniform { return &t.GetPalette()[i] }

const (
	// Light, Neutral and Dark are three color tones used to fill in widgets
	// such as buttons, menu bars and panels.
	Light   = PaletteIndex(0)
	Neutral = PaletteIndex(1)
	Dark    = PaletteIndex(2)

	// Accent is the color used to accentuate selections or suggestions.
	Accent = PaletteIndex(3)

	// Foreground is the color used for text, dividers and icons.
	Foreground = PaletteIndex(4)

	// Background is the color used behind large blocks of text. Short,
	// non-editable label text will typically be on the Neutral color.
	Background = PaletteIndex(5)

	PaletteLen = 6
)

// DefaultDPI is the fallback value of a theme's DPI, if the underlying context
// does not provide a DPI value.
const DefaultDPI = 72.0

var (
	// DefaultFontFaceCatalog is a catalog for a basic font face.
	DefaultFontFaceCatalog FontFaceCatalog = defaultFontFaceCatalog{}

	// DefaultPalette is the default theme's palette.
	DefaultPalette = Palette{
		Light:      image.Uniform{C: color.RGBA{0xf5, 0xf5, 0xf5, 0xff}}, // Material Design "Grey 100".
		Neutral:    image.Uniform{C: color.RGBA{0xee, 0xee, 0xee, 0xff}}, // Material Design "Grey 200".
		Dark:       image.Uniform{C: color.RGBA{0xe0, 0xe0, 0xe0, 0xff}}, // Material Design "Grey 300".
		Accent:     image.Uniform{C: color.RGBA{0x21, 0x96, 0xf3, 0xff}}, // Material Design "Blue 500".
		Foreground: image.Uniform{C: color.RGBA{0x00, 0x00, 0x00, 0xff}}, // Material Design "Black".
		Background: image.Uniform{C: color.RGBA{0xff, 0xff, 0xff, 0xff}}, // Material Design "White".
	}

	// Default uses the default DPI, FontFaceCatalog and Palette.
	//
	// The nil-valued pointer is a valid receiver for a Theme's methods.
	Default *Theme
)

// Note that a *basicfont.Face such as inconsolata.Regular8x16 is stateless and
// safe to use concurrently, so defaultFontFaceCatalog.ReleaseFontFace can be a
// no-op.

type defaultFontFaceCatalog struct{}

func (defaultFontFaceCatalog) AcquireFontFace(FontFaceOptions) font.Face {
	return inconsolata.Regular8x16
}

func (defaultFontFaceCatalog) ReleaseFontFace(FontFaceOptions, font.Face) {}

// Theme is used for measuring, laying out and painting widgets. It consists of
// a screen DPI resolution, a set of font faces and colors.
type Theme struct {
	// DPI is the screen resolution, in dots (i.e. pixels) per inch.
	//
	// A zero value means to use the DefaultDPI.
	DPI float64

	// FontFaceCatalog provides a theme's font faces.
	//
	// A zero value means to use the DefaultFontFaceCatalog.
	FontFaceCatalog FontFaceCatalog

	// Palette provides a theme's color palette.
	//
	// A zero value means to use the DefaultPalette.
	Palette *Palette
}

// GetDPI returns the theme's DPI, or the default DPI if the field value is
// zero.
func (t *Theme) GetDPI() float64 {
	if t != nil && t.DPI != 0 {
		return t.DPI
	}
	return DefaultDPI
}

// GetFontFaceCatalog returns the theme's font face catalog, or the default
// catalog if the field value is zero.
func (t *Theme) GetFontFaceCatalog() FontFaceCatalog {
	if t != nil && t.FontFaceCatalog != nil {
		return t.FontFaceCatalog
	}
	return DefaultFontFaceCatalog
}

// GetPalette returns the theme's palette, or the default palette if the field
// value is zero.
func (t *Theme) GetPalette() *Palette {
	if t != nil && t.Palette != nil {
		return t.Palette
	}
	return &DefaultPalette
}

// AcquireFontFace calls the same method on the result of GetFontFaceCatalog.
func (t *Theme) AcquireFontFace(o FontFaceOptions) font.Face {
	return t.GetFontFaceCatalog().AcquireFontFace(o)
}

// ReleaseFontFace calls the same method on the result of GetFontFaceCatalog.
func (t *Theme) ReleaseFontFace(o FontFaceOptions, f font.Face) {
	t.GetFontFaceCatalog().ReleaseFontFace(o, f)
}

// Pixels implements the unit.Converter interface.
func (t *Theme) Pixels(v unit.Value) fixed.Int26_6 {
	c := t.Convert(v, unit.Px)
	return fixed.Int26_6(c.F * 64)
}

// Convert implements the unit.Converter interface.
func (t *Theme) Convert(v unit.Value, to unit.Unit) unit.Value {
	if v.U == to {
		return v
	}
	return unit.Value{
		F: v.F * t.pixelsPer(v.U) / t.pixelsPer(to),
		U: to,
	}
}

// pixelsPer returns the number of pixels in the unit u.
func (t *Theme) pixelsPer(u unit.Unit) float64 {
	switch u {
	case unit.Px:
		return 1
	case unit.Dp:
		return t.GetDPI() / unit.DensityIndependentPixelsPerInch
	case unit.Pt:
		return t.GetDPI() / unit.PointsPerInch
	case unit.Mm:
		return t.GetDPI() / unit.MillimetresPerInch
	case unit.In:
		return t.GetDPI()
	}

	f := t.AcquireFontFace(FontFaceOptions{})
	defer t.ReleaseFontFace(FontFaceOptions{}, f)

	// The 64 is because Height is in 26.6 fixed-point units.
	h := float64(f.Metrics().Height) / 64
	switch u {
	case unit.Em:
		return h
	case unit.Ex:
		return h / 2
	case unit.Ch:
		if advance, ok := f.GlyphAdvance('0'); ok {
			return float64(advance) / 64
		}
		return h / 2
	}
	return 1
}