backend/internal/system/hierarchy.go

327 lines
7.3 KiB
Go
Raw Normal View History

2024-06-01 14:39:14 -06:00
package system
2024-06-02 11:23:03 -06:00
import "image"
2024-06-01 14:39:14 -06:00
import "git.tebibyte.media/tomo/tomo"
2024-06-02 11:23:03 -06:00
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/canvas"
import "git.tebibyte.media/tomo/backend/internal/util"
2024-06-01 14:39:14 -06:00
2024-06-02 11:23:03 -06:00
// Hierarchy is coupled to a tomo.Window implementation, and manages a tree of
// Boxes.
2024-06-01 14:39:14 -06:00
type Hierarchy struct {
2024-06-02 11:23:03 -06:00
link WindowLink
system *System
canvas canvas.Canvas
root anyBox
focused anyBox
hovered anyBox
windowFocused bool
modifiers input.Modifiers
mousePosition image.Point
drags [10]anyBox
minimumSize image.Point
needMinimum util.Set[anyBox]
needLayout util.Set[anyBox]
needDraw util.Set[anyBox]
needRedo bool
minimumClean bool
2024-06-01 14:39:14 -06:00
}
2024-06-02 11:33:59 -06:00
// WindowLink allows the Hierarchy to call up into the tomo.Window
// implementation which contains it. This should be a separate entity from the
// tomo.Window.
2024-06-01 14:39:14 -06:00
type WindowLink interface {
2024-06-02 11:33:59 -06:00
// GetWindow returns the tomo.Window containing the Hierarchy.
GetWindow () tomo.Window
// PushRegion pushes a region of the held canvas.Canvas to the screen.
PushRegion (image.Rectangle)
// PushAll pushes the entire canvas.Canvas to the screen.
PushAll ()
// NotifyMinimumSizeChange notifies the tomo.Window that the minimum
// size of the Hierarchy has changed, and if necessary, a resize should
// be requested.
2024-06-02 11:23:03 -06:00
NotifyMinimumSizeChange ()
2024-06-01 14:39:14 -06:00
}
2024-06-02 11:23:03 -06:00
// NewHierarchy creates a new Hierarchy.
2024-06-01 14:39:14 -06:00
func (this *System) NewHierarchy (link WindowLink) *Hierarchy {
2024-06-02 11:23:03 -06:00
hierarchy := &Hierarchy {
2024-06-02 23:51:43 -06:00
system: this,
link: link,
needMinimum: make(util.Set[anyBox]),
needLayout: make(util.Set[anyBox]),
needDraw: make(util.Set[anyBox]),
2024-06-02 11:23:03 -06:00
}
2024-06-11 16:12:47 -06:00
this.hierarchies.Add(hierarchy)
2024-06-02 11:23:03 -06:00
return hierarchy
}
// SetRoot sets the root Box of the hierarchy.
func (this *Hierarchy) SetRoot (root tomo.Box) {
if this.root != nil {
this.root.setParent(nil)
}
if root == nil {
this.root = nil
} else {
box := assertAnyBox(root.GetBox())
box.setParent(this)
box.flushActionQueue()
this.invalidateLayout(box)
this.root = box
}
this.minimumClean = false
}
2024-06-02 20:47:34 -06:00
// Empty returns whether or not the root of the Hierarchy is nil.
func (this *Hierarchy) Empty () bool {
return this.root == nil
}
2024-06-02 11:23:03 -06:00
// SetCanvas sets the held canvas of the Hierarchy that all boxes within it will
2024-06-02 11:33:59 -06:00
// draw to. The Hierarchy will use the canvas.Canvas's bounds to lay itself out.
2024-06-02 11:23:03 -06:00
func (this *Hierarchy) SetCanvas (can canvas.Canvas) {
this.canvas = can
2024-06-11 16:12:47 -06:00
if this.root != nil { this.root.recursiveLoseCanvas() }
2024-06-02 11:23:03 -06:00
this.needRedo = true
}
// MinimumSize returns the minimum size of the Hierarchy.
func (this *Hierarchy) MinimumSize () image.Point {
return this.minimumSize
}
2024-06-02 11:40:45 -06:00
// AfterEvent should be called at the end of every event cycle.
func (this *Hierarchy) AfterEvent () {
if this.canvas == nil { return }
if this.needRedo {
// set child bounds
childBounds := this.canvas.Bounds()
childBounds = childBounds.Sub(childBounds.Min)
if this.root != nil {
this.root.SetBounds(childBounds)
}
// full relayout/redraw
if this.root != nil {
this.root.recursiveRedo()
}
this.link.PushAll()
this.needRedo = false
return
}
for len(this.needMinimum) > 0 {
this.needMinimum.Pop().doMinimumSize()
}
if !this.minimumClean {
this.doMinimumSize()
}
for len(this.needLayout) > 0 {
this.needLayout.Pop().doLayout()
}
var toPush image.Rectangle
for len(this.needDraw) > 0 {
box := this.needDraw.Pop()
box.doDraw()
toPush = toPush.Union(box.Bounds())
}
if !toPush.Empty() {
this.link.PushRegion(toPush)
}
}
2024-06-11 16:12:47 -06:00
// Close closes the Hierarchy. This should be called when the Window that
// contains it has been closed.
func (this *Hierarchy) Close () {
this.system.removeHierarchy(this)
}
2024-06-11 22:39:00 -06:00
func (this *Hierarchy) setStyle () {
2024-06-11 16:12:47 -06:00
if this.root != nil { this.root.recursiveReApply() }
}
2024-06-11 22:39:00 -06:00
func (this *Hierarchy) setIcons () {
2024-06-11 16:12:47 -06:00
if this.root != nil { this.root.recursiveReApply() }
}
2024-06-02 11:23:03 -06:00
func (this *Hierarchy) getHierarchy () *Hierarchy {
return this
}
func (this *Hierarchy) getWindow () tomo.Window {
return this.link.GetWindow()
}
2024-06-11 16:12:47 -06:00
func (this *Hierarchy) getStyle () tomo.Style {
2024-06-11 22:39:00 -06:00
return this.system.style
2024-06-11 16:12:47 -06:00
}
func (this *Hierarchy) getStyleNonce () int {
2024-06-11 22:39:00 -06:00
return this.system.styleNonce
2024-06-11 16:12:47 -06:00
}
func (this *Hierarchy) getIconsNonce () int {
2024-06-11 22:39:00 -06:00
return this.system.iconsNonce
2024-06-11 16:12:47 -06:00
}
2024-06-02 11:23:03 -06:00
func (this *Hierarchy) getCanvas () canvas.Canvas {
return this.canvas
}
func (this *Hierarchy) getModifiers () input.Modifiers {
return this.modifiers
}
func (this *Hierarchy) getMousePosition () image.Point {
return this.mousePosition
}
func (this *Hierarchy) notifyMinimumSizeChange (anyBox) {
this.minimumClean = false
}
func (this *Hierarchy) invalidateMinimum (box anyBox) {
this.needMinimum.Add(box)
}
func (this *Hierarchy) invalidateDraw (box anyBox) {
this.needDraw.Add(box)
}
func (this *Hierarchy) invalidateLayout (box anyBox) {
this.needLayout.Add(box)
this.invalidateDraw(box)
}
func (this *Hierarchy) focus (box anyBox) {
if this.focused == box { return }
previous := this.focused
this.focused = box
if previous != nil {
previous.handleFocusLeave()
}
if box != nil && box.canBeFocused() {
box.handleFocusEnter()
}
}
func (this *Hierarchy) isFocused (box anyBox) bool {
return this.focused == box
}
func (this *Hierarchy) hover (box anyBox) {
if this.hovered == box { return }
previous := this.hovered
this.hovered = box
if previous != nil {
previous.handleMouseLeave()
}
if box != nil {
box.handleMouseEnter()
}
}
func (this *Hierarchy) anyFocused () bool {
return this.focused != nil
}
func (this *Hierarchy) boxUnder (point image.Point, category eventCategory) anyBox {
if this.root == nil { return nil }
return this.root.boxUnder(point, category)
}
func (this *Hierarchy) captures (eventCategory) bool {
return false
}
func (this *Hierarchy) keyboardTarget () anyBox {
focused := this.focused
if focused == nil { return nil }
parent := focused.getParent()
for {
parentBox, ok := parent.(anyBox)
if !ok { break }
if parent.captures(eventCategoryKeyboard) {
return parentBox
}
parent = parentBox.getParent()
}
return focused
}
func (this *Hierarchy) focusNext () {
found := !this.anyFocused()
focused := false
this.propagateAlt (func (box anyBox) bool {
if found {
// looking for the next box to select
if box.canBeFocused() {
// found it
this.focus(box)
focused = true
return false
}
} else {
// looking for the current focused element
if box == this.focused {
// found it
found = true
}
}
return true
})
if !focused { this.focus(nil) }
}
func (this *Hierarchy) focusPrevious () {
var behind anyBox
this.propagate (func (box anyBox) bool {
if box == this.focused {
return false
}
if box.canBeFocused() { behind = box }
return true
})
this.focus(behind)
}
func (this *Hierarchy) propagate (callback func (box anyBox) bool) {
if this.root == nil { return }
this.root.propagate(callback)
}
func (this *Hierarchy) propagateAlt (callback func (box anyBox) bool) {
if this.root == nil { return }
this.root.propagateAlt(callback)
}
func (this *Hierarchy) drawBackgroundPart (canvas.Canvas) {
// TODO
// no-op for now? maybe eventually windows will be able to have a
// background
// if so, windows should be transparent if the color has transparency
}
func (this *Hierarchy) doMinimumSize () {
this.minimumClean = true
this.minimumSize = image.Point { }
if this.root != nil {
this.minimumSize = this.root.MinimumSize()
}
this.link.NotifyMinimumSizeChange()
2024-06-01 14:39:14 -06:00
}