cove/cove-tui/src/ui/room.rs
2022-03-02 01:46:07 +01:00

199 lines
6.7 KiB
Rust

mod users;
use std::sync::Arc;
use tokio::sync::Mutex;
use tui::backend::Backend;
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use tui::style::Style;
use tui::text::{Span, Spans, Text};
use tui::widgets::{Block, BorderType, Borders, Paragraph};
use tui::Frame;
use unicode_width::UnicodeWidthStr;
use crate::room::{Room, Status};
use self::users::Users;
use super::textline::{TextLine, TextLineState};
use super::{layout, styles};
enum Main {
Empty,
Connecting,
Identifying,
ChooseNick {
nick: TextLineState,
prev_error: Option<String>,
},
Messages,
FatalError(String),
}
impl Main {
fn choose_nick() -> Self {
Self::ChooseNick {
nick: TextLineState::default(),
prev_error: None,
}
}
fn fatal<S: ToString>(s: S) -> Self {
Self::FatalError(s.to_string())
}
}
pub struct RoomInfo {
name: String,
room: Arc<Mutex<Room>>,
main: Main,
}
impl RoomInfo {
pub fn new(name: String, room: Arc<Mutex<Room>>) -> Self {
Self {
name,
room,
main: Main::Empty,
}
}
pub fn name(&self) -> &str {
&self.name
}
async fn align_main(&mut self) {
let room = self.room.lock().await;
match room.status() {
Status::Nominal if room.connected() && room.present().is_some() => {
if !matches!(self.main, Main::Messages) {
self.main = Main::Messages;
}
}
Status::Nominal if room.connected() => self.main = Main::Connecting,
Status::Nominal => self.main = Main::Identifying,
Status::NickRequired => self.main = Main::choose_nick(),
Status::CouldNotConnect => self.main = Main::fatal("Could not connect to room"),
Status::InvalidRoom(err) => self.main = Main::fatal(format!("Invalid room:\n{err}")),
Status::InvalidNick(err) => {
if let Main::ChooseNick { prev_error, .. } = &mut self.main {
*prev_error = Some(err.clone());
} else {
self.main = Main::choose_nick();
}
}
Status::InvalidIdentity(err) => {
self.main = Main::fatal(format!("Invalid identity:\n{err}"))
}
}
}
pub async fn render_main<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) {
self.align_main().await;
let areas = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1),
Constraint::Length(1),
Constraint::Min(0),
])
.split(area);
let room_name_area = areas[0];
let separator_area = areas[1];
let main_area = areas[2];
// Room name at the top
let room_name = Paragraph::new(Span::styled(
format!("&{}", self.name()),
styles::selected_room(),
))
.alignment(Alignment::Center);
frame.render_widget(room_name, room_name_area);
let separator = Block::default()
.borders(Borders::BOTTOM)
.border_type(BorderType::Double);
frame.render_widget(separator, separator_area);
// Main below
self.render_main_inner(frame, main_area).await;
}
async fn render_main_inner<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) {
match &mut self.main {
Main::Empty => {}
Main::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);
}
Main::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);
}
Main::ChooseNick {
nick,
prev_error: None,
} => {
let area = layout::centered(50, 2, area);
let top = Rect { height: 1, ..area };
let bot = Rect {
y: top.y + 1,
..top
};
let text = "Choose a nick:";
frame.render_widget(Paragraph::new(Span::styled(text, styles::title())), top);
frame.render_stateful_widget(TextLine, bot, nick);
}
Main::ChooseNick { nick, prev_error } => {
let width = prev_error
.as_ref()
.map(|e| e.width() as u16)
.unwrap_or(0)
.max(50);
let height = if prev_error.is_some() { 5 } else { 2 };
let area = layout::centered(width, height, area);
let top = Rect {
height: height - 1,
..area
};
let bot = Rect {
y: area.bottom() - 1,
height: 1,
..area
};
let mut lines = vec![];
if let Some(err) = &prev_error {
lines.push(Spans::from(Span::styled("Error:", styles::title())));
lines.push(Spans::from(Span::styled(err, styles::error())));
lines.push(Spans::from(""));
}
lines.push(Spans::from(Span::styled("Choose a nick:", styles::title())));
frame.render_widget(Paragraph::new(lines), top);
frame.render_stateful_widget(TextLine, bot, nick);
}
Main::Messages => {
// TODO Actually render messages
frame.render_widget(Paragraph::new("TODO: Messages"), area);
}
Main::FatalError(err) => {
let title = "Fatal error:";
let width = (err.width() as u16).max(title.width() as u16);
let area = layout::centered(width, 2, area);
let pg = Paragraph::new(vec![
Spans::from(Span::styled(title, styles::title())),
Spans::from(Span::styled(err as &str, styles::error())),
])
.alignment(Alignment::Center);
frame.render_widget(pg, area);
}
}
}
pub async fn render_users<B: Backend>(&mut self, frame: &mut Frame<'_, B>, area: Rect) {
if let Some(present) = self.room.lock().await.present() {
frame.render_widget(Users::new(present), area);
}
}
}