diff --git a/backends/x/window.go b/backends/x/window.go index 83eac7a..f1921da 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -89,7 +89,7 @@ func (window *Window) Adopt (child tomo.Element) { child.SetParentHooks (tomo.ParentHooks { Draw: window.childDrawCallback, MinimumSizeChange: window.childMinimumSizeChangeCallback, - ExpandingHeightChange: window.resizeChildToFit, + FlexibleHeightChange: window.resizeChildToFit, SelectionRequest: window.childSelectionRequestCallback, }) diff --git a/element.go b/element.go index 6194e70..06e37be 100644 --- a/element.go +++ b/element.go @@ -16,9 +16,9 @@ type ParentHooks struct { // event. MinimumSizeChange func (width, height int) - // ExpandingHeightChange is called when the parameters affecting the + // FlexibleHeightChange is called when the parameters affecting the // element's expanding height have changed. - ExpandingHeightChange func () + FlexibleHeightChange func () // SelectionRequest is called when the child element element wants // itself to be selected. If the parent element chooses to grant the @@ -46,11 +46,11 @@ func (hooks ParentHooks) RunMinimumSizeChange (width, height int) { } } -// RunExpandingHeightChange runs the ExpandingHeightChange hook if it is not +// RunFlexibleHeightChange runs the ExpandingHeightChange hook if it is not // nil. If it is nil, it does nothing. -func (hooks ParentHooks) RunExpandingHeightChange () { - if hooks.ExpandingHeightChange != nil { - hooks.ExpandingHeightChange() +func (hooks ParentHooks) RunFlexibleHeightChange () { + if hooks.FlexibleHeightChange != nil { + hooks.FlexibleHeightChange() } } diff --git a/elements/basic/container.go b/elements/basic/container.go index 1597ac5..d1ea2db 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -18,6 +18,7 @@ type Container struct { warping bool selected bool selectable bool + flexible bool } // NewContainer creates a new container. @@ -49,6 +50,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) { MinimumSizeChange: func (int, int) { element.updateMinimumSize() }, + FlexibleHeightChange: element.updateMinimumSize, SelectionRequest: func () (granted bool) { child, selectable := child.(tomo.Selectable) if !selectable { return } @@ -68,7 +70,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) { }) element.updateMinimumSize() - element.updateSelectable() + element.reflectChildProperties() if element.core.HasImage() && !element.warping { element.recalculate() element.draw() @@ -113,7 +115,7 @@ func (element *Container) Disown (child tomo.Element) { } element.updateMinimumSize() - element.updateSelectable() + element.reflectChildProperties() if element.core.HasImage() && !element.warping { element.recalculate() element.draw() @@ -126,7 +128,7 @@ func (element *Container) DisownAll () { element.children = nil element.updateMinimumSize() - element.updateSelectable() + element.reflectChildProperties() if element.core.HasImage() && !element.warping { element.recalculate() element.draw() @@ -183,8 +185,6 @@ func (element *Container) Resize (width, height int) { element.draw() } -// TODO: implement KeyboardTarget - func (element *Container) HandleMouseDown (x, y int, button tomo.Button) { child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget) if !handlesMouse { return } @@ -230,7 +230,7 @@ func (element *Container) HandleKeyDown ( }) } -func (element *Container) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { +func (element *Container) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) { element.forSelected (func (child tomo.Selectable) bool { child0, handlesKeyboard := child.(tomo.KeyboardTarget) if handlesKeyboard { @@ -300,6 +300,11 @@ func (element *Container) HandleSelection (direction tomo.SelectionDirection) (o return false } +// TODO: fix this! +// func (element *Container) MinimumHeightFor (width int) (height int) { + // return element.layout.MinimumHeightFor(element.children, width) +// } + func (element *Container) HandleDeselection () { element.selected = false element.forSelected (func (child tomo.Selectable) bool { @@ -326,6 +331,15 @@ func (element *Container) forSelectable (callback func (child tomo.Selectable) b } } +func (element *Container) forFlexible (callback func (child tomo.Flexible) bool) { + for _, entry := range element.children { + child, selectable := entry.Element.(tomo.Flexible) + if selectable { + if !callback(child) { break } + } + } +} + func (element *Container) forSelectableBackward (callback func (child tomo.Selectable) bool) { for index := len(element.children) - 1; index >= 0; index -- { child, selectable := element.children[index].Element.(tomo.Selectable) @@ -345,12 +359,17 @@ func (element *Container) firstSelected () (index int) { return -1 } -func (element *Container) updateSelectable () { +func (element *Container) reflectChildProperties () { element.selectable = false element.forSelectable (func (tomo.Selectable) bool { element.selectable = true return false }) + element.flexible = false + element.forFlexible (func (tomo.Flexible) bool { + element.flexible = true + return false + }) if !element.selectable { element.selected = false } @@ -374,8 +393,11 @@ func (element *Container) childSelectionRequestCallback ( } func (element *Container) updateMinimumSize () { - element.core.SetMinimumSize ( - element.layout.MinimumSize(element.children, 1e9)) + width, height := element.layout.MinimumSize(element.children) + if element.flexible { + height = element.layout.MinimumHeightFor(element.children, width) + } + element.core.SetMinimumSize(width, height) } func (element *Container) recalculate () { diff --git a/elements/basic/label.go b/elements/basic/label.go index fe0d584..b497efc 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -88,7 +88,7 @@ func (element *Label) updateMinimumSize () { if em < 1 { em = theme.Padding() } element.core.SetMinimumSize ( em, element.drawer.LineHeight().Round()) - element.core.NotifyExpandingHeightChange() + element.core.NotifyFlexibleHeightChange() } else { bounds := element.drawer.LayoutBounds() element.core.SetMinimumSize(bounds.Dx(), bounds.Dy()) diff --git a/elements/core/core.go b/elements/core/core.go index 1a5b455..9b057f0 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -144,10 +144,10 @@ func (control CoreControl) SetMinimumSize (width, height int) { } } -// NotifyExpandingHeightChange notifies the parent element that this element's -// expanding height has changed. -func (control CoreControl) NotifyExpandingHeightChange () { - control.core.hooks.RunExpandingHeightChange() +// NotifyFlexibleHeightChange notifies the parent element that this element's +// flexible height has changed. +func (control CoreControl) NotifyFlexibleHeightChange () { + control.core.hooks.RunFlexibleHeightChange() } // ConstrainSize contstrains the specified width and height to the minimum width diff --git a/examples/verticalLayout/main.go b/examples/verticalLayout/main.go index c96b86c..287d239 100644 --- a/examples/verticalLayout/main.go +++ b/examples/verticalLayout/main.go @@ -17,7 +17,7 @@ func run () { container := basic.NewContainer(layouts.Vertical { true, true }) window.Adopt(container) - label := basic.NewLabel("it is a label hehe", false) + label := basic.NewLabel("it is a label hehe", true) button := basic.NewButton("drawing pad") okButton := basic.NewButton("OK") button.OnClick (func () { diff --git a/layout.go b/layout.go index 7a3e458..4482d2e 100644 --- a/layout.go +++ b/layout.go @@ -20,8 +20,11 @@ type Layout interface { Arrange (entries []LayoutEntry, width, height int) // MinimumSize returns the minimum width and height that the layout - // needs to properly arrange the given slice of layout entries, given a - // "suqeeze" width so that the height can be determined for elements - // fulfilling the Expanding interface. - MinimumSize (entries []LayoutEntry, squeeze int) (width, height int) + // needs to properly arrange the given slice of layout entries. + MinimumSize (entries []LayoutEntry) (width, height int) + + // MinimumHeightFor Returns the minimum height the layout needs to lay + // out the specified elements at the given width, taking into account + // flexible elements. + MinimumHeightFor (entries []LayoutEntry, squeeze int) (height int) } diff --git a/layouts/vertical.go b/layouts/vertical.go index b9dabd4..1eb544c 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -25,17 +25,22 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { freeSpace := height expandingElements := 0 - // TODO: find the width first, then store the minumum height of - // everything in a list, then arrange everything. - // minimumHeights := make([]int, len(entries)) - // count the number of expanding elements and the amount of free space - // for them to collectively occupy + // for them to collectively occupy, while gathering minimum heights. + minimumHeights := make([]int, len(entries)) for index, entry := range entries { + var entryMinHeight int + + if child, flexible := entry.Element.(tomo.Flexible); flexible { + entryMinHeight = child.MinimumHeightFor(width) + } else { + _, entryMinHeight = entry.MinimumSize() + } + minimumHeights[index] = entryMinHeight + if entry.Expand { expandingElements ++ } else { - _, entryMinHeight := entry.MinimumSize() freeSpace -= entryMinHeight } if index > 0 && layout.Gap { @@ -62,7 +67,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { if entry.Expand { entryHeight = expandingElementHeight } else { - _, entryHeight = entry.MinimumSize() + entryHeight = minimumHeights[index] } y += entryHeight entryBounds := entry.Bounds() @@ -76,7 +81,6 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { // arrange the given list of entries. func (layout Vertical) MinimumSize ( entries []tomo.LayoutEntry, - squeeze int, ) ( width, height int, ) { @@ -97,3 +101,31 @@ func (layout Vertical) MinimumSize ( } return } + +// MinimumHeightFor Returns the minimum height the layout needs to lay out the +// specified elements at the given width, taking into account flexible elements. +func (layout Vertical) MinimumHeightFor ( + entries []tomo.LayoutEntry, + squeeze int, +) ( + height int, +) { + for index, entry := range entries { + child, flexible := entry.Element.(tomo.Flexible) + if flexible { + height += child.MinimumHeightFor(squeeze) + } else { + _, entryHeight := entry.MinimumSize() + height += entryHeight + } + + if layout.Gap && index > 0 { + height += theme.Padding() + } + } + + if layout.Pad { + height += theme.Padding() * 2 + } + return +}