package basic import "image" import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // Container is an element capable of containg other elements, and arranging // them in a layout. type Container struct { *core.Core core core.CoreControl layout tomo.Layout children []tomo.LayoutEntry drags [10]tomo.Element warping bool } // NewContainer creates a new container. func NewContainer (layout tomo.Layout) (element *Container) { element = &Container { } element.Core, element.core = core.NewCore(element) element.SetLayout(layout) return } // SetLayout sets the layout of this container. func (element *Container) SetLayout (layout tomo.Layout) { element.layout = layout if element.core.HasImage() { element.recalculate() element.draw() element.core.PushAll() } } // Adopt adds a new child element to the container. If expand is set to true, // the element will expand (instead of contract to its minimum size), in // whatever way is defined by the current layout. func (element *Container) Adopt (child tomo.Element, expand bool) { child.SetParentHooks (tomo.ParentHooks { MinimumSizeChange: func (int, int) { element.updateMinimumSize() }, SelectabilityChange: func (bool) { element.updateSelectable() }, SelectionRequest: func () (granted bool) { if !child.Selectable() { return } if element.core.Select() { element.propogateToSelected(tomo.EventDeselect { }) child.Handle(tomo.EventSelect { }) return true } return }, Draw: func (region tomo.Canvas) { element.drawChildRegion(child, region) }, }) element.children = append (element.children, tomo.LayoutEntry { Element: child, Expand: expand, }) element.updateMinimumSize() element.updateSelectable() if element.core.HasImage() && !element.warping { element.recalculate() element.draw() element.core.PushAll() } } // Warp runs the specified callback, deferring all layout and rendering updates // until the callback has finished executing. This allows for aplications to // perform batch gui updates without flickering and stuff. func (element *Container) Warp (callback func ()) { if element.warping { callback() return } element.warping = true callback() element.warping = false // TODO: create some sort of task list so we don't do a full recalculate // and redraw every time, because although that is the most likely use // case, it is not the only one. if element.core.HasImage() { element.recalculate() element.draw() element.core.PushAll() } } // Disown removes the given child from the container if it is contained within // it. func (element *Container) Disown (child tomo.Element) { for index, entry := range element.children { if entry.Element == child { entry.SetParentHooks(tomo.ParentHooks { }) element.children = append ( element.children[:index], element.children[index + 1:]...) break } } element.updateMinimumSize() element.updateSelectable() if element.core.HasImage() && !element.warping { element.recalculate() element.draw() element.core.PushAll() } } // DisownAll removes all child elements from the container at once. func (element *Container) DisownAll () { element.children = nil element.updateMinimumSize() element.updateSelectable() if element.core.HasImage() && !element.warping { element.recalculate() element.draw() element.core.PushAll() } } // Children returns a slice containing this element's children. func (element *Container) Children () (children []tomo.Element) { children = make([]tomo.Element, len(element.children)) for index, entry := range element.children { children[index] = entry.Element } return } // CountChildren returns the amount of children contained within this element. func (element *Container) CountChildren () (count int) { return len(element.children) } // Child returns the child at the specified index. If the index is out of // bounds, this method will return nil. func (element *Container) Child (index int) (child tomo.Element) { if index < 0 || index > len(element.children) { return } return element.children[index].Element } // ChildAt returns the child that contains the specified x and y coordinates. If // there are no children at the coordinates, this method will return nil. func (element *Container) ChildAt (point image.Point) (child tomo.Element) { for _, entry := range element.children { if point.In(entry.Bounds().Add(entry.Position)) { child = entry.Element } } return } func (element *Container) childPosition (child tomo.Element) (position image.Point) { for _, entry := range element.children { if entry.Element == child { position = entry.Position break } } return } func (element *Container) Handle (event tomo.Event) { switch event.(type) { case tomo.EventResize: resizeEvent := event.(tomo.EventResize) element.core.AllocateCanvas ( resizeEvent.Width, resizeEvent.Height) element.recalculate() element.draw() case tomo.EventMouseDown: mouseDownEvent := event.(tomo.EventMouseDown) child := element.ChildAt (image.Pt ( mouseDownEvent.X, mouseDownEvent.Y)) if child == nil { break } element.drags[mouseDownEvent.Button] = child childPosition := element.childPosition(child) child.Handle (tomo.EventMouseDown { Button: mouseDownEvent.Button, X: mouseDownEvent.X - childPosition.X, Y: mouseDownEvent.Y - childPosition.Y, }) case tomo.EventMouseUp: mouseUpEvent := event.(tomo.EventMouseUp) child := element.drags[mouseUpEvent.Button] if child == nil { break } element.drags[mouseUpEvent.Button] = nil childPosition := element.childPosition(child) child.Handle (tomo.EventMouseUp { Button: mouseUpEvent.Button, X: mouseUpEvent.X - childPosition.X, Y: mouseUpEvent.Y - childPosition.Y, }) case tomo.EventMouseMove: mouseMoveEvent := event.(tomo.EventMouseMove) for _, child := range element.drags { if child == nil { continue } childPosition := element.childPosition(child) child.Handle (tomo.EventMouseMove { X: mouseMoveEvent.X - childPosition.X, Y: mouseMoveEvent.Y - childPosition.Y, }) } case tomo.EventSelect: if !element.Selectable() { break } element.core.SetSelected(true) // select the first selectable element for _, entry := range element.children { if entry.Selectable() { entry.Handle(event) break } } case tomo.EventDeselect: element.core.SetSelected(false) element.propogateToSelected(event) default: // other events are just directly sent to the selected child. element.propogateToSelected(event) } return } func (element *Container) propogateToSelected (event tomo.Event) { for _, entry := range element.children { if entry.Selected() { entry.Handle(event) } } } func (element *Container) AdvanceSelection (direction int) (ok bool) { if !element.Selectable() { return } firstSelected := element.firstSelected() if firstSelected < 0 { for _, entry := range element.children { if entry.Selectable() { entry.Handle(tomo.EventSelect { }) return true } } } else { nextSelectable := -1 step := 1 if direction < 0 { step = - 1 } for index := firstSelected + step; index < len(element.children) && index > 0; index += step { if element.children[index].Selectable() { nextSelectable = index break } } if nextSelectable > 0 { element.children[firstSelected ].Handle(tomo.EventDeselect { }) element.children[nextSelectable].Handle(tomo.EventSelect { }) return true } } return } func (element *Container) firstSelected () (index int) { for currentIndex, entry := range element.children { if entry.Selected() { return currentIndex } } return -1 } func (element *Container) updateSelectable () { selectable := false for _, entry := range element.children { if entry.Selectable() { selectable = true } } element.core.SetSelectable(selectable) } func (element *Container) updateMinimumSize () { element.core.SetMinimumSize(element.layout.MinimumSize(element.children)) } func (element *Container) recalculate () { bounds := element.Bounds() element.layout.Arrange(element.children, bounds.Dx(), bounds.Dy()) } func (element *Container) draw () { bounds := element.core.Bounds() artist.FillRectangle ( element.core, theme.BackgroundPattern(), bounds) for _, entry := range element.children { artist.Paste(element.core, entry, entry.Position) } } func (element *Container) drawChildRegion (child tomo.Element, region tomo.Canvas) { if element.warping { return } for _, entry := range element.children { if entry.Element == child { artist.Paste(element.core, region, entry.Position) element.core.PushRegion ( region.Bounds().Add(entry.Position)) break } } }