1 package cview
2
3 import (
4 "fmt"
5 "sync"
6 "time"
7
8 "github.com/gdamore/tcell/v2"
9 )
10
11 const (
12
13 queueSize = 100
14
15
16 resizeEventThrottle = 50 * time.Millisecond
17 )
18
19
20
21
22
23
24
25
26
27
28
29
30
31 type Application struct {
32
33
34
35 screen tcell.Screen
36
37
38 width, height int
39
40
41 focus Primitive
42
43
44 root Primitive
45
46
47 rootFullscreen bool
48
49
50 enableBracketedPaste bool
51
52
53 enableMouse bool
54
55
56
57
58 inputCapture func(event *tcell.EventKey) *tcell.EventKey
59
60
61 lastResize time.Time
62
63
64 throttleResize *time.Timer
65
66
67
68
69
70 afterResize func(width int, height int)
71
72
73
74 beforeFocus func(p Primitive) bool
75
76
77
78 afterFocus func(p Primitive)
79
80
81
82 beforeDraw func(screen tcell.Screen) bool
83
84
85
86 afterDraw func(screen tcell.Screen)
87
88
89 events chan tcell.Event
90
91
92 updates chan func()
93
94
95
96
97
98 screenReplacement chan tcell.Screen
99
100
101
102
103 mouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)
104
105
106
107 doubleClickInterval time.Duration
108
109 mouseCapturingPrimitive Primitive
110 lastMouseX, lastMouseY int
111 mouseDownX, mouseDownY int
112 lastMouseClick time.Time
113 lastMouseButtons tcell.ButtonMask
114
115 sync.RWMutex
116 }
117
118
119 func NewApplication() *Application {
120 return &Application{
121 enableBracketedPaste: true,
122 events: make(chan tcell.Event, queueSize),
123 updates: make(chan func(), queueSize),
124 screenReplacement: make(chan tcell.Screen, 1),
125 }
126 }
127
128
129
130
131
132
133
134 func (a *Application) HandlePanic() {
135 p := recover()
136 if p == nil {
137 return
138 }
139
140 a.finalizeScreen()
141
142 panic(p)
143 }
144
145
146
147
148
149
150
151
152
153
154 func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) {
155 a.Lock()
156 defer a.Unlock()
157
158 a.inputCapture = capture
159
160 }
161
162
163
164 func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
165 a.RLock()
166 defer a.RUnlock()
167
168 return a.inputCapture
169 }
170
171
172
173
174
175
176 func (a *Application) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) {
177 a.mouseCapture = capture
178
179 }
180
181
182
183 func (a *Application) GetMouseCapture() func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) {
184 return a.mouseCapture
185 }
186
187
188
189
190 func (a *Application) SetDoubleClickInterval(interval time.Duration) {
191 a.doubleClickInterval = interval
192 }
193
194
195
196
197
198
199
200 func (a *Application) SetScreen(screen tcell.Screen) {
201 if screen == nil {
202 return
203 }
204
205 a.Lock()
206 if a.screen == nil {
207
208 a.screen = screen
209 a.Unlock()
210 return
211 }
212
213
214 oldScreen := a.screen
215 a.Unlock()
216 oldScreen.Fini()
217 a.screenReplacement <- screen
218 }
219
220
221
222
223 func (a *Application) GetScreen() tcell.Screen {
224 a.RLock()
225 defer a.RUnlock()
226 return a.screen
227 }
228
229
230
231 func (a *Application) GetScreenSize() (width, height int) {
232 a.RLock()
233 defer a.RUnlock()
234 return a.width, a.height
235 }
236
237
238
239
240 func (a *Application) Init() error {
241 a.Lock()
242 defer a.Unlock()
243 return a.init()
244 }
245
246 func (a *Application) init() error {
247 if a.screen != nil {
248 return nil
249 }
250
251 var err error
252 a.screen, err = tcell.NewScreen()
253 if err != nil {
254 a.Unlock()
255 return err
256 }
257 if err = a.screen.Init(); err != nil {
258 a.Unlock()
259 return err
260 }
261 a.width, a.height = a.screen.Size()
262 if a.enableBracketedPaste {
263 a.screen.EnablePaste()
264 }
265 if a.enableMouse {
266 a.screen.EnableMouse()
267 }
268 return nil
269 }
270
271
272 func (a *Application) EnableBracketedPaste(enable bool) {
273 a.Lock()
274 defer a.Unlock()
275 if enable != a.enableBracketedPaste && a.screen != nil {
276 if enable {
277 a.screen.EnablePaste()
278 } else {
279 a.screen.DisablePaste()
280 }
281 }
282 a.enableBracketedPaste = enable
283 }
284
285
286 func (a *Application) EnableMouse(enable bool) {
287 a.Lock()
288 defer a.Unlock()
289 if enable != a.enableMouse && a.screen != nil {
290 if enable {
291 a.screen.EnableMouse()
292 } else {
293 a.screen.DisableMouse()
294 }
295 }
296 a.enableMouse = enable
297 }
298
299
300
301 func (a *Application) Run() error {
302 a.Lock()
303
304
305 err := a.init()
306 if err != nil {
307 return err
308 }
309
310 defer a.HandlePanic()
311
312
313 a.Unlock()
314 a.draw()
315
316
317 var wg sync.WaitGroup
318 wg.Add(1)
319 go func() {
320 defer a.HandlePanic()
321
322 defer wg.Done()
323 for {
324 a.RLock()
325 screen := a.screen
326 a.RUnlock()
327 if screen == nil {
328
329 a.QueueEvent(nil)
330 break
331 }
332
333
334 screen = <-a.screenReplacement
335 if screen == nil {
336
337 a.QueueEvent(nil)
338 return
339 }
340
341
342 a.Lock()
343 a.screen = screen
344 a.Unlock()
345
346
347 if err := screen.Init(); err != nil {
348 panic(err)
349 }
350 if a.enableBracketedPaste {
351 screen.EnablePaste()
352 }
353 if a.enableMouse {
354 screen.EnableMouse()
355 }
356
357 a.draw()
358 }
359 }()
360
361 handle := func(event interface{}) {
362 a.RLock()
363 p := a.focus
364 inputCapture := a.inputCapture
365 screen := a.screen
366 a.RUnlock()
367
368 switch event := event.(type) {
369 case *tcell.EventKey:
370
371 if inputCapture != nil {
372 event = inputCapture(event)
373 if event == nil {
374 a.draw()
375 return
376 }
377 }
378
379
380 if event.Key() == tcell.KeyCtrlC {
381 a.Stop()
382 return
383 }
384
385
386 if p != nil {
387 if handler := p.InputHandler(); handler != nil {
388 handler(event, func(p Primitive) {
389 a.SetFocus(p)
390 })
391 a.draw()
392 }
393 }
394 case *tcell.EventResize:
395
396 if time.Since(a.lastResize) < resizeEventThrottle {
397
398 if a.throttleResize != nil && !a.throttleResize.Stop() {
399 select {
400 case <-a.throttleResize.C:
401 default:
402 }
403 }
404
405 event := event
406
407
408 a.throttleResize = time.AfterFunc(resizeEventThrottle, func() {
409 a.events <- event
410 })
411
412 return
413 }
414
415 a.lastResize = time.Now()
416
417 if screen == nil {
418 return
419 }
420
421 screen.Clear()
422 a.width, a.height = event.Size()
423
424
425 if a.afterResize != nil {
426 a.afterResize(a.width, a.height)
427 }
428
429 a.draw()
430 case *tcell.EventMouse:
431 consumed, isMouseDownAction := a.fireMouseActions(event)
432 if consumed {
433 a.draw()
434 }
435 a.lastMouseButtons = event.Buttons()
436 if isMouseDownAction {
437 a.mouseDownX, a.mouseDownY = event.Position()
438 }
439 }
440 }
441
442 semaphore := &sync.Mutex{}
443
444 go func() {
445 defer a.HandlePanic()
446
447 for update := range a.updates {
448 semaphore.Lock()
449 update()
450 semaphore.Unlock()
451 }
452 }()
453
454 go func() {
455 defer a.HandlePanic()
456
457 for event := range a.events {
458 semaphore.Lock()
459 handle(event)
460 semaphore.Unlock()
461 }
462 }()
463
464
465 for {
466 a.Lock()
467 screen := a.screen
468 a.Unlock()
469
470 if screen == nil {
471 break
472 }
473
474
475 event := screen.PollEvent()
476 if event == nil {
477 break
478 }
479
480 semaphore.Lock()
481 handle(event)
482 semaphore.Unlock()
483 }
484
485
486 wg.Wait()
487 a.screen = nil
488
489 return nil
490 }
491
492
493
494 func (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMouseDownAction bool) {
495
496 var targetPrimitive Primitive
497
498
499 fire := func(action MouseAction) {
500 switch action {
501 case MouseLeftDown, MouseMiddleDown, MouseRightDown:
502 isMouseDownAction = true
503 }
504
505
506 if a.mouseCapture != nil {
507 event, action = a.mouseCapture(event, action)
508 if event == nil {
509 consumed = true
510 return
511 }
512 }
513
514
515 var primitive, capturingPrimitive Primitive
516 if a.mouseCapturingPrimitive != nil {
517 primitive = a.mouseCapturingPrimitive
518 targetPrimitive = a.mouseCapturingPrimitive
519 } else if targetPrimitive != nil {
520 primitive = targetPrimitive
521 } else {
522 primitive = a.root
523 }
524 if primitive != nil {
525 if handler := primitive.MouseHandler(); handler != nil {
526 var wasConsumed bool
527 wasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) {
528 a.SetFocus(p)
529 })
530 if wasConsumed {
531 consumed = true
532 }
533 }
534 }
535 a.mouseCapturingPrimitive = capturingPrimitive
536 }
537
538 x, y := event.Position()
539 buttons := event.Buttons()
540 clickMoved := x != a.mouseDownX || y != a.mouseDownY
541 buttonChanges := buttons ^ a.lastMouseButtons
542
543 if x != a.lastMouseX || y != a.lastMouseY {
544 fire(MouseMove)
545 a.lastMouseX = x
546 a.lastMouseY = y
547 }
548
549 for _, buttonEvent := range []struct {
550 button tcell.ButtonMask
551 down, up, click, dclick MouseAction
552 }{
553 {tcell.ButtonPrimary, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick},
554 {tcell.ButtonMiddle, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick},
555 {tcell.ButtonSecondary, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick},
556 } {
557 if buttonChanges&buttonEvent.button != 0 {
558 if buttons&buttonEvent.button != 0 {
559 fire(buttonEvent.down)
560 } else {
561 fire(buttonEvent.up)
562 if !clickMoved {
563 if a.doubleClickInterval == 0 || a.lastMouseClick.Add(a.doubleClickInterval).Before(time.Now()) {
564 fire(buttonEvent.click)
565 a.lastMouseClick = time.Now()
566 } else {
567 fire(buttonEvent.dclick)
568 a.lastMouseClick = time.Time{}
569 }
570 }
571 }
572 }
573 }
574
575 for _, wheelEvent := range []struct {
576 button tcell.ButtonMask
577 action MouseAction
578 }{
579 {tcell.WheelUp, MouseScrollUp},
580 {tcell.WheelDown, MouseScrollDown},
581 {tcell.WheelLeft, MouseScrollLeft},
582 {tcell.WheelRight, MouseScrollRight}} {
583 if buttons&wheelEvent.button != 0 {
584 fire(wheelEvent.action)
585 }
586 }
587
588 return consumed, isMouseDownAction
589 }
590
591
592 func (a *Application) Stop() {
593 a.Lock()
594 defer a.Unlock()
595
596 a.finalizeScreen()
597 a.screenReplacement <- nil
598 }
599
600 func (a *Application) finalizeScreen() {
601 screen := a.screen
602 if screen == nil {
603 return
604 }
605
606 a.screen = nil
607 screen.Fini()
608 }
609
610
611
612
613
614
615
616
617 func (a *Application) Suspend(f func()) bool {
618 a.Lock()
619 if a.screen == nil {
620 a.Unlock()
621 return false
622 }
623 err := a.screen.Suspend()
624 a.Unlock()
625 if err != nil {
626 panic(err)
627 }
628
629
630 f()
631
632 a.Lock()
633 err = a.screen.Resume()
634 a.Unlock()
635 if err != nil {
636 panic(err)
637 }
638
639 return true
640 }
641
642
643
644
645
646
647
648
649
650
651
652 func (a *Application) Draw(p ...Primitive) {
653 a.QueueUpdate(func() {
654 if len(p) == 0 {
655 a.draw()
656 return
657 }
658
659 a.Lock()
660 if a.screen != nil {
661 for _, primitive := range p {
662 primitive.Draw(a.screen)
663 }
664 a.screen.Show()
665 }
666 a.Unlock()
667 })
668 }
669
670
671 func (a *Application) draw() {
672 a.Lock()
673
674 screen := a.screen
675 root := a.root
676 fullscreen := a.rootFullscreen
677 before := a.beforeDraw
678 after := a.afterDraw
679
680
681 if screen == nil || root == nil {
682 a.Unlock()
683 return
684 }
685
686
687 if fullscreen {
688 root.SetRect(0, 0, a.width, a.height)
689 }
690
691
692 if before != nil {
693 a.Unlock()
694 if before(screen) {
695 screen.Show()
696 return
697 }
698 } else {
699 a.Unlock()
700 }
701
702
703 root.Draw(screen)
704
705
706 if after != nil {
707 after(screen)
708 }
709
710
711 screen.Show()
712 }
713
714
715
716
717
718
719
720
721
722
723 func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) {
724 a.Lock()
725 defer a.Unlock()
726
727 a.beforeDraw = handler
728 }
729
730
731
732 func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
733 a.RLock()
734 defer a.RUnlock()
735
736 return a.beforeDraw
737 }
738
739
740
741
742
743 func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) {
744 a.Lock()
745 defer a.Unlock()
746
747 a.afterDraw = handler
748 }
749
750
751
752 func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
753 a.RLock()
754 defer a.RUnlock()
755
756 return a.afterDraw
757 }
758
759
760
761
762
763
764
765
766 func (a *Application) SetRoot(root Primitive, fullscreen bool) {
767 a.Lock()
768 a.root = root
769 a.rootFullscreen = fullscreen
770 if a.screen != nil {
771 a.screen.Clear()
772 }
773 a.Unlock()
774
775 a.SetFocus(root)
776
777 a.Draw()
778 }
779
780
781
782 func (a *Application) ResizeToFullScreen(p Primitive) {
783 a.RLock()
784 width, height := a.width, a.height
785 a.RUnlock()
786 p.SetRect(0, 0, width, height)
787 }
788
789
790
791
792
793
794
795 func (a *Application) SetAfterResizeFunc(handler func(width int, height int)) {
796 a.Lock()
797 defer a.Unlock()
798
799 a.afterResize = handler
800 }
801
802
803
804 func (a *Application) GetAfterResizeFunc() func(width int, height int) {
805 a.RLock()
806 defer a.RUnlock()
807
808 return a.afterResize
809 }
810
811
812
813
814
815
816
817 func (a *Application) SetFocus(p Primitive) {
818 a.Lock()
819
820 if a.beforeFocus != nil {
821 a.Unlock()
822 ok := a.beforeFocus(p)
823 if !ok {
824 return
825 }
826 a.Lock()
827 }
828
829 if a.focus != nil {
830 a.focus.Blur()
831 }
832
833 a.focus = p
834
835 if a.screen != nil {
836 a.screen.HideCursor()
837 }
838
839 if a.afterFocus != nil {
840 a.Unlock()
841
842 a.afterFocus(p)
843 } else {
844 a.Unlock()
845 }
846
847 if p != nil {
848 p.Focus(func(p Primitive) {
849 a.SetFocus(p)
850 })
851 }
852 }
853
854
855
856 func (a *Application) GetFocus() Primitive {
857 a.RLock()
858 defer a.RUnlock()
859
860 return a.focus
861 }
862
863
864
865
866
867 func (a *Application) SetBeforeFocusFunc(handler func(p Primitive) bool) {
868 a.Lock()
869 defer a.Unlock()
870
871 a.beforeFocus = handler
872 }
873
874
875
876
877
878 func (a *Application) SetAfterFocusFunc(handler func(p Primitive)) {
879 a.Lock()
880 defer a.Unlock()
881
882 a.afterFocus = handler
883 }
884
885
886
887
888
889
890
891 func (a *Application) QueueUpdate(f func()) {
892 a.updates <- f
893 }
894
895
896
897
898
899 func (a *Application) QueueUpdateDraw(f func(), p ...Primitive) {
900 a.QueueUpdate(func() {
901 f()
902
903 if len(p) == 0 {
904 a.draw()
905 return
906 }
907 a.Lock()
908 if a.screen != nil {
909 for _, primitive := range p {
910 primitive.Draw(a.screen)
911 }
912 a.screen.Show()
913 }
914 a.Unlock()
915 })
916 }
917
918
919
920
921 func (a *Application) QueueEvent(event tcell.Event) {
922 a.events <- event
923 }
924
925
926 func (a *Application) RingBell() {
927 a.QueueUpdate(func() {
928 fmt.Print(string(byte(7)))
929 })
930 }
931
View as plain text