From d742fba7e92d8be2dabe41ce350f80ac6e111813 Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Fri, 15 May 2020 19:00:43 +0200 Subject: [PATCH] Added Events and changed event handlers of controls. Removed EventHandlers from Control interface. --- ui/button.go | 5 +- ui/checkbox.go | 27 +++---- ui/containerbase.go | 13 +++- ui/control.go | 7 +- ui/controlbase.go | 142 ++++++++++++++++++++++++---------- ui/events.go | 46 +++++++++++ ui/examples/01_basic/basic.go | 7 +- ui/overflow.go | 5 +- ui/overlays.go | 6 +- ui/proxy.go | 20 +---- ui/scrollbar.go | 11 +-- ui/slider.go | 20 +++-- ui/textbox.go | 24 ++++-- ui/tooltip.go | 2 +- 14 files changed, 220 insertions(+), 115 deletions(-) create mode 100644 ui/events.go diff --git a/ui/button.go b/ui/button.go index 049426b..8e917bb 100644 --- a/ui/button.go +++ b/ui/button.go @@ -63,11 +63,12 @@ func (b *Button) DesiredSize(ctx Context) geom.PointF32 { return b.desiredSize(ctx) } -func (b *Button) Handle(ctx Context, e Event) { - b.ControlBase.Handle(ctx, e) +func (b *Button) Handle(ctx Context, e Event) bool { + result := b.ControlBase.Handle(ctx, e) if b.over { ctx.Renderer().SetMouseCursor(MouseCursorPointer) } + return result } func (b *Button) fillColor(p *Palette) color.Color { diff --git a/ui/checkbox.go b/ui/checkbox.go index d68b4f1..683fc1b 100644 --- a/ui/checkbox.go +++ b/ui/checkbox.go @@ -4,12 +4,10 @@ import ( "opslag.de/schobers/geom" ) -type SelectedChangedFn func(selected bool) - type Checkbox struct { ControlBase - onSelectedChanged SelectedChangedFn + selectedChanged Events Selected bool Text string @@ -79,26 +77,25 @@ func (c *Checkbox) selectedIcon(pt geom.PointF32) bool { func (c *Checkbox) DesiredSize(ctx Context) geom.PointF32 { return c.desiredSize(ctx) } -func (c *Checkbox) Handle(ctx Context, e Event) { +func (c *Checkbox) Handle(ctx Context, e Event) bool { + result := c.ControlBase.Handle(ctx, e) + if c.over { + ctx.Renderer().SetMouseCursor(MouseCursorPointer) + } + if result { + return true + } switch e := e.(type) { case *MouseButtonDownEvent: if e.Button == MouseButtonLeft && c.over { c.Selected = !c.Selected - onSelectedChanged := c.onSelectedChanged - if onSelectedChanged != nil { - onSelectedChanged(c.Selected) - } + return c.selectedChanged.Notify(ctx, c.Selected) } } - c.ControlBase.Handle(ctx, e) - if c.over { - ctx.Renderer().SetMouseCursor(MouseCursorPointer) - } + return false } -func (c *Checkbox) OnSelectedChanged(fn SelectedChangedFn) { - c.onSelectedChanged = fn -} +func (c *Checkbox) SelectedChanged() EventHandler { return &c.selectedChanged } func (c *Checkbox) Render(ctx Context) { c.RenderBackground(ctx) diff --git a/ui/containerbase.go b/ui/containerbase.go index 1cd95a2..d528e2d 100644 --- a/ui/containerbase.go +++ b/ui/containerbase.go @@ -26,11 +26,16 @@ func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset ge c.ControlBase.Arrange(ctx, bounds, offset, parent) } -func (c *ContainerBase) Handle(ctx Context, e Event) { - c.ControlBase.Handle(ctx, e) - for _, child := range c.Children { - child.Handle(ctx, e) +func (c *ContainerBase) Handle(ctx Context, e Event) bool { + if c.ControlBase.Handle(ctx, e) { + return true } + for _, child := range c.Children { + if child.Handle(ctx, e) { + return true + } + } + return false } func (c *ContainerBase) Render(ctx Context) { diff --git a/ui/control.go b/ui/control.go index 7ad5068..f1ce14b 100644 --- a/ui/control.go +++ b/ui/control.go @@ -7,17 +7,14 @@ import ( type Control interface { Arrange(Context, geom.RectangleF32, geom.PointF32, Control) DesiredSize(Context) geom.PointF32 - Handle(Context, Event) + Handle(Context, Event) bool Render(Context) Bounds() geom.RectangleF32 IsInBounds(p geom.PointF32) bool IsOver() bool Offset() geom.PointF32 - OnClick(ClickFn) - OnDragStart(DragStartFn) - OnDragMove(DragMoveFn) - OnDragEnd(DragEndFn) + Parent() Control } diff --git a/ui/controlbase.go b/ui/controlbase.go index 64ea79b..df54d3b 100644 --- a/ui/controlbase.go +++ b/ui/controlbase.go @@ -6,12 +6,84 @@ import ( "opslag.de/schobers/geom" ) -type ClickFn func(pos geom.PointF32, btn MouseButton) -type DragEndFn func(start, end geom.PointF32) -type DragMoveFn func(start, move geom.PointF32) -type DragStartFn func(start geom.PointF32) -type MouseEnterFn func() -type MouseLeaveFn func() +type ControlClickedArgs struct { + Position geom.PointF32 + Button MouseButton +} + +type ControlClickedEventHandler interface { + AddHandler(func(Context, ControlClickedArgs)) uint +} + +type ControlClickedEvents struct { + Events +} + +func (e *ControlClickedEvents) AddHandler(handler func(Context, ControlClickedArgs)) uint { + return e.Events.AddHandlerState(func(ctx Context, state interface{}) { + args := state.(ControlClickedArgs) + handler(ctx, args) + }) +} + +type DragEndedArgs struct { + Start geom.PointF32 + End geom.PointF32 +} + +type DragEndedEventHandler interface { + AddHandler(func(Context, DragEndedArgs)) uint +} + +type DragEndedEvents struct { + Events +} + +func (e *DragEndedEvents) AddHandler(handler func(Context, DragEndedArgs)) uint { + return e.Events.AddHandlerState(func(ctx Context, state interface{}) { + args := state.(DragEndedArgs) + handler(ctx, args) + }) +} + +type DragMovedArgs struct { + Start geom.PointF32 + Current geom.PointF32 +} + +type DragMovedEventHandler interface { + AddHandler(func(Context, DragMovedArgs)) uint +} + +type DragMovedEvents struct { + Events +} + +func (e *DragMovedEvents) AddHandler(handler func(Context, DragMovedArgs)) uint { + return e.Events.AddHandlerState(func(ctx Context, state interface{}) { + args := state.(DragMovedArgs) + handler(ctx, args) + }) +} + +type DragStartedArgs struct { + Start geom.PointF32 +} + +type DragStartedEventHandler interface { + AddHandler(func(Context, DragStartedArgs)) uint +} + +type DragStartedEvents struct { + Events +} + +func (e *DragStartedEvents) AddHandler(handler func(Context, DragStartedArgs)) uint { + return e.Events.AddHandlerState(func(ctx Context, state interface{}) { + args := state.(DragStartedArgs) + handler(ctx, args) + }) +} var _ Control = &ControlBase{} @@ -23,10 +95,10 @@ type ControlBase struct { over bool pressed bool - onClick ClickFn - onDragEnd DragEndFn - onDragMove DragMoveFn - onDragStart DragStartFn + clicked ControlClickedEvents + dragEnded DragEndedEvents + dragMoved DragMovedEvents + dragStarted DragStartedEvents Background color.Color Font FontStyle @@ -45,7 +117,13 @@ func (c *ControlBase) Bounds() geom.RectangleF32 { return c.bounds } func (c *ControlBase) DesiredSize(Context) geom.PointF32 { return geom.ZeroPtF32 } -func (c *ControlBase) Handle(ctx Context, e Event) { +func (c *ControlBase) Handle(ctx Context, e Event) bool { + defer func() { + if c.Tooltip != "" && c.over { + ctx.ShowTooltip(c.Tooltip) + } + }() + var over = func(e MouseEvent) bool { pos := e.Pos() if !c.IsInBounds(pos) { @@ -67,16 +145,11 @@ func (c *ControlBase) Handle(ctx Context, e Event) { if start, ok := c.drag.IsDragging(); ok { var move = c.ToControlPosition(e.Pos()) c.drag.Move(move) - if c.onDragMove != nil { - c.onDragMove(start, move) - } - } else { - var start = c.ToControlPosition(e.Pos()) - c.drag.Start(start) - if c.onDragStart != nil { - c.onDragStart(start) - } + return c.dragMoved.Notify(ctx, DragMovedArgs{Start: start, Current: move}) } + var start = c.ToControlPosition(e.Pos()) + c.drag.Start(start) + return c.dragStarted.Notify(ctx, DragStartedArgs{Start: start}) } case *MouseLeaveEvent: c.over = false @@ -84,28 +157,19 @@ func (c *ControlBase) Handle(ctx Context, e Event) { c.over = over(e.MouseEvent) if c.over && e.Button == MouseButtonLeft { c.pressed = true + return c.clicked.Notify(ctx, ControlClickedArgs{Position: e.Pos(), Button: e.Button}) } case *MouseButtonUpEvent: if e.Button == MouseButtonLeft { + c.pressed = false if start, ok := c.drag.IsDragging(); ok { var end = c.ToControlPosition(e.Pos()) c.drag.Cancel() - if c.onDragEnd != nil { - c.onDragEnd(start, end) - } + return c.dragEnded.Notify(ctx, DragEndedArgs{Start: start, End: end}) } - if c.pressed { - if c.onClick != nil { - c.onClick(c.ToControlPosition(e.Pos()), e.Button) - } - } - c.pressed = false } } - - if c.Tooltip != "" && c.over { - ctx.ShowTooltip(c.Tooltip) - } + return false } func (c *ControlBase) FontColor(ctx Context) color.Color { @@ -143,17 +207,13 @@ func (c *ControlBase) IsPressed() bool { return c.pressed } func (c *ControlBase) Offset() geom.PointF32 { return c.offset } -func (c *ControlBase) OnClick(fn ClickFn) { c.onClick = fn } +func (c *ControlBase) ControlClicked() ControlClickedEventHandler { return &c.clicked } -func (c *ControlBase) OnDragStart(fn DragStartFn) { - c.onDragStart = fn -} +func (c *ControlBase) DragEnded() DragEndedEventHandler { return &c.dragEnded } -func (c *ControlBase) OnDragMove(fn DragMoveFn) { - c.onDragMove = fn -} +func (c *ControlBase) DragMoved() DragMovedEventHandler { return &c.dragMoved } -func (c *ControlBase) OnDragEnd(fn DragEndFn) { c.onDragEnd = fn } +func (c *ControlBase) DragStarted() DragStartedEventHandler { return &c.dragStarted } func (c *ControlBase) Render(Context) {} diff --git a/ui/events.go b/ui/events.go new file mode 100644 index 0000000..066627b --- /dev/null +++ b/ui/events.go @@ -0,0 +1,46 @@ +package ui + +type EventFn func(Context) bool + +type EventStateFn func(Context, interface{}) + +func NewEvents() *Events { + return &Events{events: map[uint]EventStateFn{}} +} + +type Events struct { + nextID uint + events map[uint]EventStateFn +} + +type EventHandler interface { + AddHandler(EventFn) uint + AddHandlerState(EventStateFn) uint + RemoveHandler(uint) +} + +func (e *Events) Notify(ctx Context, state interface{}) bool { + if e.events == nil { + return false + } + for _, handler := range e.events { + handler(ctx, state) + } + return len(e.events) > 0 +} + +func (e *Events) AddHandler(handler EventFn) uint { + return e.AddHandlerState(func(ctx Context, _ interface{}) { handler(ctx) }) +} + +func (e *Events) AddHandlerState(handler EventStateFn) uint { + if e.events == nil { + e.events = map[uint]EventStateFn{} + } + id := e.nextID + e.nextID++ + e.events[id] = handler + return id +} + +func (e *Events) RemoveHandler(id uint) { delete(e.events, id) } diff --git a/ui/examples/01_basic/basic.go b/ui/examples/01_basic/basic.go index 9cb90e8..a77904b 100644 --- a/ui/examples/01_basic/basic.go +++ b/ui/examples/01_basic/basic.go @@ -4,10 +4,9 @@ import ( "image/color" "log" - // _ "opslag.de/schobers/zntg/sdlui" // import the renderer for the UI - _ "opslag.de/schobers/zntg/allg5ui" // import the renderer for the UI + _ "opslag.de/schobers/zntg/sdlui" // import the renderer for the UI + // _ "opslag.de/schobers/zntg/allg5ui" // import the renderer for the UI - "opslag.de/schobers/geom" "opslag.de/schobers/zntg/ui" ) @@ -59,7 +58,7 @@ func (b *basic) Init(ctx ui.Context) error { b.Text = "Type here..." })), 8), ui.Margin(ui.BuildButton("Quit", func(b *ui.Button) { - b.OnClick(func(geom.PointF32, ui.MouseButton) { + b.ControlClicked().AddHandler(func(ui.Context, ui.ControlClickedArgs) { ctx.Quit() }) b.Tooltip = "Will quit the application" diff --git a/ui/overflow.go b/ui/overflow.go index 037796b..7fb82ad 100644 --- a/ui/overflow.go +++ b/ui/overflow.go @@ -95,7 +95,7 @@ func (o *overflow) DesiredSize(ctx Context) geom.PointF32 { return geom.PtF32(geom.NaN32(), geom.NaN32()) } -func (o *overflow) Handle(ctx Context, e Event) { +func (o *overflow) Handle(ctx Context, e Event) bool { if o.Content != o.proxied { o.hor.ContentOffset = 0 o.ver.ContentOffset = 0 @@ -117,10 +117,11 @@ func (o *overflow) Handle(ctx Context, e Event) { content.Max = content.Max.Add(contentO) if e.MouseWheel != 0 && e.Pos().In(content) { o.ver.ContentOffset = geom.Max32(0, geom.Min32(o.ver.ContentLength-content.Dy(), o.ver.ContentOffset-36*e.MouseWheel)) + return true } } } - o.Content.Handle(ctx, e) + return o.Content.Handle(ctx, e) } func (o *overflow) IsInBounds(p geom.PointF32) bool { return p.Sub(o.offset).In(o.bounds) } diff --git a/ui/overlays.go b/ui/overlays.go index e292328..49a9b0e 100644 --- a/ui/overlays.go +++ b/ui/overlays.go @@ -40,13 +40,13 @@ func (o *Overlays) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.Po } } -func (o *Overlays) Handle(ctx Context, e Event) { +func (o *Overlays) Handle(ctx Context, e Event) bool { for overlay, visible := range o.visible { if visible { - o.overlays[overlay].Handle(ctx, e) + o.overlays[overlay].Handle(ctx, e) // ignore handled state on overlays } } - o.Proxy.Handle(ctx, e) + return o.Proxy.Handle(ctx, e) } func (o *Overlays) Hide(name string) { diff --git a/ui/proxy.go b/ui/proxy.go index 67f6c05..f64f28c 100644 --- a/ui/proxy.go +++ b/ui/proxy.go @@ -16,8 +16,8 @@ func (p *Proxy) DesiredSize(ctx Context) geom.PointF32 { return p.Content.DesiredSize(ctx) } -func (p *Proxy) Handle(ctx Context, e Event) { - p.Content.Handle(ctx, e) +func (p *Proxy) Handle(ctx Context, e Event) bool { + return p.Content.Handle(ctx, e) } func (p *Proxy) Render(ctx Context) { @@ -34,20 +34,4 @@ func (p *Proxy) IsOver() bool { return p.Content.IsOver() } func (p *Proxy) Offset() geom.PointF32 { return p.Content.Offset() } -func (p *Proxy) OnClick(fn ClickFn) { - p.Content.OnClick(fn) -} - -func (p *Proxy) OnDragStart(fn DragStartFn) { - p.Content.OnDragStart(fn) -} - -func (p *Proxy) OnDragMove(fn DragMoveFn) { - p.Content.OnDragMove(fn) -} - -func (p *Proxy) OnDragEnd(fn DragEndFn) { - p.Content.OnDragEnd(fn) -} - func (p *Proxy) Parent() Control { return p.Content.Parent() } diff --git a/ui/scrollbar.go b/ui/scrollbar.go index 487c088..0ced53b 100644 --- a/ui/scrollbar.go +++ b/ui/scrollbar.go @@ -19,14 +19,14 @@ type Scrollbar struct { func BuildScrollbar(o Orientation, fn func(s *Scrollbar)) *Scrollbar { var s = &Scrollbar{Orientation: o, ContentLength: 0, ContentOffset: 0} - s.handle.OnDragStart(func(_ geom.PointF32) { + s.handle.DragStarted().AddHandler(func(Context, DragStartedArgs) { s.startDragOffset = s.ContentOffset }) - s.handle.OnDragMove(func(start, move geom.PointF32) { + s.handle.DragMoved().AddHandler(func(_ Context, args DragMovedArgs) { var length = s.Orientation.SizeParallel(s.bounds) var handleMaxOffset = length - s.Orientation.SizeParallel(s.handle.bounds) var hidden = s.ContentLength - length - var offset = (s.Orientation.LengthParallel(move) - s.Orientation.LengthParallel(start)) / handleMaxOffset + var offset = (s.Orientation.LengthParallel(args.Current) - s.Orientation.LengthParallel(args.Start)) / handleMaxOffset s.ContentOffset = geom.Max32(0, geom.Min32(s.startDragOffset+offset*hidden, hidden)) }) if fn != nil { @@ -44,15 +44,16 @@ func (s *Scrollbar) DesiredSize(ctx Context) geom.PointF32 { return s.Orientation.Pt(geom.NaN32(), ctx.Style().Dimensions.ScrollbarWidth) } -func (s *Scrollbar) Handle(ctx Context, e Event) { +func (s *Scrollbar) Handle(ctx Context, e Event) bool { s.handle.Handle(ctx, e) switch e := e.(type) { case *MouseMoveEvent: if e.MouseWheel != 0 && e.Pos().Sub(s.offset).In(s.bounds) { s.ContentOffset = geom.Max32(0, geom.Min32(s.ContentLength-s.Orientation.SizeParallel(s.bounds), s.ContentOffset-36*e.MouseWheel)) + return true } } - s.ControlBase.Handle(ctx, e) + return s.ControlBase.Handle(ctx, e) } func (s *Scrollbar) Render(ctx Context) { diff --git a/ui/slider.go b/ui/slider.go index 95c594e..bbc4dea 100644 --- a/ui/slider.go +++ b/ui/slider.go @@ -20,8 +20,8 @@ type Slider struct { func BuildSlider(o Orientation, fn func(s *Slider)) *Slider { s := &Slider{Orientation: o} - s.handle.OnDragStart(func(start geom.PointF32) { s.handleDrag(start) }) - s.handle.OnDragMove(func(_, pos geom.PointF32) { s.handleDrag(pos) }) + s.handle.DragStarted().AddHandler(func(_ Context, args DragStartedArgs) { s.handleDrag(args.Start) }) + s.handle.DragMoved().AddHandler(func(_ Context, args DragMovedArgs) { s.handleDrag(args.Current) }) if fn != nil { fn(s) } @@ -109,10 +109,15 @@ func (s *Slider) DesiredSize(ctx Context) geom.PointF32 { return geom.PtF32(w, geom.NaN32()) } -func (s *Slider) Handle(ctx Context, e Event) { - s.handle.Handle(ctx, e) - s.ControlBase.Handle(ctx, e) - s.setValue(s.Value) +func (s *Slider) Handle(ctx Context, e Event) bool { + defer s.setValue(s.Value) + if s.handle.Handle(ctx, e) { + return true + } + if s.ControlBase.Handle(ctx, e) { + return true + } + return false } func (s *Slider) Render(ctx Context) { @@ -157,11 +162,12 @@ func (h *sliderHandle) texture(ctx Context) Texture { return h.handle } -func (h *sliderHandle) Handle(ctx Context, e Event) { +func (h *sliderHandle) Handle(ctx Context, e Event) bool { h.ControlBase.Handle(ctx, e) if h.IsOver() { ctx.Renderer().SetMouseCursor(MouseCursorPointer) } + return true } func (h *sliderHandle) Render(ctx Context) { diff --git a/ui/textbox.go b/ui/textbox.go index c441f32..aab2a72 100644 --- a/ui/textbox.go +++ b/ui/textbox.go @@ -125,9 +125,17 @@ func (b *TextBox) selectionRange() (int, int) { return start, end } -func (b *TextBox) Handle(ctx Context, e Event) { - b.ControlBase.Handle(ctx, e) +func (b *TextBox) Handle(ctx Context, e Event) bool { + if b.ControlBase.Handle(ctx, e) { + return true + } b.box.Handle(ctx, e) + + if b.over { + ctx.Renderer().SetMouseCursor(MouseCursorText) + } + ctx.Animate() + switch e := e.(type) { case *MouseButtonDownEvent: if b.over { @@ -135,13 +143,14 @@ func (b *TextBox) Handle(ctx Context, e Event) { b.Selection.Caret = b.mousePosToCaretPos(ctx, e.MouseEvent) b.Selection.SetSelectionToCaret() b.blink = time.Now() - } else { - b.Focus = false + return true } + b.Focus = false case *MouseMoveEvent: if b.Focus && b.pressed && b.over { b.Selection.Caret = b.mousePosToCaretPos(ctx, e.MouseEvent) b.Selection.End = b.Selection.Caret + return true } case *KeyDownEvent: if !b.Focus { @@ -217,16 +226,15 @@ func (b *TextBox) Handle(ctx Context, e Event) { DefaultClipboard.WriteText(b.cut()) } } + return true case *TextInputEvent: caret := b.Selection.Caret b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:]) b.Selection.Caret = caret + 1 b.Selection.SetSelectionToCaret() + return true } - if b.over { - ctx.Renderer().SetMouseCursor(MouseCursorText) - } - ctx.Animate() + return false } func (b *TextBox) Render(ctx Context) { diff --git a/ui/tooltip.go b/ui/tooltip.go index 8417a4a..cb09023 100644 --- a/ui/tooltip.go +++ b/ui/tooltip.go @@ -27,7 +27,7 @@ func (t *Tooltip) FontName(ctx Context) string { return name } -func (t *Tooltip) Handle(Context, Event) {} +func (t *Tooltip) Handle(Context, Event) bool { return false } func (t *Tooltip) Render(ctx Context) { if len(t.Text) == 0 {