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 needMinimum 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, } 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 } // 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.loseCanvas() } this.needRedo = true } // MinimumSize returns the minimum size of the Hierarchy. func (this *Hierarchy) MinimumSize () image.Point { return this.minimumSize } // 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) } } func (this *Hierarchy) getHierarchy () *Hierarchy { return this } func (this *Hierarchy) getWindow () tomo.Window { return this.link.GetWindow() } 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() }