This repository has been archived on 2023-08-08. You can view files and clone it, but cannot push or open issues or pull requests.
tomo-old/elements/basic/container.go
Sasha Koshka 34bf3038ac Replaced tomo.Image with tomo.Canvas and tomo.Pattern
This is the first step in transitioning the API over to the new
design. The new tomo.Canvas interface gives drawing functions
direct access to data buffers and eliminates overhead associated
with calling functions for every pixel.

The entire artist package will be remade around this.
2023-01-14 01:54:57 -05:00

342 lines
8.7 KiB
Go

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.BackgroundImage(),
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
}
}
}