diff --git a/pkg/panes/alignment_test.go b/pkg/panes/alignment_test.go index 3c3fd55..e98f9c6 100644 --- a/pkg/panes/alignment_test.go +++ b/pkg/panes/alignment_test.go @@ -118,7 +118,7 @@ func TestWithAlignment(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - p := panes.NewLeaf(newDummyModel("foo", nil, nil)).WithDimensions(14, 5) + p, _ := panes.NewLeaf(newDummyModel("foo", nil, nil)).WithDimensions(14, 5) p = p.WithAlignment(tc.vertical, tc.horizontal) diff --git a/pkg/panes/dummymodel_test.go b/pkg/panes/dummymodel_test.go index 7b2a022..9082043 100644 --- a/pkg/panes/dummymodel_test.go +++ b/pkg/panes/dummymodel_test.go @@ -19,13 +19,17 @@ func newDummyModel(text string, initCmd func() tea.Msg, updateCallback func(tea. } func (m dummyModel) Init() tea.Cmd { - m.initCmd() + if m.initCmd != nil { + m.initCmd() + } return m.initCmd } func (m dummyModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - m.updateCallback(msg) + if m.updateCallback != nil { + m.updateCallback(msg) + } return m, nil } diff --git a/pkg/panes/layout.go b/pkg/panes/layout.go index 275c77e..213de6e 100644 --- a/pkg/panes/layout.go +++ b/pkg/panes/layout.go @@ -1,12 +1,17 @@ package panes -func (m Pane) WithDimensions(width, height int) Pane { +import tea "github.com/charmbracelet/bubbletea" + +type ViewableSize struct { + Width int + Height int +} + +func (m Pane) WithDimensions(width, height int) (Pane, tea.Cmd) { m.width = width m.height = height - m = m.recalculateDimensions() - - return m + return m.recalculateDimensions() } func (m Pane) WithDirection(direction Direction) Pane { @@ -29,10 +34,17 @@ func (m Pane) Children() []Pane { return m.children } -func (m Pane) recalculateDimensions() Pane { +func (m Pane) recalculateDimensions() (Pane, tea.Cmd) { numChildren := len(m.children) if numChildren == 0 { - return m + var cmd tea.Cmd = nil + if m.model != nil { + m.model, cmd = m.model.Update(ViewableSize{ + Width: m.width - 2, + Height: m.height - 2, + }) + } + return m, cmd } width := m.width height := m.height @@ -43,9 +55,15 @@ func (m Pane) recalculateDimensions() Pane { width = width / numChildren } + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + for i := range m.children { - m.children[i] = m.children[i].WithDimensions(width, height) + m.children[i], cmd = m.children[i].WithDimensions(width, height) + cmds = append(cmds, cmd) } - return m + return m, tea.Batch(cmds...) } diff --git a/pkg/panes/layout_test.go b/pkg/panes/layout_test.go index 15ef8b7..f315cc6 100644 --- a/pkg/panes/layout_test.go +++ b/pkg/panes/layout_test.go @@ -9,14 +9,14 @@ import ( func TestFullPaneTakesFullDimensions(t *testing.T) { // Ensure a single pane with no children takes the entire dimensions - pane := panes.NewLeaf(nil).WithDimensions(200, 100) + pane, _ := panes.NewLeaf(nil).WithDimensions(200, 100) assert.Equal(t, 200, pane.Width(), "Wrong width") assert.Equal(t, 100, pane.Height(), "Wrong height") } func TestHorizontalPanesSplitHorizontally(t *testing.T) { // Ensure a single pane with ten children horizontally splits the width - pane := panes.NewNode(panes.DirectionHorizontal, panes.NewLeaf(nil), panes.NewLeaf(nil)).WithDimensions(100, 100) + pane, _ := panes.NewNode(panes.DirectionHorizontal, panes.NewLeaf(nil), panes.NewLeaf(nil)).WithDimensions(100, 100) for i, child := range pane.Children() { assert.Equal(t, 50, child.Width(), "Wrong width for child %d", i) assert.Equal(t, 100, child.Height(), "Wrong height for child %d", i) @@ -25,7 +25,7 @@ func TestHorizontalPanesSplitHorizontally(t *testing.T) { func TestVerticalPanesSplitVertically(t *testing.T) { // Ensure a single pane with ten children vertically splits the height - pane := panes.NewNode(panes.DirectionVertical, panes.NewLeaf(nil), panes.NewLeaf(nil)).WithDimensions(100, 100) + pane, _ := panes.NewNode(panes.DirectionVertical, panes.NewLeaf(nil), panes.NewLeaf(nil)).WithDimensions(100, 100) for i, child := range pane.Children() { assert.Equal(t, 100, child.Width(), "Wrong width for child %d", i) assert.Equal(t, 50, child.Height(), "Wrong height for child %d", i) @@ -42,7 +42,7 @@ func TestNestedPanesSplitWithinParent(t *testing.T) { sideChildren[i] = panes.NewLeaf(nil) } - pane := panes.NewNode(panes.DirectionHorizontal, + pane, _ := panes.NewNode(panes.DirectionHorizontal, panes.NewLeaf(nil), panes.NewNode(panes.DirectionVertical, sideChildren...), ).WithDimensions(100, 100) @@ -66,7 +66,7 @@ func TestNestedPanesSplitWithinParent(t *testing.T) { func TestChangingDirectionChangesLayout(t *testing.T) { // With a parent pane that contains two leaf children split horizontally, // switch to vertical and make sure the layout changes. - pane := panes.NewNode(panes.DirectionHorizontal, panes.NewLeaf(nil), panes.NewLeaf(nil)).WithDimensions(100, 100) + pane, _ := panes.NewNode(panes.DirectionHorizontal, panes.NewLeaf(nil), panes.NewLeaf(nil)).WithDimensions(100, 100) for _, child := range pane.Children() { assert.Equal(t, 50, child.Width(), "Wrong width for child") diff --git a/pkg/panes/panes_test.go b/pkg/panes/panes_test.go index 13a740c..6a4a169 100644 --- a/pkg/panes/panes_test.go +++ b/pkg/panes/panes_test.go @@ -59,11 +59,34 @@ func TestUpdateParentNodeSendsUpdateToChildren(t *testing.T) { assert.True(t, sawUpdate, "Model should have seen update") } +func TestUpdateSendsResizeEventToLeafNodes(t *testing.T) { + // Given a parent node with nested children, + // the model should receive the update message + var resizeMsg panes.ViewableSize + dummy := newDummyModel("testing", nil, func(msg tea.Msg) { + switch msg := msg.(type) { + case panes.ViewableSize: + resizeMsg = msg + } + }) + parent := panes.NewNode(panes.DirectionHorizontal, panes.NewLeaf(dummy)) + + const ( + newWidth = 48 + newHeight = 24 + ) + + parent.WithDimensions(newWidth, newHeight) + + assert.Equal(t, newWidth-2, resizeMsg.Width, "Model should have seen new width") + assert.Equal(t, newHeight-2, resizeMsg.Height, "Model should have seen new height") +} + func TestViewLeafNodeShowsInnerModel(t *testing.T) { // Given a leaf node with a simple model, // the view should return the inner model's view dummy := newDummyModel("testing", nil, nil) - pane := panes.NewLeaf(dummy).WithDimensions(100, 100) + pane, _ := panes.NewLeaf(dummy).WithDimensions(100, 100) assert.Contains(t, pane.View(), "testing", "View should return inner model's view") } @@ -73,7 +96,7 @@ func TestViewLeafNodeCropsInnerModel(t *testing.T) { // the view should crop the text to the pane's dimensions. const expectedLines = 2 text := strings.Repeat("testing\n", 100) - pane := panes.NewLeaf(newDummyModel(text, nil, nil)). + pane, _ := panes.NewLeaf(newDummyModel(text, nil, nil)). WithDimensions(10, expectedLines+2) // Account for border numTesting := strings.Count(pane.View(), "testing") @@ -86,7 +109,7 @@ func TestViewParentNodeShowsInnerModelsOfChildren(t *testing.T) { // the view should return the inner model's view dummyLeft := newDummyModel("left", nil, nil) dummyRight := newDummyModel("right", nil, nil) - pane := panes.NewNode( + pane, _ := panes.NewNode( panes.DirectionHorizontal, panes.NewLeaf(dummyLeft), panes.NewLeaf(dummyRight), @@ -99,7 +122,7 @@ func TestViewParentNodeShowsInnerModelsOfChildren(t *testing.T) { func TestViewHorizontalGoesLeftToRight(t *testing.T) { dummyLeft := newDummyModel("left", nil, nil) dummyRight := newDummyModel("right", nil, nil) - pane := panes.NewNode( + pane, _ := panes.NewNode( panes.DirectionHorizontal, panes.NewLeaf(dummyLeft), panes.NewLeaf(dummyRight), @@ -114,7 +137,7 @@ func TestViewHorizontalGoesLeftToRight(t *testing.T) { func TestViewVerticalGoesTopToBottom(t *testing.T) { dummyTop := newDummyModel("top", nil, nil) dummyBottom := newDummyModel("bottom", nil, nil) - pane := panes.NewNode(panes.DirectionVertical, panes.NewLeaf(dummyTop), panes.NewLeaf(dummyBottom)).WithDimensions(10, 100) + pane, _ := panes.NewNode(panes.DirectionVertical, panes.NewLeaf(dummyTop), panes.NewLeaf(dummyBottom)).WithDimensions(10, 100) indexTop := strings.Index(pane.View(), "top") indexBottom := strings.Index(pane.View(), "bottom") @@ -127,7 +150,7 @@ func TestViewTitleIsShown(t *testing.T) { │testing │ ╰────────╯` dummy := newDummyModel("testing", nil, nil) - pane := panes.NewLeaf(dummy).WithName("title").WithDimensions(10, 3) + pane, _ := panes.NewLeaf(dummy).WithName("title").WithDimensions(10, 3) assert.Equal(t, expectedView, pane.View()) } @@ -137,7 +160,7 @@ func TestViewNoTitleShowsBorderProperly(t *testing.T) { │testing │ ╰────────╯` dummy := newDummyModel("testing", nil, nil) - pane := panes.NewLeaf(dummy).WithDimensions(10, 3) + pane, _ := panes.NewLeaf(dummy).WithDimensions(10, 3) assert.Equal(t, expectedView, pane.View()) } @@ -147,7 +170,7 @@ func TestViewRemovingTitleShowsBorderProperly(t *testing.T) { │testing │ ╰────────╯` dummy := newDummyModel("testing", nil, nil) - pane := panes.NewLeaf(dummy).WithName("title").WithDimensions(10, 3) + pane, _ := panes.NewLeaf(dummy).WithName("title").WithDimensions(10, 3) pane = pane.WithName("") diff --git a/pkg/yakdash/yakdash.go b/pkg/yakdash/yakdash.go index 88559d7..1301df8 100644 --- a/pkg/yakdash/yakdash.go +++ b/pkg/yakdash/yakdash.go @@ -38,7 +38,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case tea.WindowSizeMsg: - m.rootPane = m.rootPane.WithDimensions(msg.Width, msg.Height) + m.rootPane, cmd = m.rootPane.WithDimensions(msg.Width, msg.Height) + cmds = append(cmds, cmd) } m.rootPane, cmd = m.rootPane.Update(msg)