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