mirror of
https://github.com/rivo/tview.git
synced 2025-04-26 13:49:06 +08:00
make primitive updates thread-safe using channels
This commit is contained in:
parent
bc39bf8d24
commit
313fed77b2
140
application.go
140
application.go
@ -46,11 +46,20 @@ type Application struct {
|
|||||||
|
|
||||||
// Halts the event loop during suspended mode.
|
// Halts the event loop during suspended mode.
|
||||||
suspendMutex sync.Mutex
|
suspendMutex sync.Mutex
|
||||||
|
|
||||||
|
// Used to send screen events from separate goroutine to main event loop
|
||||||
|
events chan tcell.Event
|
||||||
|
|
||||||
|
// Used to send primitive updates from separate goroutines to the main event loop
|
||||||
|
updates chan func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApplication creates and returns a new application.
|
// NewApplication creates and returns a new application.
|
||||||
func NewApplication() *Application {
|
func NewApplication() *Application {
|
||||||
return &Application{}
|
return &Application{
|
||||||
|
events: make(chan tcell.Event, 100),
|
||||||
|
updates: make(chan func(), 100),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInputCapture sets a function which captures all key events before they are
|
// SetInputCapture sets a function which captures all key events before they are
|
||||||
@ -136,61 +145,78 @@ func (a *Application) Run() error {
|
|||||||
a.Unlock()
|
a.Unlock()
|
||||||
a.Draw()
|
a.Draw()
|
||||||
|
|
||||||
// Start event loop.
|
// Separate loop to wait for screen events
|
||||||
for {
|
go func() {
|
||||||
// Do not poll events during suspend mode
|
for {
|
||||||
a.suspendMutex.Lock()
|
// Do not poll events during suspend mode
|
||||||
a.RLock()
|
a.suspendMutex.Lock()
|
||||||
screen := a.screen
|
|
||||||
a.RUnlock()
|
|
||||||
if screen == nil {
|
|
||||||
a.suspendMutex.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for next event.
|
|
||||||
event := a.screen.PollEvent()
|
|
||||||
a.suspendMutex.Unlock()
|
|
||||||
if event == nil {
|
|
||||||
// The screen was finalized. Exit the loop.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch event := event.(type) {
|
|
||||||
case *tcell.EventKey:
|
|
||||||
a.RLock()
|
|
||||||
p := a.focus
|
|
||||||
a.RUnlock()
|
|
||||||
|
|
||||||
// Intercept keys.
|
|
||||||
if a.inputCapture != nil {
|
|
||||||
event = a.inputCapture(event)
|
|
||||||
if event == nil {
|
|
||||||
break // Don't forward event.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ctrl-C closes the application.
|
|
||||||
if event.Key() == tcell.KeyCtrlC {
|
|
||||||
a.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass other key events to the currently focused primitive.
|
|
||||||
if p != nil {
|
|
||||||
if handler := p.InputHandler(); handler != nil {
|
|
||||||
handler(event, func(p Primitive) {
|
|
||||||
a.SetFocus(p)
|
|
||||||
})
|
|
||||||
a.Draw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *tcell.EventResize:
|
|
||||||
a.RLock()
|
a.RLock()
|
||||||
screen := a.screen
|
screen := a.screen
|
||||||
a.RUnlock()
|
a.RUnlock()
|
||||||
screen.Clear()
|
if screen == nil {
|
||||||
|
a.suspendMutex.Unlock()
|
||||||
|
// send signal to stop main event loop
|
||||||
|
a.QueueEvent(nil)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for next event.
|
||||||
|
a.QueueEvent(screen.PollEvent())
|
||||||
|
a.suspendMutex.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Start event loop.
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-a.events:
|
||||||
|
if event == nil {
|
||||||
|
// The screen was finalized. Exit the loop.
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event := event.(type) {
|
||||||
|
case *tcell.EventKey:
|
||||||
|
a.RLock()
|
||||||
|
p := a.focus
|
||||||
|
a.RUnlock()
|
||||||
|
|
||||||
|
// Intercept keys.
|
||||||
|
if a.inputCapture != nil {
|
||||||
|
event = a.inputCapture(event)
|
||||||
|
if event == nil {
|
||||||
|
break loop // Don't forward event.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl-C closes the application.
|
||||||
|
if event.Key() == tcell.KeyCtrlC {
|
||||||
|
a.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass other key events to the currently focused primitive.
|
||||||
|
if p != nil {
|
||||||
|
if handler := p.InputHandler(); handler != nil {
|
||||||
|
handler(event, func(p Primitive) {
|
||||||
|
a.SetFocus(p)
|
||||||
|
})
|
||||||
|
a.Draw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *tcell.EventResize:
|
||||||
|
a.RLock()
|
||||||
|
screen := a.screen
|
||||||
|
a.RUnlock()
|
||||||
|
screen.Clear()
|
||||||
|
a.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
case updater := <-a.updates:
|
||||||
|
updater()
|
||||||
a.Draw()
|
a.Draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -404,3 +430,15 @@ func (a *Application) GetFocus() Primitive {
|
|||||||
defer a.RUnlock()
|
defer a.RUnlock()
|
||||||
return a.focus
|
return a.focus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueueUpdate is used to synchronize changes to primitives by carrying an update function from separate goroutine to the Application event loop via channel
|
||||||
|
func (a *Application) QueueUpdate(f func()) *Application {
|
||||||
|
a.updates <- f
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueEvent takes an Event instance and sends it to the Application event loop via channel
|
||||||
|
func (a *Application) QueueEvent(e tcell.Event) *Application {
|
||||||
|
a.events <- e
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user