|
| @@ -124,6 +124,30 @@ func TestComposeFrameCacheSkipsUndamagedBlitsAndInvalidatesFocusAndLayout(t *tes |
| 124 | | require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage) | 124 | | require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage) |
| 125 | | } | 125 | | } |
| 126 | | | 126 | | |
| | | 127 | + | func TestComposeFrameStackDrawsTitleBarsAndDimsCollapsed(t *testing.T) { |
| | | 128 | + | state := cachedStackTitleState("collapsed", 1, true) |
| | | 129 | + | state.theme = themeui.Theme{ |
| | | 130 | + | Known: true, |
| | | 131 | + | TrueColor: true, |
| | | 132 | + | HasFG: true, |
| | | 133 | + | HasBG: true, |
| | | 134 | + | Foreground: renderer.RGB{R: 220, G: 210, B: 200}, |
| | | 135 | + | Background: renderer.RGB{R: 20, G: 30, B: 40}, |
| | | 136 | + | } |
| | | 137 | + | before := cloneCapturedStackState(state) |
| | | 138 | + | |
| | | 139 | + | out := composeFrame(state, composeCacheInput{}) |
| | | 140 | + | |
| | | 141 | + | collapsedTitle := out.frame.At(0, 1) |
| | | 142 | + | require.Equal(t, 'c', collapsedTitle.Rune) |
| | | 143 | + | require.True(t, collapsedTitle.Style.HasForegroundRGB, "collapsed title bar must use dimmed chrome") |
| | | 144 | + | focusedTitle := out.frame.At(0, 2) |
| | | 145 | + | require.Equal(t, 's', focusedTitle.Rune) |
| | | 146 | + | require.True(t, focusedTitle.Style.Inverse || focusedTitle.Style.HasBackgroundRGB, "focused title bar must use accent chrome") |
| | | 147 | + | require.Equal(t, 'E', out.frame.At(0, 3).Rune, "expanded pane content must be visible below its title bar") |
| | | 148 | + | require.Equal(t, before, state, "composition must not mutate the captured source frame") |
| | | 149 | + | } |
| | | 150 | + | |
| 127 | | func TestComposeFrameCacheRefreshesStackTitlesAndBars(t *testing.T) { | 151 | | func TestComposeFrameCacheRefreshesStackTitlesAndBars(t *testing.T) { |
| 128 | | initial := cachedStackTitleState("one", 1, true) | 152 | | initial := cachedStackTitleState("one", 1, true) |
| 129 | | committed := composeFrame(initial, composeCacheInput{}) | 153 | | committed := composeFrame(initial, composeCacheInput{}) |
|
| @@ -188,14 +212,31 @@ func cachedSplitState(fingerprint string, focus layout.PaneID, direction layout. |
| 188 | | | 212 | | |
| 189 | | func cachedStackTitleState(title string, generation uint64, reset bool) capturedRenderState { | 213 | | func cachedStackTitleState(title string, generation uint64, reset bool) capturedRenderState { |
| 190 | | first, second := layout.PaneID("first"), layout.PaneID("second") | 214 | | first, second := layout.PaneID("first"), layout.PaneID("second") |
| 191 | - | placements := []layout.Placement{{ID: first, TitleBar: domain.Rect{Width: 20, Height: 1}, Collapsed: true}, {ID: second, TitleBar: domain.Rect{Y: 1, Width: 20, Height: 1}, Collapsed: true}} | 215 | + | placements := []layout.Placement{ |
| | | 216 | + | {ID: first, TitleBar: domain.Rect{Width: 20, Height: 1}, Collapsed: true, InStack: true}, |
| | | 217 | + | {ID: second, TitleBar: domain.Rect{Y: 1, Width: 20, Height: 1}, Content: domain.Rect{Y: 2, Width: 20, Height: 3}, InStack: true}, |
| | | 218 | + | } |
| 192 | | return capturedRenderState{ | 219 | | return capturedRenderState{ |
| 193 | | reset: reset, | 220 | | reset: reset, |
| 194 | | layout: capturedTabLayout{root: &layout.Node{Kind: layout.Stack, Children: []*layout.Node{layout.NewLeaf(first), layout.NewLeaf(second)}, Expanded: second}, area: domain.Rect{Width: 20, Height: 5}, focus: second, placements: placements, fingerprint: "stack", valid: true}, | 221 | | layout: capturedTabLayout{root: &layout.Node{Kind: layout.Stack, Children: []*layout.Node{layout.NewLeaf(first), layout.NewLeaf(second)}, Expanded: second}, area: domain.Rect{Width: 20, Height: 5}, focus: second, placements: placements, fingerprint: "stack", valid: true}, |
| 195 | - | panes: []capturedPaneRenderState{{id: first, title: title, titleGeneration: generation, placement: placements[0]}, {id: second, title: "second", titleGeneration: 1, placement: placements[1], focused: true}}, | 222 | + | panes: []capturedPaneRenderState{{id: first, title: title, titleGeneration: generation, placement: placements[0]}, {id: second, frame: cachePaneFrame(20, 3, 'E'), title: "second", titleGeneration: 1, placement: placements[1], focused: true, damage: []renderer.Damage{renderer.FullRedraw()}}}, |
| 196 | | } | 223 | | } |
| 197 | | } | 224 | | } |
| 198 | | | 225 | | |
| | | 226 | + | func cloneCapturedStackState(in capturedRenderState) capturedRenderState { |
| | | 227 | + | out := in |
| | | 228 | + | out.layout.placements = append([]layout.Placement(nil), in.layout.placements...) |
| | | 229 | + | out.panes = append([]capturedPaneRenderState(nil), in.panes...) |
| | | 230 | + | for i := range out.panes { |
| | | 231 | + | if in.panes[i].frame.Cells != nil { |
| | | 232 | + | out.panes[i].frame = in.panes[i].frame.Clone() |
| | | 233 | + | } |
| | | 234 | + | out.panes[i].rawDamage = append([]renderer.Damage(nil), in.panes[i].rawDamage...) |
| | | 235 | + | out.panes[i].damage = append([]renderer.Damage(nil), in.panes[i].damage...) |
| | | 236 | + | } |
| | | 237 | + | return out |
| | | 238 | + | } |
| | | 239 | + | |
| 199 | | func cacheBarState(bottom string, reset bool) capturedRenderState { | 240 | | func cacheBarState(bottom string, reset bool) capturedRenderState { |
| 200 | | pane := cachePaneFrame(6, 1, 'P') | 241 | | pane := cachePaneFrame(6, 1, 'P') |
| 201 | | placement := layout.Placement{ID: "pane", Content: domain.Rect{Width: 6, Height: 1}} | 242 | | placement := layout.Placement{ID: "pane", Content: domain.Rect{Width: 6, Height: 1}} |