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/style" import "git.tebibyte.media/sashakoshka/goutil/container" // 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 ucontainer.Set[anyBox] needLayout ucontainer.Set[anyBox] needDraw ucontainer.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(ucontainer.Set[anyBox]), needLayout: make(ucontainer.Set[anyBox]), needDraw: make(ucontainer.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 () *style.Style { return this.system.style } func (this *Hierarchy) getIconSet () style.IconSet { return this.system.iconSet } func (this *Hierarchy) getFaceSet () style.FaceSet { return this.system.faceSet } func (this *Hierarchy) getStyleNonce () int { return this.system.styleNonce } func (this *Hierarchy) getIconSetNonce () int { return this.system.iconSetNonce } func (this *Hierarchy) getCanvas () canvas.Canvas { return this.canvas } func (this *Hierarchy) getInnerClippingBounds () image.Rectangle { return this.canvas.Bounds() } 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(), } }