From adc70ad233262d50fad51648eac57d6a86f56054 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 12 Apr 2023 00:49:39 +0200 Subject: [PATCH] Migrate key bindings list widget to AsyncWidget --- src/ui.rs | 20 ++++++-- src/ui/input.rs | 120 ++++++++++++++++++++++++++++-------------------- 2 files changed, 86 insertions(+), 54 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 4097ffc..b5b751f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -28,8 +28,8 @@ pub use self::chat::ChatMsg; use self::chat::ChatState; use self::input::{key, InputEvent, KeyBindingsList}; use self::rooms::Rooms; -use self::widgets::list::ListState; use self::widgets::WidgetWrapper; +use self::widgets2::ListState; /// Time to spend batch processing events before redrawing the screen. const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps @@ -184,6 +184,14 @@ impl Ui { } async fn widget(&mut self) -> BoxedAsync<'_, UiError> { + let key_bindings_list = if self.key_bindings_list.is_some() { + let mut bindings = KeyBindingsList::new(); + self.list_key_bindings(&mut bindings).await; + Some(bindings) + } else { + None + }; + let widget = match self.mode { Mode::Main => WidgetWrapper::new(self.rooms.widget().await).boxed_async(), Mode::Log => { @@ -191,10 +199,12 @@ impl Ui { } }; - if let Some(key_bindings_list) = &self.key_bindings_list { - let mut bindings = KeyBindingsList::new(key_bindings_list); - self.list_key_bindings(&mut bindings).await; - WidgetWrapper::new(bindings.widget()) + if let Some(key_bindings_list) = key_bindings_list { + // We checked whether this was Some earlier. + let list_state = self.key_bindings_list.as_mut().unwrap(); + + key_bindings_list + .widget(list_state) .above(widget) .boxed_async() } else { diff --git a/src/ui/input.rs b/src/ui/input.rs index 4c3362e..dd0a2dc 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -2,19 +2,11 @@ use std::convert::Infallible; use crossterm::event::{Event, KeyCode, KeyModifiers}; use crossterm::style::Stylize; -use toss::{Style, Styled}; +use toss::widgets::{BoxedAsync, Empty, Join2, Text}; +use toss::{Style, Styled, WidgetExt}; -use super::widgets::background::Background; -use super::widgets::border::Border; -use super::widgets::empty::Empty; -use super::widgets::float::Float; -use super::widgets::join::{HJoin, Segment}; -use super::widgets::layer::Layer; -use super::widgets::list::{List, ListState}; -use super::widgets::padding::Padding; -use super::widgets::resize::Resize; -use super::widgets::text::Text; -use super::widgets::BoxedWidget; +use super::widgets2::ListState; +use super::UiError; #[derive(Debug, Clone)] pub enum InputEvent { @@ -83,66 +75,96 @@ macro_rules! key { } pub(crate) use key; -/// Helper wrapper around a list widget for a more consistent key binding style. -pub struct KeyBindingsList(List); +enum Row { + Empty, + Heading(String), + Binding(String, String), + BindingContd(String), +} + +pub struct KeyBindingsList(Vec); impl KeyBindingsList { /// Width of the left column of key bindings. const BINDING_WIDTH: u16 = 20; - pub fn new(state: &ListState) -> Self { - Self(state.widget()) + pub fn new() -> Self { + Self(vec![]) } fn binding_style() -> Style { Style::new().cyan() } - pub fn widget(self) -> BoxedWidget { - let binding_style = Self::binding_style(); - Float::new(Layer::new(vec![ - Border::new(Background::new(Padding::new(self.0).horizontal(1))).into(), - Float::new( - Padding::new(Text::new( - Styled::new("jk/↓↑", binding_style) - .then_plain(" to scroll, ") - .then("esc", binding_style) - .then_plain(" to close"), - )) - .horizontal(1), + fn row_widget(row: Row) -> BoxedAsync<'static, UiError> { + match row { + Row::Empty => Empty::new().boxed_async(), + + Row::Heading(name) => Text::new((name, Style::new().bold())).boxed_async(), + + Row::Binding(binding, description) => Join2::horizontal( + Text::new((binding, Self::binding_style())) + .padding() + .with_right(1) + .resize() + .with_min_width(Self::BINDING_WIDTH) + .segment(), + Text::new(description).segment(), ) - .horizontal(0.5) - .into(), - ])) - .horizontal(0.5) - .vertical(0.5) - .into() + .boxed_async(), + + Row::BindingContd(description) => Join2::horizontal( + Empty::new().with_width(Self::BINDING_WIDTH).segment(), + Text::new(description).segment(), + ) + .boxed_async(), + } + } + + pub fn widget(self, list_state: &mut ListState) -> BoxedAsync<'_, UiError> { + let binding_style = Self::binding_style(); + + let hint_text = Styled::new("jk/↓↑", binding_style) + .then_plain(" to scroll, ") + .then("esc", binding_style) + .then_plain(" to close"); + + let hint = Text::new(hint_text) + .padding() + .with_horizontal(1) + .float() + .with_horizontal(0.5) + .with_vertical(0.0); + + let mut list = list_state.widget(); + for row in self.0 { + list.add_unsel(Self::row_widget(row)); + } + + list.padding() + .with_horizontal(1) + .border() + .below(hint) + .background() + .float() + .with_center() + .boxed_async() } pub fn empty(&mut self) { - self.0.add_unsel(Empty::new()); + self.0.push(Row::Empty); } pub fn heading(&mut self, name: &str) { - self.0.add_unsel(Text::new((name, Style::new().bold()))); + self.0.push(Row::Heading(name.to_string())); } pub fn binding(&mut self, binding: &str, description: &str) { - let widget = HJoin::new(vec![ - Segment::new( - Resize::new(Padding::new(Text::new((binding, Self::binding_style()))).right(1)) - .min_width(Self::BINDING_WIDTH), - ), - Segment::new(Text::new(description)), - ]); - self.0.add_unsel(widget); + self.0 + .push(Row::Binding(binding.to_string(), description.to_string())); } pub fn binding_ctd(&mut self, description: &str) { - let widget = HJoin::new(vec![ - Segment::new(Resize::new(Empty::new()).min_width(Self::BINDING_WIDTH)), - Segment::new(Text::new(description)), - ]); - self.0.add_unsel(widget); + self.0.push(Row::BindingContd(description.to_string())); } }