Render basic body contents
This commit is contained in:
parent
35bfc8d285
commit
32959cf691
6 changed files with 170 additions and 6 deletions
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
133
cove-tui/src/ui/cove/body.rs
Normal file
133
cove-tui/src/ui/cove/body.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue