backend/internal/system/hierarchy.go

392 lines
8.9 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"
2024-08-10 18:24:25 -06:00
import "git.tebibyte.media/tomo/backend/style"
import "git.tebibyte.media/sashakoshka/goutil/container"
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
2024-07-25 11:01:15 -06:00
drags [10][]anyBox
2024-06-02 11:23:03 -06:00
minimumSize image.Point
2024-07-25 11:01:15 -06:00
needStyle ucontainer.Set[anyBox]
needLayout ucontainer.Set[anyBox]
needDraw ucontainer.Set[anyBox]
2024-06-02 11:23:03 -06:00
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,
needStyle: make(ucontainer.Set[anyBox]),
needLayout: make(ucontainer.Set[anyBox]),
needDraw: make(ucontainer.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-07-25 11:01:15 -06:00
// Modifiers returns the current modifier keys being held.
func (this *Hierarchy) Modifiers () input.Modifiers {
return this.modifiers
}
// MousePosition returns the current mouse position.
func (this *Hierarchy) MousePosition () image.Point {
return this.mousePosition
}
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 {
2024-07-25 11:01:15 -06:00
this.root.setBounds(childBounds)
2024-06-02 11:40:45 -06:00
}
// full relayout/redraw
if this.root != nil {
this.root.recursiveRedo()
}
this.link.PushAll()
this.needRedo = false
return
}
2024-07-25 11:01:15 -06:00
for len(this.needStyle) > 0 {
this.needStyle.Pop().doStyle()
2024-06-02 11:40:45 -06:00
}
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-07-25 11:01:15 -06:00
func (this *Hierarchy) setIconSet () {
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-08-10 18:24:25 -06:00
func (this *Hierarchy) getStyle () *style.Style {
2024-06-11 22:39:00 -06:00
return this.system.style
2024-06-11 16:12:47 -06:00
}
2024-08-10 18:24:25 -06:00
func (this *Hierarchy) getIconSet () style.IconSet {
return this.system.iconSet
}
func (this *Hierarchy) getFaceSet () style.FaceSet {
return this.system.faceSet
}
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
}
2024-08-10 18:24:25 -06:00
func (this *Hierarchy) getIconSetNonce () int {
return this.system.iconSetNonce
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) getInnerClippingBounds () image.Rectangle {
return this.canvas.Bounds()
}
2024-06-02 11:23:03 -06:00
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
}
2024-07-25 11:01:15 -06:00
func (this *Hierarchy) invalidateStyle (box anyBox) {
this.needStyle.Add(box)
2024-06-02 11:23:03 -06:00
}
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
}
2024-07-25 11:01:15 -06:00
func (this *Hierarchy) masks () bool {
return false
}
func (this *Hierarchy) boxUnder (point image.Point) anyBox {
2024-06-02 11:23:03 -06:00
if this.root == nil { return nil }
2024-07-25 11:01:15 -06:00
return this.root.boxUnder(point)
2024-06-02 11:23:03 -06:00
}
2024-07-25 11:01:15 -06:00
func (this *Hierarchy) parents (box anyBox) func (func (anyBox) bool) {
return func (yield func (anyBox) bool) {
for box != nil && yield(box) {
parent, ok := box.getParent().(anyBox)
if !ok { break }
box = parent
}
}
2024-06-02 11:23:03 -06:00
}
2024-07-25 11:01:15 -06:00
func (this *Hierarchy) boxesUnder (point image.Point) func (func (anyBox) bool) {
return this.parents(this.boxUnder(point))
}
func (this *Hierarchy) keyboardTargets (yield func (anyBox) bool) {
2024-06-02 11:23:03 -06:00
focused := this.focused
2024-07-25 11:01:15 -06:00
if focused == nil { return }
this.parents(this.considerMaskingParents(focused))(yield)
}
func (this *Hierarchy) considerMaskingParents (box anyBox) anyBox {
parent := box.getParent()
2024-06-02 11:23:03 -06:00
for {
parentBox, ok := parent.(anyBox)
if !ok { break }
2024-07-25 11:01:15 -06:00
if parent.masks() {
2024-06-02 11:23:03 -06:00
return parentBox
}
parent = parentBox.getParent()
}
2024-07-25 11:01:15 -06:00
return box
2024-06-02 11:23:03 -06:00
}
2024-07-26 18:49:10 -06:00
func (this *Hierarchy) isMasked (box anyBox) bool {
parent := box.getParent()
for {
parentBox, ok := parent.(anyBox)
if !ok { break }
if parent.masks() {
return true
}
parent = parentBox.getParent()
}
return false
}
2024-06-02 11:23:03 -06:00
func (this *Hierarchy) focusNext () {
found := !this.anyFocused()
focused := false
2024-07-25 11:01:15 -06:00
this.propagateAlt(func (box anyBox) bool {
2024-06-02 11:23:03 -06:00
if found {
// looking for the next box to select
2024-07-26 18:49:10 -06:00
if box.canBeFocused() && !this.isMasked(box) {
2024-06-02 11:23:03 -06:00
// 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
2024-07-25 11:01:15 -06:00
this.propagate(func (box anyBox) bool {
2024-06-02 11:23:03 -06:00
if box == this.focused {
return false
}
2024-07-26 18:49:10 -06:00
if box.canBeFocused() && !this.isMasked(box) { behind = box }
2024-06-02 11:23:03 -06:00
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
}
// var minimumSizeCount = 0
2024-06-02 11:23:03 -06:00
func (this *Hierarchy) doMinimumSize () {
this.minimumClean = true
// println("doMinimumSize", minimumSizeCount)
// minimumSizeCount ++
2024-06-02 11:23:03 -06:00
previousMinimumSize := this.minimumSize
2024-06-02 11:23:03 -06:00
this.minimumSize = image.Point { }
if this.root != nil {
2024-07-25 11:01:15 -06:00
this.minimumSize = this.root.minimumSize()
2024-06-02 11:23:03 -06:00
}
if previousMinimumSize != this.minimumSize {
this.link.NotifyMinimumSizeChange()
}
2024-06-01 14:39:14 -06:00
}
2024-07-25 11:01:15 -06:00
func (this *Hierarchy) newStyleApplicator () *styleApplicator {
return &styleApplicator {
style: this.getStyle(),
}
}