ee7937d0dd
Now that tcell events are handled in a goroutine, no need for a channel to buffer them. Rename ui.Tick() to ui.Render() and ui.Run() to ui.ProcessEvents() to better reflect what these functions do. Move screen.PollEvent() into ui.ProcessEvents(). Register the panic handler in ui.ProcessEvents(). Remove aerc.ui.Tick() from DecryptKeys(). What the hell was that? Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Tim Culverhouse <tim@timculverhouse.com>
119 lines
2.4 KiB
Go
119 lines
2.4 KiB
Go
package ui
|
|
|
|
import (
|
|
"sync/atomic"
|
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
|
"github.com/gdamore/tcell/v2"
|
|
)
|
|
|
|
type UI struct {
|
|
Content DrawableInteractive
|
|
exit atomic.Value // bool
|
|
ctx *Context
|
|
screen tcell.Screen
|
|
popover *Popover
|
|
invalid int32 // access via atomic
|
|
}
|
|
|
|
func Initialize(content DrawableInteractive) (*UI, error) {
|
|
screen, err := tcell.NewScreen()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = screen.Init(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
screen.Clear()
|
|
screen.HideCursor()
|
|
|
|
width, height := screen.Size()
|
|
|
|
state := UI{
|
|
Content: content,
|
|
screen: screen,
|
|
}
|
|
state.ctx = NewContext(width, height, screen, state.onPopover)
|
|
|
|
state.exit.Store(false)
|
|
|
|
state.invalid = 1
|
|
content.OnInvalidate(func(_ Drawable) {
|
|
atomic.StoreInt32(&state.invalid, 1)
|
|
})
|
|
if beeper, ok := content.(DrawableInteractiveBeeper); ok {
|
|
beeper.OnBeep(screen.Beep)
|
|
}
|
|
content.Focus(true)
|
|
|
|
if root, ok := content.(RootDrawable); ok {
|
|
root.Initialize(&state)
|
|
}
|
|
|
|
return &state, nil
|
|
}
|
|
|
|
func (state *UI) onPopover(p *Popover) {
|
|
state.popover = p
|
|
}
|
|
|
|
func (state *UI) ShouldExit() bool {
|
|
return state.exit.Load().(bool)
|
|
}
|
|
|
|
func (state *UI) Exit() {
|
|
state.exit.Store(true)
|
|
}
|
|
|
|
func (state *UI) Close() {
|
|
state.screen.Fini()
|
|
}
|
|
|
|
func (state *UI) Render() bool {
|
|
more := false
|
|
|
|
wasInvalid := atomic.SwapInt32(&state.invalid, 0)
|
|
if wasInvalid != 0 {
|
|
if state.popover != nil {
|
|
// if the previous frame had a popover, rerender the entire display
|
|
state.Content.Invalidate()
|
|
atomic.StoreInt32(&state.invalid, 0)
|
|
}
|
|
// reset popover for the next Draw
|
|
state.popover = nil
|
|
state.Content.Draw(state.ctx)
|
|
if state.popover != nil {
|
|
// if the Draw resulted in a popover, draw it
|
|
state.popover.Draw(state.ctx)
|
|
}
|
|
state.screen.Show()
|
|
more = true
|
|
}
|
|
|
|
return more
|
|
}
|
|
|
|
func (state *UI) EnableMouse() {
|
|
state.screen.EnableMouse()
|
|
}
|
|
|
|
func (state *UI) ProcessEvents() {
|
|
defer logging.PanicHandler()
|
|
|
|
for !state.ShouldExit() {
|
|
event := state.screen.PollEvent()
|
|
if event, ok := event.(*tcell.EventResize); ok {
|
|
state.screen.Clear()
|
|
width, height := event.Size()
|
|
state.ctx = NewContext(width, height, state.screen, state.onPopover)
|
|
state.Content.Invalidate()
|
|
}
|
|
// if we have a popover, and it can handle the event, it does so
|
|
if state.popover == nil || !state.popover.Event(event) {
|
|
// otherwise, we send the event to the main content
|
|
state.Content.Event(event)
|
|
}
|
|
}
|
|
}
|