-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlayout.go
More file actions
226 lines (206 loc) · 7.12 KB
/
Copy pathlayout.go
File metadata and controls
226 lines (206 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package wui
// Layout helpers. Each returns a plain Box tree wired with the flex
// hints (Fill, Grow, MinWidth, Wrap, Justify) that both renderers
// understand — there are no dedicated layout element types.
// Fullscreen lays children out top-to-bottom in a Column that fills
// the whole terminal (minus the status bar) or browser viewport. Use
// it as the outermost element of an app that should own the screen.
func Fullscreen(children ...Element) Element {
return BoxEl{
Direction: Column,
Children: children,
Style: Style{Width: Fill, Height: Fill},
}
}
// AppShell is the classic application frame: a header pinned to the
// top, a footer pinned to the bottom, and a body that takes every
// line in between. Pass nil (or Empty()) for a part you don't need.
func AppShell(header, body, footer Element) Element {
return Fullscreen(
orEmpty(header),
Grow(1, orEmpty(body)),
orEmpty(footer),
)
}
// Centered centers child both horizontally and vertically in the full
// terminal or viewport — the login-box / splash-screen layout.
func Centered(child Element) Element {
return BoxEl{
Direction: Column,
Align: AlignCenter,
Justify: AlignCenter,
Children: []Element{orEmpty(child)},
Style: Style{Width: Fill, Height: Fill},
}
}
// Constrain caps content at an intended width and centers it in
// whatever space is left over. It is the fix for a layout that looks
// right at 100 columns and absurd at 400: panes stay together instead
// of drifting to opposite edges of the screen.
//
// wui.AppShell(header, wui.Constrain(120, body), footer)
//
// Children inside see the capped width, so Sidebar, Split and
// Responsive all lay out against the intended size rather than the
// terminal. Below the cap it is a no-op, so narrow terminals are
// unaffected.
func Constrain(maxWidth int, children ...Element) Element {
return BoxEl{
Direction: Column,
// Center the content within the capped block as well as the
// block within the screen: content narrower than the cap would
// otherwise sit against the block's left edge, which on a very
// wide terminal still reads as off-centre.
Align: AlignCenter,
Children: children,
Style: Style{
Width: Fill,
MaxWidth: maxWidth,
PlaceX: AlignCenter,
},
}
}
// ConstrainBox is Constrain with the placement and layout axis spelled
// out, for the cases where the content should hug an edge or run as a
// Row rather than being a centered Column.
func ConstrainBox(maxWidth int, dir Direction, place Align, children ...Element) Element {
return BoxEl{
Direction: dir,
Children: children,
Style: Style{
Width: Fill,
MaxWidth: maxWidth,
PlaceX: place,
},
}
}
// CenteredX centers children horizontally across the full width
// available, without claiming any vertical space. Unlike Centered it is
// safe inside an AppShell body: Centered fills the terminal height and
// would push the header and footer off screen.
//
// Use it to center a banner, a title, or a hint line within whatever
// pane it sits in.
func CenteredX(children ...Element) Element {
return BoxEl{
Direction: Column,
Align: AlignCenter,
Children: children,
Style: Style{Width: Fill},
}
}
// AlignedX places children horizontally within the full width available
// — AlignStart (the default) left, AlignCenter middle, AlignEnd right —
// without taking vertical space. CenteredX is the AlignCenter case.
func AlignedX(align Align, children ...Element) Element {
return BoxEl{
Direction: Column,
Align: align,
Children: children,
Style: Style{Width: Fill},
}
}
// Grow wraps child in a Box that takes n shares of the leftover space
// along the parent Box's axis. It is the building block AppShell and
// Split use; reach for it directly when composing custom layouts.
func Grow(n int, child Element) Element {
return BoxEl{
Direction: Column,
Children: []Element{orEmpty(child)},
Style: Style{Grow: n},
}
}
// SidebarOpts configures a Sidebar layout; set it through the
// With/Sidebar* option functions.
type SidebarOpts struct {
Width int // sidebar width in cells; default 24
MinMain int // main pane width below which the layout collapses; default 40
Gap int // cells between sidebar and main; default 2
Right bool // place the sidebar after the main pane
// Snug keeps the main pane at its natural width instead of letting
// it absorb the leftover space. Without it a wide terminal pushes
// the two panes to opposite edges with a gulf between them; with
// it they stay together and the slack falls outside the pair.
Snug bool
}
// Sidebar places side at fixed width next to a main pane that grows to
// take the rest. When the viewport is too narrow for both — main would
// drop under its minimum width — the panes collapse into a vertical
// stack, sidebar first (browser: flex-wrap; TUI: the Row re-lays
// itself out as a Column).
func Sidebar(side, main Element, opts ...func(*SidebarOpts)) Element {
o := SidebarOpts{Width: 24, MinMain: 40, Gap: 2}
for _, opt := range opts {
opt(&o)
}
sideBox := BoxEl{
Direction: Column,
Children: []Element{orEmpty(side)},
Style: Style{Width: o.Width},
}
mainStyle := Style{Grow: 1, MinWidth: o.MinMain}
if o.Snug {
// No Grow: the pane takes only what it needs. MinWidth stays so
// the collapse-to-stack threshold is unchanged.
mainStyle = Style{MinWidth: o.MinMain}
}
mainBox := BoxEl{
Direction: Column,
Children: []Element{orEmpty(main)},
Style: mainStyle,
}
children := []Element{sideBox, mainBox}
if o.Right {
children = []Element{mainBox, sideBox}
}
return BoxEl{
Direction: Row,
Gap: o.Gap,
Wrap: true,
Children: children,
Style: Style{Width: Fill},
}
}
// SidebarWidth sets the sidebar's fixed width in cells.
func SidebarWidth(n int) func(*SidebarOpts) {
return func(o *SidebarOpts) { o.Width = n }
}
// SidebarMinMain sets the main pane width below which the layout
// collapses to a vertical stack.
func SidebarMinMain(n int) func(*SidebarOpts) {
return func(o *SidebarOpts) { o.MinMain = n }
}
// SidebarGap sets the gap between the panes.
func SidebarGap(n int) func(*SidebarOpts) {
return func(o *SidebarOpts) { o.Gap = n }
}
// SidebarRight places the sidebar on the right of the main pane.
func SidebarRight() func(*SidebarOpts) {
return func(o *SidebarOpts) { o.Right = true }
}
// SidebarSnug keeps the panes side by side at their natural widths
// rather than stretching the main pane across the whole terminal.
func SidebarSnug() func(*SidebarOpts) {
return func(o *SidebarOpts) { o.Snug = true }
}
// Split lays panes side by side, each taking an equal share of the
// full width. For unequal shares compose Grow directly:
//
// wui.BoxEl{Direction: wui.Row, Style: wui.Style{Width: wui.Fill},
// Children: []wui.Element{wui.Grow(2, left), wui.Grow(1, right)}}
func Split(panes ...Element) Element {
children := make([]Element, 0, len(panes))
for _, p := range panes {
children = append(children, BoxEl{
Direction: Column,
Children: []Element{orEmpty(p)},
Style: Style{Grow: 1},
})
}
return BoxEl{
Direction: Row,
Gap: 1,
Children: children,
Style: Style{Width: Fill},
}
}