diff --git a/backends/x/entity.go b/backends/x/entity.go index 9ee447e..7e77be2 100644 --- a/backends/x/entity.go +++ b/backends/x/entity.go @@ -34,8 +34,10 @@ func (ent *entity) unlink () { delete(ent.window.system.drawingInvalid, child) return true }) - - delete(ent.window.system.drawingInvalid, ent) + + if ent.window != nil { + delete(ent.window.system.drawingInvalid, ent) + } ent.parent = nil ent.window = nil } diff --git a/backends/x/system.go b/backends/x/system.go index a9f33ce..a9c7187 100644 --- a/backends/x/system.go +++ b/backends/x/system.go @@ -98,7 +98,7 @@ func (system *system) afterEvent () { } func (system *system) layout (entity *entity, force bool) { - if entity == nil { return } + if entity == nil || !entity.isContainer { return } if entity.layoutInvalid == true || force { entity.element.(tomo.Container).Layout() entity.layoutInvalid = false diff --git a/elements/containers/container.go b/elements/containers/container.go deleted file mode 100644 index c10b5ba..0000000 --- a/elements/containers/container.go +++ /dev/null @@ -1,258 +0,0 @@ -package containers - -import "image" -import "git.tebibyte.media/sashakoshka/tomo" -import "git.tebibyte.media/sashakoshka/tomo/input" -import "git.tebibyte.media/sashakoshka/tomo/canvas" -import "git.tebibyte.media/sashakoshka/tomo/elements/core" -import "git.tebibyte.media/sashakoshka/tomo/default/theme" -import "git.tebibyte.media/sashakoshka/tomo/default/config" - -// Container is an element capable of containg other elements, and arranging -// them in a layout. -type Container struct { - *core.Core - *core.Propagator - core core.CoreControl - - layout tomo.Layout - children []tomo.LayoutEntry - warping bool - - config config.Wrapped - theme theme.Wrapped - - onFocusRequest func () (granted bool) - onFocusMotionRequest func (input.KeynavDirection) (granted bool) -} - -// NewContainer creates a new container. -func NewContainer (layout tomo.Layout) (element *Container) { - element = &Container { } - element.theme.Case = tomo.C("tomo", "container") - element.Core, element.core = core.NewCore(element, element.redoAll) - element.Propagator = core.NewPropagator(element, element.core) - element.SetLayout(layout) - return -} - -// SetLayout sets the layout of this container. -func (element *Container) SetLayout (layout tomo.Layout) { - element.layout = layout - element.updateMinimumSize() - if element.core.HasImage() { - element.redoAll() - element.core.DamageAll() - } -} - -// 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) { - if child0, ok := child.(tomo.Themeable); ok { - child0.SetTheme(element.theme.Theme) - } - if child0, ok := child.(tomo.Configurable); ok { - child0.SetConfig(element.config.Config) - } - child.SetParent(element) - - // add child - element.children = append (element.children, tomo.LayoutEntry { - Element: child, - Expand: expand, - }) - - // refresh stale data - element.updateMinimumSize() - if element.core.HasImage() && !element.warping { - element.redoAll() - element.core.DamageAll() - } -} - -// 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.redoAll() - element.core.DamageAll() - } -} - -// 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 { - element.clearChildEventHandlers(entry.Element) - element.children = append ( - element.children[:index], - element.children[index + 1:]...) - break - } - } - - element.updateMinimumSize() - if element.core.HasImage() && !element.warping { - element.redoAll() - element.core.DamageAll() - } -} - -func (element *Container) clearChildEventHandlers (child tomo.Element) { - child.DrawTo(nil, image.Rectangle { }, nil) - child.SetParent(nil) - - if child, ok := child.(tomo.Focusable); ok { - if child.Focused() { - child.HandleUnfocus() - } - } -} - -// DisownAll removes all child elements from the container at once. -func (element *Container) DisownAll () { - for _, entry := range element.children { - element.clearChildEventHandlers(entry.Element) - } - element.children = nil - - element.updateMinimumSize() - if element.core.HasImage() && !element.warping { - element.redoAll() - element.core.DamageAll() - } -} - -// 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) { - child = entry.Element - } - } - return -} - -func (element *Container) redoAll () { - if !element.core.HasImage() { return } - - // remove child canvasses so that any operations done in here will not - // cause a child to draw to a wack ass canvas. - for _, entry := range element.children { - entry.DrawTo(nil, entry.Bounds, nil) - } - - // do a layout - element.doLayout() - - // draw a background - rocks := make([]image.Rectangle, len(element.children)) - for index, entry := range element.children { - rocks[index] = entry.Bounds - } - - element.core.DrawBackgroundBoundsShatter ( - element.theme.Pattern(tomo.PatternBackground, tomo.State { }), - element.Bounds(), - rocks...) - - // cut our canvas up and give peices to child elements - for _, entry := range element.children { - entry.DrawTo ( - canvas.Cut(element.core, entry.Bounds), - entry.Bounds, func (region image.Rectangle) { - element.core.DamageRegion(region) - }) - } -} - -func (element *Container) Window () tomo.Window { - return element.core.Window() -} - -// NotifyMinimumSizeChange notifies the container that the minimum size of a -// child element has changed. -func (element *Container) NotifyMinimumSizeChange (child tomo.Element) { - element.updateMinimumSize() - element.redoAll() - element.core.DamageAll() -} - -// DrawBackground draws a portion of the container's background pattern within -// the specified bounds. The container will not push these changes. -func (element *Container) DrawBackground (bounds image.Rectangle) { - element.core.DrawBackgroundBounds ( - element.theme.Pattern(tomo.PatternBackground, tomo.State { }), - bounds) -} - -// SetTheme sets the element's theme. -func (element *Container) SetTheme (new tomo.Theme) { - if new == element.theme.Theme { return } - element.theme.Theme = new - element.Propagator.SetTheme(new) - element.updateMinimumSize() - element.redoAll() -} - -// SetConfig sets the element's configuration. -func (element *Container) SetConfig (new tomo.Config) { - if new == element.config.Config { return } - element.Propagator.SetConfig(new) - element.updateMinimumSize() - element.redoAll() -} - -func (element *Container) updateMinimumSize () { - margin := element.theme.Margin(tomo.PatternBackground) - padding := element.theme.Padding(tomo.PatternBackground) - width, height := element.layout.MinimumSize ( - element.children, margin, padding) - element.core.SetMinimumSize(width, height) -} - -func (element *Container) doLayout () { - margin := element.theme.Margin(tomo.PatternBackground) - padding := element.theme.Padding(tomo.PatternBackground) - element.layout.Arrange ( - element.children, margin, - padding, element.Bounds()) -} diff --git a/elements/containers/document.go b/elements/containers/notdone/document.go similarity index 100% rename from elements/containers/document.go rename to elements/containers/notdone/document.go diff --git a/elements/containers/scroll.go b/elements/containers/notdone/scroll.go similarity index 100% rename from elements/containers/scroll.go rename to elements/containers/notdone/scroll.go diff --git a/elements/containers/table.go b/elements/containers/notdone/table.go similarity index 100% rename from elements/containers/table.go rename to elements/containers/notdone/table.go diff --git a/elements/containers/vbox.go b/elements/containers/vbox.go new file mode 100644 index 0000000..e6b9215 --- /dev/null +++ b/elements/containers/vbox.go @@ -0,0 +1,178 @@ +package containers + +import "image" +import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/shatter" +import "git.tebibyte.media/sashakoshka/tomo/default/theme" + +type scratchEntry struct { + expand bool + minimum float64 +} + +type VBox struct { + entity tomo.ContainerEntity + scratch map[tomo.Element] scratchEntry + theme theme.Wrapped + padding bool + margin bool +} + +func NewVBox (padding, margin bool) (element *VBox) { + element = &VBox { padding: padding, margin: margin } + element.scratch = make(map[tomo.Element] scratchEntry) + element.theme.Case = tomo.C("tomo", "vBox") + element.entity = tomo.NewEntity(element).(tomo.ContainerEntity) + return +} + +func (element *VBox) Entity () tomo.Entity { + return element.entity +} + +func (element *VBox) Draw (destination canvas.Canvas) { + rocks := make([]image.Rectangle, element.entity.CountChildren()) + for index := 0; index < element.entity.CountChildren(); index ++ { + rocks[index] = element.entity.Child(index).Entity().Bounds() + } + + tiles := shatter.Shatter(element.entity.Bounds(), rocks...) + for _, tile := range tiles { + element.entity.DrawBackground(canvas.Cut(destination, tile)) + } +} + +func (element *VBox) Layout () { + margin := element.theme.Margin(tomo.PatternBackground) + padding := element.theme.Padding(tomo.PatternBackground) + bounds := element.entity.Bounds() + if element.padding { bounds = padding.Apply(bounds) } + + freeSpace, nExpanding := element.freeSpace() + expandingElementHeight := freeSpace / nExpanding + + // set the size and position of each element + x := float64(bounds.Min.X) + y := float64(bounds.Min.Y) + for index := 0; index < element.entity.CountChildren(); index ++ { + entry := element.scratch[element.entity.Child(index)] + + var height float64; if entry.expand { + height = expandingElementHeight + } else { + height = entry.minimum + } + + element.entity.PlaceChild (index, tomo.Bounds ( + int(x), int(y), + bounds.Dx(), int(height))) + + y += height + if element.margin { y += float64(margin.Y) } + } + +} + +func (element *VBox) Adopt (child tomo.Element, expand bool) { + element.entity.Adopt(child) + element.scratch[child] = scratchEntry { expand: expand } + element.updateMinimumSize() + element.entity.Invalidate() + element.entity.InvalidateLayout() +} + +func (element *VBox) Disown (child tomo.Element) { + index := element.entity.IndexOf(child) + if index < 0 { return } + element.entity.Disown(index) + delete(element.scratch, child) + element.updateMinimumSize() + element.entity.Invalidate() + element.entity.InvalidateLayout() +} + +func (element *VBox) DisownAll () { + func () { + for index := 0; index < element.entity.CountChildren(); index ++ { + index := index + defer element.entity.Disown(index) + } + } () + element.scratch = make(map[tomo.Element] scratchEntry) + element.updateMinimumSize() + element.entity.Invalidate() + element.entity.InvalidateLayout() +} + +func (element *VBox) HandleChildMinimumSizeChange (child tomo.Element) { + element.updateMinimumSize() + element.entity.Invalidate() + element.entity.InvalidateLayout() +} + +func (element *VBox) DrawBackground (destination canvas.Canvas) { + element.entity.DrawBackground(destination) +} + +// SetTheme sets the element's theme. +func (element *VBox) SetTheme (theme tomo.Theme) { + if theme == element.theme.Theme { return } + element.theme.Theme = theme + element.updateMinimumSize() + element.entity.Invalidate() + element.entity.InvalidateLayout() +} + +func (element *VBox) freeSpace () (space float64, nExpanding float64) { + margin := element.theme.Margin(tomo.PatternBackground) + padding := element.theme.Padding(tomo.PatternBackground) + space = float64(element.entity.Bounds().Dy()) + + for _, entry := range element.scratch { + if entry.expand { + nExpanding ++; + } else { + space -= float64(entry.minimum) + } + } + + if element.padding { + space -= float64(padding.Vertical()) + } + if element.margin { + space -= float64(margin.Y * len(element.scratch) - 1) + } + + return +} + +func (element *VBox) updateMinimumSize () { + margin := element.theme.Margin(tomo.PatternBackground) + padding := element.theme.Padding(tomo.PatternBackground) + var width, height int + + for index := 0; index < element.entity.CountChildren(); index ++ { + childWidth, childHeight := element.entity.ChildMinimumSize(index) + + key := element.entity.Child(index) + entry := element.scratch[key] + entry.minimum = float64(childHeight) + element.scratch[key] = entry + + if childWidth > width { + width = childWidth + } + height += childHeight + if element.margin && index > 0 { + height += margin.Y + } + } + + if element.padding { + width += padding.Horizontal() + height += padding.Vertical() + } + + element.entity.SetMinimumSize(width, height) +} diff --git a/examples/verticalLayout/main.go b/examples/verticalLayout/main.go index ddced0a..7860ec5 100644 --- a/examples/verticalLayout/main.go +++ b/examples/verticalLayout/main.go @@ -1,9 +1,8 @@ package main import "git.tebibyte.media/sashakoshka/tomo" -import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/elements" -import "git.tebibyte.media/sashakoshka/tomo/elements/testing" +// import "git.tebibyte.media/sashakoshka/tomo/elements/testing" import "git.tebibyte.media/sashakoshka/tomo/elements/containers" import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" @@ -15,8 +14,7 @@ func run () { window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 128, 128)) window.SetTitle("vertical stack") - container := containers.NewContainer(layouts.Vertical { true, true }) - window.Adopt(container) + container := containers.NewVBox(true, true) label := elements.NewLabel("it is a label hehe", true) button := elements.NewButton("drawing pad") @@ -24,7 +22,7 @@ func run () { button.OnClick (func () { container.DisownAll() container.Adopt(elements.NewLabel("Draw here:", false), false) - container.Adopt(testing.NewMouse(), true) + // container.Adopt(testing.NewMouse(), true) container.Adopt(okButton, false) okButton.Focus() }) @@ -35,6 +33,7 @@ func run () { container.Adopt(okButton, false) okButton.Focus() + window.Adopt(container) window.OnClose(tomo.Stop) window.Show() }