Render nick list with generic list

This commit is contained in:
Joscha 2022-07-06 10:10:05 +02:00
parent 4effe38c1d
commit 2a710ab727

View file

@ -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(