diff --git a/backends/x/event.go b/backends/x/event.go index 5f6b773..d5da4ad 100644 --- a/backends/x/event.go +++ b/backends/x/event.go @@ -14,7 +14,7 @@ type scrollSum struct { const scrollDistance = 16 -func (sum *scrollSum) add (button xproto.Button, window *Window, state uint16) { +func (sum *scrollSum) add (button xproto.Button, window *window, state uint16) { shift := (state & xproto.ModMaskShift) > 0 || (state & window.backend.modifierMasks.shiftLock) > 0 @@ -44,7 +44,7 @@ func (sum *scrollSum) add (button xproto.Button, window *Window, state uint16) { } -func (window *Window) handleExpose ( +func (window *window) handleExpose ( connection *xgbutil.XUtil, event xevent.ExposeEvent, ) { @@ -52,7 +52,7 @@ func (window *Window) handleExpose ( window.pushRegion(region) } -func (window *Window) handleConfigureNotify ( +func (window *window) handleConfigureNotify ( connection *xgbutil.XUtil, event xevent.ConfigureNotifyEvent, ) { @@ -81,7 +81,7 @@ func (window *Window) handleConfigureNotify ( } } -func (window *Window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (found bool) { +func (window *window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (found bool) { nextEvents := xevent.Peek(window.backend.connection) if len(nextEvents) > 0 { untypedEvent := nextEvents[0] @@ -97,7 +97,7 @@ func (window *Window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (fo return false } -func (window *Window) modifiersFromState ( +func (window *window) modifiersFromState ( state uint16, ) ( modifiers input.Modifiers, @@ -114,7 +114,7 @@ func (window *Window) modifiersFromState ( } } -func (window *Window) handleKeyPress ( +func (window *window) handleKeyPress ( connection *xgbutil.XUtil, event xevent.KeyPressEvent, ) { @@ -141,7 +141,7 @@ func (window *Window) handleKeyPress ( } } -func (window *Window) handleKeyRelease ( +func (window *window) handleKeyRelease ( connection *xgbutil.XUtil, event xevent.KeyReleaseEvent, ) { @@ -175,23 +175,25 @@ func (window *Window) handleKeyRelease ( } } -func (window *Window) handleButtonPress ( +func (window *window) handleButtonPress ( connection *xgbutil.XUtil, event xevent.ButtonPressEvent, ) { if window.child == nil { return } - if child, ok := window.child.(elements.MouseTarget); ok { - buttonEvent := *event.ButtonPressEvent - if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { + buttonEvent := *event.ButtonPressEvent + if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { + if child, ok := window.child.(elements.ScrollTarget); ok { sum := scrollSum { } sum.add(buttonEvent.Detail, window, buttonEvent.State) window.compressScrollSum(buttonEvent, &sum) - child.HandleMouseScroll ( + child.HandleScroll ( int(buttonEvent.EventX), int(buttonEvent.EventY), float64(sum.x), float64(sum.y)) - } else { + } + } else { + if child, ok := window.child.(elements.MouseTarget); ok { child.HandleMouseDown ( int(buttonEvent.EventX), int(buttonEvent.EventY), @@ -201,7 +203,7 @@ func (window *Window) handleButtonPress ( } -func (window *Window) handleButtonRelease ( +func (window *window) handleButtonRelease ( connection *xgbutil.XUtil, event xevent.ButtonReleaseEvent, ) { @@ -217,21 +219,21 @@ func (window *Window) handleButtonRelease ( } } -func (window *Window) handleMotionNotify ( +func (window *window) handleMotionNotify ( connection *xgbutil.XUtil, event xevent.MotionNotifyEvent, ) { if window.child == nil { return } - if child, ok := window.child.(elements.MouseTarget); ok { + if child, ok := window.child.(elements.MotionTarget); ok { motionEvent := window.compressMotionNotify(*event.MotionNotifyEvent) - child.HandleMouseMove ( + child.HandleMotion ( int(motionEvent.EventX), int(motionEvent.EventY)) } } -func (window *Window) compressExpose ( +func (window *window) compressExpose ( firstEvent xproto.ExposeEvent, ) ( lastEvent xproto.ExposeEvent, @@ -268,7 +270,7 @@ func (window *Window) compressExpose ( return } -func (window *Window) compressConfigureNotify ( +func (window *window) compressConfigureNotify ( firstEvent xproto.ConfigureNotifyEvent, ) ( lastEvent xproto.ConfigureNotifyEvent, @@ -296,7 +298,7 @@ func (window *Window) compressConfigureNotify ( return } -func (window *Window) compressScrollSum ( +func (window *window) compressScrollSum ( firstEvent xproto.ButtonPressEvent, sum *scrollSum, ) { @@ -323,7 +325,7 @@ func (window *Window) compressScrollSum ( return } -func (window *Window) compressMotionNotify ( +func (window *window) compressMotionNotify ( firstEvent xproto.MotionNotifyEvent, ) ( lastEvent xproto.MotionNotifyEvent, diff --git a/backends/x/window.go b/backends/x/window.go index 67d352a..92b2251 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -14,7 +14,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/elements" // import "runtime/debug" -type Window struct { +type window struct { backend *Backend xWindow *xwindow.Window xCanvas *xgraphics.Image @@ -40,7 +40,7 @@ func (backend *Backend) NewWindow ( ) { if backend == nil { panic("nil backend") } - window := &Window { backend: backend } + window := &window { backend: backend } window.xWindow, err = xwindow.Generate(backend.connection) if err != nil { return } @@ -90,37 +90,51 @@ func (backend *Backend) NewWindow ( return } -func (window *Window) Adopt (child elements.Element) { +func (window *window) NotifyMinimumSizeChange (child elements.Element) { + window.childMinimumSizeChangeCallback(child.MinimumSize()) +} + +func (window *window) RequestFocus ( + child elements.Focusable, +) ( + granted bool, +) { + return true +} + +func (window *window) RequestFocusNext (child elements.Focusable) { + if child, ok := window.child.(elements.Focusable); ok { + if !child.HandleFocus(input.KeynavDirectionForward) { + child.HandleUnfocus() + } + } +} + +func (window *window) RequestFocusPrevious (child elements.Focusable) { + if child, ok := window.child.(elements.Focusable); ok { + if !child.HandleFocus(input.KeynavDirectionBackward) { + child.HandleUnfocus() + } + } +} + +func (window *window) Adopt (child elements.Element) { // disown previous child if window.child != nil { - window.child.OnDamage(nil) - window.child.OnMinimumSizeChange(nil) - } - if previousChild, ok := window.child.(elements.Focusable); ok { - previousChild.OnFocusRequest(nil) - previousChild.OnFocusMotionRequest(nil) - if previousChild.Focused() { - previousChild.HandleUnfocus() - } + window.child.SetParent(nil) + window.child.DrawTo(nil, image.Rectangle { }, nil) } // adopt new child window.child = child + child.SetParent(window) if newChild, ok := child.(elements.Themeable); ok { newChild.SetTheme(window.theme) } if newChild, ok := child.(elements.Configurable); ok { newChild.SetConfig(window.config) } - if newChild, ok := child.(elements.Focusable); ok { - newChild.OnFocusRequest(window.childSelectionRequestCallback) - } if child != nil { - child.OnDamage(window.childDrawCallback) - child.OnMinimumSizeChange (func () { - window.childMinimumSizeChangeCallback ( - child.MinimumSize()) - }) if !window.childMinimumSizeChangeCallback(child.MinimumSize()) { window.resizeChildToFit() window.redrawChildEntirely() @@ -128,19 +142,19 @@ func (window *Window) Adopt (child elements.Element) { } } -func (window *Window) Child () (child elements.Element) { +func (window *window) Child () (child elements.Element) { child = window.child return } -func (window *Window) SetTitle (title string) { +func (window *window) SetTitle (title string) { ewmh.WmNameSet ( window.backend.connection, window.xWindow.Id, title) } -func (window *Window) SetIcon (sizes []image.Image) { +func (window *window) SetIcon (sizes []image.Image) { wmIcons := []ewmh.WmIcon { } for _, icon := range sizes { @@ -179,7 +193,7 @@ func (window *Window) SetIcon (sizes []image.Image) { wmIcons) } -func (window *Window) Show () { +func (window *window) Show () { if window.child == nil { window.xCanvas.For (func (x, y int) xgraphics.BGRA { return xgraphics.BGRA { } @@ -191,35 +205,35 @@ func (window *Window) Show () { window.xWindow.Map() } -func (window *Window) Hide () { +func (window *window) Hide () { window.xWindow.Unmap() } -func (window *Window) Close () { +func (window *window) Close () { if window.onClose != nil { window.onClose() } delete(window.backend.windows, window.xWindow.Id) window.xWindow.Destroy() } -func (window *Window) OnClose (callback func ()) { +func (window *window) OnClose (callback func ()) { window.onClose = callback } -func (window *Window) SetTheme (theme theme.Theme) { +func (window *window) SetTheme (theme theme.Theme) { window.theme = theme if child, ok := window.child.(elements.Themeable); ok { child.SetTheme(theme) } } -func (window *Window) SetConfig (config config.Config) { +func (window *window) SetConfig (config config.Config) { window.config = config if child, ok := window.child.(elements.Configurable); ok { child.SetConfig(config) } } -func (window *Window) reallocateCanvas () { +func (window *window) reallocateCanvas () { window.canvas.Reallocate(window.metrics.width, window.metrics.height) previousWidth, previousHeight := 0, 0 @@ -250,23 +264,28 @@ func (window *Window) reallocateCanvas () { } -func (window *Window) redrawChildEntirely () { - window.pushRegion(window.paste(window.canvas)) - +func (window *window) redrawChildEntirely () { + window.paste(window.canvas.Bounds()) + window.pushRegion(window.canvas.Bounds()) } -func (window *Window) resizeChildToFit () { +func (window *window) resizeChildToFit () { window.skipChildDrawCallback = true - window.child.DrawTo(window.canvas, window.canvas.Bounds()) + window.child.DrawTo ( + window.canvas, + window.canvas.Bounds(), + window.childDrawCallback) window.skipChildDrawCallback = false } -func (window *Window) childDrawCallback (region canvas.Canvas) { +func (window *window) childDrawCallback (region image.Rectangle) { if window.skipChildDrawCallback { return } - window.pushRegion(window.paste(region)) + window.paste(region) + window.pushRegion(region) } -func (window *Window) paste (canvas canvas.Canvas) (updatedRegion image.Rectangle) { +func (window *window) paste (region image.Rectangle) { + canvas := canvas.Cut(window.canvas, region) data, stride := canvas.Buffer() bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds()) @@ -286,11 +305,9 @@ func (window *Window) paste (canvas canvas.Canvas) (updatedRegion image.Rectangl dstData[index + 3] = rgba.A } } - - return bounds } -func (window *Window) childMinimumSizeChangeCallback (width, height int) (resized bool) { +func (window *window) childMinimumSizeChangeCallback (width, height int) (resized bool) { icccm.WmNormalHintsSet ( window.backend.connection, window.xWindow.Id, @@ -312,28 +329,7 @@ func (window *Window) childMinimumSizeChangeCallback (width, height int) (resize return false } -func (window *Window) childSelectionRequestCallback () (granted bool) { - if _, ok := window.child.(elements.Focusable); ok { - return true - } - return false -} - -func (window *Window) childSelectionMotionRequestCallback ( - direction input.KeynavDirection, -) ( - granted bool, -) { - if child, ok := window.child.(elements.Focusable); ok { - if !child.HandleFocus(direction) { - child.HandleUnfocus() - } - return true - } - return true -} - -func (window *Window) pushRegion (region image.Rectangle) { +func (window *window) pushRegion (region image.Rectangle) { if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") } image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image) if ok { diff --git a/backends/x/x.go b/backends/x/x.go index eefd914..d72ce1f 100644 --- a/backends/x/x.go +++ b/backends/x/x.go @@ -30,7 +30,7 @@ type Backend struct { theme theme.Theme config config.Config - windows map[xproto.Window] *Window + windows map[xproto.Window] *window open bool } @@ -38,7 +38,7 @@ type Backend struct { // NewBackend instantiates an X backend. func NewBackend () (output tomo.Backend, err error) { backend := &Backend { - windows: map[xproto.Window] *Window { }, + windows: map[xproto.Window] *window { }, doChannel: make(chan func (), 0), theme: theme.Default { }, config: config.Default { }, @@ -79,7 +79,7 @@ func (backend *Backend) Stop () { if !backend.open { return } backend.open = false - toClose := []*Window { } + toClose := []*window { } for _, window := range backend.windows { toClose = append(toClose, window) } diff --git a/elements/basic/button.go b/elements/basic/button.go index e496876..7b63b48 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -35,9 +35,9 @@ type Button struct { func NewButton (text string) (element *Button) { element = &Button { showText: true } element.theme.Case = theme.C("basic", "button") - element.Core, element.core = core.NewCore(element.drawAll) + element.Core, element.core = core.NewCore(element, element.drawAll) element.FocusableCore, - element.focusableControl = core.NewFocusableCore(element.drawAndPush) + element.focusableControl = core.NewFocusableCore(element.core, element.drawAndPush) element.SetText(text) return } @@ -61,9 +61,6 @@ func (element *Button) HandleMouseUp (x, y int, button input.Button) { element.drawAndPush() } -func (element *Button) HandleMouseMove (x, y int) { } -func (element *Button) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } - func (element *Button) HandleKeyDown (key input.Key, modifiers input.Modifiers) { if !element.Enabled() { return } if key == input.KeyEnter { diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index 14690bb..b686ffa 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -29,9 +29,9 @@ type Checkbox struct { func NewCheckbox (text string, checked bool) (element *Checkbox) { element = &Checkbox { checked: checked } element.theme.Case = theme.C("basic", "checkbox") - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.FocusableCore, - element.focusableControl = core.NewFocusableCore(element.redo) + element.focusableControl = core.NewFocusableCore(element.core, element.redo) element.SetText(text) return } @@ -65,9 +65,6 @@ func (element *Checkbox) HandleMouseUp (x, y int, button input.Button) { } } -func (element *Checkbox) HandleMouseMove (x, y int) { } -func (element *Checkbox) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } - func (element *Checkbox) HandleKeyDown (key input.Key, modifiers input.Modifiers) { if key == input.KeyEnter { element.pressed = true diff --git a/elements/basic/container.go b/elements/basic/container.go index ec8a024..8875fa9 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -32,8 +32,8 @@ type Container struct { func NewContainer (layout layouts.Layout) (element *Container) { element = &Container { } element.theme.Case = theme.C("basic", "container") - element.Core, element.core = core.NewCore(element.redoAll) - element.Propagator = core.NewPropagator(element) + element.Core, element.core = core.NewCore(element, element.redoAll) + element.Propagator = core.NewPropagator(element, element.core) element.SetLayout(layout) return } @@ -51,33 +51,13 @@ func (element *Container) SetLayout (layout layouts.Layout) { // the element will expand (instead of contract to its minimum size), in // whatever way is defined by the current layout. func (element *Container) Adopt (child elements.Element, expand bool) { - // set event handlers if child0, ok := child.(elements.Themeable); ok { child0.SetTheme(element.theme.Theme) } if child0, ok := child.(elements.Configurable); ok { child0.SetConfig(element.config.Config) } - child.OnDamage (func (region canvas.Canvas) { - element.core.DamageRegion(region.Bounds()) - }) - child.OnMinimumSizeChange (func () { - // TODO: this could probably stand to be more efficient. I mean - // seriously? - element.updateMinimumSize() - element.redoAll() - element.core.DamageAll() - }) - if child0, ok := child.(elements.Focusable); ok { - child0.OnFocusRequest (func () (granted bool) { - return element.childFocusRequestCallback(child0) - }) - child0.OnFocusMotionRequest ( - func (direction input.KeynavDirection) (granted bool) { - if element.onFocusMotionRequest == nil { return } - return element.onFocusMotionRequest(direction) - }) - } + child.SetParent(element) // add child element.children = append (element.children, layouts.LayoutEntry { @@ -136,14 +116,12 @@ func (element *Container) Disown (child elements.Element) { } func (element *Container) clearChildEventHandlers (child elements.Element) { - child.DrawTo(nil, image.Rectangle { }) - child.OnDamage(nil) - child.OnMinimumSizeChange(nil) - if child0, ok := child.(elements.Focusable); ok { - child0.OnFocusRequest(nil) - child0.OnFocusMotionRequest(nil) - if child0.Focused() { - child0.HandleUnfocus() + child.DrawTo(nil, image.Rectangle { }, nil) + child.SetParent(nil) + + if child, ok := child.(elements.Focusable); ok { + if child.Focused() { + child.HandleUnfocus() } } } @@ -200,7 +178,7 @@ func (element *Container) redoAll () { // remove child canvasses so that any operations done in here will not // cause a child to draw to a wack ass canvas. for _, entry := range element.children { - entry.DrawTo(nil, entry.Bounds) + entry.DrawTo(nil, entry.Bounds, nil) } // do a layout @@ -220,10 +198,20 @@ func (element *Container) redoAll () { for _, entry := range element.children { entry.DrawTo ( canvas.Cut(element.core, entry.Bounds), - entry.Bounds) + entry.Bounds, func (region image.Rectangle) { + element.core.DamageRegion(region) + }) } } +// NotifyMinimumSizeChange notifies the container that the minimum size of a +// child element has changed. +func (element *Container) NotifyMinimumSizeChange (child elements.Element) { + element.updateMinimumSize() + element.redoAll() + element.core.DamageAll() +} + // SetTheme sets the element's theme. func (element *Container) SetTheme (new theme.Theme) { if new == element.theme.Theme { return } @@ -241,32 +229,6 @@ func (element *Container) SetConfig (new config.Config) { element.redoAll() } -func (element *Container) OnFocusRequest (callback func () (granted bool)) { - element.onFocusRequest = callback - element.Propagator.OnFocusRequest(callback) -} - -func (element *Container) OnFocusMotionRequest ( - callback func (direction input.KeynavDirection) (granted bool), -) { - element.onFocusMotionRequest = callback - element.Propagator.OnFocusMotionRequest(callback) -} - -func (element *Container) childFocusRequestCallback ( - child elements.Focusable, -) ( - granted bool, -) { - if element.onFocusRequest != nil && element.onFocusRequest() { - element.Propagator.HandleUnfocus() - element.Propagator.HandleFocus(input.KeynavDirectionNeutral) - return true - } else { - return false - } -} - func (element *Container) updateMinimumSize () { margin := element.theme.Margin(theme.PatternBackground) padding := element.theme.Padding(theme.PatternBackground) diff --git a/elements/basic/documentContainer.go b/elements/basic/documentContainer.go index 81e8c60..d82eaf5 100644 --- a/elements/basic/documentContainer.go +++ b/elements/basic/documentContainer.go @@ -1,7 +1,6 @@ package basicElements import "image" -import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/canvas" @@ -22,9 +21,7 @@ type DocumentContainer struct { config config.Wrapped theme theme.Wrapped - - onFocusRequest func () (granted bool) - onFocusMotionRequest func (input.KeynavDirection) (granted bool) + onScrollBoundsChange func () } @@ -32,8 +29,8 @@ type DocumentContainer struct { func NewDocumentContainer () (element *DocumentContainer) { element = &DocumentContainer { } element.theme.Case = theme.C("basic", "documentContainer") - element.Core, element.core = core.NewCore(element.redoAll) - element.Propagator = core.NewPropagator(element) + element.Core, element.core = core.NewCore(element, element.redoAll) + element.Propagator = core.NewPropagator(element, element.core) return } @@ -46,35 +43,14 @@ func (element *DocumentContainer) Adopt (child elements.Element) { if child0, ok := child.(elements.Configurable); ok { child0.SetConfig(element.config.Config) } - child.OnDamage (func (region canvas.Canvas) { - element.core.DamageRegion(region.Bounds()) - }) - child.OnMinimumSizeChange (func () { - element.redoAll() - element.core.DamageAll() - }) - if child0, ok := child.(elements.Flexible); ok { - child0.OnFlexibleHeightChange (func () { - element.redoAll() - element.core.DamageAll() - }) - } - if child0, ok := child.(elements.Focusable); ok { - child0.OnFocusRequest (func () (granted bool) { - return element.childFocusRequestCallback(child0) - }) - child0.OnFocusMotionRequest ( - func (direction input.KeynavDirection) (granted bool) { - if element.onFocusMotionRequest == nil { return } - return element.onFocusMotionRequest(direction) - }) - } // add child element.children = append (element.children, layouts.LayoutEntry { Element: child, }) + child.SetParent(element) + // refresh stale data element.reflectChildProperties() if element.core.HasImage() && !element.warping { @@ -123,14 +99,12 @@ func (element *DocumentContainer) Disown (child elements.Element) { } func (element *DocumentContainer) clearChildEventHandlers (child elements.Element) { - child.DrawTo(nil, image.Rectangle { }) - child.OnDamage(nil) - child.OnMinimumSizeChange(nil) - if child0, ok := child.(elements.Focusable); ok { - child0.OnFocusRequest(nil) - child0.OnFocusMotionRequest(nil) - if child0.Focused() { - child0.HandleUnfocus() + child.DrawTo(nil, image.Rectangle { }, nil) + child.SetParent(nil) + + if child, ok := child.(elements.Focusable); ok { + if child.Focused() { + child.HandleUnfocus() } } } @@ -204,14 +178,14 @@ func (element *DocumentContainer) redoAll () { artist.DrawShatter(element.core, pattern, element.Bounds(), rocks...) element.partition() - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } func (element *DocumentContainer) partition () { for _, entry := range element.children { - entry.DrawTo(nil, entry.Bounds) + entry.DrawTo(nil, entry.Bounds, nil) } // cut our canvas up and give peices to child elements @@ -219,11 +193,30 @@ func (element *DocumentContainer) partition () { if entry.Bounds.Overlaps(element.Bounds()) { entry.DrawTo ( canvas.Cut(element.core, entry.Bounds), - entry.Bounds) + entry.Bounds, func (region image.Rectangle) { + element.core.DamageRegion(region) + }) } } } +// NotifyMinimumSizeChange notifies the container that the minimum size of a +// child element has changed. +func (element *DocumentContainer) NotifyMinimumSizeChange (child elements.Element) { + element.redoAll() + element.core.DamageAll() +} + +// NotifyFlexibleHeightChange notifies the parent that the parameters +// affecting a child's flexible height have changed. This method is +// expected to be called by flexible child element when their content +// changes. +func (element *DocumentContainer) NotifyFlexibleHeightChange (child elements.Flexible) { + element.redoAll() + element.core.DamageAll() +} + + // SetTheme sets the element's theme. func (element *DocumentContainer) SetTheme (new theme.Theme) { if new == element.theme.Theme { return } @@ -239,18 +232,6 @@ func (element *DocumentContainer) SetConfig (new config.Config) { element.redoAll() } -func (element *DocumentContainer) OnFocusRequest (callback func () (granted bool)) { - element.onFocusRequest = callback - element.Propagator.OnFocusRequest(callback) -} - -func (element *DocumentContainer) OnFocusMotionRequest ( - callback func (direction input.KeynavDirection) (granted bool), -) { - element.onFocusMotionRequest = callback - element.Propagator.OnFocusMotionRequest(callback) -} - // ScrollContentBounds returns the full content size of the element. func (element *DocumentContainer) ScrollContentBounds () image.Rectangle { return element.contentBounds @@ -282,6 +263,12 @@ func (element *DocumentContainer) ScrollTo (position image.Point) { } } +// OnScrollBoundsChange sets a function to be called when the element's viewport +// bounds, content bounds, or scroll axes change. +func (element *DocumentContainer) OnScrollBoundsChange (callback func ()) { + element.onScrollBoundsChange = callback +} + func (element *DocumentContainer) maxScrollHeight () (height int) { padding := element.theme.Padding(theme.PatternSunken) viewportHeight := element.Bounds().Dy() - padding.Vertical() @@ -295,12 +282,6 @@ func (element *DocumentContainer) ScrollAxes () (horizontal, vertical bool) { return false, true } -// OnScrollBoundsChange sets a function to be called when the element's -// ScrollContentBounds, ScrollViewportBounds, or ScrollAxes are changed. -func (element *DocumentContainer) OnScrollBoundsChange(callback func()) { - element.onScrollBoundsChange = callback -} - func (element *DocumentContainer) reflectChildProperties () { focusable := false for _, entry := range element.children { @@ -315,20 +296,6 @@ func (element *DocumentContainer) reflectChildProperties () { } } -func (element *DocumentContainer) childFocusRequestCallback ( - child elements.Focusable, -) ( - granted bool, -) { - if element.onFocusRequest != nil && element.onFocusRequest() { - element.Propagator.HandleUnfocus() - element.Propagator.HandleFocus(input.KeynavDirectionNeutral) - return true - } else { - return false - } -} - func (element *DocumentContainer) doLayout () { margin := element.theme.Margin(theme.PatternBackground) padding := element.theme.Padding(theme.PatternBackground) diff --git a/elements/basic/icon.go b/elements/basic/icon.go index bd92298..3b6750f 100644 --- a/elements/basic/icon.go +++ b/elements/basic/icon.go @@ -19,7 +19,7 @@ func NewIcon (id theme.Icon, size theme.IconSize) (element *Icon) { size: size, } element.theme.Case = theme.C("basic", "icon") - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.updateMinimumSize() return } diff --git a/elements/basic/image.go b/elements/basic/image.go index 2e5d4fd..35bfb94 100644 --- a/elements/basic/image.go +++ b/elements/basic/image.go @@ -13,7 +13,7 @@ type Image struct { func NewImage (image image.Image) (element *Image) { element = &Image { buffer: canvas.FromImage(image) } - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) bounds := image.Bounds() element.core.SetMinimumSize(bounds.Dx(), bounds.Dy()) return diff --git a/elements/basic/label.go b/elements/basic/label.go index c14674d..bd99b56 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -29,7 +29,7 @@ type Label struct { func NewLabel (text string, wrap bool) (element *Label) { element = &Label { } element.theme.Case = theme.C("basic", "label") - element.Core, element.core = core.NewCore(element.handleResize) + element.Core, element.core = core.NewCore(element, element.handleResize) element.SetWrap(wrap) element.SetText(text) return diff --git a/elements/basic/list.go b/elements/basic/list.go index 0f4c009..6ae823f 100644 --- a/elements/basic/list.go +++ b/elements/basic/list.go @@ -7,6 +7,7 @@ import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/elements" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // List is an element that contains several objects that a user can select. @@ -29,17 +30,17 @@ type List struct { config config.Wrapped theme theme.Wrapped + onNoEntrySelected func () onScrollBoundsChange func () - onNoEntrySelected func () } // NewList creates a new list element with the specified entries. func NewList (entries ...ListEntry) (element *List) { element = &List { selectedEntry: -1 } element.theme.Case = theme.C("basic", "list") - element.Core, element.core = core.NewCore(element.handleResize) + element.Core, element.core = core.NewCore(element, element.handleResize) element.FocusableCore, - element.focusableControl = core.NewFocusableCore (func () { + element.focusableControl = core.NewFocusableCore (element.core, func () { if element.core.HasImage () { element.draw() element.core.DamageAll() @@ -64,8 +65,8 @@ func (element *List) handleResize () { element.scroll = element.maxScrollHeight() } element.draw() - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -102,8 +103,8 @@ func (element *List) redo () { element.draw() element.core.DamageAll() } - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -147,7 +148,7 @@ func (element *List) HandleMouseUp (x, y int, button input.Button) { element.pressed = false } -func (element *List) HandleMouseMove (x, y int) { +func (element *List) HandleMotion (x, y int) { if element.pressed { if element.selectUnderMouse(x, y) && element.core.HasImage() { element.draw() @@ -156,8 +157,6 @@ func (element *List) HandleMouseMove (x, y int) { } } -func (element *List) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } - func (element *List) HandleKeyDown (key input.Key, modifiers input.Modifiers) { if !element.Enabled() { return } @@ -210,8 +209,8 @@ func (element *List) ScrollTo (position image.Point) { element.draw() element.core.DamageAll() } - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -233,10 +232,6 @@ func (element *List) maxScrollHeight () (height int) { return } -func (element *List) OnScrollBoundsChange (callback func ()) { - element.onScrollBoundsChange = callback -} - // OnNoEntrySelected sets a function to be called when the user chooses to // deselect the current selected entry by clicking on empty space within the // list or by pressing the escape key. @@ -244,6 +239,12 @@ func (element *List) OnNoEntrySelected (callback func ()) { element.onNoEntrySelected = callback } +// OnScrollBoundsChange sets a function to be called when the element's viewport +// bounds, content bounds, or scroll axes change. +func (element *List) OnScrollBoundsChange (callback func ()) { + element.onScrollBoundsChange = callback +} + // CountEntries returns the amount of entries in the list. func (element *List) CountEntries () (count int) { return len(element.entries) @@ -263,8 +264,8 @@ func (element *List) Append (entry ListEntry) { element.draw() element.core.DamageAll() } - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -296,8 +297,8 @@ func (element *List) Insert (index int, entry ListEntry) { element.draw() element.core.DamageAll() } - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -319,8 +320,8 @@ func (element *List) Remove (index int) { element.draw() element.core.DamageAll() } - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -341,8 +342,8 @@ func (element *List) Replace (index int, entry ListEntry) { element.draw() element.core.DamageAll() } - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index 9178e3e..02a5ff2 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -20,7 +20,7 @@ type ProgressBar struct { func NewProgressBar (progress float64) (element *ProgressBar) { element = &ProgressBar { progress: progress } element.theme.Case = theme.C("basic", "progressBar") - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) return } diff --git a/elements/basic/scrollbar.go b/elements/basic/scrollbar.go index 4f9af08..d554f80 100644 --- a/elements/basic/scrollbar.go +++ b/elements/basic/scrollbar.go @@ -49,7 +49,7 @@ func NewScrollBar (vertical bool) (element *ScrollBar) { } else { element.theme.Case = theme.C("basic", "scrollBarVertical") } - element.Core, element.core = core.NewCore(element.handleResize) + element.Core, element.core = core.NewCore(element, element.handleResize) element.updateMinimumSize() return } @@ -115,13 +115,13 @@ func (element *ScrollBar) HandleMouseUp (x, y int, button input.Button) { } } -func (element *ScrollBar) HandleMouseMove (x, y int) { +func (element *ScrollBar) HandleMotion (x, y int) { if element.dragging { element.dragTo(image.Pt(x, y)) } } -func (element *ScrollBar) HandleMouseScroll (x, y int, deltaX, deltaY float64) { +func (element *ScrollBar) HandleScroll (x, y int, deltaX, deltaY float64) { if element.vertical { element.scrollBy(int(deltaY)) } else { diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index e333dbb..df02fde 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -31,12 +31,12 @@ type ScrollContainer struct { func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { element = &ScrollContainer { } element.theme.Case = theme.C("basic", "scrollContainer") - element.Core, element.core = core.NewCore(element.redoAll) - element.Propagator = core.NewPropagator(element) + element.Core, element.core = core.NewCore(element, element.redoAll) + element.Propagator = core.NewPropagator(element, element.core) if horizontal { element.horizontal = NewScrollBar(false) - element.setChildEventHandlers(element.horizontal) + element.setUpChild(element.horizontal) element.horizontal.OnScroll (func (viewport image.Point) { if element.child != nil { element.child.ScrollTo(viewport) @@ -50,7 +50,7 @@ func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { } if vertical { element.vertical = NewScrollBar(true) - element.setChildEventHandlers(element.vertical) + element.setUpChild(element.vertical) element.vertical.OnScroll (func (viewport image.Point) { if element.child != nil { element.child.ScrollTo(viewport) @@ -72,13 +72,13 @@ func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { func (element *ScrollContainer) Adopt (child elements.Scrollable) { // disown previous child if it exists if element.child != nil { - element.clearChildEventHandlers(child) + element.disownChild(child) } // adopt new child element.child = child if child != nil { - element.setChildEventHandlers(child) + element.setUpChild(child) } element.updateEnabled() @@ -89,50 +89,47 @@ func (element *ScrollContainer) Adopt (child elements.Scrollable) { } } -func (element *ScrollContainer) setChildEventHandlers (child elements.Element) { - if child0, ok := child.(elements.Themeable); ok { - child0.SetTheme(element.theme.Theme) +func (element *ScrollContainer) setUpChild (child elements.Element) { + child.SetParent(element) + if child, ok := child.(elements.Themeable); ok { + child.SetTheme(element.theme.Theme) } - if child0, ok := child.(elements.Configurable); ok { - child0.SetConfig(element.config.Config) - } - child.OnDamage (func (region canvas.Canvas) { - element.core.DamageRegion(region.Bounds()) - }) - child.OnMinimumSizeChange (func () { - element.updateMinimumSize() - element.redoAll() - element.core.DamageAll() - }) - if child0, ok := child.(elements.Focusable); ok { - child0.OnFocusRequest (func () (granted bool) { - return element.childFocusRequestCallback(child0) - }) - child0.OnFocusMotionRequest ( - func (direction input.KeynavDirection) (granted bool) { - if element.onFocusMotionRequest == nil { return } - return element.onFocusMotionRequest(direction) - }) - } - if child0, ok := child.(elements.Scrollable); ok { - child0.OnScrollBoundsChange(element.childScrollBoundsChangeCallback) + if child, ok := child.(elements.Configurable); ok { + child.SetConfig(element.config.Config) } } -func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) { - child.DrawTo(nil, image.Rectangle { }) - child.OnDamage(nil) - child.OnMinimumSizeChange(nil) - child.OnScrollBoundsChange(nil) - if child0, ok := child.(elements.Focusable); ok { - child0.OnFocusRequest(nil) - child0.OnFocusMotionRequest(nil) - if child0.Focused() { - child0.HandleUnfocus() +func (element *ScrollContainer) disownChild (child elements.Scrollable) { + child.DrawTo(nil, image.Rectangle { }, nil) + child.SetParent(nil) + if child, ok := child.(elements.Focusable); ok { + if child.Focused() { + child.HandleUnfocus() } } } +// NotifyMinimumSizeChange notifies the container that the minimum size of a +// child element has changed. +func (element *ScrollContainer) NotifyMinimumSizeChange (child elements.Element) { + element.redoAll() + element.core.DamageAll() +} + +// NotifyScrollBoundsChange notifies the container that the scroll bounds or +// axes of a child have changed. +func (element *ScrollContainer) NotifyScrollBoundsChange (child elements.Scrollable) { + element.updateEnabled() + viewportBounds := element.child.ScrollViewportBounds() + contentBounds := element.child.ScrollContentBounds() + if element.horizontal != nil { + element.horizontal.SetBounds(contentBounds, viewportBounds) + } + if element.vertical != nil { + element.vertical.SetBounds(contentBounds, viewportBounds) + } +} + // SetTheme sets the element's theme. func (element *ScrollContainer) SetTheme (new theme.Theme) { if new == element.theme.Theme { return } @@ -150,25 +147,13 @@ func (element *ScrollContainer) SetConfig (new config.Config) { element.redoAll() } -func (element *ScrollContainer) HandleMouseScroll ( +func (element *ScrollContainer) HandleScroll ( x, y int, deltaX, deltaY float64, ) { element.scrollChildBy(int(deltaX), int(deltaY)) } -func (element *ScrollContainer) OnFocusRequest (callback func () (granted bool)) { - element.onFocusRequest = callback - element.Propagator.OnFocusRequest(callback) -} - -func (element *ScrollContainer) OnFocusMotionRequest ( - callback func (direction input.KeynavDirection) (granted bool), -) { - element.onFocusMotionRequest = callback - element.Propagator.OnFocusMotionRequest(callback) -} - // CountChildren returns the amount of children contained within this element. func (element *ScrollContainer) CountChildren () (count int) { return 3 @@ -199,25 +184,25 @@ func (element *ScrollContainer) redoAll () { if !element.core.HasImage() { return } zr := image.Rectangle { } - if element.child != nil { element.child.DrawTo(nil, zr) } - if element.horizontal != nil { element.horizontal.DrawTo(nil, zr) } - if element.vertical != nil { element.vertical.DrawTo(nil, zr) } + if element.child != nil { element.child.DrawTo(nil, zr, nil) } + if element.horizontal != nil { element.horizontal.DrawTo(nil, zr, nil) } + if element.vertical != nil { element.vertical.DrawTo(nil, zr, nil) } childBounds, horizontalBounds, verticalBounds := element.layout() if element.child != nil { element.child.DrawTo ( canvas.Cut(element.core, childBounds), - childBounds) + childBounds, element.childDamageCallback) } if element.horizontal != nil { element.horizontal.DrawTo ( canvas.Cut(element.core, horizontalBounds), - horizontalBounds) + horizontalBounds, element.childDamageCallback) } if element.vertical != nil { element.vertical.DrawTo ( canvas.Cut(element.core, verticalBounds), - verticalBounds) + verticalBounds, element.childDamageCallback) } element.draw() } @@ -230,18 +215,8 @@ func (element *ScrollContainer) scrollChildBy (x, y int) { element.child.ScrollTo(scrollPoint) } -func (element *ScrollContainer) childFocusRequestCallback ( - child elements.Focusable, -) ( - granted bool, -) { - if element.onFocusRequest != nil && element.onFocusRequest() { - element.Propagator.HandleUnfocus() - element.Propagator.HandleFocus(input.KeynavDirectionNeutral) - return true - } else { - return false - } +func (element *ScrollContainer) childDamageCallback (region image.Rectangle) { + element.core.DamageRegion(region) } func (element *ScrollContainer) layout () ( @@ -308,18 +283,6 @@ func (element *ScrollContainer) updateMinimumSize () { element.core.SetMinimumSize(width, height) } -func (element *ScrollContainer) childScrollBoundsChangeCallback () { - element.updateEnabled() - viewportBounds := element.child.ScrollViewportBounds() - contentBounds := element.child.ScrollContentBounds() - if element.horizontal != nil { - element.horizontal.SetBounds(contentBounds, viewportBounds) - } - if element.vertical != nil { - element.vertical.SetBounds(contentBounds, viewportBounds) - } -} - func (element *ScrollContainer) updateEnabled () { horizontal, vertical := element.child.ScrollAxes() if element.horizontal != nil { diff --git a/elements/basic/slider.go b/elements/basic/slider.go index 6ad8ff1..7951af7 100644 --- a/elements/basic/slider.go +++ b/elements/basic/slider.go @@ -39,9 +39,9 @@ func NewSlider (value float64, vertical bool) (element *Slider) { } else { element.theme.Case = theme.C("basic", "sliderHorizontal") } - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.FocusableCore, - element.focusableControl = core.NewFocusableCore(element.redo) + element.focusableControl = core.NewFocusableCore(element.core, element.redo) element.updateMinimumSize() return } @@ -68,7 +68,7 @@ func (element *Slider) HandleMouseUp (x, y int, button input.Button) { element.redo() } -func (element *Slider) HandleMouseMove (x, y int) { +func (element *Slider) HandleMotion (x, y int) { if element.dragging { element.dragging = true element.value = element.valueFor(x, y) @@ -79,7 +79,7 @@ func (element *Slider) HandleMouseMove (x, y int) { } } -func (element *Slider) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } +func (element *Slider) HandleScroll (x, y int, deltaX, deltaY float64) { } func (element *Slider) HandleKeyDown (key input.Key, modifiers input.Modifiers) { switch key { diff --git a/elements/basic/spacer.go b/elements/basic/spacer.go index 4d710b3..4fa5013 100644 --- a/elements/basic/spacer.go +++ b/elements/basic/spacer.go @@ -20,7 +20,7 @@ type Spacer struct { func NewSpacer (line bool) (element *Spacer) { element = &Spacer { line: line } element.theme.Case = theme.C("basic", "spacer") - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.updateMinimumSize() return } diff --git a/elements/basic/switch.go b/elements/basic/switch.go index 80491de..1133524 100644 --- a/elements/basic/switch.go +++ b/elements/basic/switch.go @@ -33,9 +33,9 @@ func NewSwitch (text string, on bool) (element *Switch) { text: text, } element.theme.Case = theme.C("basic", "switch") - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.FocusableCore, - element.focusableControl = core.NewFocusableCore(element.redo) + element.focusableControl = core.NewFocusableCore(element.core, element.redo) element.drawer.SetText([]rune(text)) element.updateMinimumSize() return @@ -67,9 +67,6 @@ func (element *Switch) HandleMouseUp (x, y int, button input.Button) { } } -func (element *Switch) HandleMouseMove (x, y int) { } -func (element *Switch) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } - func (element *Switch) HandleKeyDown (key input.Key, modifiers input.Modifiers) { if key == input.KeyEnter { element.pressed = true diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index a76358f..393a0c7 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -6,6 +6,7 @@ import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/elements" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/textmanip" import "git.tebibyte.media/sashakoshka/tomo/fixedutil" @@ -42,9 +43,9 @@ type TextBox struct { func NewTextBox (placeholder, value string) (element *TextBox) { element = &TextBox { } element.theme.Case = theme.C("basic", "textBox") - element.Core, element.core = core.NewCore(element.handleResize) + element.Core, element.core = core.NewCore(element, element.handleResize) element.FocusableCore, - element.focusableControl = core.NewFocusableCore (func () { + element.focusableControl = core.NewFocusableCore (element.core, func () { if element.core.HasImage () { element.draw() element.core.DamageAll() @@ -60,8 +61,8 @@ func NewTextBox (placeholder, value string) (element *TextBox) { func (element *TextBox) handleResize () { element.scrollToCursor() element.draw() - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -79,7 +80,7 @@ func (element *TextBox) HandleMouseDown (x, y int, button input.Button) { } } -func (element *TextBox) HandleMouseMove (x, y int) { +func (element *TextBox) HandleMotion (x, y int) { if !element.Enabled() { return } if element.dragging { @@ -114,8 +115,6 @@ func (element *TextBox) HandleMouseUp (x, y int, button input.Button) { } } -func (element *TextBox) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } - func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers) { if element.onKeyDown != nil && element.onKeyDown(key, modifiers) { return @@ -187,9 +186,10 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers) element.scrollToCursor() } - if (textChanged || scrollMemory != element.scroll) && - element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if (textChanged || scrollMemory != element.scroll) { + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) + } } if altered { @@ -240,6 +240,12 @@ func (element *TextBox) OnChange (callback func ()) { element.onChange = callback } +// OnScrollBoundsChange sets a function to be called when the element's viewport +// bounds, content bounds, or scroll axes change. +func (element *TextBox) OnScrollBoundsChange (callback func ()) { + element.onScrollBoundsChange = callback +} + // ScrollContentBounds returns the full content size of the element. func (element *TextBox) ScrollContentBounds () (bounds image.Rectangle) { bounds = element.valueDrawer.LayoutBounds() @@ -274,8 +280,8 @@ func (element *TextBox) ScrollTo (position image.Point) { if element.scroll > maxPosition { element.scroll = maxPosition } element.redo() - if element.onScrollBoundsChange != nil { - element.onScrollBoundsChange() + if parent, ok := element.core.Parent().(elements.ScrollableParent); ok { + parent.NotifyScrollBoundsChange(element) } } @@ -284,10 +290,6 @@ func (element *TextBox) ScrollAxes () (horizontal, vertical bool) { return true, false } -func (element *TextBox) OnScrollBoundsChange (callback func ()) { - element.onScrollBoundsChange = callback -} - func (element *TextBox) runOnChange () { if element.onChange != nil { element.onChange() diff --git a/elements/core/core.go b/elements/core/core.go index b5c6259..731aea1 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -3,31 +3,38 @@ package core import "image" import "image/color" import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/elements" // Core is a struct that implements some core functionality common to most // widgets. It is meant to be embedded directly into a struct. type Core struct { canvas canvas.Canvas bounds image.Rectangle + parent elements.Parent + outer elements.Element metrics struct { minimumWidth int minimumHeight int } - drawSizeChange func () - onMinimumSizeChange func () - onDamage func (region canvas.Canvas) + drawSizeChange func () + onDamage func (region image.Rectangle) } -// NewCore creates a new element core and its corresponding control. +// NewCore creates a new element core and its corresponding control given the +// element that it will be a part of. If outer is nil, this function will return +// nil. func NewCore ( + outer elements.Element, drawSizeChange func (), ) ( core *Core, control CoreControl, ) { + if outer == nil { return } core = &Core { + outer: outer, drawSizeChange: drawSizeChange, } control = CoreControl { core: core } @@ -47,28 +54,32 @@ func (core *Core) MinimumSize () (width, height int) { return core.metrics.minimumWidth, core.metrics.minimumHeight } +// MinimumSize fulfils the tomo.Element interface. This should not need to be +// overridden, unless you want to detect when the element is parented or +// unparented. +func (core *Core) SetParent (parent elements.Parent) { + if parent != nil && core.parent != nil { + panic("core.SetParent: element already has a parent") + } + + core.parent = parent +} + // DrawTo fulfills the tomo.Element interface. This should not need to be // overridden. -func (core *Core) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) { - core.canvas = canvas - core.bounds = bounds +func (core *Core) DrawTo ( + canvas canvas.Canvas, + bounds image.Rectangle, + onDamage func (region image.Rectangle), +) { + core.canvas = canvas + core.bounds = bounds + core.onDamage = onDamage if core.drawSizeChange != nil && core.canvas != nil { core.drawSizeChange() } } -// OnDamage fulfils the tomo.Element interface. This should not need to be -// overridden. -func (core *Core) OnDamage (callback func (region canvas.Canvas)) { - core.onDamage = callback -} - -// OnMinimumSizeChange fulfils the tomo.Element interface. This should not need -// to be overridden. -func (core *Core) OnMinimumSizeChange (callback func ()) { - core.onMinimumSizeChange = callback -} - // CoreControl is a struct that can exert control over a Core struct. It can be // used as a canvas. It must not be directly embedded into an element, but // instead kept as a private member. When a Core struct is created, a @@ -106,6 +117,16 @@ func (control CoreControl) Buffer () (data []color.RGBA, stride int) { return control.core.canvas.Buffer() } +// Parent returns the element's parent. +func (control CoreControl) Parent () elements.Parent { + return control.core.parent +} + +// Outer returns the outer element given when the control was constructed. +func (control CoreControl) Outer () elements.Element { + return control.core.outer +} + // HasImage returns true if the core has an allocated image buffer, and false if // it doesn't. func (control CoreControl) HasImage () (has bool) { @@ -118,8 +139,7 @@ func (control CoreControl) DamageRegion (regions ...image.Rectangle) { if control.core.canvas == nil { return } if control.core.onDamage != nil { for _, region := range regions { - control.core.onDamage ( - canvas.Cut(control.core.canvas, region)) + control.core.onDamage(region) } } } @@ -141,8 +161,8 @@ func (control CoreControl) SetMinimumSize (width, height int) { core.metrics.minimumWidth = width core.metrics.minimumHeight = height - if control.core.onMinimumSizeChange != nil { - control.core.onMinimumSizeChange() + if control.core.parent != nil { + control.core.parent.NotifyMinimumSizeChange(control.core.outer) } } diff --git a/elements/core/selectable.go b/elements/core/focusable.go similarity index 70% rename from elements/core/selectable.go rename to elements/core/focusable.go index 824dcae..37947aa 100644 --- a/elements/core/selectable.go +++ b/elements/core/focusable.go @@ -2,15 +2,15 @@ package core // import "runtime/debug" import "git.tebibyte.media/sashakoshka/tomo/input" +import "git.tebibyte.media/sashakoshka/tomo/elements" // FocusableCore is a struct that can be embedded into objects to make them // focusable, giving them the default keynav behavior. type FocusableCore struct { + core CoreControl focused bool enabled bool drawFocusChange func () - onFocusRequest func () (granted bool) - onFocusMotionRequest func(input.KeynavDirection) (granted bool) } // NewFocusableCore creates a new focusability core and its corresponding @@ -18,16 +18,18 @@ type FocusableCore struct { // state changes (which it should), a callback to draw and push the update can // be specified. func NewFocusableCore ( + core CoreControl, drawFocusChange func (), ) ( - core *FocusableCore, + focusable *FocusableCore, control FocusableCoreControl, ) { - core = &FocusableCore { + focusable = &FocusableCore { + core: core, drawFocusChange: drawFocusChange, enabled: true, } - control = FocusableCoreControl { core: core } + control = FocusableCoreControl { core: focusable } return } @@ -39,13 +41,10 @@ func (core *FocusableCore) Focused () (focused bool) { // Focus focuses this element, if its parent element grants the request. func (core *FocusableCore) Focus () { if !core.enabled || core.focused { return } - if core.onFocusRequest != nil { - if core.onFocusRequest() { - core.focused = true - if core.drawFocusChange != nil { - core.drawFocusChange() - } - } + parent := core.core.Parent() + if parent, ok := parent.(elements.FocusableParent); ok { + core.focused = parent.RequestFocus ( + core.core.Outer().(elements.Focusable)) } } @@ -76,24 +75,6 @@ func (core *FocusableCore) HandleUnfocus () { if core.drawFocusChange != nil { core.drawFocusChange() } } -// OnFocusRequest sets a function to be called when this element -// wants its parent element to focus it. Parent elements should return -// true if the request was granted, and false if it was not. -func (core *FocusableCore) OnFocusRequest (callback func () (granted bool)) { - core.onFocusRequest = callback -} - -// OnFocusMotionRequest sets a function to be called when this -// element wants its parent element to focus the element behind or in -// front of it, depending on the specified direction. Parent elements -// should return true if the request was granted, and false if it was -// not. -func (core *FocusableCore) OnFocusMotionRequest ( - callback func (direction input.KeynavDirection) (granted bool), -) { - core.onFocusMotionRequest = callback -} - // Enabled returns whether or not the element is enabled. func (core *FocusableCore) Enabled () (enabled bool) { return core.enabled diff --git a/elements/core/propagator.go b/elements/core/propagator.go index 2375f5b..b6e8dc5 100644 --- a/elements/core/propagator.go +++ b/elements/core/propagator.go @@ -6,9 +6,9 @@ import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/elements" -// Parent represents an object that can provide access to a list of child +// Container represents an object that can provide access to a list of child // elements. -type Parent interface { +type Container interface { Child (index int) elements.Element CountChildren () int } @@ -18,20 +18,20 @@ type Parent interface { // all of the event handlers. It also implements standard behavior for focus // propagation and keyboard navigation. type Propagator struct { - parent Parent - drags [10]elements.MouseTarget - focused bool - - onFocusRequest func () (granted bool) + core CoreControl + container Container + drags [10]elements.MouseTarget + focused bool } -// NewPropagator creates a new event propagator that uses the specified parent -// to access a list of child elements that will have events propagated to them. -// If parent is nil, the function will return nil. -func NewPropagator (parent Parent) (propagator *Propagator) { - if parent == nil { return nil } +// NewPropagator creates a new event propagator that uses the specified +// container to access a list of child elements that will have events propagated +// to them. If container is nil, the function will return nil. +func NewPropagator (container Container, core CoreControl) (propagator *Propagator) { + if container == nil { return nil } propagator = &Propagator { - parent: parent, + core: core, + container: container, } return } @@ -47,8 +47,11 @@ func (propagator *Propagator) Focused () (focused bool) { // Focus focuses this element, if its parent element grants the // request. func (propagator *Propagator) Focus () { - if propagator.onFocusRequest != nil { - propagator.onFocusRequest() + if propagator.focused == true { return } + parent := propagator.core.Parent() + if parent, ok := parent.(elements.FocusableParent); ok && parent != nil { + propagator.focused = parent.RequestFocus ( + propagator.core.Outer().(elements.Focusable)) } } @@ -86,7 +89,7 @@ func (propagator *Propagator) HandleFocus (direction input.KeynavDirection) (acc // an element is currently focused, so we need to move the // focus in the specified direction firstFocusedChild := - propagator.parent.Child(firstFocused). + propagator.container.Child(firstFocused). (elements.Focusable) // before we move the focus, the currently focused child @@ -99,11 +102,11 @@ func (propagator *Propagator) HandleFocus (direction input.KeynavDirection) (acc // find the previous/next focusable element relative to the // currently focused element, if it exists. for index := firstFocused + int(direction); - index < propagator.parent.CountChildren() && index >= 0; + index < propagator.container.CountChildren() && index >= 0; index += int(direction) { child, focusable := - propagator.parent.Child(index). + propagator.container.Child(index). (elements.Focusable) if focusable && child.HandleFocus(direction) { // we have found one, so we now actually move @@ -118,6 +121,43 @@ func (propagator *Propagator) HandleFocus (direction input.KeynavDirection) (acc return false } +// RequestFocus notifies the parent that a child element is requesting +// keyboard focus. If the parent grants the request, the method will +// return true and the child element should behave as if a HandleFocus +// call was made. +func (propagator *Propagator) RequestFocus ( + child elements.Focusable, +) ( + granted bool, +) { + if parent, ok := propagator.core.Parent().(elements.FocusableParent); ok { + if parent.RequestFocus(propagator.core.Outer().(elements.Focusable)) { + propagator.HandleUnfocus() + propagator.focused = true + granted = true + } + } + return +} + +// RequestFocusMotion notifies the parent that a child element wants the +// focus to be moved to the next focusable element. +func (propagator *Propagator) RequestFocusNext (child elements.Focusable) { + if !propagator.focused { return } + if parent, ok := propagator.core.Parent().(elements.FocusableParent); ok { + parent.RequestFocusNext(propagator.core.Outer().(elements.Focusable)) + } +} + +// RequestFocusMotion notifies the parent that a child element wants the +// focus to be moved to the previous focusable element. +func (propagator *Propagator) RequestFocusPrevious (child elements.Focusable) { + if !propagator.focused { return } + if parent, ok := propagator.core.Parent().(elements.FocusableParent); ok { + parent.RequestFocusPrevious(propagator.core.Outer().(elements.Focusable)) + } +} + // HandleDeselection causes this element to mark itself and all of its children // as unfocused. func (propagator *Propagator) HandleUnfocus () { @@ -128,22 +168,6 @@ func (propagator *Propagator) HandleUnfocus () { propagator.focused = false } -// OnFocusRequest sets a function to be called when this element wants its -// parent element to focus it. Parent elements should return true if the request -// was granted, and false if it was not. If the parent element returns true, the -// element acts as if a HandleFocus call was made with KeynavDirectionNeutral. -func (propagator *Propagator) OnFocusRequest (callback func () (granted bool)) { - propagator.onFocusRequest = callback -} - -// OnFocusMotionRequest sets a function to be called when this element wants its -// parent element to focus the element behind or in front of it, depending on -// the specified direction. Parent elements should return true if the request -// was granted, and false if it was not. -func (propagator *Propagator) OnFocusMotionRequest ( - callback func (direction input.KeynavDirection) (granted bool), -) { } - // HandleKeyDown propogates the keyboard event to the currently selected child. func (propagator *Propagator) HandleKeyDown (key input.Key, modifiers input.Modifiers) { propagator.forFocused (func (child elements.Focusable) bool { @@ -188,36 +212,32 @@ func (propagator *Propagator) HandleMouseUp (x, y int, button input.Button) { } } -// HandleMouseMove propagates the mouse event to the element that was last +// HandleMotion propagates the mouse event to the element that was last // pressed down by the mouse if the mouse is currently being held down, else it // propagates the event to whichever element is underneath the mouse pointer. -func (propagator *Propagator) HandleMouseMove (x, y int) { +func (propagator *Propagator) HandleMotion (x, y int) { handled := false for _, child := range propagator.drags { - if child != nil { - child.HandleMouseMove(x, y) + if child, ok := child.(elements.MotionTarget); ok { + child.HandleMotion(x, y) handled = true } } - if handled { - child, handlesMouse := - propagator.childAt(image.Pt(x, y)). - (elements.MouseTarget) - if handlesMouse { - child.HandleMouseMove(x, y) + if !handled { + child := propagator.childAt(image.Pt(x, y)) + if child, ok := child.(elements.MotionTarget); ok { + child.HandleMotion(x, y) } } } // HandleScroll propagates the mouse event to the element under the mouse // pointer. -func (propagator *Propagator) HandleMouseScroll (x, y int, deltaX, deltaY float64) { - child, handlesMouse := - propagator.childAt(image.Pt(x, y)). - (elements.MouseTarget) - if handlesMouse { - child.HandleMouseScroll(x, y, deltaX, deltaY) +func (propagator *Propagator) HandleScroll (x, y int, deltaX, deltaY float64) { + child := propagator.childAt(image.Pt(x, y)) + if child, ok := child.(elements.ScrollTarget); ok { + child.HandleScroll(x, y, deltaX, deltaY) } } @@ -281,16 +301,16 @@ func (propagator *Propagator) focusLastFocusableElement ( // ----------- Iterator utilities ----------- // func (propagator *Propagator) forChildren (callback func (child elements.Element) bool) { - for index := 0; index < propagator.parent.CountChildren(); index ++ { - child := propagator.parent.Child(index) + for index := 0; index < propagator.container.CountChildren(); index ++ { + child := propagator.container.Child(index) if child == nil { continue } if !callback(child) { break } } } func (propagator *Propagator) forChildrenReverse (callback func (child elements.Element) bool) { - for index := propagator.parent.CountChildren() - 1; index > 0; index -- { - child := propagator.parent.Child(index) + for index := propagator.container.CountChildren() - 1; index > 0; index -- { + child := propagator.container.Child(index) if child == nil { continue } if !callback(child) { break } } @@ -327,8 +347,8 @@ func (propagator *Propagator) forFocusable (callback func (child elements.Focusa } func (propagator *Propagator) firstFocused () int { - for index := 0; index < propagator.parent.CountChildren(); index ++ { - child, focusable := propagator.parent.Child(index).(elements.Focusable) + for index := 0; index < propagator.container.CountChildren(); index ++ { + child, focusable := propagator.container.Child(index).(elements.Focusable) if focusable && child.Focused() { return index } diff --git a/elements/element.go b/elements/element.go index 3df2466..5f6d298 100644 --- a/elements/element.go +++ b/elements/element.go @@ -10,27 +10,26 @@ import "git.tebibyte.media/sashakoshka/tomo/config" type Element interface { // Bounds reports the element's bounding box. This must reflect the // bounding last given to the element by DrawTo. - Bounds () (bounds image.Rectangle) - - // DrawTo gives the element a canvas to draw on, along with a bounding - // box to be used for laying out the element. This should only be called - // by the parent element. This is typically a region of the parent - // element's canvas. - DrawTo (canvas canvas.Canvas, bounds image.Rectangle) - - // OnDamage sets a function to be called when an area of the element is - // drawn on and should be pushed to the screen. - OnDamage (callback func (region canvas.Canvas)) - + Bounds () image.Rectangle + // MinimumSize specifies the minimum amount of pixels this element's // width and height may be set to. If the element is given a resize // event with dimensions smaller than this, it will use its minimum // instead of the offending dimension(s). MinimumSize () (width, height int) - // OnMinimumSizeChange sets a function to be called when the element's - // minimum size is changed. - OnMinimumSizeChange (callback func ()) + // SetParent sets the parent container of the element. This should only + // be called by the parent when the element is adopted. If parent is set + // to nil, it will mark itself as not having a parent. If this method is + // passed a non-nil value and the element already has a parent, it will + // panic. + SetParent (Parent) + + // DrawTo gives the element a canvas to draw on, along with a bounding + // box to be used for laying out the element. This should only be called + // by the parent element. This is typically a region of the parent + // element's canvas. + DrawTo (canvas canvas.Canvas, bounds image.Rectangle, onDamage func (region image.Rectangle)) } // Focusable represents an element that has keyboard navigation support. This @@ -41,7 +40,7 @@ type Focusable interface { // Focused returns whether or not this element or any of its children // are currently focused. - Focused () (selected bool) + Focused () bool // Focus focuses this element, if its parent element grants the // request. @@ -57,20 +56,6 @@ type Focusable interface { // HandleDeselection causes this element to mark itself and all of its // children as unfocused. HandleUnfocus () - - // OnFocusRequest sets a function to be called when this element wants - // its parent element to focus it. Parent elements should return true if - // the request was granted, and false if it was not. If the parent - // element returns true, the element must act as if a HandleFocus call - // was made with KeynavDirectionNeutral. - OnFocusRequest (func () (granted bool)) - - // OnFocusMotionRequest sets a function to be called when this - // element wants its parent element to focus the element behind or in - // front of it, depending on the specified direction. Parent elements - // should return true if the request was granted, and false if it was - // not. - OnFocusMotionRequest (func (direction input.KeynavDirection) (granted bool)) } // KeyboardTarget represents an element that can receive keyboard input. @@ -93,9 +78,6 @@ type KeyboardTarget interface { type MouseTarget interface { Element - // Each of these handler methods is passed the position of the mouse - // cursor at the time of the event as x, y. - // HandleMouseDown is called when a mouse button is pressed down on this // element. HandleMouseDown (x, y int, button input.Button) @@ -103,15 +85,25 @@ type MouseTarget interface { // HandleMouseUp is called when a mouse button is released that was // originally pressed down on this element. HandleMouseUp (x, y int, button input.Button) +} - // HandleMouseMove is called when the mouse is moved over this element, +// MotionTarget represents an element that can receive mouse motion events. +type MotionTarget interface { + Element + + // HandleMotion is called when the mouse is moved over this element, // or the mouse is moving while being held down and originally pressed // down on this element. - HandleMouseMove (x, y int) + HandleMotion (x, y int) +} + +// ScrollTarget represents an element that can receive mouse scroll events. +type ScrollTarget interface { + Element // HandleScroll is called when the mouse is scrolled. The X and Y // direction of the scroll event are passed as deltaX and deltaY. - HandleMouseScroll (x, y int, deltaX, deltaY float64) + HandleScroll (x, y int, deltaX, deltaY float64) } // Flexible represents an element who's preferred minimum height can change in @@ -132,11 +124,7 @@ type Flexible interface { // // It is important to note that if a parent container checks for // flexible chilren, it itself will likely need to be flexible. - FlexibleHeightFor (width int) (height int) - - // OnFlexibleHeightChange sets a function to be called when the - // parameters affecting this element's flexible height are changed. - OnFlexibleHeightChange (callback func ()) + FlexibleHeightFor (width int) int } // Scrollable represents an element that can be scrolled. It acts as a viewport @@ -145,11 +133,11 @@ type Scrollable interface { Element // ScrollContentBounds returns the full content size of the element. - ScrollContentBounds () (bounds image.Rectangle) + ScrollContentBounds () image.Rectangle // ScrollViewportBounds returns the size and position of the element's // viewport relative to ScrollBounds. - ScrollViewportBounds () (bounds image.Rectangle) + ScrollViewportBounds () image.Rectangle // ScrollTo scrolls the viewport to the specified point relative to // ScrollBounds. @@ -157,10 +145,6 @@ type Scrollable interface { // ScrollAxes returns the supported axes for scrolling. ScrollAxes () (horizontal, vertical bool) - - // OnScrollBoundsChange sets a function to be called when the element's - // ScrollContentBounds, ScrollViewportBounds, or ScrollAxes are changed. - OnScrollBoundsChange (callback func ()) } // Collapsible represents an element who's minimum width and height can be diff --git a/elements/fun/clock.go b/elements/fun/clock.go index e2c3677..a9e9974 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -23,7 +23,7 @@ type AnalogClock struct { func NewAnalogClock (newTime time.Time) (element *AnalogClock) { element = &AnalogClock { } element.theme.Case = theme.C("fun", "clock") - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.core.SetMinimumSize(64, 64) return } diff --git a/elements/fun/piano.go b/elements/fun/piano.go index e18cac8..f7ca895 100644 --- a/elements/fun/piano.go +++ b/elements/fun/piano.go @@ -53,12 +53,12 @@ func NewPiano (low, high music.Octave) (element *Piano) { } element.theme.Case = theme.C("fun", "piano") - element.Core, element.core = core.NewCore (func () { + element.Core, element.core = core.NewCore (element, func () { element.recalculate() element.draw() }) element.FocusableCore, - element.focusableControl = core.NewFocusableCore(element.redo) + element.focusableControl = core.NewFocusableCore(element.core, element.redo) element.updateMinimumSize() return } @@ -88,13 +88,11 @@ func (element *Piano) HandleMouseUp (x, y int, button input.Button) { element.redo() } -func (element *Piano) HandleMouseMove (x, y int) { +func (element *Piano) HandleMotion (x, y int) { if element.pressed == nil { return } element.pressUnderMouseCursor(image.Pt(x, y)) } -func (element *Piano) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } - func (element *Piano) pressUnderMouseCursor (point image.Point) { // find out which note is being pressed newKey := (*pianoKey)(nil) diff --git a/elements/parent.go b/elements/parent.go new file mode 100644 index 0000000..6b6235b --- /dev/null +++ b/elements/parent.go @@ -0,0 +1,53 @@ +package elements + +// Parent represents a type capable of containing child elements. +type Parent interface { + // NotifyMinimumSizeChange notifies the container that a child element's + // minimum size has changed. This method is expected to be called by + // child elements when their minimum size changes. + NotifyMinimumSizeChange (child Element) +} + +// FocusableParent represents a parent with keyboard navigation support. +type FocusableParent interface { + Parent + + // RequestFocus notifies the parent that a child element is requesting + // keyboard focus. If the parent grants the request, the method will + // return true and the child element should behave as if a HandleFocus + // call was made. + RequestFocus (child Focusable) (granted bool) + + // RequestFocusMotion notifies the parent that a child element wants the + // focus to be moved to the next focusable element. + RequestFocusNext (child Focusable) + + // RequestFocusMotion notifies the parent that a child element wants the + // focus to be moved to the previous focusable element. + RequestFocusPrevious (child Focusable) +} + +// FlexibleParent represents a parent that accounts for elements with +// flexible height. +type FlexibleParent interface { + Parent + + // NotifyFlexibleHeightChange notifies the parent that the parameters + // affecting a child's flexible height have changed. This method is + // expected to be called by flexible child element when their content + // changes. + NotifyFlexibleHeightChange (child Flexible) +} + +// ScrollableParent represents a parent that can change the scroll +// position of its child element(s). +type ScrollableParent interface { + Parent + + // NotifyScrollBoundsChange notifies the parent that a child's scroll + // content bounds or viewport bounds have changed. This is expected to + // be called by child elements when they change their supported scroll + // axes, their scroll position (either autonomously or as a result of a + // call to ScrollTo()), or their content size. + NotifyScrollBoundsChange (child Scrollable) +} diff --git a/elements/testing/artist.go b/elements/testing/artist.go index fe5c162..2af49a5 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -23,7 +23,7 @@ type Artist struct { // NewArtist creates a new artist test element. func NewArtist () (element *Artist) { element = &Artist { } - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.core.SetMinimumSize(240, 240) return } diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index 194518a..0cd9ecd 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -24,7 +24,7 @@ type Mouse struct { // NewMouse creates a new mouse test element. func NewMouse () (element *Mouse) { element = &Mouse { c: theme.C("testing", "mouse") } - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element, element.draw) element.core.SetMinimumSize(32, 32) return } @@ -82,7 +82,7 @@ func (element *Mouse) HandleMouseUp (x, y int, button input.Button) { element.lastMousePos = mousePos } -func (element *Mouse) HandleMouseMove (x, y int) { +func (element *Mouse) HandleMotion (x, y int) { if !element.drawing { return } mousePos := image.Pt(x, y) element.core.DamageRegion (shapes.ColorLine ( @@ -90,5 +90,3 @@ func (element *Mouse) HandleMouseMove (x, y int) { element.lastMousePos, mousePos)) element.lastMousePos = mousePos } - -func (element *Mouse) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } diff --git a/elements/window.go b/elements/window.go index 618d795..95f0f0c 100644 --- a/elements/window.go +++ b/elements/window.go @@ -4,19 +4,20 @@ import "image" // Window represents a top-level container generated by the currently running // backend. It can contain a single element. It is hidden by default, and must -// be explicitly shown with the Show() method. If it contains no element, it -// displays a black (or transprent) background. +// be explicitly shown with the Show() method. type Window interface { + Parent + // Adopt sets the root element of the window. There can only be one of // these at one time. - Adopt (child Element) + Adopt (Element) // Child returns the root element of the window. - Child () (child Element) + Child () Element // SetTitle sets the title that appears on the window's title bar. This // method might have no effect with some backends. - SetTitle (title string) + SetTitle (string) // SetIcon taks in a list different sizes of the same icon and selects // the best one to display on the window title bar, dock, or whatever is diff --git a/examples/raycaster/game.go b/examples/raycaster/game.go index e0413c1..2c66cf3 100644 --- a/examples/raycaster/game.go +++ b/examples/raycaster/game.go @@ -31,7 +31,11 @@ func NewGame (world World, textures Textures) (game *Game) { return } -func (game *Game) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) { +func (game *Game) DrawTo ( + canvas canvas.Canvas, + bounds image.Rectangle, + onDamage func (image.Rectangle), +) { if canvas == nil { select { case game.stopChan <- true: @@ -41,7 +45,7 @@ func (game *Game) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) { game.running = true go game.run() } - game.Raycaster.DrawTo(canvas, bounds) + game.Raycaster.DrawTo(canvas, bounds, onDamage) } func (game *Game) Stamina () float64 { diff --git a/examples/raycaster/raycaster.go b/examples/raycaster/raycaster.go index 3ccd703..54ee19d 100644 --- a/examples/raycaster/raycaster.go +++ b/examples/raycaster/raycaster.go @@ -49,9 +49,9 @@ func NewRaycaster (world World, textures Textures) (element *Raycaster) { textures: textures, renderDistance: 8, } - element.Core, element.core = core.NewCore(element.drawAll) + element.Core, element.core = core.NewCore(element, element.drawAll) element.FocusableCore, - element.focusableControl = core.NewFocusableCore(element.Draw) + element.focusableControl = core.NewFocusableCore(element.core, element.Draw) element.core.SetMinimumSize(64, 64) return }