From 11df6cbda95772e8f86dcef81492daa3aecd7bb3 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 25 Jul 2018 21:24:37 -0700 Subject: [PATCH] Move most properties from Button to parent Widget These properties will be globally useful to all sorts of Widgets and have been moved from the Button up into the Common widget, and its interface extended to configure these: * Padding int32 * Background color * Foreground color * Border size, color and style (default solid; raised; sunken) * Outline size and color The button adjusts its border style from "raised" to "sunken" for MouseDown events and its Background color for MouseOver events. Other widgets such as Labels and Frames will be able to have borders, paddings and outlines too, but they will be off by default. --- render/interface.go | 26 ++++++ render/sdl/sdl.go | 10 --- ui/button.go | 86 +++++-------------- ui/theme/theme.go | 5 +- ui/widget.go | 205 ++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 250 insertions(+), 82 deletions(-) diff --git a/render/interface.go b/render/interface.go index ff31400..72fd530 100644 --- a/render/interface.go +++ b/render/interface.go @@ -63,6 +63,32 @@ func (c Color) String() string { ) } +// Add a relative color value to the color. +func (c Color) Add(r, g, b, a int32) Color { + var ( + R = int32(c.Red) + r + G = int32(c.Green) + g + B = int32(c.Blue) + b + A = int32(c.Alpha) + a + ) + + cap8 := func(v int32) uint8 { + if v > 255 { + v = 255 + } else if v < 0 { + v = 0 + } + return uint8(v) + } + + return Color{ + Red: cap8(R), + Green: cap8(G), + Blue: cap8(B), + Alpha: cap8(A), + } +} + // Point holds an X,Y coordinate value. type Point struct { X int32 diff --git a/render/sdl/sdl.go b/render/sdl/sdl.go index 5338e9e..abfa114 100644 --- a/render/sdl/sdl.go +++ b/render/sdl/sdl.go @@ -131,17 +131,7 @@ func (r *Renderer) Poll() (*events.State, error) { } if eventName != "" { - log.Debug("tick:%d Mouse Button1 %s BEFORE: %+v", - r.ticks, - eventName, - s.Button1, - ) s.Button1.Push(eventName == "DOWN") - log.Debug("tick:%d Mouse Button1 %s AFTER: %+v", - r.ticks, - eventName, - s.Button1, - ) // Return the event immediately. return s, nil diff --git a/ui/button.go b/ui/button.go index b68f288..b19486b 100644 --- a/ui/button.go +++ b/ui/button.go @@ -8,16 +8,7 @@ import ( // Button is a clickable button. type Button struct { BaseWidget - Label Label - Padding int32 - Border int32 - Outline int32 - - // Color options. - Background render.Color - HighlightColor render.Color - ShadowColor render.Color - OutlineColor render.Color + Label Label // Private options. hovering bool @@ -27,30 +18,32 @@ type Button struct { // NewButton creates a new Button. func NewButton(label Label) *Button { w := &Button{ - Label: label, - Padding: 4, // TODO magic number - Border: 2, - Outline: 1, - - // Default theme colors. - Background: theme.ButtonBackgroundColor, - HighlightColor: theme.ButtonHighlightColor, - ShadowColor: theme.ButtonShadowColor, - OutlineColor: theme.ButtonOutlineColor, + Label: label, } + w.SetPadding(4) + w.SetBorderSize(2) + w.SetBorderStyle(BorderRaised) + w.SetOutlineSize(1) + w.SetOutlineColor(theme.ButtonOutlineColor) + w.SetBackground(theme.ButtonBackgroundColor) + w.Handle("MouseOver", func(p render.Point) { w.hovering = true + w.SetBackground(theme.ButtonHoverColor) }) w.Handle("MouseOut", func(p render.Point) { w.hovering = false + w.SetBackground(theme.ButtonBackgroundColor) }) w.Handle("MouseDown", func(p render.Point) { w.clicked = true + w.SetBorderStyle(BorderSunken) }) w.Handle("MouseUp", func(p render.Point) { w.clicked = false + w.SetBorderStyle(BorderRaised) }) return w @@ -67,8 +60,8 @@ func (w *Button) Compute(e render.Engine) { w.Label.Compute(e) size := w.Label.Size() w.Resize(render.Rect{ - W: size.W + (w.Padding * 2) + (w.Border * 2) + (w.Outline * 2), - H: size.H + (w.Padding * 2) + (w.Border * 2) + (w.Outline * 2), + W: size.W + w.BoxThickness(2), + H: size.H + w.BoxThickness(2), }) } @@ -76,55 +69,20 @@ func (w *Button) Compute(e render.Engine) { func (w *Button) Present(e render.Engine) { w.Compute(e) P := w.Point() - S := w.Size() - box := render.Rect{ - X: P.X, - Y: P.Y, - W: S.W, - H: S.H, - } + // Draw the widget's border and everything. + w.DrawBox(e) - // Draw the outline layer as the full size of the widget. - e.DrawBox(w.OutlineColor, render.Rect{ - X: P.X - w.Outline, - Y: P.Y - w.Outline, - W: S.W + (w.Outline * 2), - H: S.H + (w.Outline * 2), - }) - - // Highlight on the top left edge. - color := w.HighlightColor + // Offset further if we are currently sunken. + var clickOffset int32 if w.clicked { - color = w.ShadowColor - } - e.DrawBox(color, box) - box.W = S.W - - // Shadow on the bottom right edge. - box.X += w.Border - box.Y += w.Border - box.W -= w.Border - box.H -= w.Border - color = w.ShadowColor - if w.clicked { - color = w.HighlightColor - } - e.DrawBox(color, box) - - // Background color of the button. - box.W -= w.Border - box.H -= w.Border - if w.hovering { - e.DrawBox(render.Yellow, box) - } else { - e.DrawBox(w.Background, box) + clickOffset++ } // Draw the text label inside. w.Label.MoveTo(render.Point{ - X: P.X + w.Padding + w.Border + w.Outline, - Y: P.Y + w.Padding + w.Border + w.Outline, + X: P.X + w.BoxThickness(1) + clickOffset, + Y: P.Y + w.BoxThickness(1) + clickOffset, }) w.Label.Present(e) } diff --git a/ui/theme/theme.go b/ui/theme/theme.go index c81f415..729a2c4 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -4,8 +4,7 @@ import "git.kirsle.net/apps/doodle/render" // Color schemes. var ( - ButtonBackgroundColor = render.RGBA(250, 250, 250, 255) - ButtonHighlightColor = render.RGBA(128, 128, 128, 255) - ButtonShadowColor = render.RGBA(20, 20, 20, 255) + ButtonBackgroundColor = render.RGBA(200, 200, 200, 255) + ButtonHoverColor = render.RGBA(200, 255, 255, 255) ButtonOutlineColor = render.Black ) diff --git a/ui/widget.go b/ui/widget.go index 947e681..475e80f 100644 --- a/ui/widget.go +++ b/ui/widget.go @@ -1,6 +1,18 @@ package ui -import "git.kirsle.net/apps/doodle/render" +import ( + "git.kirsle.net/apps/doodle/render" +) + +// BorderStyle options for widget.SetBorderStyle() +type BorderStyle string + +// Styles for a widget border. +const ( + BorderSolid BorderStyle = "solid" + BorderRaised = "raised" + BorderSunken = "sunken" +) // Widget is a user interface element. type Widget interface { @@ -13,6 +25,28 @@ type Widget interface { Handle(string, func(render.Point)) Event(string, render.Point) // called internally to trigger an event + // Thickness of the padding + border + outline. + BoxThickness(multiplier int32) int32 + DrawBox(render.Engine) + + // Widget configuration getters. + Padding() int32 // Padding + SetPadding(int32) // + Background() render.Color // Background color + SetBackground(render.Color) // + Foreground() render.Color // Foreground color + SetForeground(render.Color) // + BorderStyle() BorderStyle // Border style: none, raised, sunken + SetBorderStyle(BorderStyle) // + BorderColor() render.Color // Border color (default is Background) + SetBorderColor(render.Color) // + BorderSize() int32 // Border size (default 0) + SetBorderSize(int32) // + OutlineColor() render.Color // Outline color (default Invisible) + SetOutlineColor(render.Color) // + OutlineSize() int32 // Outline size (default 0) + SetOutlineSize(int32) // + // Run any render computations; by the end the widget must know its // Width and Height. For example the Label widget will render itself onto // an SDL Surface and then it will know its bounding box, but not before. @@ -25,10 +59,18 @@ type Widget interface { // BaseWidget holds common functionality for all widgets, such as managing // their widths and heights. type BaseWidget struct { - width int32 - height int32 - point render.Point - handlers map[string][]func(render.Point) + width int32 + height int32 + point render.Point + padding int32 + background render.Color + foreground render.Color + borderStyle BorderStyle + borderColor render.Color + borderSize int32 + outlineColor render.Color + outlineSize int32 + handlers map[string][]func(render.Point) } // Point returns the X,Y position of the widget on the window. @@ -62,6 +104,159 @@ func (w *BaseWidget) Resize(v render.Rect) { w.height = v.H } +// BoxThickness returns the full sum of the padding, border and outline. +// m = multiplier, i.e., 1 or 2 +func (w *BaseWidget) BoxThickness(m int32) int32 { + if m == 0 { + m = 1 + } + return (w.Padding() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m) +} + +// DrawBox draws the border and outline. +func (w *BaseWidget) DrawBox(e render.Engine) { + var ( + P = w.Point() + S = w.Size() + outline = w.OutlineSize() + border = w.BorderSize() + borderColor = w.BorderColor() + highlight = borderColor.Add(20, 20, 20, 0) + shadow = borderColor.Add(-20, -20, -20, 0) + color render.Color + box = render.Rect{ + X: P.X, + Y: P.Y, + W: S.W, + H: S.H, + } + ) + + // Draw the outline layer as the full size of the widget. + e.DrawBox(w.OutlineColor(), render.Rect{ + X: P.X - outline, + Y: P.Y - outline, + W: S.W + (outline * 2), + H: S.H + (outline * 2), + }) + + // Highlight on the top left edge. + if w.BorderStyle() == BorderRaised { + color = highlight + } else if w.BorderStyle() == BorderSunken { + color = shadow + } else { + color = borderColor + } + e.DrawBox(color, box) + box.W = S.W + + // Shadow on the bottom right edge. + box.X += border + box.Y += border + box.W -= border + box.H -= border + if w.BorderStyle() == BorderRaised { + color = shadow + } else if w.BorderStyle() == BorderSunken { + color = highlight + } else { + color = borderColor + } + e.DrawBox(color.Add(-20, -20, -20, 0), box) + + // Background color of the button. + box.W -= border + box.H -= border + // if w.hovering { + // e.DrawBox(render.Yellow, box) + // } else { + e.DrawBox(color, box) +} + +// Padding returns the padding width. +func (w *BaseWidget) Padding() int32 { + return w.padding +} + +// SetPadding sets the padding width. +func (w *BaseWidget) SetPadding(v int32) { + w.padding = v +} + +// Background returns the background color. +func (w *BaseWidget) Background() render.Color { + return w.background +} + +// SetBackground sets the color. +func (w *BaseWidget) SetBackground(c render.Color) { + w.background = c +} + +// Foreground returns the foreground color. +func (w *BaseWidget) Foreground() render.Color { + return w.foreground +} + +// SetForeground sets the color. +func (w *BaseWidget) SetForeground(c render.Color) { + w.foreground = c +} + +// BorderStyle returns the border style. +func (w *BaseWidget) BorderStyle() BorderStyle { + return w.borderStyle +} + +// SetBorderStyle sets the border style. +func (w *BaseWidget) SetBorderStyle(v BorderStyle) { + w.borderStyle = v +} + +// BorderColor returns the border color, or defaults to the background color. +func (w *BaseWidget) BorderColor() render.Color { + if w.borderColor == render.Invisible { + return w.Background() + } + return w.borderColor +} + +// SetBorderColor sets the border color. +func (w *BaseWidget) SetBorderColor(c render.Color) { + w.borderColor = c +} + +// BorderSize returns the border thickness. +func (w *BaseWidget) BorderSize() int32 { + return w.borderSize +} + +// SetBorderSize sets the border thickness. +func (w *BaseWidget) SetBorderSize(v int32) { + w.borderSize = v +} + +// OutlineColor returns the background color. +func (w *BaseWidget) OutlineColor() render.Color { + return w.outlineColor +} + +// SetOutlineColor sets the color. +func (w *BaseWidget) SetOutlineColor(c render.Color) { + w.outlineColor = c +} + +// OutlineSize returns the outline thickness. +func (w *BaseWidget) OutlineSize() int32 { + return w.outlineSize +} + +// SetOutlineSize sets the outline thickness. +func (w *BaseWidget) SetOutlineSize(v int32) { + w.outlineSize = v +} + // Event is called internally by Doodle to trigger an event. func (w *BaseWidget) Event(name string, p render.Point) { if handlers, ok := w.handlers[name]; ok {