diff --git a/termdash.go b/termdash.go index fa02238..c84aa41 100644 --- a/termdash.go +++ b/termdash.go @@ -31,6 +31,7 @@ import ( "time" "github.com/mum4k/termdash/container" + "github.com/mum4k/termdash/event" "github.com/mum4k/termdash/terminalapi" ) @@ -157,6 +158,9 @@ type termdash struct { // container maintains terminal splits and places widgets. container *container.Container + // eds distributes input events to subscribers. + eds *event.DistributionSystem + // closeCh gets closed when Stop() is called, which tells the event // collecting goroutine to exit. closeCh chan struct{} @@ -182,6 +186,7 @@ func newTermdash(t terminalapi.Terminal, c *container.Container, opts ...Option) td := &termdash{ term: t, container: c, + eds: event.NewDistributionSystem(), closeCh: make(chan struct{}), exitCh: make(chan struct{}), redrawInterval: DefaultRedrawInterval, @@ -190,9 +195,45 @@ func newTermdash(t terminalapi.Terminal, c *container.Container, opts ...Option) for _, opt := range opts { opt.set(td) } + td.subscribers() return td } +// subscribers subscribes event receivers that live in this package to EDS. +func (td *termdash) subscribers() { + // Handler for all errors that occur during input event processing. + td.eds.Subscribe([]terminalapi.Event{terminalapi.NewError("")}, func(ev terminalapi.Event) { + td.handleError(ev.(*terminalapi.Error).Error()) + }) + + // Handles terminal resize events. + td.eds.Subscribe([]terminalapi.Event{&terminalapi.Resize{}}, func(terminalapi.Event) { + td.setClearNeeded() + }) + + // Redraws the screen on Keyboard and Mouse events. + // These events very likely change the content of the widgets (e.g. zooming + // a LineChart) so a redraw is needed to make that visible. + td.eds.Subscribe([]terminalapi.Event{&terminalapi.Keyboard{}}, func(ev terminalapi.Event) { + td.keyEvRedraw(ev.(*terminalapi.Keyboard)) + }) + td.eds.Subscribe([]terminalapi.Event{&terminalapi.Mouse{}}, func(ev terminalapi.Event) { + td.mouseEvRedraw(ev.(*terminalapi.Mouse)) + }) + + // Keyboard and Mouse subscribers specified via options. + if td.keyboardSubscriber != nil { + td.eds.Subscribe([]terminalapi.Event{&terminalapi.Keyboard{}}, func(ev terminalapi.Event) { + td.keyboardSubscriber(ev.(*terminalapi.Keyboard)) + }) + } + if td.mouseSubscriber != nil { + td.eds.Subscribe([]terminalapi.Event{&terminalapi.Mouse{}}, func(ev terminalapi.Event) { + td.mouseSubscriber(ev.(*terminalapi.Mouse)) + }) + } +} + // handleError forwards the error to the error handler if one was // provided or panics. func (td *termdash) handleError(err error) { @@ -240,9 +281,6 @@ func (td *termdash) keyEvRedraw(ev *terminalapi.Keyboard) error { if err := td.container.Keyboard(ev); err != nil { return err } - if td.keyboardSubscriber != nil { - td.keyboardSubscriber(ev) - } return td.redraw() } @@ -255,9 +293,6 @@ func (td *termdash) mouseEvRedraw(ev *terminalapi.Mouse) error { if err := td.container.Mouse(ev); err != nil { return err } - if td.mouseSubscriber != nil { - td.mouseSubscriber(ev) - } return td.redraw() } @@ -274,29 +309,9 @@ func (td *termdash) processEvents(ctx context.Context) { defer close(td.exitCh) for { - event := td.term.Event(ctx) - switch ev := event.(type) { - case *terminalapi.Keyboard: - if err := td.keyEvRedraw(ev); err != nil { - td.handleError(err) - } - - case *terminalapi.Mouse: - if err := td.mouseEvRedraw(ev); err != nil { - td.handleError(err) - } - - case *terminalapi.Resize: - td.setClearNeeded() - - case *terminalapi.Error: - // Don't forward the error if the context is closed. - // It just says that the context expired. - select { - case <-ctx.Done(): - default: - td.handleError(ev.Error()) - } + ev := td.term.Event(ctx) + if ev != nil { + td.eds.Event(ev) } select { diff --git a/terminal/faketerm/faketerm.go b/terminal/faketerm/faketerm.go index 9e3d624..eef7948 100644 --- a/terminal/faketerm/faketerm.go +++ b/terminal/faketerm/faketerm.go @@ -197,7 +197,7 @@ func (t *Terminal) Event(ctx context.Context) terminalapi.Event { ev := t.events.Pull(ctx) if ev == nil { - return terminalapi.NewError("unable to pull the next event") + return nil } if res, ok := ev.(*terminalapi.Resize); ok { diff --git a/terminal/termbox/termbox.go b/terminal/termbox/termbox.go index 8d29d0f..4efa940 100644 --- a/terminal/termbox/termbox.go +++ b/terminal/termbox/termbox.go @@ -150,7 +150,7 @@ func (t *Terminal) pollEvents() { func (t *Terminal) Event(ctx context.Context) terminalapi.Event { ev := t.events.Pull(ctx) if ev == nil { - return terminalapi.NewError("unable to pull the next event") + return nil } return ev } diff --git a/terminalapi/terminalapi.go b/terminalapi/terminalapi.go index e6d9b2c..d45935a 100644 --- a/terminalapi/terminalapi.go +++ b/terminalapi/terminalapi.go @@ -47,5 +47,6 @@ type Terminal interface { // Event waits for the next event and returns it. // This call blocks until the next event or cancellation of the context. + // Returns nil when the context gets canceled. Event(ctx context.Context) Event }