Browse Source

Tooltip Widget and Event Refactor

* Tooltip can be added to any target widget (e.g. Button) and pop up on
  mouse over.
* Refactor the event system. Instead of passing a render.Point to all
  event handlers, pass an EventData struct which can hold the Point or
  the render.Engine.
* Add event types Computed and Present, so a widget can set a handler on
  whenever its Computed or Present method is called.
menus
Noah Petherbridge 5 months ago
parent
commit
f9b305679a
20 changed files with 587 additions and 47 deletions
  1. +3
    -0
      README.md
  2. +8
    -4
      button.go
  3. +5
    -5
      check_button.go
  4. +2
    -4
      checkbox.go
  5. +1
    -1
      eg/frame-place/main.go
  6. +1
    -5
      eg/hello-world/main.go
  7. +1
    -1
      eg/main.go
  8. +148
    -0
      eg/tooltip/main.go
  9. +13
    -0
      enums.go
  10. +25
    -8
      frame.go
  11. +1
    -1
      frame_pack.go
  12. +3
    -1
      frame_place.go
  13. +6
    -0
      image.go
  14. +6
    -0
      label.go
  15. +0
    -1
      main_window.go
  16. +7
    -1
      menu.go
  17. +32
    -7
      supervisor.go
  18. +294
    -0
      tooltip.go
  19. +25
    -8
      widget.go
  20. +6
    -0
      window.go

+ 3
- 0
README.md View File

@@ -105,6 +105,9 @@ most complex.
* Pack() lets you add child widgets to the Frame, aligned against one side
or another, and ability to expand widgets to take up remaining space in
their part of the Frame.
* Place() lets you place child widgets relative to the parent. You can place
it at an exact Point, or against the Top, Left, Bottom or Right sides, or
aligned to the Center (horizontal) or Middle (vertical) of the parent.
* [x] **Label**: Textual labels for your UI.
* Supports TrueType fonts, color, stroke, drop shadow, font size, etc.
* Variable binding support: TextVariable or IntVariable can point to a


+ 8
- 4
button.go View File

@@ -35,20 +35,20 @@ func NewButton(name string, child Widget) *Button {
Background: theme.ButtonBackgroundColor,
})

w.Handle(MouseOver, func(p render.Point) {
w.Handle(MouseOver, func(e EventData) {
w.hovering = true
w.SetBackground(theme.ButtonHoverColor)
})
w.Handle(MouseOut, func(p render.Point) {
w.Handle(MouseOut, func(e EventData) {
w.hovering = false
w.SetBackground(theme.ButtonBackgroundColor)
})

w.Handle(MouseDown, func(p render.Point) {
w.Handle(MouseDown, func(e EventData) {
w.clicked = true
w.SetBorderStyle(BorderSunken)
})
w.Handle(MouseUp, func(p render.Point) {
w.Handle(MouseUp, func(e EventData) {
w.clicked = false
w.SetBorderStyle(BorderRaised)
})
@@ -74,6 +74,8 @@ func (w *Button) Compute(e render.Engine) {
H: size.H + w.BoxThickness(2),
})
}

w.BaseWidget.Compute(e)
}

// SetText conveniently sets the button text, for Label children only.
@@ -118,4 +120,6 @@ func (w *Button) Present(e render.Engine, P render.Point) {

// Draw the text label inside.
w.child.Present(e, moveTo)

w.BaseWidget.Present(e, P)
}

+ 5
- 5
check_button.go View File

@@ -78,24 +78,24 @@ func (w *CheckButton) setup() {
Background: theme.ButtonBackgroundColor,
})

w.Handle(MouseOver, func(p render.Point) {
w.Handle(MouseOver, func(ed EventData) {
w.hovering = true
w.SetBackground(theme.ButtonHoverColor)
})
w.Handle(MouseOut, func(p render.Point) {
w.Handle(MouseOut, func(ed EventData) {
w.hovering = false
w.SetBackground(theme.ButtonBackgroundColor)
})

w.Handle(MouseDown, func(p render.Point) {
w.Handle(MouseDown, func(ed EventData) {
w.clicked = true
w.SetBorderStyle(BorderSunken)
})
w.Handle(MouseUp, func(p render.Point) {
w.Handle(MouseUp, func(ed EventData) {
w.clicked = false
})

w.Handle(Click, func(p render.Point) {
w.Handle(Click, func(ed EventData) {
var sunken bool
if w.BoolVar != nil {
if *w.BoolVar {


+ 2
- 4
checkbox.go View File

@@ -1,7 +1,5 @@
package ui

import "git.kirsle.net/go/render"

// Checkbox combines a CheckButton with a widget like a Label.
type Checkbox struct {
Frame
@@ -37,8 +35,8 @@ func makeCheckbox(name string, boolVar *bool, stringVar *string, value string, c
// Forward clicks on the child widget to the CheckButton.
for _, e := range []Event{MouseOver, MouseOut, MouseUp, MouseDown} {
func(e Event) {
w.child.Handle(e, func(p render.Point) {
w.button.Event(e, p)
w.child.Handle(e, func(ed EventData) {
w.button.Event(e, ed)
})
}(e)
}


+ 1
- 1
eg/frame-place/main.go View File

@@ -134,7 +134,7 @@ func CreateButtons(window *ui.MainWindow, parent *ui.Frame) {
}))

// When clicked, change the window title to ID this button.
button.Handle(ui.Click, func(p render.Point) {
button.Handle(ui.Click, func(ed ui.EventData) {
window.SetTitle(parent.Name + ": " + setting.Label)
})



+ 1
- 5
eg/hello-world/main.go View File

@@ -40,16 +40,12 @@ func main() {
Padding: 4,
},
}))
button.Handle(ui.Click, func(p render.Point) {
button.Handle(ui.Click, func(ed ui.EventData) {
fmt.Println("I've been clicked!")
})
mw.Pack(button, ui.Pack{
Side: ui.N,
})

// Add the button to the MainWindow's Supervisor so it can be
// clicked on and interacted with.
mw.Add(button)

mw.MainLoop()
}

+ 1
- 1
eg/main.go View File

@@ -50,7 +50,7 @@ func main() {
btn := ui.NewButton(fmt.Sprintf("Button-%d", i), ui.NewLabel(ui.Label{
Text: fmt.Sprintf("Button #%d", i),
}))
btn.Handle(ui.Click, func(p render.Point) {
btn.Handle(ui.Click, func(ed ui.EventData) {
fmt.Printf("Button %d was clicked\n", i)
})



+ 148
- 0
eg/tooltip/main.go View File

@@ -0,0 +1,148 @@
package main

import (
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/sdl"
"git.kirsle.net/go/ui"
)

func init() {
sdl.DefaultFontFilename = "../DejaVuSans.ttf"
}

func main() {
mw, err := ui.NewMainWindow("Tooltip Demo", 800, 600)
if err != nil {
panic(err)
}

mw.SetBackground(render.White)
CreateButtons(mw, mw.Frame())

btn := ui.NewButton("Test", ui.NewLabel(ui.Label{
Text: "Click me",
Font: render.Text{
Size: 32,
},
}))
mw.Place(btn, ui.Place{
Center: true,
Middle: true,
})

ui.NewTooltip(btn, ui.Tooltip{
Text: "Hello world\nGoodbye mars!\nBlah blah blah...\nLOL",
Edge: ui.Right,
})

mw.MainLoop()
}

// CreateButtons creates a set of Placed buttons around all the edges and
// center of the parent frame.
func CreateButtons(window *ui.MainWindow, parent *ui.Frame) {
// Draw buttons around the edges of the window.
buttons := []struct {
Label string
Edge ui.Edge
Place ui.Place
}{
{
Label: "Top Left",
Edge: ui.Right,
Place: ui.Place{
Point: render.NewPoint(12, 12),
},
},
{
Label: "Top Middle",
Edge: ui.Bottom,
Place: ui.Place{
Top: 12,
Center: true,
},
},
{
Label: "Top Right",
Edge: ui.Left,
Place: ui.Place{
Top: 12,
Right: 12,
},
},
{
Label: "Left Middle",
Edge: ui.Right,
Place: ui.Place{
Left: 12,
Middle: true,
},
},
{
Label: "Center",
Edge: ui.Bottom,
Place: ui.Place{
Center: true,
Middle: true,
},
},
{
Label: "Right Middle",
Edge: ui.Left,
Place: ui.Place{
Right: 12,
Middle: true,
},
},
{
Label: "Bottom Left",
Edge: ui.Right,
Place: ui.Place{
Left: 12,
Bottom: 12,
},
},
{
Label: "Bottom Center",
Edge: ui.Top,
Place: ui.Place{
Bottom: 12,
Center: true,
},
},
{
Label: "Bottom Right",
Edge: ui.Left,
Place: ui.Place{
Bottom: 12,
Right: 12,
},
},
}
for _, setting := range buttons {
setting := setting

button := ui.NewButton(setting.Label, ui.NewLabel(ui.Label{
Text: setting.Label,
Font: render.Text{
FontFilename: "../DejaVuSans.ttf",
Size: 12,
Color: render.Black,
},
}))

// When clicked, change the window title to ID this button.
button.Handle(ui.Click, func(ed ui.EventData) {
window.SetTitle(parent.Name + ": " + setting.Label)
})

// Tooltip for it.
ui.NewTooltip(button, ui.Tooltip{
Text: setting.Label + " Tooltip",
Edge: setting.Edge,
})

parent.Place(button, setting.Place)
window.Add(button)
}
}

+ 13
- 0
enums.go View File

@@ -0,0 +1,13 @@
package ui

// Edge name
type Edge int

// Edge values.
const (
Top Edge = iota
Left
Right
Bottom
FollowCursor
)

+ 25
- 8
frame.go View File

@@ -1,6 +1,7 @@
package ui

import (
"errors"
"fmt"

"git.kirsle.net/go/render"
@@ -43,6 +44,24 @@ func (w *Frame) Setup() {
}
}

// Add a child widget to the frame. When the frame Presents itself, it also
// presents child widgets. This method is safe to call multiple times: it ensures
// the widget is not already a child of the Frame before adding it.
func (w *Frame) Add(child Widget) error {
if child == w {
return errors.New("can't add self to frame")
}

// Ensure child is new to the frame.
for _, widget := range w.widgets {
if widget == child {
return errors.New("widget already added to frame")
}
}
w.widgets = append(w.widgets, child)
return nil
}

// Children returns all of the child widgets.
func (w *Frame) Children() []Widget {
return w.widgets
@@ -52,6 +71,9 @@ func (w *Frame) Children() []Widget {
func (w *Frame) Compute(e render.Engine) {
w.computePacked(e)
w.computePlaced(e)

// Call the BaseWidget Compute in case we have subscribers.
w.BaseWidget.Compute(e)
}

// Present the Frame.
@@ -83,14 +105,9 @@ func (w *Frame) Present(e render.Engine, P render.Point) {
P.X+p.X+w.BoxThickness(1),
P.Y+p.Y+w.BoxThickness(1),
)
// if child.ID() == "Canvas" {
// log.Debug("Frame X=%d Child X=%d Box=%d Point=%s", P.X, p.X, w.BoxThickness(1), p)
// log.Debug("Frame Y=%d Child Y=%d Box=%d MoveTo=%s", P.Y, p.Y, w.BoxThickness(1), moveTo)
// }
// child.MoveTo(moveTo) // TODO: if uncommented the child will creep down the parent each tick
// if child.ID() == "Canvas" {
// log.Debug("New Point: %s", child.Point())
// }
child.Present(e, moveTo)
}

// Call the BaseWidget Present in case we have subscribers.
w.BaseWidget.Present(e, P)
}

+ 1
- 1
frame_pack.go View File

@@ -53,7 +53,7 @@ func (w *Frame) Pack(child Widget, config ...Pack) {
widget: child,
pack: C,
})
w.widgets = append(w.widgets, child)
w.Add(child)
}

// computePacked processes all the Pack layout widgets in the Frame.


+ 3
- 1
frame_place.go View File

@@ -43,7 +43,7 @@ func (w *Frame) Place(child Widget, config Place) {
widget: child,
place: config,
})
w.widgets = append(w.widgets, child)
w.Add(child)

// Adopt the child widget so it can access the Frame.
child.SetParent(w)
@@ -61,6 +61,7 @@ func (w *Frame) computePlaced(e render.Engine) {
switch row.place.Strategy() {
case "Point":
row.widget.MoveTo(row.place.Point)
row.widget.Compute(e)
case "Side":
var moveTo render.Point

@@ -87,6 +88,7 @@ func (w *Frame) computePlaced(e render.Engine) {
moveTo.Y = frameSize.H - (w.Size().H / 2) - (row.widget.Size().H / 2)
}
row.widget.MoveTo(moveTo)
row.widget.Compute(e)
}

// If this widget itself has placed widgets, call its function too.


+ 6
- 0
image.go View File

@@ -119,6 +119,9 @@ func (w *Image) GetRGBA() *image.RGBA {
// Compute the widget.
func (w *Image) Compute(e render.Engine) {
w.Resize(w.texture.Size())

// Call the BaseWidget Compute in case we have subscribers.
w.BaseWidget.Compute(e)
}

// Present the widget.
@@ -131,4 +134,7 @@ func (w *Image) Present(e render.Engine, p render.Point) {
H: size.H,
}
e.Copy(w.texture, size, dst)

// Call the BaseWidget Present in case we have subscribers.
w.BaseWidget.Present(e, p)
}

+ 6
- 0
label.go View File

@@ -101,6 +101,9 @@ func (w *Label) Compute(e render.Engine) {
H: maxRect.H + (padY * 2),
})
}

// Call the BaseWidget Compute in case we have subscribers.
w.BaseWidget.Compute(e)
}

// Present the label widget.
@@ -125,4 +128,7 @@ func (w *Label) Present(e render.Engine, P render.Point) {
Y: P.Y + border + padY + (i * w.lineHeight),
})
}

// Call the BaseWidget Present in case we have subscribers.
w.BaseWidget.Present(e, P)
}

+ 0
- 1
main_window.go View File

@@ -69,7 +69,6 @@ func NewMainWindow(title string, dimensions ...int) (*MainWindow, error) {
// Add a default frame to the window.
mw.frame = NewFrame("MainWindow Body")
mw.frame.SetBackground(render.RGBA(0, 153, 255, 100))
mw.Add(mw.frame)

// Compute initial window size.
mw.resized()


+ 7
- 1
menu.go View File

@@ -36,11 +36,17 @@ func NewMenu(name string) *Menu {
// Compute the menu
func (w *Menu) Compute(e render.Engine) {
w.body.Compute(e)

// Call the BaseWidget Compute in case we have subscribers.
w.BaseWidget.Compute(e)
}

// Present the menu
func (w *Menu) Present(e render.Engine, p render.Point) {
w.body.Present(e, p)

// Call the BaseWidget Present in case we have subscribers.
w.BaseWidget.Present(e, p)
}

// AddItem quickly adds an item to a menu.
@@ -90,7 +96,7 @@ func NewMenuItem(label string, command func()) *MenuItem {
Background: render.Blue,
})

w.Button.Handle(Click, func(p render.Point) {
w.Button.Handle(Click, func(ed EventData) {
w.Command()
})



+ 32
- 7
supervisor.go View File

@@ -24,8 +24,19 @@ const (
KeyUp
KeyPress
Drop
Compute // fired whenever the widget runs Compute
Present // fired whenever the widget runs Present
)

// EventData carries common data to event handlers.
type EventData struct {
// Point is usually the cursor position on click and mouse events.
Point render.Point

// Engine is the render engine on Compute and Present events.
Engine render.Engine
}

// Supervisor keeps track of widgets of interest to notify them about
// interaction events such as mouse hovers and clicks in their general
// vicinity.
@@ -97,7 +108,9 @@ func (s *Supervisor) Loop(ev *event.State) error {
if !ev.Button1 && !ev.Button3 {
// The mouse has been released. TODO: make mouse button important?
for _, child := range hovering {
child.widget.Event(Drop, XY)
child.widget.Event(Drop, EventData{
Point: XY,
})
}
s.DragStop()
}
@@ -117,19 +130,27 @@ func (s *Supervisor) Loop(ev *event.State) error {

// Cursor has intersected the widget.
if _, ok := s.hovering[id]; !ok {
w.Event(MouseOver, XY)
w.Event(MouseOver, EventData{
Point: XY,
})
s.hovering[id] = nil
}

_, isClicked := s.clicked[id]
if ev.Button1 {
if !isClicked {
w.Event(MouseDown, XY)
w.Event(MouseDown, EventData{
Point: XY,
})
s.clicked[id] = nil
}
} else if isClicked {
w.Event(MouseUp, XY)
w.Event(Click, XY)
w.Event(MouseUp, EventData{
Point: XY,
})
w.Event(Click, EventData{
Point: XY,
})
delete(s.clicked, id)
}
}
@@ -141,12 +162,16 @@ func (s *Supervisor) Loop(ev *event.State) error {

// Cursor is not intersecting the widget.
if _, ok := s.hovering[id]; ok {
w.Event(MouseOut, XY)
w.Event(MouseOut, EventData{
Point: XY,
})
delete(s.hovering, id)
}

if _, ok := s.clicked[id]; ok {
w.Event(MouseUp, XY)
w.Event(MouseUp, EventData{
Point: XY,
})
delete(s.clicked, id)
}
}


+ 294
- 0
tooltip.go View File

@@ -0,0 +1,294 @@
package ui

import (
"fmt"
"strings"

"git.kirsle.net/go/render"
)

func init() {
precomputeArrows()
}

// Tooltip attaches a mouse-over popup to another widget.
type Tooltip struct {
BaseWidget

// Configurable attributes.
Text string // Text to show in the tooltip.
TextVariable *string // String pointer instead of text.
Edge Edge // side to display tooltip on

target Widget
lineHeight int
font render.Text
}

// Constants for tooltips.
const (
tooltipArrowSize = 5
)

// NewTooltip creates a new tooltip attached to a widget.
func NewTooltip(target Widget, tt Tooltip) *Tooltip {
w := &Tooltip{
Text: tt.Text,
TextVariable: tt.TextVariable,
Edge: tt.Edge,
target: target,
}

// Default style.
w.Hide()
w.SetBackground(render.RGBA(0, 0, 0, 230))
w.font = render.Text{
Size: 10,
Color: render.White,
Padding: 4,
}

// Add event bindings to the target widget.
// - Show the tooltip on MouseOver
// - Hide it on MouseOut
// - Compute the tooltip when the parent widget Computes
// - Present the tooltip when the parent widget Presents
target.Handle(MouseOver, func(ed EventData) {
w.Show()
})
target.Handle(MouseOut, func(ed EventData) {
w.Hide()
})
target.Handle(Compute, func(ed EventData) {
w.Compute(ed.Engine)
})
target.Handle(Present, func(ed EventData) {
w.Present(ed.Engine, w.Point())
})

w.IDFunc(func() string {
return fmt.Sprintf(`Tooltip<"%s">`, w.Value())
})

return w
}

// Value returns the current text displayed in the tooltop, whether from the
// configured Text or the TextVariable pointer.
func (w *Tooltip) Value() string {
return w.text().Text
}

// text returns the raw render.Text holding the current value to be displayed
// in the tooltip, either from Text or TextVariable.
func (w *Tooltip) text() render.Text {
if w.TextVariable != nil {
w.font.Text = *w.TextVariable
} else {
w.font.Text = w.Text
}
return w.font
}

// Compute the size of the tooltip.
func (w *Tooltip) Compute(e render.Engine) {
// Compute the size based on the text.
w.computeText(e)

// Compute the position based on the Edge and the target widget.
var (
size = w.Size()

target = w.target
tSize = target.Size()
tPoint = AbsolutePosition(target)

moveTo render.Point
)

switch w.Edge {
case Top:
moveTo.Y = tPoint.Y - size.H - tooltipArrowSize
moveTo.X = tPoint.X + (tSize.W / 2) - (size.W / 2)
case Left:
moveTo.X = tPoint.X - size.W - tooltipArrowSize
moveTo.Y = tPoint.Y + (tSize.H / 2) - (size.H / 2)
case Right:
moveTo.X = tPoint.X + tSize.W + tooltipArrowSize
moveTo.Y = tPoint.Y + (tSize.H / 2) - (size.H / 2)
case Bottom:
moveTo.Y = tPoint.Y + tSize.H + tooltipArrowSize
moveTo.X = tPoint.X + (tSize.W / 2) - (size.W / 2)
}

w.MoveTo(moveTo)
}

// computeText handles the text compute, very similar to Label.Compute.
func (w *Tooltip) computeText(e render.Engine) {
text := w.text()
lines := strings.Split(text.Text, "\n")

// Max rect to encompass all lines of text.
var maxRect = render.Rect{}
for _, line := range lines {
if line == "" {
line = "<empty>"
}

text.Text = line // only this line at this time.
rect, err := e.ComputeTextRect(text)
if err != nil {
panic(fmt.Sprintf("%s: failed to compute text rect: %s", w, err)) // TODO return an error
}

if rect.W > maxRect.W {
maxRect.W = rect.W
}
maxRect.H += rect.H
w.lineHeight = int(rect.H)
}

var (
padX = w.font.Padding + w.font.PadX
padY = w.font.Padding + w.font.PadY
)

w.Resize(render.Rect{
W: maxRect.W + (padX * 2),
H: maxRect.H + (padY * 2),
})
}

// Present the tooltip.
func (w *Tooltip) Present(e render.Engine, P render.Point) {
if w.Hidden() {
return
}

// Draw the text.
w.presentText(e, P)

// Draw the arrow.
w.presentArrow(e, P)
}

// presentText draws the text similar to Label.
func (w *Tooltip) presentText(e render.Engine, P render.Point) {
var (
text = w.text()
padX = w.font.Padding + w.font.PadX
padY = w.font.Padding + w.font.PadY
)

w.DrawBox(e, P)
for i, line := range strings.Split(text.Text, "\n") {
text.Text = line
e.DrawText(text, render.Point{
X: P.X + padX,
Y: P.Y + padY + (i * w.lineHeight),
})
}
}

// presentArrow draws the arrow between the tooltip and its target widget.
func (w *Tooltip) presentArrow(e render.Engine, P render.Point) {
var (
// size = w.Size()

target = w.target
tSize = target.Size()
tPoint = AbsolutePosition(target)

drawAt render.Point
arrow [][]render.Point
)

switch w.Edge {
case Top:
arrow = arrowDown
drawAt = render.Point{
X: tPoint.X + (tSize.W / 2) - tooltipArrowSize,
Y: tPoint.Y - tooltipArrowSize,
}
case Bottom:
arrow = arrowUp
drawAt = render.Point{
X: tPoint.X + (tSize.W / 2) - tooltipArrowSize,
Y: tPoint.Y + tSize.H,
}
case Left:
arrow = arrowRight
drawAt = render.Point{
X: tPoint.X - tooltipArrowSize,
Y: tPoint.Y + (tSize.H / 2) - tooltipArrowSize,
}
case Right:
arrow = arrowLeft
drawAt = render.Point{
X: tPoint.X + tSize.W,
Y: tPoint.Y + (tSize.H / 2) - tooltipArrowSize,
}
}
drawArrow(e, w.Background(), drawAt, arrow)
}

// Draw an arrow at a given top/left coordinate.
func drawArrow(e render.Engine, color render.Color, p render.Point, arrow [][]render.Point) {
for _, row := range arrow {
if len(row) == 1 {
point := render.NewPoint(row[0].X, row[0].Y)
point.Add(p)
e.DrawPoint(color, point)
} else {
start := render.NewPoint(row[0].X, row[0].Y)
end := render.NewPoint(row[1].X, row[1].Y)
start.Add(p)
end.Add(p)
e.DrawLine(color, start, end)
}
}
}

// Arrows for the tooltip widget.
var (
arrowDown [][]render.Point
arrowUp [][]render.Point
arrowLeft [][]render.Point
arrowRight [][]render.Point
)

func precomputeArrows() {
arrowDown = [][]render.Point{
{render.NewPoint(0, 0), render.NewPoint(10, 0)},
{render.NewPoint(1, 1), render.NewPoint(9, 1)},
{render.NewPoint(2, 2), render.NewPoint(8, 2)},
{render.NewPoint(3, 3), render.NewPoint(7, 3)},
{render.NewPoint(4, 4), render.NewPoint(6, 4)},
{render.NewPoint(5, 5)},
}
arrowUp = [][]render.Point{
{render.NewPoint(5, 0)},
{render.NewPoint(4, 1), render.NewPoint(6, 1)},
{render.NewPoint(3, 2), render.NewPoint(7, 2)},
{render.NewPoint(2, 3), render.NewPoint(8, 3)},
{render.NewPoint(1, 4), render.NewPoint(9, 4)},
// {render.NewPoint(0, 5), render.NewPoint(10, 5)},
}
arrowLeft = [][]render.Point{
{render.NewPoint(0, 5)},
{render.NewPoint(1, 4), render.NewPoint(1, 6)},
{render.NewPoint(2, 3), render.NewPoint(2, 7)},
{render.NewPoint(3, 2), render.NewPoint(3, 8)},
{render.NewPoint(4, 1), render.NewPoint(4, 9)},
// {render.NewPoint(5, 0), render.NewPoint(5, 10)},
}
arrowRight = [][]render.Point{
{render.NewPoint(0, 0), render.NewPoint(0, 10)},
{render.NewPoint(1, 1), render.NewPoint(1, 9)},
{render.NewPoint(2, 2), render.NewPoint(2, 8)},
{render.NewPoint(3, 3), render.NewPoint(3, 7)},
{render.NewPoint(4, 4), render.NewPoint(4, 6)},
{render.NewPoint(5, 5)},
}
}

+ 25
- 8
widget.go View File

@@ -32,8 +32,8 @@ type Widget interface {
ResizeAuto(render.Rect)
Rect() render.Rect // Return the full absolute rect combining the Size() and Point()

Handle(Event, func(render.Point))
Event(Event, render.Point) // called internally to trigger an event
Handle(Event, func(EventData))
Event(Event, EventData) // called internally to trigger an event

// Thickness of the padding + border + outline.
BoxThickness(multiplier int) int
@@ -117,7 +117,7 @@ type BaseWidget struct {
borderSize int
outlineColor render.Color
outlineSize int
handlers map[Event][]func(render.Point)
handlers map[Event][]func(EventData)
hasParent bool
parent Widget
}
@@ -471,23 +471,40 @@ func (w *BaseWidget) SetOutlineSize(v int) {
w.outlineSize = v
}

// Compute calls the base widget's Compute function, which just triggers
// events on widgets that want to be notified when the widget computes.
func (w *BaseWidget) Compute(e render.Engine) {
w.Event(Compute, EventData{
Engine: e,
})
}

// Present calls the base widget's Present function, which just triggers
// events on widgets that want to be notified when the widget presents.
func (w *BaseWidget) Present(e render.Engine, p render.Point) {
w.Event(Present, EventData{
Point: p,
Engine: e,
})
}

// Event is called internally by Doodle to trigger an event.
func (w *BaseWidget) Event(event Event, p render.Point) {
func (w *BaseWidget) Event(event Event, e EventData) {
if handlers, ok := w.handlers[event]; ok {
for _, fn := range handlers {
fn(p)
fn(e)
}
}
}

// Handle an event in the widget.
func (w *BaseWidget) Handle(event Event, fn func(render.Point)) {
func (w *BaseWidget) Handle(event Event, fn func(EventData)) {
if w.handlers == nil {
w.handlers = map[Event][]func(render.Point){}
w.handlers = map[Event][]func(EventData){}
}

if _, ok := w.handlers[event]; !ok {
w.handlers[event] = []func(render.Point){}
w.handlers[event] = []func(EventData){}
}

w.handlers[event] = append(w.handlers[event], fn)


+ 6
- 0
window.go View File

@@ -104,11 +104,17 @@ func (w *Window) ConfigureTitle(C Config) {
// Compute the window.
func (w *Window) Compute(e render.Engine) {
w.body.Compute(e)

// Call the BaseWidget Compute in case we have subscribers.
w.BaseWidget.Compute(e)
}

// Present the window.
func (w *Window) Present(e render.Engine, P render.Point) {
w.body.Present(e, P)

// Call the BaseWidget Present in case we have subscribers.
w.BaseWidget.Present(e, P)
}

// Pack a widget into the window's frame.


Loading…
Cancel
Save