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