Render basic body contents

This commit is contained in:
Joscha 2022-03-05 23:03:07 +01:00
parent 35bfc8d285
commit 32959cf691
6 changed files with 170 additions and 6 deletions

View file

@ -122,6 +122,10 @@ impl Connected {
} }
} }
pub fn status(&self) -> &Status {
&self.status
}
pub fn present(&self) -> Option<&Present> { pub fn present(&self) -> Option<&Present> {
self.status.present() self.status.present()
} }

View file

@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot::{self, Sender}; use tokio::sync::oneshot::{self, Sender};
use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{Mutex, MutexGuard};
@ -15,7 +14,7 @@ struct ConnConfig {
url: String, url: String,
room: String, room: String,
timeout: Duration, timeout: Duration,
ev_tx: UnboundedSender<conn::Event>, ev_tx: UnboundedSender<Event>,
} }
impl ConnConfig { impl ConnConfig {
@ -68,11 +67,19 @@ impl CoveRoom {
dead_mans_switch: tx, dead_mans_switch: tx,
}; };
// Spawned separately because otherwise, the last few elements before a
// connection is closed might not get shoveled.
tokio::spawn(Self::shovel_events(
name,
ev_rx,
event_sender,
convert_event,
));
let conn_clone = room.conn.clone(); let conn_clone = room.conn.clone();
tokio::spawn(async move { tokio::spawn(async move {
tokio::select! { tokio::select! {
_ = rx => {} // Watch dead man's switch _ = rx => {} // Watch dead man's switch
_ = Self::shovel_events(name, ev_rx, event_sender, convert_event) => {}
_ = Self::run(conn_clone, mt, conf) => {} _ = Self::run(conn_clone, mt, conf) => {}
} }
}); });
@ -91,7 +98,7 @@ impl CoveRoom {
async fn shovel_events<E>( async fn shovel_events<E>(
name: String, name: String,
mut ev_rx: UnboundedReceiver<conn::Event>, mut ev_rx: UnboundedReceiver<Event>,
ev_tx: UnboundedSender<E>, ev_tx: UnboundedSender<E>,
convert_event: impl Fn(&str, Event) -> E, convert_event: impl Fn(&str, Event) -> E,
) { ) {
@ -115,6 +122,7 @@ impl CoveRoom {
// TODO Exponential backoff? // TODO Exponential backoff?
tokio::time::sleep(Duration::from_secs(10)).await; tokio::time::sleep(Duration::from_secs(10)).await;
} }
// TODO Note these errors somewhere in the room state
Err(conn::Error::CouldNotConnect(_)) => return, Err(conn::Error::CouldNotConnect(_)) => return,
Err(conn::Error::InvalidRoom(_)) => return, Err(conn::Error::InvalidRoom(_)) => return,
Err(conn::Error::InvalidIdentity(_)) => return, Err(conn::Error::InvalidIdentity(_)) => return,

View file

@ -1,3 +1,5 @@
// TODO Make as few things async as necessary
#![warn(clippy::use_self)] #![warn(clippy::use_self)]
pub mod client; pub mod client;

View file

@ -1,3 +1,4 @@
mod body;
mod users; mod users;
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
@ -9,6 +10,7 @@ use tui::Frame;
use crate::client::cove::room::CoveRoom; use crate::client::cove::room::CoveRoom;
use self::body::Body;
use self::users::CoveUsers; use self::users::CoveUsers;
use super::input::EventHandler; use super::input::EventHandler;
@ -16,11 +18,15 @@ use super::styles;
pub struct CoveUi { pub struct CoveUi {
room: CoveRoom, room: CoveRoom,
body: Body,
} }
impl CoveUi { impl CoveUi {
pub fn new(room: CoveRoom) -> Self { pub fn new(room: CoveRoom) -> Self {
Self { room } Self {
room,
body: Body::default(),
}
} }
fn name(&self) -> &str { fn name(&self) -> &str {
@ -63,7 +69,8 @@ impl CoveUi {
} }
async fn render_body<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) { async fn render_body<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) {
// TODO Implement self.body.update(&self.room).await;
self.body.render(frame, area).await
} }
pub async fn render_users<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) { pub async fn render_users<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) {

View file

@ -0,0 +1,133 @@
use tui::backend::Backend;
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use tui::text::Span;
use tui::widgets::Paragraph;
use tui::Frame;
use unicode_width::UnicodeWidthStr;
use crate::client::cove::conn::{State, Status};
use crate::client::cove::room::CoveRoom;
use crate::ui::textline::{TextLine, TextLineState};
use crate::ui::{layout, styles};
pub enum Body {
Empty,
Connecting,
ChoosingRoom,
Identifying,
ChooseNick {
nick: TextLineState,
prev_error: Option<String>,
},
Present,
Stopped, // TODO Display reason for stoppage
}
impl Default for Body {
fn default() -> Self {
Self::Empty
}
}
impl Body {
pub async fn update(&mut self, room: &CoveRoom) {
match &*room.conn().await.state().await {
State::Connecting => *self = Self::Connecting,
State::Connected(conn) => match conn.status() {
Status::ChoosingRoom => *self = Self::ChoosingRoom,
Status::Identifying => *self = Self::Identifying,
Status::IdRequired(error) => self.choose_nick(error.clone()),
Status::Present(_) => *self = Self::Present,
},
State::Stopped => *self = Self::Stopped,
}
}
fn choose_nick(&mut self, error: Option<String>) {
match self {
Self::ChooseNick { prev_error, .. } => *prev_error = error,
_ => {
*self = Self::ChooseNick {
nick: TextLineState::default(),
prev_error: error,
}
}
}
}
pub async fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) {
match self {
Body::Empty => todo!(),
Body::Connecting => {
let text = "Connecting...";
let area = layout::centered(text.width() as u16, 1, area);
frame.render_widget(Paragraph::new(Span::styled(text, styles::title())), area);
}
Body::ChoosingRoom => {
let text = "Entering room...";
let area = layout::centered(text.width() as u16, 1, area);
frame.render_widget(Paragraph::new(Span::styled(text, styles::title())), area);
}
Body::Identifying => {
let text = "Identifying...";
let area = layout::centered(text.width() as u16, 1, area);
frame.render_widget(Paragraph::new(Span::styled(text, styles::title())), area);
}
Body::ChooseNick {
nick,
prev_error: None,
} => {
let area = layout::centered_v(2, area);
let areas = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Length(1)])
.split(area);
let title_area = areas[0];
let text_area = areas[1];
frame.render_widget(
Paragraph::new(Span::styled("Choose a nick:", styles::title())),
title_area,
);
frame.render_stateful_widget(TextLine, text_area, nick);
}
Body::ChooseNick {
nick,
prev_error: Some(error),
} => {
let area = layout::centered_v(3, area);
let areas = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1),
Constraint::Length(1),
Constraint::Length(1),
])
.split(area);
let title_area = areas[0];
let text_area = areas[1];
let error_area = areas[2];
frame.render_widget(
Paragraph::new(Span::styled("Choose a nick:", styles::title())),
title_area,
);
frame.render_stateful_widget(TextLine, text_area, nick);
frame.render_widget(
Paragraph::new(Span::styled(error as &str, styles::error())),
error_area,
);
}
Body::Present => {
let text = "Present";
let area = layout::centered(text.width() as u16, 1, area);
frame.render_widget(Paragraph::new(Span::styled(text, styles::title())), area);
}
Body::Stopped => {
let text = "Stopped";
let area = layout::centered(text.width() as u16, 1, area);
frame.render_widget(Paragraph::new(Span::styled(text, styles::title())), area);
}
}
}
}

View file

@ -12,3 +12,13 @@ pub fn centered(width: u16, height: u16, area: Rect) -> Rect {
height, height,
} }
} }
pub fn centered_v(height: u16, area: Rect) -> Rect {
let height = height.min(area.height);
let dy = (area.height - height) / 2;
Rect {
y: area.y + dy,
height,
..area
}
}