1// Copyright 2016 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package theme provides widget themes. 6package theme 7 8import ( 9 "image" 10 "image/color" 11 12 "golang.org/x/exp/shiny/unit" 13 "golang.org/x/image/font" 14 "golang.org/x/image/font/inconsolata" 15 "golang.org/x/image/math/fixed" 16) 17 18// FontFaceOptions allows asking for font face variants, such as style (e.g. 19// italic) or weight (e.g. bold). 20// 21// TODO: include font.Hinting and font.Stretch typed fields? 22// 23// TODO: include font size? If so, directly as "12pt" or indirectly as an enum 24// (Heading1, Heading2, Body, etc)? 25type FontFaceOptions struct { 26 Style font.Style 27 Weight font.Weight 28} 29 30// FontFaceCatalog provides a theme's font faces. 31// 32// AcquireFontFace returns a font.Face. ReleaseFontFace should be called, with 33// the same options, once a widget's measure, layout or paint is done with the 34// font.Face returned. 35// 36// A FontFaceCatalog is safe for use by multiple goroutines simultaneously, but 37// in general, a font.Face is not safe for concurrent use, as its methods may 38// re-use implementation-specific caches and mask image buffers. 39type FontFaceCatalog interface { 40 AcquireFontFace(FontFaceOptions) font.Face 41 ReleaseFontFace(FontFaceOptions, font.Face) 42 43 // TODO: add a "Metrics(FontFaceOptions) font.Metrics" method? 44} 45 46// Color is a theme-dependent color, such as "the foreground color". Combining 47// a Color with a Theme results in a color.Color in the sense of the standard 48// library's image/color package. It can also result in an *image.Uniform, 49// suitable for passing as the src argument to image/draw functions. 50type Color interface { 51 Color(*Theme) color.Color 52 Uniform(*Theme) *image.Uniform 53} 54 55// StaticColor adapts a color.Color to a theme Color. 56func StaticColor(c color.Color) Color { return staticColor{image.Uniform{c}} } 57 58type staticColor struct { 59 u image.Uniform 60} 61 62func (s staticColor) Color(*Theme) color.Color { return s.u.C } 63func (s staticColor) Uniform(*Theme) *image.Uniform { return &s.u } 64 65// Palette provides a theme's color palette. The array is indexed by 66// PaletteIndex constants such as Accent and Foreground. 67// 68// The colors are expressed as image.Uniform values so that they can be easily 69// passed as the src argument to image/draw functions. 70type Palette [PaletteLen]image.Uniform 71 72func (p *Palette) Light() *image.Uniform { return &p[Light] } 73func (p *Palette) Neutral() *image.Uniform { return &p[Neutral] } 74func (p *Palette) Dark() *image.Uniform { return &p[Dark] } 75func (p *Palette) Accent() *image.Uniform { return &p[Accent] } 76func (p *Palette) Foreground() *image.Uniform { return &p[Foreground] } 77func (p *Palette) Background() *image.Uniform { return &p[Background] } 78 79// PaletteIndex is both an integer index into a Palette array and a Color. 80type PaletteIndex int 81 82func (i PaletteIndex) Color(t *Theme) color.Color { return t.GetPalette()[i].C } 83func (i PaletteIndex) Uniform(t *Theme) *image.Uniform { return &t.GetPalette()[i] } 84 85const ( 86 // Light, Neutral and Dark are three color tones used to fill in widgets 87 // such as buttons, menu bars and panels. 88 Light = PaletteIndex(0) 89 Neutral = PaletteIndex(1) 90 Dark = PaletteIndex(2) 91 92 // Accent is the color used to accentuate selections or suggestions. 93 Accent = PaletteIndex(3) 94 95 // Foreground is the color used for text, dividers and icons. 96 Foreground = PaletteIndex(4) 97 98 // Background is the color used behind large blocks of text. Short, 99 // non-editable label text will typically be on the Neutral color. 100 Background = PaletteIndex(5) 101 102 PaletteLen = 6 103) 104 105// DefaultDPI is the fallback value of a theme's DPI, if the underlying context 106// does not provide a DPI value. 107const DefaultDPI = 72.0 108 109var ( 110 // DefaultFontFaceCatalog is a catalog for a basic font face. 111 DefaultFontFaceCatalog FontFaceCatalog = defaultFontFaceCatalog{} 112 113 // DefaultPalette is the default theme's palette. 114 DefaultPalette = Palette{ 115 Light: image.Uniform{C: color.RGBA{0xf5, 0xf5, 0xf5, 0xff}}, // Material Design "Grey 100". 116 Neutral: image.Uniform{C: color.RGBA{0xee, 0xee, 0xee, 0xff}}, // Material Design "Grey 200". 117 Dark: image.Uniform{C: color.RGBA{0xe0, 0xe0, 0xe0, 0xff}}, // Material Design "Grey 300". 118 Accent: image.Uniform{C: color.RGBA{0x21, 0x96, 0xf3, 0xff}}, // Material Design "Blue 500". 119 Foreground: image.Uniform{C: color.RGBA{0x00, 0x00, 0x00, 0xff}}, // Material Design "Black". 120 Background: image.Uniform{C: color.RGBA{0xff, 0xff, 0xff, 0xff}}, // Material Design "White". 121 } 122 123 // Default uses the default DPI, FontFaceCatalog and Palette. 124 // 125 // The nil-valued pointer is a valid receiver for a Theme's methods. 126 Default *Theme 127) 128 129// Note that a *basicfont.Face such as inconsolata.Regular8x16 is stateless and 130// safe to use concurrently, so defaultFontFaceCatalog.ReleaseFontFace can be a 131// no-op. 132 133type defaultFontFaceCatalog struct{} 134 135func (defaultFontFaceCatalog) AcquireFontFace(FontFaceOptions) font.Face { 136 return inconsolata.Regular8x16 137} 138 139func (defaultFontFaceCatalog) ReleaseFontFace(FontFaceOptions, font.Face) {} 140 141// Theme is used for measuring, laying out and painting widgets. It consists of 142// a screen DPI resolution, a set of font faces and colors. 143type Theme struct { 144 // DPI is the screen resolution, in dots (i.e. pixels) per inch. 145 // 146 // A zero value means to use the DefaultDPI. 147 DPI float64 148 149 // FontFaceCatalog provides a theme's font faces. 150 // 151 // A zero value means to use the DefaultFontFaceCatalog. 152 FontFaceCatalog FontFaceCatalog 153 154 // Palette provides a theme's color palette. 155 // 156 // A zero value means to use the DefaultPalette. 157 Palette *Palette 158} 159 160// GetDPI returns the theme's DPI, or the default DPI if the field value is 161// zero. 162func (t *Theme) GetDPI() float64 { 163 if t != nil && t.DPI != 0 { 164 return t.DPI 165 } 166 return DefaultDPI 167} 168 169// GetFontFaceCatalog returns the theme's font face catalog, or the default 170// catalog if the field value is zero. 171func (t *Theme) GetFontFaceCatalog() FontFaceCatalog { 172 if t != nil && t.FontFaceCatalog != nil { 173 return t.FontFaceCatalog 174 } 175 return DefaultFontFaceCatalog 176} 177 178// GetPalette returns the theme's palette, or the default palette if the field 179// value is zero. 180func (t *Theme) GetPalette() *Palette { 181 if t != nil && t.Palette != nil { 182 return t.Palette 183 } 184 return &DefaultPalette 185} 186 187// AcquireFontFace calls the same method on the result of GetFontFaceCatalog. 188func (t *Theme) AcquireFontFace(o FontFaceOptions) font.Face { 189 return t.GetFontFaceCatalog().AcquireFontFace(o) 190} 191 192// ReleaseFontFace calls the same method on the result of GetFontFaceCatalog. 193func (t *Theme) ReleaseFontFace(o FontFaceOptions, f font.Face) { 194 t.GetFontFaceCatalog().ReleaseFontFace(o, f) 195} 196 197// Pixels implements the unit.Converter interface. 198func (t *Theme) Pixels(v unit.Value) fixed.Int26_6 { 199 c := t.Convert(v, unit.Px) 200 return fixed.Int26_6(c.F * 64) 201} 202 203// Convert implements the unit.Converter interface. 204func (t *Theme) Convert(v unit.Value, to unit.Unit) unit.Value { 205 if v.U == to { 206 return v 207 } 208 return unit.Value{ 209 F: v.F * t.pixelsPer(v.U) / t.pixelsPer(to), 210 U: to, 211 } 212} 213 214// pixelsPer returns the number of pixels in the unit u. 215func (t *Theme) pixelsPer(u unit.Unit) float64 { 216 switch u { 217 case unit.Px: 218 return 1 219 case unit.Dp: 220 return t.GetDPI() / unit.DensityIndependentPixelsPerInch 221 case unit.Pt: 222 return t.GetDPI() / unit.PointsPerInch 223 case unit.Mm: 224 return t.GetDPI() / unit.MillimetresPerInch 225 case unit.In: 226 return t.GetDPI() 227 } 228 229 f := t.AcquireFontFace(FontFaceOptions{}) 230 defer t.ReleaseFontFace(FontFaceOptions{}, f) 231 232 // The 64 is because Height is in 26.6 fixed-point units. 233 h := float64(f.Metrics().Height) / 64 234 switch u { 235 case unit.Em: 236 return h 237 case unit.Ex: 238 return h / 2 239 case unit.Ch: 240 if advance, ok := f.GlyphAdvance('0'); ok { 241 return float64(advance) / 64 242 } 243 return h / 2 244 } 245 return 1 246} 247