-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresponsive.go
More file actions
126 lines (114 loc) · 4.25 KB
/
Copy pathresponsive.go
File metadata and controls
126 lines (114 loc) · 4.25 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
package wui
import "sort"
// Responsive elements pick their content from the width actually
// available to them, so a layout can condense instead of overflowing or
// wrapping when a terminal — or an enclosing pane — gets narrow.
//
// The available width is the inner width of the nearest ancestor that
// resolves to a concrete size (a Box with Style.Width, a Card, a
// sidebar pane), falling back to the terminal width at the root. That
// makes a breakpoint container-relative: the same element inside a
// 30-cell sidebar and inside a full-width body sees different widths.
//
// In the browser the same tree renders as a set of variants that CSS
// container queries switch between, so resizing the window does not
// need a round trip through the model.
// Breakpoint is one variant of a Responsive element: the content to use
// when at least MinWidth cells are available.
type Breakpoint struct {
// MinWidth is the smallest available width, in cells, at which
// Content applies. The variant with the largest satisfied
// MinWidth wins, so breakpoints read like CSS min-width media
// queries: 0 is the fallback for the narrowest case.
MinWidth int
Content Element
}
// ResponsiveEl renders the variant matching the available width. It is
// built by Responsive; the renderers select from Variants.
type ResponsiveEl struct {
// Variants is sorted by ascending MinWidth by Responsive, which
// both renderers rely on.
Variants []Breakpoint
}
func (ResponsiveEl) isElement() {}
// Responsive picks the content that fits the width available to it.
//
// Each variant applies from its MinWidth up to the next one; the widest
// satisfied variant wins. Include a MinWidth of 0 to give the narrowest
// case a definite answer — without one, a width below every breakpoint
// falls back to the smallest variant anyway, but stating it is clearer.
//
// wui.Responsive(
// wui.At(0, wui.Text("NR")), // very narrow
// wui.At(24, wui.Text("NETRUNNER")), // roomier
// wui.At(80, bigAsciiLogo), // full width
// )
//
// Variants are given in any order; ties on MinWidth keep the first.
func Responsive(variants ...Breakpoint) Element {
out := make([]Breakpoint, 0, len(variants))
for _, v := range variants {
if v.Content == nil {
v.Content = Empty()
}
if v.MinWidth < 0 {
v.MinWidth = 0
}
out = append(out, v)
}
sort.SliceStable(out, func(i, j int) bool { return out[i].MinWidth < out[j].MinWidth })
return ResponsiveEl{Variants: out}
}
// At pairs a minimum available width with the content to show at or
// above it, for use with Responsive.
func At(minWidth int, content Element) Breakpoint {
return Breakpoint{MinWidth: minWidth, Content: content}
}
// ResponsiveText is the common case of Responsive over plain strings:
// the same message written for progressively wider space.
//
// wui.ResponsiveText(style, wui.TextAt(0, "err"), wui.TextAt(40, "connection failed"))
func ResponsiveText(style Style, variants ...TextVariant) Element {
bps := make([]Breakpoint, 0, len(variants))
for _, v := range variants {
bps = append(bps, At(v.MinWidth, Text(v.Content, WithTextStyle(style))))
}
return Responsive(bps...)
}
// TextVariant is one string of a ResponsiveText.
type TextVariant struct {
MinWidth int
Content string
}
// TextAt pairs a minimum available width with the string to show at or
// above it, for use with ResponsiveText.
func TextAt(minWidth int, content string) TextVariant {
return TextVariant{MinWidth: minWidth, Content: content}
}
// pick returns the variant for an available width of avail cells.
// Variants must be sorted ascending by MinWidth, as Responsive leaves
// them. With avail below every breakpoint the narrowest variant wins,
// so there is always something to draw.
func (e ResponsiveEl) pick(avail int) Element {
if len(e.Variants) == 0 {
return Empty()
}
chosen := e.Variants[0].Content
for _, v := range e.Variants {
if avail >= v.MinWidth {
chosen = v.Content
}
}
if chosen == nil {
return Empty()
}
return chosen
}
// widest returns the variant with the largest MinWidth — what an
// unconstrained context (an intrinsic-width measurement) should use.
func (e ResponsiveEl) widest() Element {
if len(e.Variants) == 0 {
return Empty()
}
return e.Variants[len(e.Variants)-1].Content
}