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
}
|