Add ui.Supervisor for Widget Event Handling
The Buttons can now be managed by a ui.Supervisor and be notified when the mouse enters or leaves their bounding box and handle click events. Current event handlers supported: * MouseOver * MouseOut * MouseDown * MouseUp * Click Each of those events are only fired when the state of the event has changed, i.e. the first time the mouse enters the widget MouseOver is called and then when the mouse leaves later, MouseOut is called. A completed click event (mouse was released while pressed and hovering the button) triggers both MouseOut and Click, so the button can pop itself out and also run the click handler.
This commit is contained in:
parent
41e1838549
commit
602273aa16
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
// MainScene implements the main menu of Doodle.
|
||||
type MainScene struct {
|
||||
Supervisor *ui.Supervisor
|
||||
}
|
||||
|
||||
// Name of the scene.
|
||||
|
@ -17,11 +18,43 @@ func (s *MainScene) Name() string {
|
|||
|
||||
// Setup the scene.
|
||||
func (s *MainScene) Setup(d *Doodle) error {
|
||||
s.Supervisor = ui.NewSupervisor()
|
||||
|
||||
button1 := ui.NewButton(*ui.NewLabel(render.Text{
|
||||
Text: "New Map",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
}))
|
||||
button1.Compute(d.Engine)
|
||||
button1.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (button1.Size().W / 2),
|
||||
Y: 200,
|
||||
})
|
||||
button1.Handle("Click", func(p render.Point) {
|
||||
d.NewMap()
|
||||
})
|
||||
|
||||
button2 := ui.NewButton(*ui.NewLabel(render.Text{
|
||||
Text: "New Map",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
}))
|
||||
button2.SetText("Load Map")
|
||||
button2.Compute(d.Engine)
|
||||
button2.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (button2.Size().W / 2),
|
||||
Y: 260,
|
||||
})
|
||||
|
||||
s.Supervisor.Add(button1)
|
||||
s.Supervisor.Add(button2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop the editor scene.
|
||||
func (s *MainScene) Loop(d *Doodle, ev *events.State) error {
|
||||
s.Supervisor.Loop(ev)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -44,26 +77,7 @@ func (s *MainScene) Draw(d *Doodle) error {
|
|||
})
|
||||
label.Present(d.Engine)
|
||||
|
||||
button := ui.NewButton(*ui.NewLabel(render.Text{
|
||||
Text: "New Map",
|
||||
Size: 14,
|
||||
Color: render.Black,
|
||||
}))
|
||||
button.Compute(d.Engine)
|
||||
|
||||
button.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (button.Size().W / 2),
|
||||
Y: 200,
|
||||
})
|
||||
button.Present(d.Engine)
|
||||
|
||||
button.SetText("Load Map")
|
||||
button.Compute(d.Engine)
|
||||
button.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (button.Size().W / 2),
|
||||
Y: 260,
|
||||
})
|
||||
button.Present(d.Engine)
|
||||
s.Supervisor.Present(d.Engine)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -124,12 +124,10 @@ func (r *Renderer) Poll() (*events.State, error) {
|
|||
// Is a mouse button pressed down?
|
||||
if t.Button == 1 {
|
||||
var eventName string
|
||||
if DebugClickEvents {
|
||||
if t.State == 1 && s.Button1.Now == false {
|
||||
eventName = "DOWN"
|
||||
} else if t.State == 0 && s.Button1.Now == true {
|
||||
eventName = "UP"
|
||||
}
|
||||
if t.State == 1 && s.Button1.Now == false {
|
||||
eventName = "DOWN"
|
||||
} else if t.State == 0 && s.Button1.Now == true {
|
||||
eventName = "UP"
|
||||
}
|
||||
|
||||
if eventName != "" {
|
||||
|
|
|
@ -7,7 +7,12 @@ import (
|
|||
|
||||
// ColorToSDL converts Doodle's Color type to an sdl.Color.
|
||||
func ColorToSDL(c render.Color) sdl.Color {
|
||||
return sdl.Color{c.Red, c.Green, c.Blue, c.Alpha}
|
||||
return sdl.Color{
|
||||
R: c.Red,
|
||||
G: c.Green,
|
||||
B: c.Blue,
|
||||
A: c.Alpha,
|
||||
}
|
||||
}
|
||||
|
||||
// RectToSDL converts Doodle's Rect type to an sdl.Rect.
|
||||
|
|
40
ui/button.go
40
ui/button.go
|
@ -18,11 +18,15 @@ type Button struct {
|
|||
HighlightColor render.Color
|
||||
ShadowColor render.Color
|
||||
OutlineColor render.Color
|
||||
|
||||
// Private options.
|
||||
hovering bool
|
||||
clicked bool
|
||||
}
|
||||
|
||||
// NewButton creates a new Button.
|
||||
func NewButton(label Label) *Button {
|
||||
return &Button{
|
||||
w := &Button{
|
||||
Label: label,
|
||||
Padding: 4, // TODO magic number
|
||||
Border: 2,
|
||||
|
@ -34,6 +38,22 @@ func NewButton(label Label) *Button {
|
|||
ShadowColor: theme.ButtonShadowColor,
|
||||
OutlineColor: theme.ButtonOutlineColor,
|
||||
}
|
||||
|
||||
w.Handle("MouseOver", func(p render.Point) {
|
||||
w.hovering = true
|
||||
})
|
||||
w.Handle("MouseOut", func(p render.Point) {
|
||||
w.hovering = false
|
||||
})
|
||||
|
||||
w.Handle("MouseDown", func(p render.Point) {
|
||||
w.clicked = true
|
||||
})
|
||||
w.Handle("MouseUp", func(p render.Point) {
|
||||
w.clicked = false
|
||||
})
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// SetText quickly changes the text of the label.
|
||||
|
@ -74,7 +94,11 @@ func (w *Button) Present(e render.Engine) {
|
|||
})
|
||||
|
||||
// Highlight on the top left edge.
|
||||
e.DrawBox(w.HighlightColor, box)
|
||||
color := w.HighlightColor
|
||||
if w.clicked {
|
||||
color = w.ShadowColor
|
||||
}
|
||||
e.DrawBox(color, box)
|
||||
box.W = S.W
|
||||
|
||||
// Shadow on the bottom right edge.
|
||||
|
@ -82,12 +106,20 @@ func (w *Button) Present(e render.Engine) {
|
|||
box.Y += w.Border
|
||||
box.W -= w.Border
|
||||
box.H -= w.Border
|
||||
e.DrawBox(w.ShadowColor, box)
|
||||
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
|
||||
e.DrawBox(w.Background, box)
|
||||
if w.hovering {
|
||||
e.DrawBox(render.Yellow, box)
|
||||
} else {
|
||||
e.DrawBox(w.Background, box)
|
||||
}
|
||||
|
||||
// Draw the text label inside.
|
||||
w.Label.MoveTo(render.Point{
|
||||
|
|
96
ui/supervisor.go
Normal file
96
ui/supervisor.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.kirsle.net/apps/doodle/events"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
)
|
||||
|
||||
// Supervisor keeps track of widgets of interest to notify them about
|
||||
// interaction events such as mouse hovers and clicks in their general
|
||||
// vicinity.
|
||||
type Supervisor struct {
|
||||
lock sync.RWMutex
|
||||
widgets []Widget
|
||||
hovering map[int]interface{}
|
||||
clicked map[int]interface{}
|
||||
}
|
||||
|
||||
// NewSupervisor creates a supervisor.
|
||||
func NewSupervisor() *Supervisor {
|
||||
return &Supervisor{
|
||||
hovering: map[int]interface{}{},
|
||||
clicked: map[int]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Loop to check events and pass them to managed widgets.
|
||||
func (s *Supervisor) Loop(ev *events.State) {
|
||||
var (
|
||||
XY = render.Point{
|
||||
X: ev.CursorX.Now,
|
||||
Y: ev.CursorY.Now,
|
||||
}
|
||||
)
|
||||
|
||||
// See if we are hovering over any widgets.
|
||||
for id, w := range s.widgets {
|
||||
var (
|
||||
P = w.Point()
|
||||
S = w.Size()
|
||||
P2 = render.Point{
|
||||
X: P.X + S.W,
|
||||
Y: P.Y + S.H,
|
||||
}
|
||||
)
|
||||
|
||||
if XY.X >= P.X && XY.X <= P2.X && XY.Y >= P.Y && XY.Y <= P2.Y {
|
||||
// Cursor has intersected the widget.
|
||||
if _, ok := s.hovering[id]; !ok {
|
||||
w.Event("MouseOver", XY)
|
||||
s.hovering[id] = nil
|
||||
}
|
||||
|
||||
_, isClicked := s.clicked[id]
|
||||
if ev.Button1.Now {
|
||||
if !isClicked {
|
||||
w.Event("MouseDown", XY)
|
||||
s.clicked[id] = nil
|
||||
}
|
||||
} else if isClicked {
|
||||
w.Event("MouseUp", XY)
|
||||
w.Event("Click", XY)
|
||||
delete(s.clicked, id)
|
||||
}
|
||||
} else {
|
||||
// Cursor is not intersecting the widget.
|
||||
if _, ok := s.hovering[id]; ok {
|
||||
w.Event("MouseOut", XY)
|
||||
delete(s.hovering, id)
|
||||
}
|
||||
|
||||
if _, ok := s.clicked[id]; ok {
|
||||
w.Event("MouseUp", XY)
|
||||
delete(s.clicked, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Present all widgets managed by the supervisor.
|
||||
func (s *Supervisor) Present(e render.Engine) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
for _, w := range s.widgets {
|
||||
w.Present(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a widget to be supervised.
|
||||
func (s *Supervisor) Add(w Widget) {
|
||||
s.lock.Lock()
|
||||
s.widgets = append(s.widgets, w)
|
||||
s.lock.Unlock()
|
||||
}
|
39
ui/widget.go
39
ui/widget.go
|
@ -4,16 +4,15 @@ import "git.kirsle.net/apps/doodle/render"
|
|||
|
||||
// Widget is a user interface element.
|
||||
type Widget interface {
|
||||
Width() int32 // Get width
|
||||
Height() int32 // Get height
|
||||
SetWidth(int32) // Set
|
||||
SetHeight(int32) // Set
|
||||
Point() render.Point
|
||||
MoveTo(render.Point)
|
||||
MoveBy(render.Point)
|
||||
Size() render.Rect // Return the Width and Height of the widget.
|
||||
Resize(render.Rect)
|
||||
|
||||
Handle(string, func(render.Point))
|
||||
Event(string, render.Point) // called internally to trigger an event
|
||||
|
||||
// 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.
|
||||
|
@ -26,9 +25,10 @@ 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
|
||||
width int32
|
||||
height int32
|
||||
point render.Point
|
||||
handlers map[string][]func(render.Point)
|
||||
}
|
||||
|
||||
// Point returns the X,Y position of the widget on the window.
|
||||
|
@ -61,3 +61,28 @@ func (w *BaseWidget) Resize(v render.Rect) {
|
|||
w.width = v.W
|
||||
w.height = v.H
|
||||
}
|
||||
|
||||
// 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 {
|
||||
for _, fn := range handlers {
|
||||
fn(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle an event in the widget.
|
||||
func (w *BaseWidget) Handle(name string, fn func(render.Point)) {
|
||||
if w.handlers == nil {
|
||||
w.handlers = map[string][]func(render.Point){}
|
||||
}
|
||||
|
||||
if _, ok := w.handlers[name]; !ok {
|
||||
w.handlers[name] = []func(render.Point){}
|
||||
}
|
||||
|
||||
w.handlers[name] = append(w.handlers[name], fn)
|
||||
}
|
||||
|
||||
// OnMouseOut should be overridden on widgets who want this event.
|
||||
func (w *BaseWidget) OnMouseOut(render.Point) {}
|
||||
|
|
Loading…
Reference in New Issue
Block a user