Migrate rooms list to AsyncWidget

This commit is contained in:
Joscha 2023-04-12 13:30:25 +02:00
parent adc70ad233
commit ead4fa7c8a
2 changed files with 114 additions and 86 deletions

View file

@ -193,7 +193,7 @@ impl Ui {
}; };
let widget = match self.mode { let widget = match self.mode {
Mode::Main => WidgetWrapper::new(self.rooms.widget().await).boxed_async(), Mode::Main => self.rooms.widget().await,
Mode::Log => { Mode::Log => {
WidgetWrapper::new(self.log_chat.widget(String::new(), true)).boxed_async() WidgetWrapper::new(self.log_chat.widget(String::new(), true)).boxed_async()
} }

View file

@ -8,7 +8,8 @@ use euphoxide::bot::instance::{Event, ServerConfig};
use euphoxide::conn::{self, Joined}; use euphoxide::conn::{self, Joined};
use parking_lot::FairMutex; use parking_lot::FairMutex;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use toss::{Style, Styled, Terminal}; use toss::widgets::{BoxedAsync, EditorState, Empty, Join2, Text};
use toss::{Style, Styled, Terminal, WidgetExt};
use crate::config::{Config, RoomsSortOrder}; use crate::config::{Config, RoomsSortOrder};
use crate::euph; use crate::euph;
@ -17,15 +18,9 @@ use crate::vault::Vault;
use super::euph::room::EuphRoom; use super::euph::room::EuphRoom;
use super::input::{key, InputEvent, KeyBindingsList}; use super::input::{key, InputEvent, KeyBindingsList};
use super::widgets::editor::EditorState; use super::widgets::WidgetWrapper;
use super::widgets::join::{HJoin, Segment, VJoin}; use super::widgets2::{List, ListState, Popup};
use super::widgets::layer::Layer; use super::{util2, UiError, UiEvent};
use super::widgets::list::{List, ListState};
use super::widgets::popup::Popup;
use super::widgets::resize::Resize;
use super::widgets::text::Text;
use super::widgets::BoxedWidget;
use super::{util, UiEvent};
enum State { enum State {
ShowList, ShowList,
@ -34,6 +29,7 @@ enum State {
Delete(String, EditorState), Delete(String, EditorState),
} }
#[derive(Clone, Copy)]
enum Order { enum Order {
Alphabet, Alphabet,
Importance, Importance,
@ -169,49 +165,59 @@ impl Rooms {
} }
} }
pub async fn widget(&mut self) -> BoxedWidget { pub async fn widget(&mut self) -> BoxedAsync<'_, UiError> {
match &self.state { match &self.state {
State::ShowRoom(_) => {} State::ShowRoom(_) => {}
_ => self.stabilize_rooms().await, _ => self.stabilize_rooms().await,
} }
match &self.state { match &mut self.state {
State::ShowList => self.rooms_widget().await, State::ShowList => {
State::ShowRoom(name) => { Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order).await
}
State::ShowRoom(name) => WidgetWrapper::new(
self.euph_rooms self.euph_rooms
.get_mut(name) .get_mut(name)
.expect("room exists after stabilization") .expect("room exists after stabilization")
.widget() .widget()
.await,
)
.boxed_async(),
State::Connect(editor) => {
Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order)
.await .await
.below(Self::new_room_widget(editor))
.boxed_async()
}
State::Delete(name, editor) => {
Self::rooms_widget(&mut self.list, &self.euph_rooms, self.order)
.await
.below(Self::delete_room_widget(name, editor))
.boxed_async()
} }
State::Connect(editor) => Layer::new(vec![
self.rooms_widget().await,
Self::new_room_widget(editor),
])
.into(),
State::Delete(name, editor) => Layer::new(vec![
self.rooms_widget().await,
Self::delete_room_widget(name, editor),
])
.into(),
} }
} }
fn new_room_widget(editor: &EditorState) -> BoxedWidget { fn new_room_widget(editor: &mut EditorState) -> BoxedAsync<'_, UiError> {
let room_style = Style::new().bold().blue(); let room_style = Style::new().bold().blue();
let editor = editor.widget().highlight(|s| Styled::new(s, room_style));
Popup::new(HJoin::new(vec![ let inner = Join2::horizontal(
Segment::new(Text::new(("&", room_style))), Text::new(("&", room_style)).segment().with_fixed(true),
Segment::new(editor).priority(0), editor
])) .widget()
.title("Connect to") .with_highlight(|s| Styled::new(s, room_style))
.build() .segment(),
);
Popup::new(inner, "Connect to").boxed_async()
} }
fn delete_room_widget(name: &str, editor: &EditorState) -> BoxedWidget { fn delete_room_widget<'a>(name: &str, editor: &'a mut EditorState) -> BoxedAsync<'a, UiError> {
let warn_style = Style::new().bold().red(); let warn_style = Style::new().bold().red();
let room_style = Style::new().bold().blue(); let room_style = Style::new().bold().blue();
let editor = editor.widget().highlight(|s| Styled::new(s, room_style));
let text = Styled::new_plain("Are you sure you want to delete ") let text = Styled::new_plain("Are you sure you want to delete ")
.then("&", room_style) .then("&", room_style)
.then(name, room_style) .then(name, room_style)
@ -222,20 +228,32 @@ impl Rooms {
.then_plain(".\n\n") .then_plain(".\n\n")
.then_plain("To confirm the deletion, ") .then_plain("To confirm the deletion, ")
.then_plain("enter the full name of the room and press enter:"); .then_plain("enter the full name of the room and press enter:");
Popup::new(VJoin::new(vec![
// The HJoin prevents the text from filling up the entire available let inner = Join2::vertical(
// The Join prevents the text from filling up the entire available
// space if the editor is wider than the text. // space if the editor is wider than the text.
Segment::new(HJoin::new(vec![Segment::new( Join2::horizontal(
Resize::new(Text::new(text).wrap(true)).max_width(54), Text::new(text)
)])), .resize()
Segment::new(HJoin::new(vec![ .with_max_width(54)
Segment::new(Text::new(("&", room_style))), .segment()
Segment::new(editor).priority(0), .with_growing(false),
])), Empty::new().segment(),
])) )
.title(("Delete room", warn_style)) .segment(),
.border(warn_style) Join2::horizontal(
.build() Text::new(("&", room_style)).segment().with_fixed(true),
editor
.widget()
.with_highlight(|s| Styled::new(s, room_style))
.segment(),
)
.segment(),
);
Popup::new(inner, "Delete room")
.with_border_style(warn_style)
.boxed_async()
} }
fn format_pbln(joined: &Joined) -> String { fn format_pbln(joined: &Joined) -> String {
@ -322,8 +340,8 @@ impl Rooms {
} }
} }
fn sort_rooms(&self, rooms: &mut [(&String, Option<&euph::State>, usize)]) { fn sort_rooms(rooms: &mut [(&String, Option<&euph::State>, usize)], order: Order) {
match self.order { match order {
Order::Alphabet => rooms.sort_unstable_by_key(|(name, _, _)| *name), Order::Alphabet => rooms.sort_unstable_by_key(|(name, _, _)| *name),
Order::Importance => rooms.sort_unstable_by_key(|(name, state, unseen)| { Order::Importance => rooms.sort_unstable_by_key(|(name, state, unseen)| {
(state.is_none(), *unseen == 0, *name) (state.is_none(), *unseen == 0, *name)
@ -331,8 +349,12 @@ impl Rooms {
} }
} }
async fn render_rows(&self, list: &mut List<String>) { async fn render_rows(
if self.euph_rooms.is_empty() { list: &mut List<'_, String, Text>,
euph_rooms: &HashMap<String, EuphRoom>,
order: Order,
) {
if euph_rooms.is_empty() {
list.add_unsel(Text::new(( list.add_unsel(Text::new((
"Press F1 for key bindings", "Press F1 for key bindings",
Style::new().grey().italic(), Style::new().grey().italic(),
@ -340,37 +362,43 @@ impl Rooms {
} }
let mut rooms = vec![]; let mut rooms = vec![];
for (name, room) in &self.euph_rooms { for (name, room) in euph_rooms {
let state = room.room_state(); let state = room.room_state();
let unseen = room.unseen_msgs_count().await; let unseen = room.unseen_msgs_count().await;
rooms.push((name, state, unseen)); rooms.push((name, state, unseen));
} }
self.sort_rooms(&mut rooms); Self::sort_rooms(&mut rooms, order);
for (name, state, unseen) in rooms { for (name, state, unseen) in rooms {
let room_style = Style::new().bold().blue(); let style = if list.state().selected() == Some(name) {
let room_sel_style = Style::new().bold().black().on_white(); Style::new().bold().black().on_white()
} else {
Style::new().bold().blue()
};
let mut normal = Styled::new(format!("&{name}"), room_style); let text = Styled::new(format!("&{name}"), style)
let mut selected = Styled::new(format!("&{name}"), room_sel_style); .and_then(Self::format_room_info(state, unseen));
let info = Self::format_room_info(state, unseen); list.add_sel(name.clone(), Text::new(text));
normal = normal.and_then(info.clone());
selected = selected.and_then(info);
list.add_sel(name.clone(), Text::new(normal), Text::new(selected));
} }
} }
async fn rooms_widget(&self) -> BoxedWidget { async fn rooms_widget<'a>(
list: &'a mut ListState<String>,
euph_rooms: &HashMap<String, EuphRoom>,
order: Order,
) -> BoxedAsync<'a, UiError> {
let heading_style = Style::new().bold(); let heading_style = Style::new().bold();
let amount = self.euph_rooms.len(); let heading_text =
let heading = Styled::new("Rooms", heading_style).then_plain(format!(" ({})", euph_rooms.len()));
Text::new(Styled::new("Rooms", heading_style).then_plain(format!(" ({amount})")));
let mut list = self.list.widget().focus(true); let mut list = list.widget();
self.render_rows(&mut list).await; Self::render_rows(&mut list, euph_rooms, order).await;
VJoin::new(vec![Segment::new(heading), Segment::new(list).priority(0)]).into() Join2::vertical(
Text::new(heading_text).segment().with_fixed(true),
list.segment(),
)
.boxed_async()
} }
fn room_char(c: char) -> bool { fn room_char(c: char) -> bool {
@ -379,7 +407,7 @@ impl Rooms {
fn list_showlist_key_bindings(bindings: &mut KeyBindingsList) { fn list_showlist_key_bindings(bindings: &mut KeyBindingsList) {
bindings.heading("Rooms"); bindings.heading("Rooms");
util::list_list_key_bindings(bindings); util2::list_list_key_bindings(bindings);
bindings.empty(); bindings.empty();
bindings.binding("enter", "enter selected room"); bindings.binding("enter", "enter selected room");
bindings.binding("c", "connect to selected room"); bindings.binding("c", "connect to selected room");
@ -395,20 +423,20 @@ impl Rooms {
} }
fn handle_showlist_input_event(&mut self, event: &InputEvent) -> bool { fn handle_showlist_input_event(&mut self, event: &InputEvent) -> bool {
if util::handle_list_input_event(&mut self.list, event) { if util2::handle_list_input_event(&mut self.list, event) {
return true; return true;
} }
match event { match event {
key!(Enter) => { key!(Enter) => {
if let Some(name) = self.list.cursor() { if let Some(name) = self.list.selected() {
self.state = State::ShowRoom(name); self.state = State::ShowRoom(name.clone());
} }
return true; return true;
} }
key!('c') => { key!('c') => {
if let Some(name) = self.list.cursor() { if let Some(name) = self.list.selected() {
self.connect_to_room(name); self.connect_to_room(name.clone());
} }
return true; return true;
} }
@ -417,8 +445,8 @@ impl Rooms {
return true; return true;
} }
key!('d') => { key!('d') => {
if let Some(name) = self.list.cursor() { if let Some(name) = self.list.selected() {
self.disconnect_from_room(&name); self.disconnect_from_room(&name.clone());
} }
return true; return true;
} }
@ -454,8 +482,8 @@ impl Rooms {
return true; return true;
} }
key!('X') => { key!('X') => {
if let Some(name) = self.list.cursor() { if let Some(name) = self.list.selected() {
self.state = State::Delete(name, EditorState::new()); self.state = State::Delete(name.clone(), EditorState::new());
} }
return true; return true;
} }
@ -493,13 +521,13 @@ impl Rooms {
bindings.heading("Rooms"); bindings.heading("Rooms");
bindings.binding("esc", "abort"); bindings.binding("esc", "abort");
bindings.binding("enter", "connect to room"); bindings.binding("enter", "connect to room");
util::list_editor_key_bindings(bindings, Self::room_char); util2::list_editor_key_bindings(bindings, Self::room_char);
} }
State::Delete(_, _) => { State::Delete(_, _) => {
bindings.heading("Rooms"); bindings.heading("Rooms");
bindings.binding("esc", "abort"); bindings.binding("esc", "abort");
bindings.binding("enter", "delete room"); bindings.binding("enter", "delete room");
util::list_editor_key_bindings(bindings, Self::room_char); util2::list_editor_key_bindings(bindings, Self::room_char);
} }
} }
} }
@ -512,7 +540,7 @@ impl Rooms {
) -> bool { ) -> bool {
self.stabilize_rooms().await; self.stabilize_rooms().await;
match &self.state { match &mut self.state {
State::ShowList => { State::ShowList => {
if self.handle_showlist_input_event(event) { if self.handle_showlist_input_event(event) {
return true; return true;
@ -539,7 +567,7 @@ impl Rooms {
return true; return true;
} }
key!(Enter) => { key!(Enter) => {
let name = ed.text(); let name = ed.text().to_string();
if !name.is_empty() { if !name.is_empty() {
self.connect_to_room(name.clone()); self.connect_to_room(name.clone());
self.state = State::ShowRoom(name); self.state = State::ShowRoom(name);
@ -547,7 +575,7 @@ impl Rooms {
return true; return true;
} }
_ => { _ => {
if util::handle_editor_input_event(ed, terminal, event, Self::room_char) { if util2::handle_editor_input_event(ed, terminal, event, Self::room_char) {
return true; return true;
} }
} }
@ -564,7 +592,7 @@ impl Rooms {
return true; return true;
} }
_ => { _ => {
if util::handle_editor_input_event(editor, terminal, event, Self::room_char) { if util2::handle_editor_input_event(editor, terminal, event, Self::room_char) {
return true; return true;
} }
} }