Render and interact with individual rooms

This commit is contained in:
Joscha 2022-06-28 10:24:20 +02:00
parent 9cd7ee008d
commit 6fbc0c5ff7
8 changed files with 228 additions and 48 deletions

135
src/ui/room.rs Normal file
View file

@ -0,0 +1,135 @@
use std::sync::Arc;
use crossterm::event::{KeyCode, KeyEvent};
use crossterm::style::ContentStyle;
use parking_lot::FairMutex;
use tokio::sync::mpsc;
use toss::frame::{Frame, Pos, Size};
use toss::terminal::Terminal;
use crate::chat::Chat;
use crate::euph::{self, Status};
use crate::vault::{EuphMsg, EuphVault};
use super::{util, UiEvent};
pub struct EuphRoom {
ui_event_tx: mpsc::UnboundedSender<UiEvent>,
room: Option<euph::Room>,
chat: Chat<EuphMsg, EuphVault>,
}
impl EuphRoom {
pub fn new(vault: EuphVault, ui_event_tx: mpsc::UnboundedSender<UiEvent>) -> Self {
Self {
ui_event_tx,
room: None,
chat: Chat::new(vault),
}
}
pub fn connect(&mut self) {
if self.room.is_none() {
self.room = Some(euph::Room::new(
self.chat.store().clone(),
self.ui_event_tx.clone(),
));
}
}
pub fn disconnect(&mut self) {
self.room = None;
}
pub fn retain(&mut self) {
if let Some(room) = &self.room {
if room.stopped() {
self.room = None;
}
}
}
pub async fn render(&mut self, frame: &mut Frame) {
let size = frame.size();
let chat_pos = Pos::new(0, 2);
let chat_size = Size {
height: size.height - 2,
..size
};
self.chat.render(frame, chat_pos, chat_size).await;
let room = self.chat.store().room();
let status = if let Some(room) = &self.room {
room.status().await.ok()
} else {
None
};
Self::render_top_bar(frame, room, status);
}
fn render_top_bar(frame: &mut Frame, room: &str, status: Option<Option<Status>>) {
// Clear area in case something accidentally wrote on it already
let size = frame.size();
for x in 0..size.width as i32 {
frame.write(Pos::new(x, 0), " ", ContentStyle::default());
frame.write(Pos::new(x, 1), "", ContentStyle::default());
}
// Write status
let status = match status {
None => format!("&{room}, archive"),
Some(None) => format!("&{room}, connecting..."),
Some(Some(Status::Joining(j))) => {
if j.bounce.is_none() {
format!("&{room}, joining...")
} else {
format!("&{room}, auth required")
}
}
Some(Some(Status::Joined(j))) => {
let nick = &j.session.name;
if nick.is_empty() {
format!("&{room}, present without nick")
} else {
format!("&{room}, present as {nick}",)
}
}
};
frame.write(Pos::new(0, 0), &status, ContentStyle::default());
}
pub async fn handle_key_event(
&mut self,
terminal: &mut Terminal,
size: Size,
crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent,
) {
let chat_size = Size {
height: size.height - 2,
..size
};
self.chat
.handle_navigation(terminal, chat_size, event)
.await;
if let Some(room) = &self.room {
if let Ok(Some(Status::Joined(_))) = room.status().await {
if let KeyCode::Char('n' | 'N') = event.code {
if let Some(new_nick) = util::prompt(terminal, crossterm_lock) {
let _ = room.nick(new_nick);
}
}
if let Some((parent, content)) = self
.chat
.handle_messaging(terminal, crossterm_lock, event)
.await
{
let _ = room.send(parent, content);
}
}
}
}
}

View file

@ -11,6 +11,7 @@ use crate::chat::Chat;
use crate::euph;
use crate::vault::{EuphMsg, EuphVault, Vault};
use super::room::EuphRoom;
use super::{util, UiEvent};
mod style {
@ -33,6 +34,7 @@ struct Cursor {
pub struct Rooms {
vault: Vault,
ui_event_tx: mpsc::UnboundedSender<UiEvent>,
/// Cursor position inside the room list.
///
@ -42,18 +44,17 @@ pub struct Rooms {
/// If set, a single room is displayed in full instead of the room list.
focus: Option<String>,
euph_rooms: HashMap<String, euph::Room>,
euph_chats: HashMap<String, Chat<EuphMsg, EuphVault>>,
euph_rooms: HashMap<String, EuphRoom>,
}
impl Rooms {
pub fn new(vault: Vault) -> Self {
pub fn new(vault: Vault, ui_event_tx: mpsc::UnboundedSender<UiEvent>) -> Self {
Self {
vault,
ui_event_tx,
cursor: None,
focus: None,
euph_rooms: HashMap::new(),
euph_chats: HashMap::new(),
}
}
@ -105,9 +106,10 @@ impl Rooms {
.map(|n| n.to_string())
.collect::<HashSet<String>>();
self.euph_rooms
.retain(|n, r| rooms.contains(n) && !r.stopped());
self.euph_chats.retain(|n, _| rooms.contains(n));
self.euph_rooms.retain(|n, r| rooms.contains(n));
for room in self.euph_rooms.values_mut() {
room.retain();
}
}
fn make_consistent(&mut self, rooms: &[String], height: i32) {
@ -117,11 +119,10 @@ impl Rooms {
pub async fn render(&mut self, frame: &mut Frame) {
if let Some(room) = &self.focus {
let chat = self
.euph_chats
.entry(room.clone())
.or_insert_with(|| Chat::new(self.vault.euph(room.clone())));
chat.render(frame, Pos::new(0, 0), frame.size()).await;
let actual_room = self.euph_rooms.entry(room.clone()).or_insert_with(|| {
EuphRoom::new(self.vault.euph(room.clone()), self.ui_event_tx.clone())
});
actual_room.render(frame).await;
} else {
self.render_rooms(frame).await;
}
@ -160,13 +161,19 @@ impl Rooms {
&mut self,
terminal: &mut Terminal,
size: Size,
ui_event_tx: &mpsc::UnboundedSender<UiEvent>,
crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent,
) {
if let Some(focus) = &self.focus {
if let Some(room) = &self.focus {
if event.code == KeyCode::Esc {
self.focus = None;
} else {
let actual_room = self.euph_rooms.entry(room.clone()).or_insert_with(|| {
EuphRoom::new(self.vault.euph(room.clone()), self.ui_event_tx.clone())
});
actual_room
.handle_key_event(terminal, size, crossterm_lock, event)
.await;
}
} else {
let rooms = self.rooms().await;
@ -196,26 +203,28 @@ impl Rooms {
if let Some(cursor) = &self.cursor {
if let Some(room) = rooms.get(cursor.index) {
let room = room.clone();
self.euph_rooms.entry(room.clone()).or_insert_with(|| {
euph::Room::new(
room.clone(),
self.vault.euph(room),
ui_event_tx.clone(),
)
});
let actual_room =
self.euph_rooms.entry(room.clone()).or_insert_with(|| {
EuphRoom::new(
self.vault.euph(room.clone()),
self.ui_event_tx.clone(),
)
});
actual_room.connect();
}
}
}
KeyCode::Char('C') => {
if let Some(room) = util::prompt(terminal, crossterm_lock) {
let room = room.trim().to_string();
self.euph_rooms.entry(room.clone()).or_insert_with(|| {
euph::Room::new(
room.clone(),
self.vault.euph(room),
ui_event_tx.clone(),
)
});
let actual_room =
self.euph_rooms.entry(room.clone()).or_insert_with(|| {
EuphRoom::new(
self.vault.euph(room.clone()),
self.ui_event_tx.clone(),
)
});
actual_room.connect();
}
}
KeyCode::Char('d') => {
@ -229,7 +238,6 @@ impl Rooms {
if let Some(cursor) = &self.cursor {
if let Some(room) = rooms.get(cursor.index) {
self.euph_rooms.remove(room);
self.euph_chats.remove(room);
self.vault.euph(room.clone()).delete();
}
}