1 package cview
2
3 import (
4 "sync"
5
6 "github.com/gdamore/tcell/v2"
7 )
8
9
10
11
12 type Box struct {
13
14 x, y, width, height int
15
16
17 paddingTop, paddingBottom, paddingLeft, paddingRight int
18
19
20 innerX, innerY, innerWidth, innerHeight int
21
22
23 visible bool
24
25
26 borderColorFocused tcell.Color
27
28
29 backgroundColor tcell.Color
30
31
32 backgroundTransparent bool
33
34
35
36 border bool
37
38
39 borderColor tcell.Color
40
41
42 borderAttributes tcell.AttrMask
43
44
45 title []byte
46
47
48 titleColor tcell.Color
49
50
51 titleAlign int
52
53
54
55 focus Focusable
56
57
58 hasFocus bool
59
60
61 showFocus bool
62
63
64
65
66 inputCapture func(event *tcell.EventKey) *tcell.EventKey
67
68
69 draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
70
71
72
73
74 mouseCapture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)
75
76 l sync.RWMutex
77 }
78
79
80 func NewBox() *Box {
81 b := &Box{
82 width: 15,
83 height: 10,
84 visible: true,
85 backgroundColor: Styles.PrimitiveBackgroundColor,
86 borderColor: Styles.BorderColor,
87 titleColor: Styles.TitleColor,
88 borderColorFocused: ColorUnset,
89 titleAlign: AlignCenter,
90 showFocus: true,
91 }
92 b.focus = b
93 b.updateInnerRect()
94 return b
95 }
96
97 func (b *Box) updateInnerRect() {
98 x, y, width, height := b.x, b.y, b.width, b.height
99
100
101 if b.border {
102 x++
103 y++
104 width -= 2
105 height -= 2
106 }
107
108
109 x, y, width, height =
110 x+b.paddingLeft,
111 y+b.paddingTop,
112 width-b.paddingLeft-b.paddingRight,
113 height-b.paddingTop-b.paddingBottom
114
115 if width < 0 {
116 width = 0
117 }
118 if height < 0 {
119 height = 0
120 }
121
122 b.innerX, b.innerY, b.innerWidth, b.innerHeight = x, y, width, height
123 }
124
125
126 func (b *Box) GetPadding() (top, bottom, left, right int) {
127 b.l.RLock()
128 defer b.l.RUnlock()
129
130 return b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight
131 }
132
133
134 func (b *Box) SetPadding(top, bottom, left, right int) {
135 b.l.Lock()
136 defer b.l.Unlock()
137
138 b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight = top, bottom, left, right
139
140 b.updateInnerRect()
141 }
142
143
144
145 func (b *Box) GetRect() (int, int, int, int) {
146 b.l.RLock()
147 defer b.l.RUnlock()
148
149 return b.x, b.y, b.width, b.height
150 }
151
152
153
154
155 func (b *Box) GetInnerRect() (int, int, int, int) {
156 b.l.RLock()
157 defer b.l.RUnlock()
158
159 return b.innerX, b.innerY, b.innerWidth, b.innerHeight
160 }
161
162
163
164
165
166
167 func (b *Box) SetRect(x, y, width, height int) {
168 b.l.Lock()
169 defer b.l.Unlock()
170
171 b.x, b.y, b.width, b.height = x, y, width, height
172
173 b.updateInnerRect()
174 }
175
176
177 func (b *Box) SetVisible(v bool) {
178 b.l.Lock()
179 defer b.l.Unlock()
180
181 b.visible = v
182 }
183
184
185 func (b *Box) GetVisible() bool {
186 b.l.RLock()
187 defer b.l.RUnlock()
188
189 return b.visible
190 }
191
192
193
194
195
196
197
198
199
200 func (b *Box) SetDrawFunc(handler func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)) {
201 b.l.Lock()
202 defer b.l.Unlock()
203
204 b.draw = handler
205 }
206
207
208
209 func (b *Box) GetDrawFunc() func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
210 b.l.RLock()
211 defer b.l.RUnlock()
212
213 return b.draw
214 }
215
216
217
218
219
220
221 func (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primitive))) func(*tcell.EventKey, func(p Primitive)) {
222 return func(event *tcell.EventKey, setFocus func(p Primitive)) {
223 if b.inputCapture != nil {
224 event = b.inputCapture(event)
225 }
226 if event != nil && inputHandler != nil {
227 inputHandler(event, setFocus)
228 }
229 }
230 }
231
232
233 func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
234 b.l.RLock()
235 defer b.l.RUnlock()
236
237 return b.WrapInputHandler(nil)
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) {
255 b.l.Lock()
256 defer b.l.Unlock()
257
258 b.inputCapture = capture
259 }
260
261
262
263 func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
264 b.l.RLock()
265 defer b.l.RUnlock()
266
267 return b.inputCapture
268 }
269
270
271
272
273
274
275 func (b *Box) WrapMouseHandler(mouseHandler func(MouseAction, *tcell.EventMouse, func(p Primitive)) (bool, Primitive)) func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
276 return func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
277 if b.mouseCapture != nil {
278 action, event = b.mouseCapture(action, event)
279 }
280 if event != nil && mouseHandler != nil {
281 consumed, capture = mouseHandler(action, event, setFocus)
282 }
283 return
284 }
285 }
286
287
288 func (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
289 return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
290 if action == MouseLeftClick && b.InRect(event.Position()) {
291 setFocus(b)
292 consumed = true
293 }
294 return
295 })
296 }
297
298
299
300
301
302
303
304
305
306 func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) {
307 b.mouseCapture = capture
308 }
309
310
311
312 func (b *Box) InRect(x, y int) bool {
313 rectX, rectY, width, height := b.GetRect()
314 return x >= rectX && x < rectX+width && y >= rectY && y < rectY+height
315 }
316
317
318
319 func (b *Box) GetMouseCapture() func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse) {
320 return b.mouseCapture
321 }
322
323
324 func (b *Box) SetBackgroundColor(color tcell.Color) {
325 b.l.Lock()
326 defer b.l.Unlock()
327
328 b.backgroundColor = color
329 }
330
331
332 func (b *Box) GetBackgroundColor() tcell.Color {
333 b.l.RLock()
334 defer b.l.RUnlock()
335 return b.backgroundColor
336 }
337
338
339
340
341
342
343
344 func (b *Box) SetBackgroundTransparent(transparent bool) {
345 b.l.Lock()
346 defer b.l.Unlock()
347
348 b.backgroundTransparent = transparent
349 }
350
351
352
353 func (b *Box) GetBorder() bool {
354 b.l.RLock()
355 defer b.l.RUnlock()
356 return b.border
357 }
358
359
360
361 func (b *Box) SetBorder(show bool) {
362 b.l.Lock()
363 defer b.l.Unlock()
364
365 b.border = show
366
367 b.updateInnerRect()
368 }
369
370
371 func (b *Box) SetBorderColor(color tcell.Color) {
372 b.l.Lock()
373 defer b.l.Unlock()
374
375 b.borderColor = color
376 }
377
378
379 func (b *Box) SetBorderColorFocused(color tcell.Color) {
380 b.l.Lock()
381 defer b.l.Unlock()
382 b.borderColorFocused = color
383 }
384
385
386
387
388
389 func (b *Box) SetBorderAttributes(attr tcell.AttrMask) {
390 b.l.Lock()
391 defer b.l.Unlock()
392
393 b.borderAttributes = attr
394 }
395
396
397 func (b *Box) SetTitle(title string) {
398 b.l.Lock()
399 defer b.l.Unlock()
400
401 b.title = []byte(title)
402 }
403
404
405 func (b *Box) GetTitle() string {
406 b.l.RLock()
407 defer b.l.RUnlock()
408
409 return string(b.title)
410 }
411
412
413 func (b *Box) SetTitleColor(color tcell.Color) {
414 b.l.Lock()
415 defer b.l.Unlock()
416
417 b.titleColor = color
418 }
419
420
421
422 func (b *Box) SetTitleAlign(align int) {
423 b.l.Lock()
424 defer b.l.Unlock()
425
426 b.titleAlign = align
427 }
428
429
430 func (b *Box) Draw(screen tcell.Screen) {
431 b.l.Lock()
432 defer b.l.Unlock()
433
434
435 if !b.visible {
436 return
437 }
438
439
440 if b.width <= 0 || b.height <= 0 {
441 return
442 }
443
444 def := tcell.StyleDefault
445
446
447 background := def.Background(b.backgroundColor)
448 if !b.backgroundTransparent {
449 for y := b.y; y < b.y+b.height; y++ {
450 for x := b.x; x < b.x+b.width; x++ {
451 screen.SetContent(x, y, ' ', nil, background)
452 }
453 }
454 }
455
456
457 if b.border && b.width >= 2 && b.height >= 2 {
458 border := SetAttributes(background.Foreground(b.borderColor), b.borderAttributes)
459 var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
460
461 var hasFocus bool
462 if b.focus == b {
463 hasFocus = b.hasFocus
464 } else {
465 hasFocus = b.focus.HasFocus()
466 }
467
468 if hasFocus && b.borderColorFocused != ColorUnset {
469 border = SetAttributes(background.Foreground(b.borderColorFocused), b.borderAttributes)
470 }
471
472 if hasFocus && b.showFocus {
473 horizontal = Borders.HorizontalFocus
474 vertical = Borders.VerticalFocus
475 topLeft = Borders.TopLeftFocus
476 topRight = Borders.TopRightFocus
477 bottomLeft = Borders.BottomLeftFocus
478 bottomRight = Borders.BottomRightFocus
479 } else {
480 horizontal = Borders.Horizontal
481 vertical = Borders.Vertical
482 topLeft = Borders.TopLeft
483 topRight = Borders.TopRight
484 bottomLeft = Borders.BottomLeft
485 bottomRight = Borders.BottomRight
486 }
487 for x := b.x + 1; x < b.x+b.width-1; x++ {
488 screen.SetContent(x, b.y, horizontal, nil, border)
489 screen.SetContent(x, b.y+b.height-1, horizontal, nil, border)
490 }
491 for y := b.y + 1; y < b.y+b.height-1; y++ {
492 screen.SetContent(b.x, y, vertical, nil, border)
493 screen.SetContent(b.x+b.width-1, y, vertical, nil, border)
494 }
495 screen.SetContent(b.x, b.y, topLeft, nil, border)
496 screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border)
497 screen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, border)
498 screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, border)
499
500
501 if len(b.title) > 0 && b.width >= 4 {
502 printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
503 if len(b.title)-printed > 0 && printed > 0 {
504 _, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
505 fg, _, _ := style.Decompose()
506 Print(screen, []byte(string(SemigraphicsHorizontalEllipsis)), b.x+b.width-2, b.y, 1, AlignLeft, fg)
507 }
508 }
509 }
510
511
512 if b.draw != nil {
513 b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)
514 }
515 }
516
517
518
519 func (b *Box) ShowFocus(showFocus bool) {
520 b.l.Lock()
521 defer b.l.Unlock()
522
523 b.showFocus = showFocus
524 }
525
526
527 func (b *Box) Focus(delegate func(p Primitive)) {
528 b.l.Lock()
529 defer b.l.Unlock()
530
531 b.hasFocus = true
532 }
533
534
535 func (b *Box) Blur() {
536 b.l.Lock()
537 defer b.l.Unlock()
538
539 b.hasFocus = false
540 }
541
542
543 func (b *Box) HasFocus() bool {
544 b.l.RLock()
545 defer b.l.RUnlock()
546
547 return b.hasFocus
548 }
549
550
551 func (b *Box) GetFocusable() Focusable {
552 b.l.RLock()
553 defer b.l.RUnlock()
554
555 return b.focus
556 }
557
558
559
560
561
562 func (b *Box) GetBorderPadding() (top, bottom, left, right int) {
563 return b.GetPadding()
564 }
565
566
567
568
569
570 func (b *Box) SetBorderPadding(top, bottom, left, right int) {
571 b.SetPadding(top, bottom, left, right)
572 }
573
View as plain text