From 7754679710ec713d039508bb79662c6e5725549c Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 16 Jan 2023 13:49:32 -0500 Subject: [PATCH 1/9] Selectable elements can now request the selection to be moved --- backends/x/window.go | 14 ++++++++++++++ element.go | 20 +++++++++++++++++++- elements/basic/button.go | 1 + elements/basic/container.go | 11 +++++++++-- elements/core/core.go | 12 ++++++++++++ 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/backends/x/window.go b/backends/x/window.go index aeffb03..56110b6 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -261,6 +261,20 @@ func (window *Window) childSelectionRequestCallback () (granted bool) { return true } +func (window *Window) childSelectionMotionRequestCallback ( + direction tomo.SelectionDirection, +) ( + granted bool, +) { + if child, ok := window.child.(tomo.Selectable); ok { + if !child.HandleSelection(direction) { + child.HandleDeselection() + } + return true + } + return true +} + func (window *Window) pushRegion (region image.Rectangle) { if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") } image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image) diff --git a/element.go b/element.go index 61a4b1f..bf6be5a 100644 --- a/element.go +++ b/element.go @@ -3,7 +3,8 @@ package tomo // ParentHooks is a struct that contains callbacks that let child elements send // information to their parent element without the child element knowing // anything about the parent element or containing any reference to it. When a -// parent element adopts a child element, it must set these callbacks. +// parent element adopts a child element, it must set these callbacks. They are +// allowed to be nil. type ParentHooks struct { // Draw is called when a part of the child element's surface is updated. // The updated region will be passed to the callback as a sub-image. @@ -20,6 +21,10 @@ type ParentHooks struct { // request, it must send the child element a selection event and return // true. SelectionRequest func () (granted bool) + + // SelectionMotionRequest is called when the child element wants the + // parent element to select the previous/next element in relation to it. + SelectionMotionRequest func (direction SelectionDirection) (granted bool) } // RunDraw runs the Draw hook if it is not nil. If it is nil, it does nothing. @@ -46,6 +51,19 @@ func (hooks ParentHooks) RunSelectionRequest () (granted bool) { return } +// RunSelectionMotionRequest runs the SelectionMotionRequest hook if it is not +// nil. If it is nil, it does nothing. +func (hooks ParentHooks) RunSelectionMotionRequest ( + direction SelectionDirection, +) ( + granted bool, +) { + if hooks.SelectionMotionRequest != nil { + granted = hooks.SelectionMotionRequest(direction) + } + return +} + // Element represents a basic on-screen object. type Element interface { // Element must implement the Canvas interface. Elements should start diff --git a/elements/basic/button.go b/elements/basic/button.go index bb03733..9bfe0ed 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -103,6 +103,7 @@ func (element *Button) HandleSelection ( ) ( accepted bool, ) { + direction = direction.Canon() if !element.enabled { return false } if element.selected && direction != tomo.SelectionDirectionNeutral { return false diff --git a/elements/basic/container.go b/elements/basic/container.go index e8d58c9..1597ac5 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -43,6 +43,9 @@ func (element *Container) SetLayout (layout tomo.Layout) { // whatever way is defined by the current layout. func (element *Container) Adopt (child tomo.Element, expand bool) { child.SetParentHooks (tomo.ParentHooks { + Draw: func (region tomo.Canvas) { + element.drawChildRegion(child, region) + }, MinimumSizeChange: func (int, int) { element.updateMinimumSize() }, @@ -51,8 +54,12 @@ func (element *Container) Adopt (child tomo.Element, expand bool) { if !selectable { return } return element.childSelectionRequestCallback(child) }, - Draw: func (region tomo.Canvas) { - element.drawChildRegion(child, region) + SelectionMotionRequest: func ( + direction tomo.SelectionDirection, + ) ( + granted bool, + ) { + return element.core.RequestSelectionMotion(direction) }, }) element.children = append (element.children, tomo.LayoutEntry { diff --git a/elements/core/core.go b/elements/core/core.go index 9f0fd3e..e88cacb 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -80,6 +80,18 @@ func (control CoreControl) RequestSelection () (granted bool) { return control.core.hooks.RunSelectionRequest() } +// RequestSelectionMotion requests that the element's parent deselect this +// element and select the one to the left or right of it, depending on the +// direction. If the requests was granted, it returns true. If it was denied, it +// returns false. +func (control CoreControl) RequestSelectionMotion ( + direction tomo.SelectionDirection, +) ( + granted bool, +) { + return control.core.hooks.RunSelectionMotionRequest(direction) +} + // HasImage returns true if the core has an allocated image buffer, and false if // it doesn't. func (control CoreControl) HasImage () (has bool) { From d9281b139f9eacfe94bd5d48d7212747df92fe2a Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 16 Jan 2023 18:04:41 -0500 Subject: [PATCH 2/9] Labels may request an expanding height change --- artist/text.go | 24 ++++++++++++++---------- backends/x/window.go | 22 ++++++++++++++++++++-- element.go | 12 ++++++++++++ elements/basic/label.go | 13 +++++++++++++ elements/core/core.go | 6 ++++++ examples/flow/main.go | 10 +++++----- examples/label/main.go | 2 +- 7 files changed, 71 insertions(+), 18 deletions(-) diff --git a/artist/text.go b/artist/text.go index 2953c03..1cbd460 100644 --- a/artist/text.go +++ b/artist/text.go @@ -14,10 +14,11 @@ type characterLayout struct { } type wordLayout struct { - position image.Point - width int - spaceAfter int - text []characterLayout + position image.Point + width int + spaceAfter int + breaksAfter int + text []characterLayout } // Align specifies a text alignment method. @@ -167,13 +168,16 @@ func (drawer *TextDrawer) LineHeight () (height fixed.Int26_6) { func (drawer *TextDrawer) ReccomendedHeightFor (width int) (height int) { if !drawer.layoutClean { drawer.recalculate() } metrics := drawer.face.Metrics() - dot := fixed.Point26_6 { 0, 0 } + dot := fixed.Point26_6 { 0, metrics.Height } for _, word := range drawer.layout { - dot.X += fixed.Int26_6((word.width + word.spaceAfter) << 6) - - if word.width + word.position.X > width && word.position.X > 0 { + if word.width + dot.X.Round() > width { dot.Y += metrics.Height - dot.X = fixed.Int26_6(word.width << 6) + dot.X = 0 + } + dot.X += fixed.I(word.width + word.spaceAfter) + if word.breaksAfter > 0 { + dot.Y += fixed.I(word.breaksAfter).Mul(metrics.Height) + dot.X = 0 } } @@ -246,6 +250,7 @@ func (drawer *TextDrawer) recalculate () { if character == '\n' { dot.Y += metrics.Height dot.X = 0 + word.breaksAfter ++ previousCharacter = character index ++ } else { @@ -290,7 +295,6 @@ func (drawer *TextDrawer) recalculate () { if drawer.wrap { drawer.layoutBounds.Max.X = drawer.width - println("aaa") } else { drawer.layoutBounds.Max.X = horizontalExtent } diff --git a/backends/x/window.go b/backends/x/window.go index 56110b6..18dea52 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -89,6 +89,7 @@ func (window *Window) Adopt (child tomo.Element) { child.SetParentHooks (tomo.ParentHooks { Draw: window.childDrawCallback, MinimumSizeChange: window.childMinimumSizeChangeCallback, + ExpandingHeightChange: window.resizeChildToFit, SelectionRequest: window.childSelectionRequestCallback, }) @@ -205,8 +206,25 @@ func (window *Window) resizeChildToFit () { if child, ok := window.child.(tomo.Expanding); ok { minimumHeight := child.MinimumHeightFor(window.metrics.width) _, minimumWidth := child.MinimumSize() - window.childMinimumSizeChangeCallback ( - minimumWidth, minimumHeight) + + + icccm.WmNormalHintsSet ( + window.backend.connection, + window.xWindow.Id, + &icccm.NormalHints { + Flags: icccm.SizeHintPMinSize, + MinWidth: uint(minimumWidth), + MinHeight: uint(minimumHeight), + }) + + if window.metrics.height >= minimumHeight && + window.metrics.width >= minimumWidth { + + window.child.Resize ( + window.metrics.width, + window.metrics.height) + window.redrawChildEntirely() + } } else { window.child.Resize ( window.metrics.width, diff --git a/element.go b/element.go index bf6be5a..2bb31d9 100644 --- a/element.go +++ b/element.go @@ -15,6 +15,10 @@ type ParentHooks struct { // have already been resized and there is no need to send it a resize // event. MinimumSizeChange func (width, height int) + + // ExpandingHeightChange is called when the parameters affecting the + // element's expanding height have changed. + ExpandingHeightChange func () // SelectionRequest is called when the child element element wants // itself to be selected. If the parent element chooses to grant the @@ -42,6 +46,14 @@ func (hooks ParentHooks) RunMinimumSizeChange (width, height int) { } } +// RunExpandingHeightChange 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() + } +} + // RunSelectionRequest runs the SelectionRequest hook if it is not nil. If it is // nil, it does nothing. func (hooks ParentHooks) RunSelectionRequest () (granted bool) { diff --git a/elements/basic/label.go b/elements/basic/label.go index 1b33373..fe0d584 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -27,6 +27,7 @@ func NewLabel (text string, wrap bool) (element *Label) { return } +// Resize resizes the label and re-wraps the text if wrapping is enabled. func (element *Label) Resize (width, height int) { element.core.AllocateCanvas(width, height) if element.wrap { @@ -37,6 +38,17 @@ func (element *Label) Resize (width, height int) { return } +// MinimumHeightFor returns the reccomended height for this element based on the +// given width in order to allow the text to wrap properly. +func (element *Label) MinimumHeightFor (width int) (height int) { + if element.wrap { + return element.drawer.ReccomendedHeightFor(width) + } else { + _, height = element.MinimumSize() + return + } +} + // SetText sets the label's text. func (element *Label) SetText (text string) { if element.text == text { return } @@ -76,6 +88,7 @@ func (element *Label) updateMinimumSize () { if em < 1 { em = theme.Padding() } element.core.SetMinimumSize ( em, element.drawer.LineHeight().Round()) + element.core.NotifyExpandingHeightChange() } 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 e88cacb..1a5b455 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -144,6 +144,12 @@ 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() +} + // ConstrainSize contstrains the specified width and height to the minimum width // and height, and returns wether or not anything ended up being constrained. func (control CoreControl) ConstrainSize ( diff --git a/examples/flow/main.go b/examples/flow/main.go index 5d9d7ef..40f09dd 100644 --- a/examples/flow/main.go +++ b/examples/flow/main.go @@ -21,7 +21,7 @@ func run () { world.Stages = map [string] func () { "start": func () { label := basic.NewLabel ( - "you are standing next to a river.", false) + "you are standing next to a river.", true) button0 := basic.NewButton("go in the river") button0.OnClick(world.SwitchFunc("wet")) @@ -41,7 +41,7 @@ func run () { "wet": func () { label := basic.NewLabel ( "you get completely soaked.\n" + - "you die of hypothermia.", false) + "you die of hypothermia.", true) button0 := basic.NewButton("try again") button0.OnClick(world.SwitchFunc("start")) @@ -58,7 +58,7 @@ func run () { "house": func () { label := basic.NewLabel ( "you are standing in front of a delapidated " + - "house.", false) + "house.", true) button1 := basic.NewButton("go inside") button1.OnClick(world.SwitchFunc("inside")) @@ -78,7 +78,7 @@ func run () { "it is dark, but rays of light stream " + "through the window.\n" + "there is nothing particularly interesting " + - "here.", false) + "here.", true) button0 := basic.NewButton("go back outside") button0.OnClick(world.SwitchFunc("house")) @@ -92,7 +92,7 @@ func run () { "bear": func () { label := basic.NewLabel ( "you come face to face with a bear.\n" + - "it eats you (it was hungry).", false) + "it eats you (it was hungry).", true) button0 := basic.NewButton("try again") button0.OnClick(world.SwitchFunc("start")) diff --git a/examples/label/main.go b/examples/label/main.go index 3e5611e..9a0f59b 100644 --- a/examples/label/main.go +++ b/examples/label/main.go @@ -9,7 +9,7 @@ func main () { } func run () { - window, _ := tomo.NewWindow(480, 360) + window, _ := tomo.NewWindow(480, 2) window.SetTitle("example label") window.Adopt(basic.NewLabel(text, true)) window.OnClose(tomo.Stop) From 76d50bb01a9f7f30d6414c79596137379741db83 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 16 Jan 2023 22:27:17 -0500 Subject: [PATCH 3/9] Renamed Expanding to Flexible --- backends/x/window.go | 2 +- element.go | 6 +++--- layouts/vertical.go | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backends/x/window.go b/backends/x/window.go index 18dea52..83eac7a 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -203,7 +203,7 @@ func (window *Window) redrawChildEntirely () { func (window *Window) resizeChildToFit () { window.skipChildDrawCallback = true - if child, ok := window.child.(tomo.Expanding); ok { + if child, ok := window.child.(tomo.Flexible); ok { minimumHeight := child.MinimumHeightFor(window.metrics.width) _, minimumWidth := child.MinimumSize() diff --git a/element.go b/element.go index 2bb31d9..6194e70 100644 --- a/element.go +++ b/element.go @@ -183,9 +183,9 @@ type MouseTarget interface { HandleScroll (x, y int, deltaX, deltaY float64) } -// Expanding represents an element who's preferred minimum height can change in +// Flexible represents an element who's preferred minimum height can change in // response to its width. -type Expanding interface { +type Flexible interface { Element // HeightForWidth returns what the element's minimum height would be if @@ -200,6 +200,6 @@ type Expanding interface { // minimum size that the element may be resized to. // // It is important to note that if a parent container checks for - // expanding chilren, it itself will likely need to be expanding. + // flexible chilren, it itself will likely need to be flexible. MinimumHeightFor (width int) (height int) } diff --git a/layouts/vertical.go b/layouts/vertical.go index c366419..b9dabd4 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -25,6 +25,10 @@ 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 index, entry := range entries { From e94e170a047ffee590eee1252368cb424e8c9b33 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 16 Jan 2023 23:34:17 -0500 Subject: [PATCH 4/9] Half-working container flexibility propagation --- backends/x/window.go | 2 +- element.go | 12 ++++----- elements/basic/container.go | 40 ++++++++++++++++++++------- elements/basic/label.go | 2 +- elements/core/core.go | 8 +++--- examples/verticalLayout/main.go | 2 +- layout.go | 11 +++++--- layouts/vertical.go | 48 +++++++++++++++++++++++++++------ 8 files changed, 91 insertions(+), 34 deletions(-) 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 +} From 9459bcd942d849744547a2f3597169bc83905e99 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 16 Jan 2023 23:58:20 -0500 Subject: [PATCH 5/9] Horizontal layouts now take into account flexible elements --- elements/basic/container.go | 2 +- examples/horizontalLayout/main.go | 9 ++++----- layouts/horizontal.go | 29 ++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/elements/basic/container.go b/elements/basic/container.go index d1ea2db..c2e3cf2 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -300,7 +300,7 @@ func (element *Container) HandleSelection (direction tomo.SelectionDirection) (o return false } -// TODO: fix this! +// FIXME: fix this! // func (element *Container) MinimumHeightFor (width int) (height int) { // return element.layout.MinimumHeightFor(element.children, width) // } diff --git a/examples/horizontalLayout/main.go b/examples/horizontalLayout/main.go index 28f1699..f35bf54 100644 --- a/examples/horizontalLayout/main.go +++ b/examples/horizontalLayout/main.go @@ -3,7 +3,6 @@ package main import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/elements/basic" -import "git.tebibyte.media/sashakoshka/tomo/elements/testing" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" func main () { @@ -11,15 +10,15 @@ func main () { } func run () { - window, _ := tomo.NewWindow(2, 2) + window, _ := tomo.NewWindow(256, 2) window.SetTitle("horizontal stack") container := basic.NewContainer(layouts.Horizontal { true, true }) window.Adopt(container) - container.Adopt(testing.NewMouse(), true) - container.Adopt(basic.NewLabel("<- left\nright ->", false), false) - container.Adopt(testing.NewMouse(), true) + container.Adopt(basic.NewLabel("this is sample text", true), true) + container.Adopt(basic.NewLabel("this is sample text", true), true) + container.Adopt(basic.NewLabel("this is sample text", true), true) window.OnClose(tomo.Stop) window.Show() diff --git a/layouts/horizontal.go b/layouts/horizontal.go index 5ca5000..2450d61 100644 --- a/layouts/horizontal.go +++ b/layouts/horizontal.go @@ -72,7 +72,6 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int) // arrange the given list of entries. func (layout Horizontal) MinimumSize ( entries []tomo.LayoutEntry, - squeeze int, ) ( width, height int, ) { @@ -93,3 +92,31 @@ func (layout Horizontal) MinimumSize ( } return } + +func (layout Horizontal) MinimumHeightFor ( + entries []tomo.LayoutEntry, + width int, +) ( + height int, +) { + if layout.Pad { + width -= theme.Padding() * 2 + } + + for _, entry := range entries { + var entryHeight int + if child, flexible := entry.Element.(tomo.Flexible); flexible { + entryHeight = child.MinimumHeightFor(width) + } else { + _, entryHeight = entry.MinimumSize() + } + if entryHeight > height { + height = entryHeight + } + } + + if layout.Pad { + height += theme.Padding() * 2 + } + return +} From 40bdffc8bee8526611631bf047afce2def234ad4 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 17 Jan 2023 01:40:49 -0500 Subject: [PATCH 6/9] Horizontal layouts work nearly perfectly --- elements/basic/container.go | 6 ++--- layouts/horizontal.go | 49 ++++++++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/elements/basic/container.go b/elements/basic/container.go index c2e3cf2..158170d 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -301,9 +301,9 @@ func (element *Container) HandleSelection (direction tomo.SelectionDirection) (o } // FIXME: fix this! -// func (element *Container) MinimumHeightFor (width int) (height int) { - // return element.layout.MinimumHeightFor(element.children, width) -// } +func (element *Container) MinimumHeightFor (width int) (height int) { + return element.layout.MinimumHeightFor(element.children, width) +} func (element *Container) HandleDeselection () { element.selected = false diff --git a/layouts/horizontal.go b/layouts/horizontal.go index 2450d61..ebe3a2a 100644 --- a/layouts/horizontal.go +++ b/layouts/horizontal.go @@ -99,20 +99,51 @@ func (layout Horizontal) MinimumHeightFor ( ) ( height int, ) { + // TODO: maybe put calculating the expanding element width in a separate + // method if layout.Pad { - width -= theme.Padding() * 2 + width -= theme.Padding() * 2 + } + 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 { + 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 + } + + x, y := 0, 0 + if layout.Pad { + x += theme.Padding() + y += theme.Padding() } - for _, entry := range entries { - var entryHeight int + // set the size and position of each element + for index, entry := range entries { + entryWidth, entryHeight := entry.MinimumSize() + if entry.Expand { + entryWidth = expandingElementWidth + } if child, flexible := entry.Element.(tomo.Flexible); flexible { - entryHeight = child.MinimumHeightFor(width) - } else { - _, entryHeight = entry.MinimumSize() - } - if entryHeight > height { - height = entryHeight + entryHeight = child.MinimumHeightFor(entryWidth) } + if entryHeight > height { height = entryHeight } + + x += entryWidth + if index > 0 && layout.Gap { x += theme.Padding() } } if layout.Pad { From 7398f146ba25a65fe1607ceef58ec5cc4ab38baa Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 17 Jan 2023 10:55:38 -0500 Subject: [PATCH 7/9] Progress? --- elements/basic/container.go | 1 - layouts/vertical.go | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/elements/basic/container.go b/elements/basic/container.go index 158170d..1e4e785 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -300,7 +300,6 @@ func (element *Container) HandleSelection (direction tomo.SelectionDirection) (o return false } -// FIXME: fix this! func (element *Container) MinimumHeightFor (width int) (height int) { return element.layout.MinimumHeightFor(element.children, width) } diff --git a/layouts/vertical.go b/layouts/vertical.go index 1eb544c..84c05fd 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -106,14 +106,18 @@ func (layout Vertical) MinimumSize ( // specified elements at the given width, taking into account flexible elements. func (layout Vertical) MinimumHeightFor ( entries []tomo.LayoutEntry, - squeeze int, + width int, ) ( height int, ) { + if layout.Pad { + width -= theme.Padding() * 2 + } + for index, entry := range entries { child, flexible := entry.Element.(tomo.Flexible) if flexible { - height += child.MinimumHeightFor(squeeze) + height += child.MinimumHeightFor(width) } else { _, entryHeight := entry.MinimumSize() height += entryHeight From 91e60900ad4e9393aed87ac93d390a3247d3140d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 17 Jan 2023 14:17:26 -0500 Subject: [PATCH 8/9] X backend now understands flexible min widths --- backends/x/window.go | 3 ++- layouts/vertical.go | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/backends/x/window.go b/backends/x/window.go index f1921da..0602007 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -205,7 +205,7 @@ func (window *Window) resizeChildToFit () { window.skipChildDrawCallback = true if child, ok := window.child.(tomo.Flexible); ok { minimumHeight := child.MinimumHeightFor(window.metrics.width) - _, minimumWidth := child.MinimumSize() + minimumWidth, _ := child.MinimumSize() icccm.WmNormalHintsSet ( @@ -254,6 +254,7 @@ func (window *Window) childDrawCallback (region tomo.Canvas) { } func (window *Window) childMinimumSizeChangeCallback (width, height int) { + println("x thinks:",width, height) icccm.WmNormalHintsSet ( window.backend.connection, window.xWindow.Id, diff --git a/layouts/vertical.go b/layouts/vertical.go index 84c05fd..46614da 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -99,6 +99,7 @@ func (layout Vertical) MinimumSize ( width += theme.Padding() * 2 height += theme.Padding() * 2 } + println(width, height) return } @@ -112,6 +113,7 @@ func (layout Vertical) MinimumHeightFor ( ) { if layout.Pad { width -= theme.Padding() * 2 + height += theme.Padding() * 2 } for index, entry := range entries { @@ -127,9 +129,5 @@ func (layout Vertical) MinimumHeightFor ( height += theme.Padding() } } - - if layout.Pad { - height += theme.Padding() * 2 - } return } From 375205a4d2d8c0401b237fce191fc48d117ee552 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 17 Jan 2023 14:31:06 -0500 Subject: [PATCH 9/9] Core no longer naievely constrains canvas resize dimensions --- backends/x/window.go | 4 +--- elements/core/core.go | 1 - layouts/vertical.go | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/backends/x/window.go b/backends/x/window.go index 0602007..58fa4df 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -207,7 +207,6 @@ func (window *Window) resizeChildToFit () { minimumHeight := child.MinimumHeightFor(window.metrics.width) minimumWidth, _ := child.MinimumSize() - icccm.WmNormalHintsSet ( window.backend.connection, window.xWindow.Id, @@ -219,7 +218,7 @@ func (window *Window) resizeChildToFit () { if window.metrics.height >= minimumHeight && window.metrics.width >= minimumWidth { - + window.child.Resize ( window.metrics.width, window.metrics.height) @@ -254,7 +253,6 @@ func (window *Window) childDrawCallback (region tomo.Canvas) { } func (window *Window) childMinimumSizeChangeCallback (width, height int) { - println("x thinks:",width, height) icccm.WmNormalHintsSet ( window.backend.connection, window.xWindow.Id, diff --git a/elements/core/core.go b/elements/core/core.go index 9b057f0..91ceac9 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -113,7 +113,6 @@ func (control CoreControl) PushAll () { // AllocateCanvas resizes the canvas, constraining the width and height so that // they are not less than the specified minimum width and height. func (control *CoreControl) AllocateCanvas (width, height int) { - width, height, _ = control.ConstrainSize(width, height) control.core.canvas = tomo.NewBasicCanvas(width, height) control.BasicCanvas = control.core.canvas } diff --git a/layouts/vertical.go b/layouts/vertical.go index 46614da..e3f4a50 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -99,7 +99,6 @@ func (layout Vertical) MinimumSize ( width += theme.Padding() * 2 height += theme.Padding() * 2 } - println(width, height) return }