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