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.
type Point struct {
X int32

View File

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

View File

@ -9,15 +9,6 @@ import (
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
// Private options.
hovering bool
@ -28,29 +19,31 @@ type Button struct {
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,
}
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)
}

View File

@ -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
)

View File

@ -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.
@ -28,6 +62,14 @@ type BaseWidget struct {
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)
}
@ -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 {