Render nick list with generic list
This commit is contained in:
parent
4effe38c1d
commit
2a710ab727
1 changed files with 182 additions and 37 deletions
219
src/ui/room.rs
219
src/ui/room.rs
|
|
@ -1,21 +1,29 @@
|
|||
use std::iter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use crossterm::style::{Color, ContentStyle, Stylize};
|
||||
use parking_lot::FairMutex;
|
||||
use tokio::sync::mpsc;
|
||||
use toss::frame::{Frame, Pos, Size};
|
||||
use toss::styled::Styled;
|
||||
use toss::terminal::Terminal;
|
||||
|
||||
use crate::euph::{self, Status};
|
||||
use crate::euph::api::{SessionType, SessionView};
|
||||
use crate::euph::{self, Joined, Status};
|
||||
use crate::vault::{EuphMsg, EuphVault};
|
||||
|
||||
use super::chat::Chat;
|
||||
use super::list::{List, Row};
|
||||
use super::{util, UiEvent};
|
||||
|
||||
pub struct EuphRoom {
|
||||
ui_event_tx: mpsc::UnboundedSender<UiEvent>,
|
||||
room: Option<euph::Room>,
|
||||
chat: Chat<EuphMsg, EuphVault>,
|
||||
|
||||
nick_list_width: u16,
|
||||
nick_list: List<String>,
|
||||
}
|
||||
|
||||
impl EuphRoom {
|
||||
|
|
@ -24,6 +32,8 @@ impl EuphRoom {
|
|||
ui_event_tx,
|
||||
room: None,
|
||||
chat: Chat::new(vault),
|
||||
nick_list_width: 24,
|
||||
nick_list: List::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,53 +71,188 @@ impl EuphRoom {
|
|||
}
|
||||
|
||||
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);
|
||||
let status = self.status().await;
|
||||
match &status {
|
||||
Some(Some(Status::Joined(joined))) => {
|
||||
self.render_with_nick_list(frame, &status, joined).await
|
||||
}
|
||||
_ => self.render_without_nick_list(frame, &status).await,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_top_bar(frame: &mut Frame, room: &str, status: Option<Option<Status>>) {
|
||||
// Clear area in case something accidentally wrote on it already
|
||||
async fn render_without_nick_list(
|
||||
&mut self,
|
||||
frame: &mut Frame,
|
||||
status: &Option<Option<Status>>,
|
||||
) {
|
||||
let size = frame.size();
|
||||
for x in 0..size.width as i32 {
|
||||
frame.write(Pos::new(x, 0), " ");
|
||||
frame.write(Pos::new(x, 1), "─");
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
// Position of horizontal line between status and chat
|
||||
let hsplit = 1_i32;
|
||||
|
||||
let status_pos = Pos::new(0, 0);
|
||||
// let status_size = Size::new(size.width, 1);
|
||||
|
||||
let chat_pos = Pos::new(0, hsplit + 1);
|
||||
let chat_size = Size::new(size.width, size.height.saturating_sub(hsplit as u16 + 1));
|
||||
|
||||
self.chat.render(frame, chat_pos, chat_size).await;
|
||||
self.render_status(frame, status_pos, status);
|
||||
Self::render_hsplit(frame, hsplit);
|
||||
}
|
||||
|
||||
async fn render_with_nick_list(
|
||||
&mut self,
|
||||
frame: &mut Frame,
|
||||
status: &Option<Option<Status>>,
|
||||
joined: &Joined,
|
||||
) {
|
||||
let size = frame.size();
|
||||
|
||||
// Position of vertical line between main part and nick list
|
||||
let vsplit = size.width.saturating_sub(self.nick_list_width + 1) as i32;
|
||||
// Position of horizontal line between status and chat
|
||||
let hsplit = 1_i32;
|
||||
|
||||
let status_pos = Pos::new(0, 0);
|
||||
// let status_size = Size::new(vsplit as u16, 1);
|
||||
|
||||
let chat_pos = Pos::new(0, hsplit + 1);
|
||||
let chat_size = Size::new(vsplit as u16, size.height.saturating_sub(hsplit as u16 + 1));
|
||||
|
||||
let nick_list_pos = Pos::new(vsplit + 1, 0);
|
||||
let nick_list_size = Size::new(self.nick_list_width, size.height);
|
||||
|
||||
self.chat.render(frame, chat_pos, chat_size).await;
|
||||
self.render_status(frame, status_pos, status);
|
||||
self.render_nick_list(frame, nick_list_pos, nick_list_size, joined);
|
||||
Self::render_vsplit_hsplit(frame, vsplit, hsplit);
|
||||
}
|
||||
|
||||
fn render_status(&self, frame: &mut Frame, pos: Pos, status: &Option<Option<Status>>) {
|
||||
let room = self.chat.store().room();
|
||||
let room_style = ContentStyle::default().bold().blue();
|
||||
let mut info = Styled::new((format!("&{room}"), room_style));
|
||||
info = match status {
|
||||
None => info.then(", archive"),
|
||||
Some(None) => info.then(", connecting..."),
|
||||
Some(Some(Status::Joining(j))) if j.bounce.is_some() => info.then(", auth required"),
|
||||
Some(Some(Status::Joining(_))) => info.then(", joining..."),
|
||||
Some(Some(Status::Joined(j))) => {
|
||||
let nick = &j.session.name;
|
||||
if nick.is_empty() {
|
||||
format!("&{room}, present without nick")
|
||||
info.then(", present without nick")
|
||||
} else {
|
||||
format!("&{room}, present as {nick}",)
|
||||
let nick_style = euph::nick_style(nick);
|
||||
info.then(", present as ").then((nick, nick_style))
|
||||
}
|
||||
}
|
||||
};
|
||||
frame.write(Pos::new(0, 0), status);
|
||||
frame.write(pos, info);
|
||||
}
|
||||
|
||||
fn render_row(session: &SessionView) -> Row<String> {
|
||||
if session.name.is_empty() {
|
||||
let name = "lurk";
|
||||
let style = ContentStyle::default().grey();
|
||||
let style_inv = ContentStyle::default().black().on_grey();
|
||||
Row::sel(
|
||||
session.session_id.clone(),
|
||||
Styled::new((name, style)),
|
||||
style,
|
||||
Styled::new((name, style_inv)),
|
||||
style_inv,
|
||||
)
|
||||
} else {
|
||||
let name = &session.name;
|
||||
let (r, g, b) = euph::nick_color(name);
|
||||
let color = Color::Rgb { r, g, b };
|
||||
let style = ContentStyle::default().bold().with(color);
|
||||
let style_inv = ContentStyle::default().bold().black().on(color);
|
||||
Row::sel(
|
||||
session.session_id.clone(),
|
||||
Styled::new((name, style)),
|
||||
style,
|
||||
Styled::new((name, style_inv)),
|
||||
style_inv,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_section(rows: &mut Vec<Row<String>>, name: &str, sessions: &[&SessionView]) {
|
||||
if sessions.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let heading_style = ContentStyle::new().bold();
|
||||
|
||||
if !rows.is_empty() {
|
||||
rows.push(Row::unsel(""));
|
||||
}
|
||||
|
||||
let row = Styled::new((name, heading_style)).then(format!(" ({})", sessions.len()));
|
||||
rows.push(Row::unsel(row));
|
||||
|
||||
for sess in sessions {
|
||||
rows.push(Self::render_row(sess));
|
||||
}
|
||||
}
|
||||
|
||||
fn render_rows(joined: &Joined) -> Vec<Row<String>> {
|
||||
let mut people = vec![];
|
||||
let mut bots = vec![];
|
||||
let mut lurkers = vec![];
|
||||
let mut nurkers = vec![];
|
||||
|
||||
for sess in iter::once(&joined.session).chain(joined.listing.values()) {
|
||||
match sess.id.session_type() {
|
||||
Some(SessionType::Bot) if sess.name.is_empty() => nurkers.push(sess),
|
||||
Some(SessionType::Bot) => bots.push(sess),
|
||||
_ if sess.name.is_empty() => lurkers.push(sess),
|
||||
_ => people.push(sess),
|
||||
}
|
||||
}
|
||||
|
||||
people.sort_unstable_by_key(|s| (&s.name, &s.session_id));
|
||||
bots.sort_unstable_by_key(|s| (&s.name, &s.session_id));
|
||||
lurkers.sort_unstable_by_key(|s| &s.session_id);
|
||||
nurkers.sort_unstable_by_key(|s| &s.session_id);
|
||||
|
||||
let mut rows: Vec<Row<String>> = vec![];
|
||||
Self::render_section(&mut rows, "People", &people);
|
||||
Self::render_section(&mut rows, "Bots", &bots);
|
||||
Self::render_section(&mut rows, "Lurkers", &lurkers);
|
||||
Self::render_section(&mut rows, "Nurkers", &nurkers);
|
||||
rows
|
||||
}
|
||||
|
||||
fn render_nick_list(&mut self, frame: &mut Frame, pos: Pos, size: Size, joined: &Joined) {
|
||||
// Clear area in case there's overdraw from the chat or status
|
||||
for y in pos.y..(pos.y + size.height as i32) {
|
||||
for x in pos.x..(pos.x + size.width as i32) {
|
||||
frame.write(Pos::new(x, y), " ");
|
||||
}
|
||||
}
|
||||
|
||||
let rows = Self::render_rows(joined);
|
||||
self.nick_list.render(frame, pos, size, rows);
|
||||
}
|
||||
|
||||
fn render_hsplit(frame: &mut Frame, hsplit: i32) {
|
||||
for x in 0..frame.size().width as i32 {
|
||||
frame.write(Pos::new(x, hsplit), "─");
|
||||
}
|
||||
}
|
||||
|
||||
fn render_vsplit_hsplit(frame: &mut Frame, vsplit: i32, hsplit: i32) {
|
||||
for x in 0..vsplit {
|
||||
frame.write(Pos::new(x, hsplit), "─");
|
||||
}
|
||||
|
||||
for y in 0..frame.size().height as i32 {
|
||||
let symbol = if y == hsplit { "┤" } else { "│" };
|
||||
frame.write(Pos::new(vsplit, y), symbol);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_key_event(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue