Overhaul UI event handling

This commit is contained in:
Joscha 2022-08-20 18:36:20 +02:00
parent ade06efa01
commit ded927b9f0
9 changed files with 177 additions and 103 deletions

View file

@ -14,6 +14,9 @@ Procedure when bumping the version number:
## Unreleased ## Unreleased
### Changed
- Reduced amount of unnecessary redraws
### Fixed ### Fixed
- Crash when connecting to nonexistent rooms - Crash when connecting to nonexistent rooms
- Crash when connecting to rooms that require authentication - Crash when connecting to rooms that require authentication

View file

@ -2,6 +2,6 @@ mod room;
mod small_message; mod small_message;
mod util; mod util;
pub use room::Room; pub use room::*;
pub use small_message::SmallMessage; pub use small_message::*;
pub use util::{nick_color, nick_style}; pub use util::*;

View file

@ -17,7 +17,6 @@ use tokio_tungstenite::tungstenite::handshake::client::Response;
use tokio_tungstenite::tungstenite::http::{header, HeaderValue}; use tokio_tungstenite::tungstenite::http::{header, HeaderValue};
use crate::macros::ok_or_return; use crate::macros::ok_or_return;
use crate::ui::UiEvent;
use crate::vault::{EuphVault, Vault}; use crate::vault::{EuphVault, Vault};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -26,11 +25,19 @@ pub enum Error {
Stopped, Stopped,
} }
pub enum EuphRoomEvent {
Connected,
Disconnected,
Data(Box<Data>),
}
#[derive(Debug)] #[derive(Debug)]
enum Event { enum Event {
// Events
Connected(ConnTx), Connected(ConnTx),
Disconnected, Disconnected,
Data(Box<Data>), Data(Box<Data>),
// Commands
Status(oneshot::Sender<Option<Status>>), Status(oneshot::Sender<Option<Status>>),
RequestLogs, RequestLogs,
Nick(String), Nick(String),
@ -41,7 +48,6 @@ enum Event {
struct State { struct State {
name: String, name: String,
vault: EuphVault, vault: EuphVault,
ui_event_tx: mpsc::UnboundedSender<UiEvent>,
conn_tx: Option<ConnTx>, conn_tx: Option<ConnTx>,
/// `None` before any `snapshot-event`, then either `Some(None)` or /// `None` before any `snapshot-event`, then either `Some(None)` or
@ -56,6 +62,7 @@ impl State {
canary: oneshot::Receiver<Infallible>, canary: oneshot::Receiver<Infallible>,
event_tx: mpsc::UnboundedSender<Event>, event_tx: mpsc::UnboundedSender<Event>,
mut event_rx: mpsc::UnboundedReceiver<Event>, mut event_rx: mpsc::UnboundedReceiver<Event>,
euph_room_event_tx: mpsc::UnboundedSender<EuphRoomEvent>,
) { ) {
let vault = self.vault.clone(); let vault = self.vault.clone();
let name = self.name.clone(); let name = self.name.clone();
@ -63,7 +70,7 @@ impl State {
_ = canary => Ok(()), _ = canary => Ok(()),
_ = Self::reconnect(&vault, &name, &event_tx) => Ok(()), _ = Self::reconnect(&vault, &name, &event_tx) => Ok(()),
_ = Self::regularly_request_logs(&event_tx) => Ok(()), _ = Self::regularly_request_logs(&event_tx) => Ok(()),
e = self.handle_events(&mut event_rx) => e, e = self.handle_events(&mut event_rx, &euph_room_event_tx) => e,
}; };
if let Err(e) = result { if let Err(e) = result {
@ -150,22 +157,23 @@ impl State {
async fn handle_events( async fn handle_events(
&mut self, &mut self,
event_rx: &mut mpsc::UnboundedReceiver<Event>, event_rx: &mut mpsc::UnboundedReceiver<Event>,
euph_room_event_tx: &mpsc::UnboundedSender<EuphRoomEvent>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
while let Some(event) = event_rx.recv().await { while let Some(event) = event_rx.recv().await {
// TODO Send UI events on more occasions
// Example: When a room disconnects at the moment, the screen is
// redrawn. Why? Because tungstenite debug-logs that the connection
// was closed and the log then causes a full-screen redraw. This is
// a clear case of "works but only because mistakes cancel out". A
// first step towards fixing this would be to only redraw if an
// event affected the currently visible screen.
match event { match event {
Event::Connected(conn_tx) => self.conn_tx = Some(conn_tx), Event::Connected(conn_tx) => {
self.conn_tx = Some(conn_tx);
let _ = euph_room_event_tx.send(EuphRoomEvent::Connected);
}
Event::Disconnected => { Event::Disconnected => {
self.conn_tx = None; self.conn_tx = None;
self.last_msg_id = None; self.last_msg_id = None;
let _ = euph_room_event_tx.send(EuphRoomEvent::Disconnected);
}
Event::Data(data) => {
self.on_data(&*data).await?;
let _ = euph_room_event_tx.send(EuphRoomEvent::Data(data));
} }
Event::Data(data) => self.on_data(*data).await?,
Event::Status(reply_tx) => self.on_status(reply_tx).await, Event::Status(reply_tx) => self.on_status(reply_tx).await,
Event::RequestLogs => self.on_request_logs(), Event::RequestLogs => self.on_request_logs(),
Event::Nick(name) => self.on_nick(name), Event::Nick(name) => self.on_nick(name),
@ -182,7 +190,7 @@ impl State {
}) })
} }
async fn on_data(&mut self, data: Data) -> anyhow::Result<()> { async fn on_data(&mut self, data: &Data) -> anyhow::Result<()> {
match data { match data {
Data::BounceEvent(_) => {} Data::BounceEvent(_) => {}
Data::DisconnectEvent(d) => { Data::DisconnectEvent(d) => {
@ -217,7 +225,8 @@ impl State {
let own_user_id = self.own_user_id().await; let own_user_id = self.own_user_id().await;
if let Some(last_msg_id) = &mut self.last_msg_id { if let Some(last_msg_id) = &mut self.last_msg_id {
let id = d.0.id; let id = d.0.id;
self.vault.add_message(d.0, *last_msg_id, own_user_id); self.vault
.add_message(d.0.clone(), *last_msg_id, own_user_id);
*last_msg_id = Some(id); *last_msg_id = Some(id);
} else { } else {
bail!("send event before snapshot event"); bail!("send event before snapshot event");
@ -228,17 +237,19 @@ impl State {
self.vault.join(Time::now()); self.vault.join(Time::now());
self.last_msg_id = Some(d.log.last().map(|m| m.id)); self.last_msg_id = Some(d.log.last().map(|m| m.id));
let own_user_id = self.own_user_id().await; let own_user_id = self.own_user_id().await;
self.vault.add_messages(d.log, None, own_user_id); self.vault.add_messages(d.log.clone(), None, own_user_id);
} }
Data::LogReply(d) => { Data::LogReply(d) => {
let own_user_id = self.own_user_id().await; let own_user_id = self.own_user_id().await;
self.vault.add_messages(d.log, d.before, own_user_id); self.vault
.add_messages(d.log.clone(), d.before, own_user_id);
} }
Data::SendReply(d) => { Data::SendReply(d) => {
let own_user_id = self.own_user_id().await; let own_user_id = self.own_user_id().await;
if let Some(last_msg_id) = &mut self.last_msg_id { if let Some(last_msg_id) = &mut self.last_msg_id {
let id = d.0.id; let id = d.0.id;
self.vault.add_message(d.0, *last_msg_id, own_user_id); self.vault
.add_message(d.0.clone(), *last_msg_id, own_user_id);
*last_msg_id = Some(id); *last_msg_id = Some(id);
} else { } else {
bail!("send reply before snapshot event"); bail!("send reply before snapshot event");
@ -246,7 +257,6 @@ impl State {
} }
_ => {} _ => {}
} }
let _ = self.ui_event_tx.send(UiEvent::Redraw);
Ok(()) Ok(())
} }
@ -332,25 +342,26 @@ pub struct Room {
} }
impl Room { impl Room {
pub fn new(vault: EuphVault, ui_event_tx: mpsc::UnboundedSender<UiEvent>) -> Self { pub fn new(vault: EuphVault) -> (Self, mpsc::UnboundedReceiver<EuphRoomEvent>) {
let (canary_tx, canary_rx) = oneshot::channel(); let (canary_tx, canary_rx) = oneshot::channel();
let (event_tx, event_rx) = mpsc::unbounded_channel(); let (event_tx, event_rx) = mpsc::unbounded_channel();
let (euph_room_event_tx, euph_room_event_rx) = mpsc::unbounded_channel();
let state = State { let state = State {
name: vault.room().to_string(), name: vault.room().to_string(),
vault, vault,
ui_event_tx,
conn_tx: None, conn_tx: None,
last_msg_id: None, last_msg_id: None,
requesting_logs: Arc::new(Mutex::new(false)), requesting_logs: Arc::new(Mutex::new(false)),
}; };
task::spawn(state.run(canary_rx, event_tx.clone(), event_rx)); task::spawn(state.run(canary_rx, event_tx.clone(), event_rx, euph_room_event_tx));
Self { let new_room = Self {
canary: canary_tx, canary: canary_tx,
event_tx, event_tx,
} };
(new_room, euph_room_event_rx)
} }
pub fn stopped(&self) -> bool { pub fn stopped(&self) -> bool {

127
src/ui.rs
View file

@ -6,17 +6,20 @@ mod util;
mod widgets; mod widgets;
use std::convert::Infallible; use std::convert::Infallible;
use std::io;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crossterm::event::{Event, KeyCode}; use crossterm::event::KeyCode;
use parking_lot::FairMutex; use parking_lot::FairMutex;
use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::error::TryRecvError;
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio::task; use tokio::task;
use toss::terminal::Terminal; use toss::terminal::Terminal;
use crate::euph::EuphRoomEvent;
use crate::logger::{LogMsg, Logger}; use crate::logger::{LogMsg, Logger};
use crate::macros::{ok_or_return, some_or_return};
use crate::vault::Vault; use crate::vault::Vault;
pub use self::chat::ChatMsg; pub use self::chat::ChatMsg;
@ -30,17 +33,20 @@ use self::widgets::BoxedWidget;
/// Time to spend batch processing events before redrawing the screen. /// Time to spend batch processing events before redrawing the screen.
const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps
#[derive(Debug)]
pub enum UiEvent { pub enum UiEvent {
Redraw, GraphemeWidthsChanged,
Term(Event), LogChanged,
Term(crossterm::event::Event),
EuphRoom { name: String, event: EuphRoomEvent },
} }
enum EventHandleResult { enum EventHandleResult {
Redraw,
Continue, Continue,
Stop, Stop,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode { enum Mode {
Main, Main,
Log, Log,
@ -92,34 +98,34 @@ impl Ui {
key_bindings_list: None, key_bindings_list: None,
}; };
tokio::select! { tokio::select! {
e = ui.run_main(terminal, event_rx, crossterm_lock) => Ok(e), e = ui.run_main(terminal, event_rx, crossterm_lock) => e?,
_ = Self::update_on_log_event(logger_rx, &event_tx) => Ok(Ok(())), _ = Self::update_on_log_event(logger_rx, &event_tx) => (),
e = crossterm_event_task => e, e = crossterm_event_task => e??,
}? }
Ok(())
} }
fn poll_crossterm_events( fn poll_crossterm_events(
tx: UnboundedSender<UiEvent>, tx: UnboundedSender<UiEvent>,
lock: Weak<FairMutex<()>>, lock: Weak<FairMutex<()>>,
) -> anyhow::Result<()> { ) -> crossterm::Result<()> {
while let Some(lock) = lock.upgrade() { loop {
let lock = some_or_return!(lock.upgrade(), Ok(()));
let _guard = lock.lock(); let _guard = lock.lock();
if crossterm::event::poll(Self::POLL_DURATION)? { if crossterm::event::poll(Self::POLL_DURATION)? {
let event = crossterm::event::read()?; let event = crossterm::event::read()?;
tx.send(UiEvent::Term(event))?; ok_or_return!(tx.send(UiEvent::Term(event)), Ok(()));
} }
} }
Ok(())
} }
async fn update_on_log_event( async fn update_on_log_event(
mut logger_rx: UnboundedReceiver<()>, mut logger_rx: UnboundedReceiver<()>,
event_tx: &UnboundedSender<UiEvent>, event_tx: &UnboundedSender<UiEvent>,
) { ) {
while let Some(()) = logger_rx.recv().await { loop {
if event_tx.send(UiEvent::Redraw).is_err() { some_or_return!(logger_rx.recv().await);
break; ok_or_return!(event_tx.send(UiEvent::LogChanged));
}
} }
} }
@ -128,7 +134,7 @@ impl Ui {
terminal: &mut Terminal, terminal: &mut Terminal,
mut event_rx: UnboundedReceiver<UiEvent>, mut event_rx: UnboundedReceiver<UiEvent>,
crossterm_lock: Arc<FairMutex<()>>, crossterm_lock: Arc<FairMutex<()>>,
) -> anyhow::Result<()> { ) -> io::Result<()> {
// Initial render so we don't show a blank screen until the first event // Initial render so we don't show a blank screen until the first event
terminal.autoresize()?; terminal.autoresize()?;
terminal.frame().reset(); terminal.frame().reset();
@ -140,7 +146,7 @@ impl Ui {
if terminal.measuring_required() { if terminal.measuring_required() {
let _guard = crossterm_lock.lock(); let _guard = crossterm_lock.lock();
terminal.measure_widths()?; terminal.measure_widths()?;
self.event_tx.send(UiEvent::Redraw)?; ok_or_return!(self.event_tx.send(UiEvent::GraphemeWidthsChanged), Ok(()));
} }
// 2. Handle events (in batches) // 2. Handle events (in batches)
@ -148,25 +154,11 @@ impl Ui {
Some(event) => event, Some(event) => event,
None => return Ok(()), None => return Ok(()),
}; };
let mut redraw = false;
let end_time = Instant::now() + EVENT_PROCESSING_TIME; let end_time = Instant::now() + EVENT_PROCESSING_TIME;
loop { loop {
// Render in-between events so the next event is handled in an match self.handle_event(terminal, &crossterm_lock, event).await {
// up-to-date state. The results of these intermediate renders EventHandleResult::Redraw => redraw = true,
// will be thrown away before the final render.
terminal.autoresize()?;
self.widget().await.render(terminal.frame()).await;
let result = match event {
UiEvent::Redraw => EventHandleResult::Continue,
UiEvent::Term(event) => {
if let Some(event) = InputEvent::from_event(event) {
self.handle_event(terminal, &crossterm_lock, &event).await
} else {
EventHandleResult::Continue
}
}
};
match result {
EventHandleResult::Continue => {} EventHandleResult::Continue => {}
EventHandleResult::Stop => return Ok(()), EventHandleResult::Stop => return Ok(()),
} }
@ -181,10 +173,12 @@ impl Ui {
} }
// 3. Render and present final state // 3. Render and present final state
terminal.autoresize()?; if redraw {
terminal.frame().reset(); terminal.autoresize()?;
self.widget().await.render(terminal.frame()).await; terminal.frame().reset();
terminal.present()?; self.widget().await.render(terminal.frame()).await;
terminal.present()?;
}
} }
} }
@ -224,8 +218,35 @@ impl Ui {
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent, event: UiEvent,
) -> EventHandleResult { ) -> EventHandleResult {
match event {
UiEvent::GraphemeWidthsChanged => EventHandleResult::Redraw,
UiEvent::LogChanged if self.mode == Mode::Log => EventHandleResult::Redraw,
UiEvent::LogChanged => EventHandleResult::Continue,
UiEvent::Term(event) => {
self.handle_term_event(terminal, crossterm_lock, event)
.await
}
UiEvent::EuphRoom { name, event } => {
let handled = self.handle_euph_room_event(name, event).await;
if self.mode == Mode::Main && handled {
EventHandleResult::Redraw
} else {
EventHandleResult::Continue
}
}
}
}
async fn handle_term_event(
&mut self,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: crossterm::event::Event,
) -> EventHandleResult {
let event = some_or_return!(InputEvent::from_event(event), EventHandleResult::Continue);
if let key!(Ctrl + 'c') = event { if let key!(Ctrl + 'c') = event {
// Exit unconditionally on ctrl+c. Previously, shift+q would also // Exit unconditionally on ctrl+c. Previously, shift+q would also
// unconditionally exit, but that interfered with typing text in // unconditionally exit, but that interfered with typing text in
@ -239,35 +260,35 @@ impl Ui {
key!(Esc) | key!(F 1) | key!('?') => self.key_bindings_list = None, key!(Esc) | key!(F 1) | key!('?') => self.key_bindings_list = None,
key!('k') | key!(Up) => key_bindings_list.scroll_up(1), key!('k') | key!(Up) => key_bindings_list.scroll_up(1),
key!('j') | key!(Down) => key_bindings_list.scroll_down(1), key!('j') | key!(Down) => key_bindings_list.scroll_down(1),
_ => {} _ => return EventHandleResult::Continue,
} }
return EventHandleResult::Continue; return EventHandleResult::Redraw;
} }
match event { match event {
key!(F 1) => { key!(F 1) => {
self.key_bindings_list = Some(ListState::new()); self.key_bindings_list = Some(ListState::new());
return EventHandleResult::Continue; return EventHandleResult::Redraw;
} }
key!(F 12) => { key!(F 12) => {
self.mode = match self.mode { self.mode = match self.mode {
Mode::Main => Mode::Log, Mode::Main => Mode::Log,
Mode::Log => Mode::Main, Mode::Log => Mode::Main,
}; };
return EventHandleResult::Continue; return EventHandleResult::Redraw;
} }
_ => {} _ => {}
} }
let handled = match self.mode { let mut handled = match self.mode {
Mode::Main => { Mode::Main => {
self.rooms self.rooms
.handle_event(terminal, crossterm_lock, event) .handle_input_event(terminal, crossterm_lock, &event)
.await .await
} }
Mode::Log => self Mode::Log => self
.log_chat .log_chat
.handle_event(terminal, crossterm_lock, event, false) .handle_input_event(terminal, crossterm_lock, &event, false)
.await .await
.handled(), .handled(),
}; };
@ -278,9 +299,19 @@ impl Ui {
if !handled { if !handled {
if let key!('?') = event { if let key!('?') = event {
self.show_key_bindings(); self.show_key_bindings();
handled = true;
} }
} }
EventHandleResult::Continue if handled {
EventHandleResult::Redraw
} else {
EventHandleResult::Continue
}
}
async fn handle_euph_room_event(&mut self, name: String, event: EuphRoomEvent) -> bool {
// TODO Redirect this to the euph room
true
} }
} }

View file

@ -90,7 +90,7 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
} }
} }
pub async fn handle_event( pub async fn handle_input_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
@ -100,7 +100,7 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
match self.mode { match self.mode {
Mode::Tree => { Mode::Tree => {
self.tree self.tree
.handle_event(terminal, crossterm_lock, event, can_compose) .handle_input_event(terminal, crossterm_lock, event, can_compose)
.await .await
} }
} }

View file

@ -79,7 +79,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
bindings.binding("z", "center cursor on screen"); bindings.binding("z", "center cursor on screen");
} }
async fn handle_movement_event(&mut self, frame: &mut Frame, event: &InputEvent) -> bool { async fn handle_movement_input_event(&mut self, frame: &mut Frame, event: &InputEvent) -> bool {
let chat_height = frame.size().height - 3; let chat_height = frame.size().height - 3;
match event { match event {
@ -115,7 +115,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
bindings.binding("ctrl+s", "mark all older messages as seen"); bindings.binding("ctrl+s", "mark all older messages as seen");
} }
async fn handle_action_event(&mut self, event: &InputEvent, id: Option<&M::Id>) -> bool { async fn handle_action_input_event(&mut self, event: &InputEvent, id: Option<&M::Id>) -> bool {
match event { match event {
key!(' ') => { key!(' ') => {
if let Some(id) = id { if let Some(id) = id {
@ -162,7 +162,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
bindings.binding("t", "start a new thread"); bindings.binding("t", "start a new thread");
} }
async fn handle_edit_initiating_event( async fn handle_edit_initiating_input_event(
&mut self, &mut self,
event: &InputEvent, event: &InputEvent,
id: Option<M::Id>, id: Option<M::Id>,
@ -198,7 +198,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
} }
async fn handle_normal_event( async fn handle_normal_input_event(
&mut self, &mut self,
frame: &mut Frame, frame: &mut Frame,
event: &InputEvent, event: &InputEvent,
@ -206,12 +206,12 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
id: Option<M::Id>, id: Option<M::Id>,
) -> bool { ) -> bool {
#[allow(clippy::if_same_then_else)] #[allow(clippy::if_same_then_else)]
if self.handle_movement_event(frame, event).await { if self.handle_movement_input_event(frame, event).await {
true true
} else if self.handle_action_event(event, id.as_ref()).await { } else if self.handle_action_input_event(event, id.as_ref()).await {
true true
} else if can_compose { } else if can_compose {
self.handle_edit_initiating_event(event, id).await self.handle_edit_initiating_input_event(event, id).await
} else { } else {
false false
} }
@ -223,7 +223,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
util::list_editor_key_bindings(bindings, |_| true, true); util::list_editor_key_bindings(bindings, |_| true, true);
} }
fn handle_editor_event( fn handle_editor_input_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
@ -251,7 +251,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
_ => { _ => {
let handled = util::handle_editor_event( let handled = util::handle_editor_input_event(
&self.editor, &self.editor,
terminal, terminal,
crossterm_lock, crossterm_lock,
@ -282,7 +282,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
} }
async fn handle_event( async fn handle_input_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
@ -292,7 +292,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
match &self.cursor { match &self.cursor {
Cursor::Bottom => { Cursor::Bottom => {
if self if self
.handle_normal_event(terminal.frame(), event, can_compose, None) .handle_normal_input_event(terminal.frame(), event, can_compose, None)
.await .await
{ {
Reaction::Handled Reaction::Handled
@ -303,7 +303,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Msg(id) => { Cursor::Msg(id) => {
let id = id.clone(); let id = id.clone();
if self if self
.handle_normal_event(terminal.frame(), event, can_compose, Some(id)) .handle_normal_input_event(terminal.frame(), event, can_compose, Some(id))
.await .await
{ {
Reaction::Handled Reaction::Handled
@ -314,7 +314,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Editor { Cursor::Editor {
coming_from, coming_from,
parent, parent,
} => self.handle_editor_event( } => self.handle_editor_input_event(
terminal, terminal,
crossterm_lock, crossterm_lock,
event, event,
@ -322,7 +322,10 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
parent.clone(), parent.clone(),
), ),
Cursor::Pseudo { .. } => { Cursor::Pseudo { .. } => {
if self.handle_movement_event(terminal.frame(), event).await { if self
.handle_movement_input_event(terminal.frame(), event)
.await
{
Reaction::Handled Reaction::Handled
} else { } else {
Reaction::NotHandled Reaction::NotHandled
@ -365,7 +368,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
self.0.lock().await.list_key_bindings(bindings, can_compose); self.0.lock().await.list_key_bindings(bindings, can_compose);
} }
pub async fn handle_event( pub async fn handle_input_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
@ -375,7 +378,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
self.0 self.0
.lock() .lock()
.await .await
.handle_event(terminal, crossterm_lock, event, can_compose) .handle_input_event(terminal, crossterm_lock, event, can_compose)
.await .await
} }

View file

@ -11,7 +11,8 @@ use tokio::sync::{mpsc, oneshot};
use toss::styled::Styled; use toss::styled::Styled;
use toss::terminal::Terminal; use toss::terminal::Terminal;
use crate::euph; use crate::euph::{self, EuphRoomEvent};
use crate::macros::{ok_or_return, some_or_return};
use crate::store::MsgStore; use crate::store::MsgStore;
use crate::ui::chat::{ChatState, Reaction}; use crate::ui::chat::{ChatState, Reaction};
use crate::ui::input::{key, InputEvent, KeyBindingsList, KeyEvent}; use crate::ui::input::{key, InputEvent, KeyBindingsList, KeyEvent};
@ -61,10 +62,32 @@ impl EuphRoom {
} }
} }
async fn shovel_room_events(
name: String,
mut euph_room_event_rx: mpsc::UnboundedReceiver<EuphRoomEvent>,
ui_event_tx: mpsc::UnboundedSender<UiEvent>,
) {
loop {
let event = some_or_return!(euph_room_event_rx.recv().await);
let event = UiEvent::EuphRoom {
name: name.clone(),
event,
};
ok_or_return!(ui_event_tx.send(event));
}
}
pub fn connect(&mut self) { pub fn connect(&mut self) {
if self.room.is_none() { if self.room.is_none() {
self.room = Some(euph::Room::new( let store = self.chat.store().clone();
self.chat.store().clone(), let name = store.room().to_string();
let (room, euph_room_event_rx) = euph::Room::new(store);
self.room = Some(room);
tokio::task::spawn(Self::shovel_room_events(
name,
euph_room_event_rx,
self.ui_event_tx.clone(), self.ui_event_tx.clone(),
)); ));
} }
@ -353,7 +376,7 @@ impl EuphRoom {
} }
} }
pub async fn handle_event( pub async fn handle_input_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
@ -366,7 +389,7 @@ impl EuphRoom {
if let Ok(Some(Status::Joined(joined))) = room.status().await { if let Ok(Some(Status::Joined(joined))) = room.status().await {
match self match self
.chat .chat
.handle_event(terminal, crossterm_lock, event, true) .handle_input_event(terminal, crossterm_lock, event, true)
.await .await
{ {
Reaction::NotHandled => {} Reaction::NotHandled => {}
@ -392,7 +415,7 @@ impl EuphRoom {
} }
self.chat self.chat
.handle_event(terminal, crossterm_lock, event, false) .handle_input_event(terminal, crossterm_lock, event, false)
.await .await
.handled() .handled()
} }
@ -408,7 +431,7 @@ impl EuphRoom {
self.state = State::Normal; self.state = State::Normal;
true true
} }
_ => util::handle_editor_event( _ => util::handle_editor_input_event(
ed, ed,
terminal, terminal,
crossterm_lock, crossterm_lock,

View file

@ -278,7 +278,7 @@ impl Rooms {
} }
} }
pub async fn handle_event( pub async fn handle_input_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
@ -326,7 +326,10 @@ impl Rooms {
}, },
State::ShowRoom(name) => { State::ShowRoom(name) => {
if let Some(room) = self.euph_rooms.get_mut(name) { if let Some(room) = self.euph_rooms.get_mut(name) {
if room.handle_event(terminal, crossterm_lock, event).await { if room
.handle_input_event(terminal, crossterm_lock, event)
.await
{
return true; return true;
} }
@ -348,7 +351,7 @@ impl Rooms {
} }
} }
_ => { _ => {
return util::handle_editor_event( return util::handle_editor_input_event(
ed, ed,
terminal, terminal,
crossterm_lock, crossterm_lock,

View file

@ -59,7 +59,7 @@ pub fn list_editor_key_bindings(
bindings.binding("↑/↓", "move cursor up/down"); bindings.binding("↑/↓", "move cursor up/down");
} }
pub fn handle_editor_event( pub fn handle_editor_input_event(
editor: &EditorState, editor: &EditorState,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,