379 lines
8.6 KiB
Go
379 lines
8.6 KiB
Go
package system
|
|
|
|
import "image"
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
import "git.tebibyte.media/tomo/tomo/input"
|
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
|
import "git.tebibyte.media/tomo/backend/internal/util"
|
|
|
|
// Hierarchy is coupled to a tomo.Window implementation, and manages a tree of
|
|
// Boxes.
|
|
type Hierarchy struct {
|
|
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
|
|
|
|
needStyle util.Set[anyBox]
|
|
needLayout util.Set[anyBox]
|
|
needDraw util.Set[anyBox]
|
|
needRedo bool
|
|
minimumClean bool
|
|
}
|
|
|
|
// 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.
|
|
type WindowLink interface {
|
|
// 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.
|
|
NotifyMinimumSizeChange ()
|
|
}
|
|
|
|
// NewHierarchy creates a new Hierarchy.
|
|
func (this *System) NewHierarchy (link WindowLink) *Hierarchy {
|
|
hierarchy := &Hierarchy {
|
|
system: this,
|
|
link: link,
|
|
needStyle: make(util.Set[anyBox]),
|
|
needLayout: make(util.Set[anyBox]),
|
|
needDraw: make(util.Set[anyBox]),
|
|
}
|
|
this.hierarchies.Add(hierarchy)
|
|
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
|
|
}
|
|
|
|
// Empty returns whether or not the root of the Hierarchy is nil.
|
|
func (this *Hierarchy) Empty () bool {
|
|
return this.root == nil
|
|
}
|
|
|
|
// SetCanvas sets the held canvas of the Hierarchy that all boxes within it will
|
|
// draw to. The Hierarchy will use the canvas.Canvas's bounds to lay itself out.
|
|
func (this *Hierarchy) SetCanvas (can canvas.Canvas) {
|
|
this.canvas = can
|
|
if this.root != nil { this.root.recursiveLoseCanvas() }
|
|
this.needRedo = true
|
|
}
|
|
|
|
// MinimumSize returns the minimum size of the Hierarchy.
|
|
func (this *Hierarchy) MinimumSize () image.Point {
|
|
return this.minimumSize
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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.needStyle) > 0 {
|
|
this.needStyle.Pop().doStyle()
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
func (this *Hierarchy) setStyle () {
|
|
if this.root != nil { this.root.recursiveReApply() }
|
|
}
|
|
|
|
func (this *Hierarchy) setIconSet () {
|
|
if this.root != nil { this.root.recursiveReApply() }
|
|
}
|
|
|
|
func (this *Hierarchy) getHierarchy () *Hierarchy {
|
|
return this
|
|
}
|
|
|
|
func (this *Hierarchy) getWindow () tomo.Window {
|
|
return this.link.GetWindow()
|
|
}
|
|
|
|
func (this *Hierarchy) getStyle () *tomo.Style {
|
|
return this.system.style
|
|
}
|
|
|
|
func (this *Hierarchy) getStyleNonce () int {
|
|
return this.system.styleNonce
|
|
}
|
|
|
|
func (this *Hierarchy) getIconsNonce () int {
|
|
return this.system.iconsNonce
|
|
}
|
|
|
|
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) invalidateStyle (box anyBox) {
|
|
this.needStyle.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) masks () bool {
|
|
return false
|
|
}
|
|
|
|
func (this *Hierarchy) boxUnder (point image.Point) anyBox {
|
|
if this.root == nil { return nil }
|
|
return this.root.boxUnder(point)
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
focused := this.focused
|
|
if focused == nil { return }
|
|
this.parents(this.considerMaskingParents(focused))(yield)
|
|
}
|
|
|
|
func (this *Hierarchy) considerMaskingParents (box anyBox) anyBox {
|
|
parent := box.getParent()
|
|
for {
|
|
parentBox, ok := parent.(anyBox)
|
|
if !ok { break }
|
|
if parent.masks() {
|
|
return parentBox
|
|
}
|
|
parent = parentBox.getParent()
|
|
}
|
|
return box
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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() && !this.isMasked(box) {
|
|
// 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() && !this.isMasked(box) { 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
|
|
}
|
|
|
|
// var minimumSizeCount = 0
|
|
func (this *Hierarchy) doMinimumSize () {
|
|
this.minimumClean = true
|
|
// println("doMinimumSize", minimumSizeCount)
|
|
// minimumSizeCount ++
|
|
|
|
previousMinimumSize := this.minimumSize
|
|
this.minimumSize = image.Point { }
|
|
if this.root != nil {
|
|
this.minimumSize = this.root.minimumSize()
|
|
}
|
|
|
|
if previousMinimumSize != this.minimumSize {
|
|
this.link.NotifyMinimumSizeChange()
|
|
}
|
|
}
|
|
|
|
func (this *Hierarchy) newStyleApplicator () *styleApplicator {
|
|
return &styleApplicator {
|
|
style: this.getStyle(),
|
|
}
|
|
}
|