doodle/ui/button.go
Noah Petherbridge 5434484b6e Abstract Drawing Canvas into Reusable Widget
The `level.Canvas` is a widget that holds onto its Palette and Grid and
has interactions to allow scrolling and editing the grid using the
swatches available on the palette.

Thus all of the logic in the Editor Mode for drawing directly onto the
root SDL surface are now handled inside a level.Canvas instance.

The `level.Canvas` widget has the following properties:
* Like any widget it has an X,Y position and a width/height.
* It has a Scroll position to control which slice of its drawing will be
  visible inside its bounding box.
* It supports levels having negative coordinates for their pixels. It
  doesn't care. The default Scroll position is (0,0) at the top left
  corner of the widget but you can scroll into the negatives and see the
  negative pixels.
* Keyboard keys will scroll the viewport inside the canvas.
* The canvas draws only the pixels that are visible inside its bounding
  box.

This feature will eventually pave the way toward:
* Doodads being dropped on top of your map, each Doodad being its own
  Canvas widget.
* Using drawings as button icons for the user interface, as the Canvas
  is a normal widget.
2018-08-16 20:37:19 -07:00

113 lines
2.3 KiB
Go

package ui
import (
"errors"
"fmt"
"git.kirsle.net/apps/doodle/render"
"git.kirsle.net/apps/doodle/ui/theme"
)
// Button is a clickable button.
type Button struct {
BaseWidget
child Widget
// Private options.
hovering bool
clicked bool
}
// NewButton creates a new Button.
func NewButton(name string, child Widget) *Button {
w := &Button{
child: child,
}
w.IDFunc(func() string {
return fmt.Sprintf("Button<%s>", name)
})
w.Configure(Config{
BorderSize: 2,
BorderStyle: BorderRaised,
OutlineSize: 1,
OutlineColor: theme.ButtonOutlineColor,
Background: 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
}
// Compute the size of the button.
func (w *Button) Compute(e render.Engine) {
// Compute the size of the inner widget first.
w.child.Compute(e)
// Auto-resize only if we haven't been given a fixed size.
if !w.FixedSize() {
size := w.child.Size()
w.Resize(render.Rect{
W: size.W + w.BoxThickness(2),
H: size.H + w.BoxThickness(2),
})
}
}
// SetText conveniently sets the button text, for Label children only.
func (w *Button) SetText(text string) error {
if label, ok := w.child.(*Label); ok {
label.Text = text
}
return errors.New("child is not a Label widget")
}
// Present the button.
func (w *Button) Present(e render.Engine, P render.Point) {
w.Compute(e)
var (
S = w.Size()
ChildSize = w.child.Size()
)
// Draw the widget's border and everything.
w.DrawBox(e, P)
// Offset further if we are currently sunken.
var clickOffset int32
if w.clicked {
clickOffset++
}
// Where to place the child widget.
moveTo := render.Point{
X: P.X + w.BoxThickness(1) + clickOffset,
Y: P.Y + w.BoxThickness(1) + clickOffset,
}
// If we're bigger than we need to be, center the child widget.
if S.Bigger(ChildSize) {
moveTo.X = P.X + (S.W / 2) - (ChildSize.W / 2)
}
// Draw the text label inside.
w.child.Present(e, moveTo)
}