Added Events and changed event handlers of controls.

Removed EventHandlers from Control interface.
This commit is contained in:
Sander Schobers 2020-05-15 19:00:43 +02:00
parent 4e37f4b23e
commit d742fba7e9
14 changed files with 220 additions and 115 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

46
ui/events.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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