shiny/widget/theme: add a Color type.

Change-Id: I333ab9ea0f7b4a488c09bc47398d049810d71082
Reviewed-on: https://go-review.googlesource.com/25162
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/example/basicgl/main.go b/shiny/example/basicgl/main.go
index 0433dca..3542e37 100644
--- a/shiny/example/basicgl/main.go
+++ b/shiny/example/basicgl/main.go
@@ -25,12 +25,13 @@
 	"golang.org/x/exp/shiny/widget"
 	"golang.org/x/exp/shiny/widget/flex"
 	"golang.org/x/exp/shiny/widget/glwidget"
+	"golang.org/x/exp/shiny/widget/theme"
 	"golang.org/x/image/colornames"
 	"golang.org/x/mobile/gl"
 )
 
 func colorPatch(c color.Color, w, h unit.Value) *widget.Sizer {
-	return widget.NewSizer(w, h, widget.NewUniform(c, nil))
+	return widget.NewSizer(w, h, widget.NewUniform(theme.StaticColor(c), nil))
 }
 
 func main() {
diff --git a/shiny/example/layout/main.go b/shiny/example/layout/main.go
index 5c78f43..a04cd83 100644
--- a/shiny/example/layout/main.go
+++ b/shiny/example/layout/main.go
@@ -34,7 +34,7 @@
 var px = unit.Pixels
 
 func colorPatch(c color.Color, w, h unit.Value) *widget.Sizer {
-	return widget.NewSizer(w, h, widget.NewUniform(c, nil))
+	return widget.NewSizer(w, h, widget.NewUniform(theme.StaticColor(c), nil))
 }
 
 func main() {
@@ -76,7 +76,7 @@
 
 	// Make the RGBA image.
 	rgba := image.NewRGBA(image.Rect(0, 0, 640, 480))
-	draw.Draw(rgba, rgba.Bounds(), t.GetPalette().Neutral, image.Point{}, draw.Src)
+	draw.Draw(rgba, rgba.Bounds(), t.GetPalette().Neutral(), image.Point{}, draw.Src)
 
 	// Measure, layout and paint.
 	vf.Measure(t)
diff --git a/shiny/widget/flex/flex_test.go b/shiny/widget/flex/flex_test.go
index 85cc29b..247c7a7 100644
--- a/shiny/widget/flex/flex_test.go
+++ b/shiny/widget/flex/flex_test.go
@@ -14,6 +14,7 @@
 	"golang.org/x/exp/shiny/unit"
 	"golang.org/x/exp/shiny/widget"
 	"golang.org/x/exp/shiny/widget/node"
+	"golang.org/x/exp/shiny/widget/theme"
 )
 
 type layoutTest struct {
@@ -382,7 +383,8 @@
 	for testNum, test := range layoutTests {
 		var children []node.Node
 		for i, sz := range test.measured {
-			n := widget.NewSizer(unit.Pixels(sz[0]), unit.Pixels(sz[1]), widget.NewUniform(colors[i], nil))
+			u := widget.NewUniform(theme.StaticColor(colors[i]), nil)
+			n := widget.NewSizer(unit.Pixels(sz[0]), unit.Pixels(sz[1]), u)
 			if test.layoutData != nil {
 				n.LayoutData = test.layoutData[i]
 			}
diff --git a/shiny/widget/label.go b/shiny/widget/label.go
index d3a24d1..858ba15 100644
--- a/shiny/widget/label.go
+++ b/shiny/widget/label.go
@@ -16,7 +16,8 @@
 // Label is a leaf widget that holds a text label.
 type Label struct {
 	node.LeafEmbed
-	Text string
+	Text       string
+	ThemeColor theme.Color
 }
 
 // NewLabel returns a new Label widget.
@@ -51,9 +52,14 @@
 	m := face.Metrics()
 	ascent := m.Ascent.Ceil()
 
+	tc := w.ThemeColor
+	if tc == nil {
+		tc = theme.Foreground
+	}
+
 	d := font.Drawer{
 		Dst:  dst,
-		Src:  t.GetPalette().Foreground,
+		Src:  tc.Uniform(t),
 		Face: face,
 		Dot: fixed.Point26_6{
 			X: fixed.I(origin.X + w.Rect.Min.X),
diff --git a/shiny/widget/text.go b/shiny/widget/text.go
index 8aa30cd..e5b386e 100644
--- a/shiny/widget/text.go
+++ b/shiny/widget/text.go
@@ -80,7 +80,7 @@
 
 	padding := t.Pixels(unit.Ems(0.5)).Ceil()
 
-	draw.Draw(dst, dst.Bounds(), t.GetPalette().Background, image.Point{}, draw.Src)
+	draw.Draw(dst, dst.Bounds(), t.GetPalette().Background(), image.Point{}, draw.Src)
 
 	minDotY := fixed.I(dst.Bounds().Min.Y - descent)
 	maxDotY := fixed.I(dst.Bounds().Max.Y + ascent)
@@ -88,7 +88,7 @@
 	x0 := fixed.I(origin.X + w.Rect.Min.X + padding)
 	d := font.Drawer{
 		Dst:  dst,
-		Src:  t.GetPalette().Foreground,
+		Src:  t.GetPalette().Foreground(),
 		Face: face,
 		Dot: fixed.Point26_6{
 			X: x0,
diff --git a/shiny/widget/theme/theme.go b/shiny/widget/theme/theme.go
index 3483a96..073bace 100644
--- a/shiny/widget/theme/theme.go
+++ b/shiny/widget/theme/theme.go
@@ -43,27 +43,64 @@
 	// TODO: add a "Metrics(FontFaceOptions) font.Metrics" method?
 }
 
-// Palette provides a theme's color palette.
+// 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
+// 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 struct {
+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   *image.Uniform
-	Neutral *image.Uniform
-	Dark    *image.Uniform
+	Light   = PaletteIndex(0)
+	Neutral = PaletteIndex(1)
+	Dark    = PaletteIndex(2)
 
 	// Accent is the color used to accentuate selections or suggestions.
-	Accent *image.Uniform
+	Accent = PaletteIndex(3)
 
 	// Foreground is the color used for text, dividers and icons.
-	Foreground *image.Uniform
+	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 *image.Uniform
-}
+	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.
@@ -75,12 +112,12 @@
 
 	// 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".
+		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.
diff --git a/shiny/widget/uniform.go b/shiny/widget/uniform.go
index 5597cde..175aad9 100644
--- a/shiny/widget/uniform.go
+++ b/shiny/widget/uniform.go
@@ -6,7 +6,6 @@
 
 import (
 	"image"
-	"image/color"
 	"image/draw"
 
 	"golang.org/x/exp/shiny/widget/node"
@@ -17,13 +16,13 @@
 // image.Uniform.
 type Uniform struct {
 	node.ShellEmbed
-	Uniform image.Uniform
+	ThemeColor theme.Color
 }
 
 // NewUniform returns a new Uniform widget of the given color.
-func NewUniform(c color.Color, inner node.Node) *Uniform {
+func NewUniform(c theme.Color, inner node.Node) *Uniform {
 	w := &Uniform{
-		Uniform: image.Uniform{c},
+		ThemeColor: c,
 	}
 	w.Wrapper = w
 	if inner != nil {
@@ -34,9 +33,9 @@
 
 func (w *Uniform) Paint(t *theme.Theme, dst *image.RGBA, origin image.Point) {
 	w.Marks.UnmarkNeedsPaint()
-	if w.Uniform.C != nil {
+	if w.ThemeColor != nil {
 		// TODO: should draw.Src be draw.Over?
-		draw.Draw(dst, w.Rect.Add(origin), &w.Uniform, image.Point{}, draw.Src)
+		draw.Draw(dst, w.Rect.Add(origin), w.ThemeColor.Uniform(t), image.Point{}, draw.Src)
 	}
 	if c := w.FirstChild; c != nil {
 		c.Wrapper.Paint(t, dst, origin.Add(w.Rect.Min))