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.
This commit is contained in:
Noah 2018-07-25 21:24:37 -07:00
parent 602273aa16
commit 11df6cbda9
5 changed files with 250 additions and 82 deletions

View File

@ -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. // Point holds an X,Y coordinate value.
type Point struct { type Point struct {
X int32 X int32

View File

@ -131,17 +131,7 @@ func (r *Renderer) Poll() (*events.State, error) {
} }
if eventName != "" { if eventName != "" {
log.Debug("tick:%d Mouse Button1 %s BEFORE: %+v",
r.ticks,
eventName,
s.Button1,
)
s.Button1.Push(eventName == "DOWN") s.Button1.Push(eventName == "DOWN")
log.Debug("tick:%d Mouse Button1 %s AFTER: %+v",
r.ticks,
eventName,
s.Button1,
)
// Return the event immediately. // Return the event immediately.
return s, nil return s, nil

View File

@ -8,16 +8,7 @@ import (
// Button is a clickable button. // Button is a clickable button.
type Button struct { type Button struct {
BaseWidget BaseWidget
Label Label Label Label
Padding int32
Border int32
Outline int32
// Color options.
Background render.Color
HighlightColor render.Color
ShadowColor render.Color
OutlineColor render.Color
// Private options. // Private options.
hovering bool hovering bool
@ -27,30 +18,32 @@ type Button struct {
// NewButton creates a new Button. // NewButton creates a new Button.
func NewButton(label Label) *Button { func NewButton(label Label) *Button {
w := &Button{ w := &Button{
Label: label, 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,
} }
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.Handle("MouseOver", func(p render.Point) {
w.hovering = true w.hovering = true
w.SetBackground(theme.ButtonHoverColor)
}) })
w.Handle("MouseOut", func(p render.Point) { w.Handle("MouseOut", func(p render.Point) {
w.hovering = false w.hovering = false
w.SetBackground(theme.ButtonBackgroundColor)
}) })
w.Handle("MouseDown", func(p render.Point) { w.Handle("MouseDown", func(p render.Point) {
w.clicked = true w.clicked = true
w.SetBorderStyle(BorderSunken)
}) })
w.Handle("MouseUp", func(p render.Point) { w.Handle("MouseUp", func(p render.Point) {
w.clicked = false w.clicked = false
w.SetBorderStyle(BorderRaised)
}) })
return w return w
@ -67,8 +60,8 @@ func (w *Button) Compute(e render.Engine) {
w.Label.Compute(e) w.Label.Compute(e)
size := w.Label.Size() size := w.Label.Size()
w.Resize(render.Rect{ w.Resize(render.Rect{
W: size.W + (w.Padding * 2) + (w.Border * 2) + (w.Outline * 2), W: size.W + w.BoxThickness(2),
H: size.H + (w.Padding * 2) + (w.Border * 2) + (w.Outline * 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) { func (w *Button) Present(e render.Engine) {
w.Compute(e) w.Compute(e)
P := w.Point() P := w.Point()
S := w.Size()
box := render.Rect{ // Draw the widget's border and everything.
X: P.X, w.DrawBox(e)
Y: P.Y,
W: S.W,
H: S.H,
}
// Draw the outline layer as the full size of the widget. // Offset further if we are currently sunken.
e.DrawBox(w.OutlineColor, render.Rect{ var clickOffset int32
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
if w.clicked { if w.clicked {
color = w.ShadowColor clickOffset++
}
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)
} }
// Draw the text label inside. // Draw the text label inside.
w.Label.MoveTo(render.Point{ w.Label.MoveTo(render.Point{
X: P.X + w.Padding + w.Border + w.Outline, X: P.X + w.BoxThickness(1) + clickOffset,
Y: P.Y + w.Padding + w.Border + w.Outline, Y: P.Y + w.BoxThickness(1) + clickOffset,
}) })
w.Label.Present(e) w.Label.Present(e)
} }

View File

@ -4,8 +4,7 @@ import "git.kirsle.net/apps/doodle/render"
// Color schemes. // Color schemes.
var ( var (
ButtonBackgroundColor = render.RGBA(250, 250, 250, 255) ButtonBackgroundColor = render.RGBA(200, 200, 200, 255)
ButtonHighlightColor = render.RGBA(128, 128, 128, 255) ButtonHoverColor = render.RGBA(200, 255, 255, 255)
ButtonShadowColor = render.RGBA(20, 20, 20, 255)
ButtonOutlineColor = render.Black ButtonOutlineColor = render.Black
) )

View File

@ -1,6 +1,18 @@
package ui 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. // Widget is a user interface element.
type Widget interface { type Widget interface {
@ -13,6 +25,28 @@ type Widget interface {
Handle(string, func(render.Point)) Handle(string, func(render.Point))
Event(string, render.Point) // called internally to trigger an event 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 // Run any render computations; by the end the widget must know its
// Width and Height. For example the Label widget will render itself onto // 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. // 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 // BaseWidget holds common functionality for all widgets, such as managing
// their widths and heights. // their widths and heights.
type BaseWidget struct { type BaseWidget struct {
width int32 width int32
height int32 height int32
point render.Point point render.Point
handlers map[string][]func(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. // 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 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. // Event is called internally by Doodle to trigger an event.
func (w *BaseWidget) Event(name string, p render.Point) { func (w *BaseWidget) Event(name string, p render.Point) {
if handlers, ok := w.handlers[name]; ok { if handlers, ok := w.handlers[name]; ok {