diff --git a/elements/basic/container.go b/elements/basic/container.go index dbd617e..ed66a22 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -26,8 +26,8 @@ func NewContainer (layout tomo.Layout) (element *Container) { func (element *Container) SetLayout (layout tomo.Layout) { element.layout = layout - element.recalculate() if element.core.HasImage() { + element.recalculate() element.draw() element.core.PushAll() } @@ -52,8 +52,8 @@ func (element *Container) Adopt (child tomo.Element, expand bool) { element.updateMinimumSize() element.updateSelectable() - element.recalculate() if element.core.HasImage() { + element.recalculate() element.draw() element.core.PushAll() } @@ -74,8 +74,8 @@ func (element *Container) Disown (child tomo.Element) { element.updateMinimumSize() element.updateSelectable() - element.recalculate() if element.core.HasImage() { + element.recalculate() element.draw() element.core.PushAll() } @@ -87,8 +87,8 @@ func (element *Container) DisownAll () { element.updateMinimumSize() element.updateSelectable() - element.recalculate() if element.core.HasImage() { + element.recalculate() element.draw() element.core.PushAll() } diff --git a/elements/basic/test.go b/elements/basic/test.go index 4c8307d..d5ab164 100644 --- a/elements/basic/test.go +++ b/elements/basic/test.go @@ -46,6 +46,7 @@ func (element *Test) Handle (event tomo.Event) { element.core, artist.NewUniform(color.White), 1, image.Pt(1, resizeEvent.Height - 2), image.Pt(resizeEvent.Width - 2, 1)) + // println(resizeEvent.Width, resizeEvent.Height) case tomo.EventMouseDown: element.drawing = true diff --git a/elements/layouts/dialog.go b/elements/layouts/dialog.go new file mode 100644 index 0000000..e166cb1 --- /dev/null +++ b/elements/layouts/dialog.go @@ -0,0 +1,150 @@ +package layouts + +import "image" +import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/theme" + +type Dialog struct { + // If Gap is true, a gap will be placed between each element. + Gap bool + + // If Pad is true, there will be padding running along the inside of the + // layout's border. + Pad bool +} + +// Arrange arranges a list of entries into a dialog. +func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { + if layout.Pad { + width -= theme.Padding() * 2 + height -= theme.Padding() * 2 + } + + controlRowWidth, controlRowHeight := 0, 0 + if len(entries) > 1 { + controlRowWidth, + controlRowHeight = layout.minimumSizeOf(entries[1:]) + } + + if len(entries) > 0 { + entries[0].Position = image.Point { } + if layout.Pad { + entries[0].Position.X += theme.Padding() + entries[0].Position.Y += theme.Padding() + } + mainHeight := height - controlRowHeight + if layout.Gap { + mainHeight -= theme.Padding() + } + mainBounds := entries[0].Bounds() + if mainBounds.Dy() != mainHeight || + mainBounds.Dx() != width { + entries[0].Handle (tomo.EventResize { + Width: width, + Height: mainHeight, + }) + } + } + + if len(entries) > 1 { + freeSpace := width + expandingElements := 0 + + // count the number of expanding elements and the amount of free + // space for them to collectively occupy + for index, entry := range entries[1:] { + if entry.Expand { + expandingElements ++ + } else { + entryMinWidth, _ := entry.MinimumSize() + freeSpace -= entryMinWidth + } + if index > 0 && layout.Gap { + freeSpace -= theme.Padding() + } + } + expandingElementWidth := 0 + if expandingElements > 0 { + expandingElementWidth = freeSpace / expandingElements + } + + // determine starting position and dimensions for control row + x, y := 0, height - controlRowHeight + if expandingElements == 0 { + x = width - controlRowWidth + } + if layout.Pad { + x += theme.Padding() + y += theme.Padding() + } + height -= controlRowHeight + + // set the size and position of each element in the control row + for index, entry := range entries[1:] { + if index > 0 && layout.Gap { x += theme.Padding() } + + entries[index + 1].Position = image.Pt(x, y) + entryWidth := 0 + if entry.Expand { + entryWidth = expandingElementWidth + } else { + entryWidth, _ = entry.MinimumSize() + } + x += entryWidth + entryBounds := entry.Bounds() + if entryBounds.Dy() != controlRowHeight || + entryBounds.Dx() != entryWidth { + entry.Handle (tomo.EventResize { + Width: entryWidth, + Height: controlRowHeight, + }) + } + } + } + + +} + +// MinimumSize returns the minimum width and height that will be needed to +// arrange the given list of entries. +func (layout Dialog) MinimumSize (entries []tomo.LayoutEntry) (width, height int) { + if len(entries) > 0 { + mainChildHeight := 0 + width, mainChildHeight = entries[0].MinimumSize() + height += mainChildHeight + } + + if len(entries) > 1 { + if layout.Gap { height += theme.Padding() } + additionalWidth, + additionalHeight := layout.minimumSizeOf(entries[1:]) + height += additionalHeight + if additionalWidth > width { + width = additionalWidth + } + } + + if layout.Pad { + width += theme.Padding() * 2 + height += theme.Padding() * 2 + } + return +} + +func (layout Dialog) minimumSizeOf ( + entries []tomo.LayoutEntry, +) ( + width, height int, +) { + for index, entry := range entries { + entryWidth, entryHeight := entry.MinimumSize() + if entryHeight > height { + height = entryHeight + } + width += entryWidth + if layout.Gap && index > 0 { + width += theme.Padding() + } + } + return +} diff --git a/elements/layouts/vertical.go b/elements/layouts/vertical.go index 81783f1..971729c 100644 --- a/elements/layouts/vertical.go +++ b/elements/layouts/vertical.go @@ -63,6 +63,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { y += entryHeight entryBounds := entry.Bounds() if entryBounds.Dx() != width || entryBounds.Dy() != entryHeight { + // println(entryHeight) entry.Handle (tomo.EventResize { Width: width, Height: entryHeight, diff --git a/examples/dialogLayout/main.go b/examples/dialogLayout/main.go new file mode 100644 index 0000000..d824937 --- /dev/null +++ b/examples/dialogLayout/main.go @@ -0,0 +1,27 @@ +package main + +import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/elements/basic" +import "git.tebibyte.media/sashakoshka/tomo/elements/layouts" +import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" + +func main () { + tomo.Run(run) +} + +func run () { + window, _ := tomo.NewWindow(2, 2) + window.SetTitle("dialog") + + container := basic.NewContainer(layouts.Dialog { true, true }) + window.Adopt(container) + + container.Adopt(basic.NewLabel("you will explode", false), true) + cancel := basic.NewButton("Cancel") + cancel.SetEnabled(false) + container.Adopt(cancel, false) + container.Adopt(basic.NewButton("OK"), false) + + window.OnClose(tomo.Stop) + window.Show() +} diff --git a/examples/horizontalLayout/main.go b/examples/horizontalLayout/main.go index e7cf116..092ed5e 100644 --- a/examples/horizontalLayout/main.go +++ b/examples/horizontalLayout/main.go @@ -11,7 +11,7 @@ func main () { func run () { window, _ := tomo.NewWindow(2, 2) - window.SetTitle("vertical stack") + window.SetTitle("horizontal stack") container := basic.NewContainer(layouts.Horizontal { true, true }) window.Adopt(container)