2023-04-13 22:25:05 -06:00
|
|
|
package x
|
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
2023-04-14 23:14:36 -06:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
2023-04-13 22:25:05 -06:00
|
|
|
|
|
|
|
type entitySet map[*entity] struct { }
|
|
|
|
|
|
|
|
func (set entitySet) Empty () bool {
|
|
|
|
return len(set) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (set entitySet) Has (entity *entity) bool {
|
|
|
|
_, ok := set[entity]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func (set entitySet) Add (entity *entity) {
|
|
|
|
set[entity] = struct { } { }
|
|
|
|
}
|
|
|
|
|
|
|
|
type system struct {
|
|
|
|
child *entity
|
|
|
|
focused *entity
|
|
|
|
canvas canvas.BasicCanvas
|
|
|
|
|
2023-04-14 23:14:36 -06:00
|
|
|
theme theme.Wrapped
|
|
|
|
config config.Wrapped
|
2023-04-13 22:25:05 -06:00
|
|
|
|
|
|
|
invalidateIgnore bool
|
|
|
|
drawingInvalid entitySet
|
|
|
|
anyLayoutInvalid bool
|
2023-04-14 17:08:14 -06:00
|
|
|
|
2023-04-17 00:05:53 -06:00
|
|
|
drags [10]*entity
|
2023-04-13 22:25:05 -06:00
|
|
|
|
|
|
|
pushFunc func (image.Rectangle)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) initialize () {
|
|
|
|
system.drawingInvalid = make(entitySet)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) SetTheme (theme tomo.Theme) {
|
2023-04-14 23:14:36 -06:00
|
|
|
system.theme.Theme = theme
|
2023-04-14 17:08:14 -06:00
|
|
|
system.propagate (func (entity *entity) bool {
|
|
|
|
if child, ok := system.child.element.(tomo.Themeable); ok {
|
|
|
|
child.SetTheme(theme)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2023-04-13 22:25:05 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) SetConfig (config tomo.Config) {
|
2023-04-14 23:14:36 -06:00
|
|
|
system.config.Config = config
|
2023-04-14 17:08:14 -06:00
|
|
|
system.propagate (func (entity *entity) bool {
|
|
|
|
if child, ok := system.child.element.(tomo.Configurable); ok {
|
|
|
|
child.SetConfig(config)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-04-15 19:49:40 -06:00
|
|
|
func (system *system) focus (entity *entity) {
|
|
|
|
previous := system.focused
|
|
|
|
system.focused = entity
|
|
|
|
if previous != nil {
|
|
|
|
previous.element.(tomo.Focusable).HandleFocusChange()
|
|
|
|
}
|
|
|
|
if entity != nil {
|
|
|
|
entity.element.(tomo.Focusable).HandleFocusChange()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-14 17:08:14 -06:00
|
|
|
func (system *system) focusNext () {
|
2023-04-15 19:49:40 -06:00
|
|
|
found := system.focused == nil
|
|
|
|
focused := false
|
|
|
|
system.propagate (func (entity *entity) bool {
|
|
|
|
if found {
|
|
|
|
// looking for the next element to select
|
|
|
|
child, ok := entity.element.(tomo.Focusable)
|
|
|
|
if ok && child.Enabled() {
|
|
|
|
// found it
|
|
|
|
entity.Focus()
|
|
|
|
focused = true
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// looking for the current focused element
|
|
|
|
if entity == system.focused {
|
|
|
|
// found it
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
if !focused { system.focus(nil) }
|
2023-04-14 17:08:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) focusPrevious () {
|
2023-04-15 19:49:40 -06:00
|
|
|
var behind *entity
|
|
|
|
system.propagate (func (entity *entity) bool {
|
|
|
|
if entity == system.focused {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
child, ok := entity.element.(tomo.Focusable)
|
|
|
|
if ok && child.Enabled() { behind = entity }
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
system.focus(behind)
|
2023-04-14 17:08:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) propagate (callback func (*entity) bool) {
|
2023-04-13 22:25:05 -06:00
|
|
|
if system.child == nil { return }
|
2023-04-14 17:08:14 -06:00
|
|
|
system.child.propagate(callback)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) childAt (point image.Point) *entity {
|
|
|
|
if system.child == nil { return nil }
|
|
|
|
return system.child.childAt(point)
|
2023-04-13 22:25:05 -06:00
|
|
|
}
|
|
|
|
|
2023-04-16 12:12:55 -06:00
|
|
|
func (system *system) scrollTargetChildAt (point image.Point) *entity {
|
|
|
|
if system.child == nil { return nil }
|
|
|
|
return system.child.scrollTargetChildAt(point)
|
|
|
|
}
|
|
|
|
|
2023-04-13 22:25:05 -06:00
|
|
|
func (system *system) resizeChildToFit () {
|
|
|
|
system.child.bounds = system.canvas.Bounds()
|
|
|
|
system.child.clippedBounds = system.child.bounds
|
|
|
|
system.child.Invalidate()
|
|
|
|
if system.child.isContainer {
|
|
|
|
system.child.InvalidateLayout()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) afterEvent () {
|
|
|
|
if system.anyLayoutInvalid {
|
|
|
|
system.layout(system.child, false)
|
|
|
|
system.anyLayoutInvalid = false
|
|
|
|
}
|
|
|
|
system.draw()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) layout (entity *entity, force bool) {
|
2023-04-16 01:37:28 -06:00
|
|
|
if entity == nil { return }
|
2023-04-13 22:25:05 -06:00
|
|
|
if entity.layoutInvalid == true || force {
|
2023-04-16 01:37:28 -06:00
|
|
|
if element, ok := entity.element.(tomo.Layoutable); ok {
|
|
|
|
element.Layout()
|
|
|
|
entity.layoutInvalid = false
|
|
|
|
force = true
|
|
|
|
}
|
2023-04-13 22:25:05 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, child := range entity.children {
|
|
|
|
system.layout(child, force)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (system *system) draw () {
|
|
|
|
finalBounds := image.Rectangle { }
|
|
|
|
|
|
|
|
// ignore invalidations that result from drawing elements, because if an
|
|
|
|
// element decides to do that it really needs to rethink its life
|
|
|
|
// choices.
|
|
|
|
system.invalidateIgnore = true
|
|
|
|
defer func () { system.invalidateIgnore = false } ()
|
|
|
|
|
|
|
|
for entity := range system.drawingInvalid {
|
2023-04-18 01:07:06 -06:00
|
|
|
if entity.clippedBounds.Empty() { continue }
|
2023-04-13 22:25:05 -06:00
|
|
|
entity.element.Draw (canvas.Cut (
|
|
|
|
system.canvas,
|
|
|
|
entity.clippedBounds))
|
|
|
|
finalBounds = finalBounds.Union(entity.clippedBounds)
|
|
|
|
}
|
|
|
|
system.drawingInvalid = make(entitySet)
|
|
|
|
|
|
|
|
// TODO: don't just union all the bounds together, we can definetly
|
|
|
|
// consolidateupdated regions more efficiently than this.
|
|
|
|
if !finalBounds.Empty() {
|
|
|
|
system.pushFunc(finalBounds)
|
|
|
|
}
|
|
|
|
}
|