Migrate key bindings list widget to AsyncWidget

This commit is contained in:
Joscha 2023-04-12 00:49:39 +02:00
parent d5b6dd9802
commit adc70ad233
2 changed files with 86 additions and 54 deletions

View file

@ -28,8 +28,8 @@ pub use self::chat::ChatMsg;
use self::chat::ChatState; use self::chat::ChatState;
use self::input::{key, InputEvent, KeyBindingsList}; use self::input::{key, InputEvent, KeyBindingsList};
use self::rooms::Rooms; use self::rooms::Rooms;
use self::widgets::list::ListState;
use self::widgets::WidgetWrapper; use self::widgets::WidgetWrapper;
use self::widgets2::ListState;
/// Time to spend batch processing events before redrawing the screen. /// Time to spend batch processing events before redrawing the screen.
const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps 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> { 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 { let widget = match self.mode {
Mode::Main => WidgetWrapper::new(self.rooms.widget().await).boxed_async(), Mode::Main => WidgetWrapper::new(self.rooms.widget().await).boxed_async(),
Mode::Log => { Mode::Log => {
@ -191,10 +199,12 @@ impl Ui {
} }
}; };
if let Some(key_bindings_list) = &self.key_bindings_list { if let Some(key_bindings_list) = key_bindings_list {
let mut bindings = KeyBindingsList::new(key_bindings_list); // We checked whether this was Some earlier.
self.list_key_bindings(&mut bindings).await; let list_state = self.key_bindings_list.as_mut().unwrap();
WidgetWrapper::new(bindings.widget())
key_bindings_list
.widget(list_state)
.above(widget) .above(widget)
.boxed_async() .boxed_async()
} else { } else {

View file

@ -2,19 +2,11 @@ use std::convert::Infallible;
use crossterm::event::{Event, KeyCode, KeyModifiers}; use crossterm::event::{Event, KeyCode, KeyModifiers};
use crossterm::style::Stylize; 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::widgets2::ListState;
use super::widgets::border::Border; use super::UiError;
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;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum InputEvent { pub enum InputEvent {
@ -83,66 +75,96 @@ macro_rules! key {
} }
pub(crate) use key; pub(crate) use key;
/// Helper wrapper around a list widget for a more consistent key binding style. enum Row {
pub struct KeyBindingsList(List<Infallible>); Empty,
Heading(String),
Binding(String, String),
BindingContd(String),
}
pub struct KeyBindingsList(Vec<Row>);
impl KeyBindingsList { impl KeyBindingsList {
/// Width of the left column of key bindings. /// Width of the left column of key bindings.
const BINDING_WIDTH: u16 = 20; const BINDING_WIDTH: u16 = 20;
pub fn new(state: &ListState<Infallible>) -> Self { pub fn new() -> Self {
Self(state.widget()) Self(vec![])
} }
fn binding_style() -> Style { fn binding_style() -> Style {
Style::new().cyan() Style::new().cyan()
} }
pub fn widget(self) -> BoxedWidget { fn row_widget(row: Row) -> BoxedAsync<'static, UiError> {
let binding_style = Self::binding_style(); match row {
Float::new(Layer::new(vec![ Row::Empty => Empty::new().boxed_async(),
Border::new(Background::new(Padding::new(self.0).horizontal(1))).into(),
Float::new( Row::Heading(name) => Text::new((name, Style::new().bold())).boxed_async(),
Padding::new(Text::new(
Styled::new("jk/↓↑", binding_style) Row::Binding(binding, description) => Join2::horizontal(
.then_plain(" to scroll, ") Text::new((binding, Self::binding_style()))
.then("esc", binding_style) .padding()
.then_plain(" to close"), .with_right(1)
)) .resize()
.horizontal(1), .with_min_width(Self::BINDING_WIDTH)
.segment(),
Text::new(description).segment(),
) )
.horizontal(0.5) .boxed_async(),
.into(),
])) Row::BindingContd(description) => Join2::horizontal(
.horizontal(0.5) Empty::new().with_width(Self::BINDING_WIDTH).segment(),
.vertical(0.5) Text::new(description).segment(),
.into() )
.boxed_async(),
}
}
pub fn widget(self, list_state: &mut ListState<Infallible>) -> 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) { pub fn empty(&mut self) {
self.0.add_unsel(Empty::new()); self.0.push(Row::Empty);
} }
pub fn heading(&mut self, name: &str) { 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) { pub fn binding(&mut self, binding: &str, description: &str) {
let widget = HJoin::new(vec![ self.0
Segment::new( .push(Row::Binding(binding.to_string(), description.to_string()));
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);
} }
pub fn binding_ctd(&mut self, description: &str) { pub fn binding_ctd(&mut self, description: &str) {
let widget = HJoin::new(vec![ self.0.push(Row::BindingContd(description.to_string()));
Segment::new(Resize::new(Empty::new()).min_width(Self::BINDING_WIDTH)),
Segment::new(Text::new(description)),
]);
self.0.add_unsel(widget);
} }
} }