-
Notifications
You must be signed in to change notification settings - Fork 0
/
flex.go
273 lines (232 loc) · 7.08 KB
/
flex.go
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
package cview
import (
"sync"
"github.com/gdamore/tcell/v2"
)
// Configuration values.
const (
FlexRow = iota
FlexColumn
)
// flexItem holds layout options for one item.
type flexItem struct {
Item Primitive // The item to be positioned. May be nil for an empty item.
FixedSize int // The item's fixed size which may not be changed, 0 if it has no fixed size.
Proportion int // The item's proportion.
Focus bool // Whether or not this item attracts the layout's focus.
}
// Flex is a basic implementation of the Flexbox layout. The contained
// primitives are arranged horizontally or vertically. The way they are
// distributed along that dimension depends on their layout settings, which is
// either a fixed length or a proportional length. See AddItem() for details.
type Flex struct {
*Box
// The items to be positioned.
items []*flexItem
// FlexRow or FlexColumn.
direction int
// If set to true, Flex will use the entire screen as its available space
// instead its box dimensions.
fullScreen bool
sync.RWMutex
}
// NewFlex returns a new flexbox layout container with no primitives and its
// direction set to FlexColumn. To add primitives to this layout, see AddItem().
// To change the direction, see SetDirection().
//
// Note that Flex will have a transparent background by default so that any nil
// flex items will show primitives behind the Flex.
// To disable this transparency:
//
// flex.SetBackgroundTransparent(false)
func NewFlex() *Flex {
f := &Flex{
Box: NewBox(),
direction: FlexColumn,
}
f.SetBackgroundTransparent(true)
f.focus = f
return f
}
// GetDirection returns the direction in which the contained primitives are
// distributed. This can be either FlexColumn (default) or FlexRow.
func (f *Flex) GetDirection() int {
f.RLock()
defer f.RUnlock()
return f.direction
}
// SetDirection sets the direction in which the contained primitives are
// distributed. This can be either FlexColumn (default) or FlexRow.
func (f *Flex) SetDirection(direction int) {
f.Lock()
defer f.Unlock()
f.direction = direction
}
// SetFullScreen sets the flag which, when true, causes the flex layout to use
// the entire screen space instead of whatever size it is currently assigned to.
func (f *Flex) SetFullScreen(fullScreen bool) {
f.Lock()
defer f.Unlock()
f.fullScreen = fullScreen
}
// AddItem adds a new item to the container. The "fixedSize" argument is a width
// or height that may not be changed by the layout algorithm. A value of 0 means
// that its size is flexible and may be changed. The "proportion" argument
// defines the relative size of the item compared to other flexible-size items.
// For example, items with a proportion of 2 will be twice as large as items
// with a proportion of 1. The proportion must be at least 1 if fixedSize == 0
// (ignored otherwise).
//
// If "focus" is set to true, the item will receive focus when the Flex
// primitive receives focus. If multiple items have the "focus" flag set to
// true, the first one will receive focus.
//
// A nil value for the primitive represents empty space.
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) {
f.Lock()
defer f.Unlock()
if item == nil {
item = NewBox()
item.SetVisible(false)
}
f.items = append(f.items, &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
}
// AddItemAtIndex adds an item to the flex at a given index.
// For more information see AddItem.
func (f *Flex) AddItemAtIndex(index int, item Primitive, fixedSize, proportion int, focus bool) {
f.Lock()
defer f.Unlock()
newItem := &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus}
if index == 0 {
f.items = append([]*flexItem{newItem}, f.items...)
} else {
f.items = append(f.items[:index], append([]*flexItem{newItem}, f.items[index:]...)...)
}
}
// RemoveItem removes all items for the given primitive from the container,
// keeping the order of the remaining items intact.
func (f *Flex) RemoveItem(p Primitive) {
f.Lock()
defer f.Unlock()
for index := len(f.items) - 1; index >= 0; index-- {
if f.items[index].Item == p {
f.items = append(f.items[:index], f.items[index+1:]...)
}
}
}
// ResizeItem sets a new size for the item(s) with the given primitive. If there
// are multiple Flex items with the same primitive, they will all receive the
// same size. For details regarding the size parameters, see AddItem().
func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) {
f.Lock()
defer f.Unlock()
for _, item := range f.items {
if item.Item == p {
item.FixedSize = fixedSize
item.Proportion = proportion
}
}
}
// Draw draws this primitive onto the screen.
func (f *Flex) Draw(screen tcell.Screen) {
if !f.GetVisible() {
return
}
f.Box.Draw(screen)
f.Lock()
defer f.Unlock()
// Calculate size and position of the items.
// Do we use the entire screen?
if f.fullScreen {
width, height := screen.Size()
f.SetRect(0, 0, width, height)
}
// How much space can we distribute?
x, y, width, height := f.GetInnerRect()
var proportionSum int
distSize := width
if f.direction == FlexRow {
distSize = height
}
for _, item := range f.items {
if item.FixedSize > 0 {
distSize -= item.FixedSize
} else {
proportionSum += item.Proportion
}
}
// Calculate positions and draw items.
pos := x
if f.direction == FlexRow {
pos = y
}
for _, item := range f.items {
size := item.FixedSize
if size <= 0 {
if proportionSum > 0 {
size = distSize * item.Proportion / proportionSum
distSize -= size
proportionSum -= item.Proportion
} else {
size = 0
}
}
if item.Item != nil {
if f.direction == FlexColumn {
item.Item.SetRect(pos, y, size, height)
} else {
item.Item.SetRect(x, pos, width, size)
}
}
pos += size
if item.Item != nil {
if item.Item.GetFocusable().HasFocus() {
defer item.Item.Draw(screen)
} else {
item.Item.Draw(screen)
}
}
}
}
// Focus is called when this primitive receives focus.
func (f *Flex) Focus(delegate func(p Primitive)) {
f.Lock()
for _, item := range f.items {
if item.Item != nil && item.Focus {
f.Unlock()
delegate(item.Item)
return
}
}
f.Unlock()
}
// HasFocus returns whether or not this primitive has focus.
func (f *Flex) HasFocus() bool {
f.RLock()
defer f.RUnlock()
for _, item := range f.items {
if item.Item != nil && item.Item.GetFocusable().HasFocus() {
return true
}
}
return false
}
// MouseHandler returns the mouse handler for this primitive.
func (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
if !f.InRect(event.Position()) {
return false, nil
}
// Pass mouse events along to the first child item that takes it.
for _, item := range f.items {
if item.Item == nil {
continue
}
consumed, capture = item.Item.MouseHandler()(action, event, setFocus)
if consumed {
return
}
}
return
})
}