1 package cview
2
3 import (
4 "fmt"
5 "sync"
6 "time"
7
8 "github.com/gdamore/tcell/v3"
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 return err
255 }
256 if err = a.screen.Init(); err != nil {
257 return err
258 }
259 a.width, a.height = a.screen.Size()
260 if a.enableBracketedPaste {
261 a.screen.EnablePaste()
262 }
263 if a.enableMouse {
264 a.screen.EnableMouse()
265 }
266 return nil
267 }
268
269
270 func (a *Application) EnableBracketedPaste(enable bool) {
271 a.Lock()
272 defer a.Unlock()
273 if enable != a.enableBracketedPaste && a.screen != nil {
274 if enable {
275 a.screen.EnablePaste()
276 } else {
277 a.screen.DisablePaste()
278 }
279 }
280 a.enableBracketedPaste = enable
281 }
282
283
284 func (a *Application) EnableMouse(enable bool) {
285 a.Lock()
286 defer a.Unlock()
287 if enable != a.enableMouse && a.screen != nil {
288 if enable {
289 a.screen.EnableMouse()
290 } else {
291 a.screen.DisableMouse()
292 }
293 }
294 a.enableMouse = enable
295 }
296
297
298
299 func (a *Application) Run() error {
300 a.Lock()
301
302
303 err := a.init()
304 if err != nil {
305 a.Unlock()
306 return err
307 }
308
309 defer a.HandlePanic()
310
311
312 a.Unlock()
313 a.draw()
314
315
316 var wg sync.WaitGroup
317 wg.Add(1)
318 go func() {
319 defer a.HandlePanic()
320
321 defer wg.Done()
322 for {
323 a.RLock()
324 screen := a.screen
325 a.RUnlock()
326 if screen == nil {
327
328 a.QueueEvent(nil)
329 break
330 }
331
332
333 screen = <-a.screenReplacement
334 if screen == nil {
335
336 a.QueueEvent(nil)
337 return
338 }
339
340
341 a.Lock()
342 a.screen = screen
343 a.Unlock()
344
345
346 if err := screen.Init(); err != nil {
347 panic(err)
348 }
349 if a.enableBracketedPaste {
350 screen.EnablePaste()
351 }
352 if a.enableMouse {
353 screen.EnableMouse()
354 }
355
356 a.draw()
357 }
358 }()
359
360 handle := func(event interface{}) {
361 a.RLock()
362 p := a.focus
363 inputCapture := a.inputCapture
364 screen := a.screen
365 a.RUnlock()
366
367 switch event := event.(type) {
368 case *tcell.EventKey:
369
370 if inputCapture != nil {
371 event = inputCapture(event)
372 if event == nil {
373 a.draw()
374 return
375 }
376 }
377
378
379 if event.Key() == tcell.KeyCtrlC {
380 a.Stop()
381 return
382 }
383
384
385 if p != nil {
386 if handler := p.InputHandler(); handler != nil {
387 handler(event, func(p Primitive) {
388 a.SetFocus(p)
389 })
390 a.draw()
391 }
392 }
393 case *tcell.EventResize:
394
395 if time.Since(a.lastResize) < resizeEventThrottle {
396
397 if a.throttleResize != nil && !a.throttleResize.Stop() {
398 select {
399 case <-a.throttleResize.C:
400 default:
401 }
402 }
403
404 event := event
405
406
407 a.throttleResize = time.AfterFunc(resizeEventThrottle, func() {
408 a.events <- event
409 })
410
411 return
412 }
413
414 a.lastResize = time.Now()
415
416 if screen == nil {
417 return
418 }
419
420 screen.Clear()
421 a.width, a.height = event.Size()
422
423
424 if a.afterResize != nil {
425 a.afterResize(a.width, a.height)
426 }
427
428 a.draw()
429 case *tcell.EventMouse:
430 consumed, isMouseDownAction := a.fireMouseActions(event)
431 if consumed {
432 a.draw()
433 }
434 a.lastMouseButtons = event.Buttons()
435 if isMouseDownAction {
436 a.mouseDownX, a.mouseDownY = event.Position()
437 }
438 }
439 }
440
441 semaphore := &sync.Mutex{}
442
443 go func() {
444 defer a.HandlePanic()
445
446 for update := range a.updates {
447 semaphore.Lock()
448 update()
449 semaphore.Unlock()
450 }
451 }()
452
453 go func() {
454 defer a.HandlePanic()
455
456 for event := range a.events {
457 semaphore.Lock()
458 handle(event)
459 semaphore.Unlock()
460 }
461 }()
462
463
464 a.Lock()
465 screen := a.screen
466 a.Unlock()
467 if screen != nil {
468 for event := range screen.EventQ() {
469 if event == nil {
470 break
471 }
472
473 semaphore.Lock()
474 handle(event)
475 semaphore.Unlock()
476 }
477 }
478
479
480 wg.Wait()
481 a.Lock()
482 a.screen = nil
483 a.Unlock()
484
485 return nil
486 }
487
488
489
490 func (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMouseDownAction bool) {
491
492 var targetPrimitive Primitive
493
494
495 fire := func(action MouseAction) {
496 switch action {
497 case MouseLeftDown, MouseMiddleDown, MouseRightDown:
498 isMouseDownAction = true
499 }
500
501
502 if a.mouseCapture != nil {
503 event, action = a.mouseCapture(event, action)
504 if event == nil {
505 consumed = true
506 return
507 }
508 }
509
510
511 var primitive, capturingPrimitive Primitive
512 if a.mouseCapturingPrimitive != nil {
513 primitive = a.mouseCapturingPrimitive
514 targetPrimitive = a.mouseCapturingPrimitive
515 } else if targetPrimitive != nil {
516 primitive = targetPrimitive
517 } else {
518 primitive = a.root
519 }
520 if primitive != nil {
521 if handler := primitive.MouseHandler(); handler != nil {
522 var wasConsumed bool
523 wasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) {
524 a.SetFocus(p)
525 })
526 if wasConsumed {
527 consumed = true
528 }
529 }
530 }
531 a.mouseCapturingPrimitive = capturingPrimitive
532 }
533
534 x, y := event.Position()
535 buttons := event.Buttons()
536 clickMoved := x != a.mouseDownX || y != a.mouseDownY
537 buttonChanges := buttons ^ a.lastMouseButtons
538
539 if x != a.lastMouseX || y != a.lastMouseY {
540 fire(MouseMove)
541 a.lastMouseX = x
542 a.lastMouseY = y
543 }
544
545 for _, buttonEvent := range []struct {
546 button tcell.ButtonMask
547 down, up, click, dclick MouseAction
548 }{
549 {tcell.ButtonPrimary, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick},
550 {tcell.ButtonMiddle, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick},
551 {tcell.ButtonSecondary, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick},
552 } {
553 if buttonChanges&buttonEvent.button != 0 {
554 if buttons&buttonEvent.button != 0 {
555 fire(buttonEvent.down)
556 } else {
557 fire(buttonEvent.up)
558 if !clickMoved {
559 if a.doubleClickInterval == 0 || a.lastMouseClick.Add(a.doubleClickInterval).Before(time.Now()) {
560 fire(buttonEvent.click)
561 a.lastMouseClick = time.Now()
562 } else {
563 fire(buttonEvent.dclick)
564 a.lastMouseClick = time.Time{}
565 }
566 }
567 }
568 }
569 }
570
571 for _, wheelEvent := range []struct {
572 button tcell.ButtonMask
573 action MouseAction
574 }{
575 {tcell.WheelUp, MouseScrollUp},
576 {tcell.WheelDown, MouseScrollDown},
577 {tcell.WheelLeft, MouseScrollLeft},
578 {tcell.WheelRight, MouseScrollRight}} {
579 if buttons&wheelEvent.button != 0 {
580 fire(wheelEvent.action)
581 }
582 }
583
584 return consumed, isMouseDownAction
585 }
586
587
588 func (a *Application) Stop() {
589 a.Lock()
590 defer a.Unlock()
591
592 a.finalizeScreen()
593 a.screenReplacement <- nil
594 }
595
596 func (a *Application) finalizeScreen() {
597 screen := a.screen
598 if screen == nil {
599 return
600 }
601
602 a.screen = nil
603 screen.Fini()
604 }
605
606
607
608
609
610
611
612
613 func (a *Application) Suspend(f func()) bool {
614 a.Lock()
615 if a.screen == nil {
616 a.Unlock()
617 return false
618 }
619 err := a.screen.Suspend()
620 a.Unlock()
621 if err != nil {
622 panic(err)
623 }
624
625
626 f()
627
628 a.Lock()
629 err = a.screen.Resume()
630 a.Unlock()
631 if err != nil {
632 panic(err)
633 }
634
635 return true
636 }
637
638
639
640
641
642
643
644
645
646
647
648 func (a *Application) Draw(p ...Primitive) {
649 a.QueueUpdate(func() {
650 if len(p) == 0 {
651 a.draw()
652 return
653 }
654
655 a.Lock()
656 if a.screen != nil {
657 for _, primitive := range p {
658 primitive.Draw(a.screen)
659 }
660 a.screen.Show()
661 }
662 a.Unlock()
663 })
664 }
665
666
667 func (a *Application) draw() {
668 a.Lock()
669
670 screen := a.screen
671 root := a.root
672 fullscreen := a.rootFullscreen
673 before := a.beforeDraw
674 after := a.afterDraw
675
676
677 if screen == nil || root == nil {
678 a.Unlock()
679 return
680 }
681
682
683 if fullscreen {
684 root.SetRect(0, 0, a.width, a.height)
685 }
686
687
688 if before != nil {
689 a.Unlock()
690 if before(screen) {
691 screen.Show()
692 return
693 }
694 } else {
695 a.Unlock()
696 }
697
698
699 root.Draw(screen)
700
701
702 if after != nil {
703 after(screen)
704 }
705
706
707 screen.Show()
708 }
709
710
711
712
713
714
715
716
717
718
719 func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) {
720 a.Lock()
721 defer a.Unlock()
722
723 a.beforeDraw = handler
724 }
725
726
727
728 func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
729 a.RLock()
730 defer a.RUnlock()
731
732 return a.beforeDraw
733 }
734
735
736
737
738
739 func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) {
740 a.Lock()
741 defer a.Unlock()
742
743 a.afterDraw = handler
744 }
745
746
747
748 func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
749 a.RLock()
750 defer a.RUnlock()
751
752 return a.afterDraw
753 }
754
755
756
757
758
759
760
761
762 func (a *Application) SetRoot(root Primitive, fullscreen bool) {
763 a.Lock()
764 a.root = root
765 a.rootFullscreen = fullscreen
766 if a.screen != nil {
767 a.screen.Clear()
768 }
769 a.Unlock()
770
771 a.SetFocus(root)
772
773 a.Draw()
774 }
775
776
777
778 func (a *Application) ResizeToFullScreen(p Primitive) {
779 a.RLock()
780 width, height := a.width, a.height
781 a.RUnlock()
782 p.SetRect(0, 0, width, height)
783 }
784
785
786
787
788
789
790
791 func (a *Application) SetAfterResizeFunc(handler func(width int, height int)) {
792 a.Lock()
793 defer a.Unlock()
794
795 a.afterResize = handler
796 }
797
798
799
800 func (a *Application) GetAfterResizeFunc() func(width int, height int) {
801 a.RLock()
802 defer a.RUnlock()
803
804 return a.afterResize
805 }
806
807
808
809
810
811
812
813 func (a *Application) SetFocus(p Primitive) {
814 a.Lock()
815
816 if a.beforeFocus != nil {
817 a.Unlock()
818 ok := a.beforeFocus(p)
819 if !ok {
820 return
821 }
822 a.Lock()
823 }
824
825 if a.focus != nil {
826 a.focus.Blur()
827 }
828
829 a.focus = p
830
831 if a.screen != nil {
832 a.screen.HideCursor()
833 }
834
835 if a.afterFocus != nil {
836 a.Unlock()
837
838 a.afterFocus(p)
839 } else {
840 a.Unlock()
841 }
842
843 if p != nil {
844 p.Focus(func(p Primitive) {
845 a.SetFocus(p)
846 })
847 }
848 }
849
850
851
852 func (a *Application) GetFocus() Primitive {
853 a.RLock()
854 defer a.RUnlock()
855
856 return a.focus
857 }
858
859
860
861
862
863 func (a *Application) SetBeforeFocusFunc(handler func(p Primitive) bool) {
864 a.Lock()
865 defer a.Unlock()
866
867 a.beforeFocus = handler
868 }
869
870
871
872
873
874 func (a *Application) SetAfterFocusFunc(handler func(p Primitive)) {
875 a.Lock()
876 defer a.Unlock()
877
878 a.afterFocus = handler
879 }
880
881
882
883
884
885
886
887 func (a *Application) QueueUpdate(f func()) {
888 a.updates <- f
889 }
890
891
892
893
894
895 func (a *Application) QueueUpdateDraw(f func(), p ...Primitive) {
896 a.QueueUpdate(func() {
897 f()
898
899 if len(p) == 0 {
900 a.draw()
901 return
902 }
903 a.Lock()
904 if a.screen != nil {
905 for _, primitive := range p {
906 primitive.Draw(a.screen)
907 }
908 a.screen.Show()
909 }
910 a.Unlock()
911 })
912 }
913
914
915
916
917 func (a *Application) QueueEvent(event tcell.Event) {
918 a.events <- event
919 }
920
921
922 func (a *Application) RingBell() {
923 a.QueueUpdate(func() {
924 fmt.Print(string(byte(7)))
925 })
926 }
927
View as plain text