Fix list cursor being invisible until first redraw
This commit is contained in:
parent
3f18b76c7d
commit
07b761e0f9
5 changed files with 129 additions and 107 deletions
|
|
@ -6,7 +6,7 @@ use toss::widgets::{BoxedAsync, Text};
|
|||
use toss::{Style, Styled, WidgetExt};
|
||||
|
||||
use crate::ui::input::{key, InputEvent, KeyBindingsList};
|
||||
use crate::ui::widgets::{ListState, Popup};
|
||||
use crate::ui::widgets::{ListBuilder, ListState, Popup};
|
||||
use crate::ui::UiError;
|
||||
|
||||
pub struct LinksState {
|
||||
|
|
@ -41,36 +41,40 @@ impl LinksState {
|
|||
pub fn widget(&mut self) -> BoxedAsync<'_, UiError> {
|
||||
let style_selected = Style::new().black().on_white();
|
||||
|
||||
let mut list = self.list.widget();
|
||||
let mut list_builder = ListBuilder::new();
|
||||
|
||||
if self.links.is_empty() {
|
||||
list.add_unsel(Text::new(("No links found", Style::new().grey().italic())))
|
||||
list_builder.add_unsel(Text::new(("No links found", Style::new().grey().italic())))
|
||||
}
|
||||
|
||||
for (id, link) in self.links.iter().enumerate() {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
let text = if list.state().selected() == Some(&id) {
|
||||
if let Some(number_key) = NUMBER_KEYS.get(id) {
|
||||
Styled::new(format!("[{number_key}]"), style_selected.bold())
|
||||
.then(" ", style_selected)
|
||||
.then(link, style_selected)
|
||||
} else {
|
||||
Styled::new(format!(" {link}"), style_selected)
|
||||
}
|
||||
let link = link.clone();
|
||||
if let Some(&number_key) = NUMBER_KEYS.get(id) {
|
||||
list_builder.add_sel(id, move |selected| {
|
||||
let text = if selected {
|
||||
Styled::new(format!("[{number_key}]"), style_selected.bold())
|
||||
.then(" ", style_selected)
|
||||
.then(link, style_selected)
|
||||
} else {
|
||||
Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold())
|
||||
.then_plain(" ")
|
||||
.then_plain(link)
|
||||
};
|
||||
Text::new(text)
|
||||
});
|
||||
} else {
|
||||
if let Some(number_key) = NUMBER_KEYS.get(id) {
|
||||
Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold())
|
||||
.then_plain(" ")
|
||||
.then_plain(link)
|
||||
} else {
|
||||
Styled::new_plain(format!(" {link}"))
|
||||
}
|
||||
};
|
||||
|
||||
list.add_sel(id, Text::new(text));
|
||||
list_builder.add_sel(id, move |selected| {
|
||||
let text = if selected {
|
||||
Styled::new(format!(" {link}"), style_selected)
|
||||
} else {
|
||||
Styled::new_plain(format!(" {link}"))
|
||||
};
|
||||
Text::new(text)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Popup::new(list, "Links").boxed_async()
|
||||
Popup::new(list_builder.build(&mut self.list), "Links").boxed_async()
|
||||
}
|
||||
|
||||
fn open_link_by_id(&self, id: usize) -> EventResult {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use std::borrow::Cow;
|
||||
use std::iter;
|
||||
|
||||
use crossterm::style::{Color, Stylize};
|
||||
|
|
@ -8,7 +7,7 @@ use toss::widgets::{BoxedAsync, Empty, Text};
|
|||
use toss::{Style, Styled, WidgetExt};
|
||||
|
||||
use crate::euph;
|
||||
use crate::ui::widgets::{List, ListState};
|
||||
use crate::ui::widgets::{ListBuilder, ListState};
|
||||
use crate::ui::UiError;
|
||||
|
||||
pub fn widget<'a>(
|
||||
|
|
@ -16,9 +15,9 @@ pub fn widget<'a>(
|
|||
joined: &Joined,
|
||||
focused: bool,
|
||||
) -> BoxedAsync<'a, UiError> {
|
||||
let mut list = list.widget();
|
||||
render_rows(&mut list, joined, focused);
|
||||
list.boxed_async()
|
||||
let mut list_builder = ListBuilder::new();
|
||||
render_rows(&mut list_builder, joined, focused);
|
||||
list_builder.build(list).boxed_async()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
|
@ -60,7 +59,7 @@ impl HalfSession {
|
|||
}
|
||||
|
||||
fn render_rows(
|
||||
list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>,
|
||||
list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>,
|
||||
joined: &Joined,
|
||||
focused: bool,
|
||||
) {
|
||||
|
|
@ -88,14 +87,14 @@ fn render_rows(
|
|||
lurkers.sort_unstable();
|
||||
nurkers.sort_unstable();
|
||||
|
||||
render_section(list, "People", &people, &joined.session, focused);
|
||||
render_section(list, "Bots", &bots, &joined.session, focused);
|
||||
render_section(list, "Lurkers", &lurkers, &joined.session, focused);
|
||||
render_section(list, "Nurkers", &nurkers, &joined.session, focused);
|
||||
render_section(list_builder, "People", &people, &joined.session, focused);
|
||||
render_section(list_builder, "Bots", &bots, &joined.session, focused);
|
||||
render_section(list_builder, "Lurkers", &lurkers, &joined.session, focused);
|
||||
render_section(list_builder, "Nurkers", &nurkers, &joined.session, focused);
|
||||
}
|
||||
|
||||
fn render_section(
|
||||
list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>,
|
||||
list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>,
|
||||
name: &str,
|
||||
sessions: &[HalfSession],
|
||||
own_session: &SessionView,
|
||||
|
|
@ -107,39 +106,40 @@ fn render_section(
|
|||
|
||||
let heading_style = Style::new().bold();
|
||||
|
||||
if !list.is_empty() {
|
||||
list.add_unsel(Empty::new().boxed_async());
|
||||
if !list_builder.is_empty() {
|
||||
list_builder.add_unsel(Empty::new().boxed_async());
|
||||
}
|
||||
|
||||
let row = Styled::new_plain(" ")
|
||||
.then(name, heading_style)
|
||||
.then_plain(format!(" ({})", sessions.len()));
|
||||
list.add_unsel(Text::new(row).boxed_async());
|
||||
list_builder.add_unsel(Text::new(row).boxed_async());
|
||||
|
||||
for session in sessions {
|
||||
render_row(list, session, own_session, focused);
|
||||
render_row(list_builder, session, own_session, focused);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_row(
|
||||
list: &mut List<'_, SessionId, BoxedAsync<'static, UiError>>,
|
||||
list_builder: &mut ListBuilder<'_, SessionId, BoxedAsync<'static, UiError>>,
|
||||
session: &HalfSession,
|
||||
own_session: &SessionView,
|
||||
focused: bool,
|
||||
) {
|
||||
let (name, style, style_inv, perms_style_inv) = if session.name.is_empty() {
|
||||
let name = "lurk";
|
||||
let name = "lurk".to_string();
|
||||
let style = Style::new().grey();
|
||||
let style_inv = Style::new().black().on_grey();
|
||||
(Cow::Borrowed(name), style, style_inv, style_inv)
|
||||
(name, style, style_inv, style_inv)
|
||||
} else {
|
||||
let name = &session.name as &str;
|
||||
let (r, g, b) = euph::nick_color(name);
|
||||
let name = euph::EMOJI.replace(name).to_string();
|
||||
let color = Color::Rgb { r, g, b };
|
||||
let style = Style::new().bold().with(color);
|
||||
let style_inv = Style::new().bold().black().on(color);
|
||||
let perms_style_inv = Style::new().black().on(color);
|
||||
(euph::EMOJI.replace(name), style, style_inv, perms_style_inv)
|
||||
(name, style, style_inv, perms_style_inv)
|
||||
};
|
||||
|
||||
let perms = if session.is_staff {
|
||||
|
|
@ -158,20 +158,20 @@ fn render_row(
|
|||
" "
|
||||
};
|
||||
|
||||
let widget = if focused && list.state().selected() == Some(&session.session_id) {
|
||||
let text = Styled::new_plain(owner)
|
||||
.then(name, style_inv)
|
||||
.then(perms, perms_style_inv);
|
||||
Text::new(text)
|
||||
.background()
|
||||
.with_style(style_inv)
|
||||
.boxed_async()
|
||||
} else {
|
||||
let text = Styled::new_plain(owner)
|
||||
.then(&name, style)
|
||||
.then_plain(perms);
|
||||
Text::new(text).boxed_async()
|
||||
};
|
||||
|
||||
list.add_sel(session.session_id.clone(), widget);
|
||||
list_builder.add_sel(session.session_id.clone(), move |selected| {
|
||||
if focused && selected {
|
||||
let text = Styled::new_plain(owner)
|
||||
.then(name, style_inv)
|
||||
.then(perms, perms_style_inv);
|
||||
Text::new(text)
|
||||
.background()
|
||||
.with_style(style_inv)
|
||||
.boxed_async()
|
||||
} else {
|
||||
let text = Styled::new_plain(owner)
|
||||
.then(&name, style)
|
||||
.then_plain(perms);
|
||||
Text::new(text).boxed_async()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crossterm::style::Stylize;
|
|||
use toss::widgets::{BoxedAsync, Empty, Join2, Text};
|
||||
use toss::{Style, Styled, WidgetExt};
|
||||
|
||||
use super::widgets::ListState;
|
||||
use super::widgets::{ListBuilder, ListState};
|
||||
use super::UiError;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -136,12 +136,14 @@ impl KeyBindingsList {
|
|||
.with_horizontal(0.5)
|
||||
.with_vertical(0.0);
|
||||
|
||||
let mut list = list_state.widget();
|
||||
let mut list_builder = ListBuilder::new();
|
||||
for row in self.0 {
|
||||
list.add_unsel(Self::row_widget(row));
|
||||
list_builder.add_unsel(Self::row_widget(row));
|
||||
}
|
||||
|
||||
list.padding()
|
||||
list_builder
|
||||
.build(list_state)
|
||||
.padding()
|
||||
.with_horizontal(1)
|
||||
.border()
|
||||
.below(hint)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use crate::vault::Vault;
|
|||
|
||||
use super::euph::room::EuphRoom;
|
||||
use super::input::{key, InputEvent, KeyBindingsList};
|
||||
use super::widgets::{List, ListState, Popup};
|
||||
use super::widgets::{ListBuilder, ListState, Popup};
|
||||
use super::{util, UiError, UiEvent};
|
||||
|
||||
enum State {
|
||||
|
|
@ -348,12 +348,12 @@ impl Rooms {
|
|||
}
|
||||
|
||||
async fn render_rows(
|
||||
list: &mut List<'_, String, Text>,
|
||||
list_builder: &mut ListBuilder<'_, String, Text>,
|
||||
euph_rooms: &HashMap<String, EuphRoom>,
|
||||
order: Order,
|
||||
) {
|
||||
if euph_rooms.is_empty() {
|
||||
list.add_unsel(Text::new((
|
||||
list_builder.add_unsel(Text::new((
|
||||
"Press F1 for key bindings",
|
||||
Style::new().grey().italic(),
|
||||
)))
|
||||
|
|
@ -367,16 +367,19 @@ impl Rooms {
|
|||
}
|
||||
Self::sort_rooms(&mut rooms, order);
|
||||
for (name, state, unseen) in rooms {
|
||||
let style = if list.state().selected() == Some(name) {
|
||||
Style::new().bold().black().on_white()
|
||||
} else {
|
||||
Style::new().bold().blue()
|
||||
};
|
||||
let name = name.clone();
|
||||
let info = Self::format_room_info(state, unseen);
|
||||
list_builder.add_sel(name.clone(), move |selected| {
|
||||
let style = if selected {
|
||||
Style::new().bold().black().on_white()
|
||||
} else {
|
||||
Style::new().bold().blue()
|
||||
};
|
||||
|
||||
let text = Styled::new(format!("&{name}"), style)
|
||||
.and_then(Self::format_room_info(state, unseen));
|
||||
let text = Styled::new(format!("&{name}"), style).and_then(info);
|
||||
|
||||
list.add_sel(name.clone(), Text::new(text));
|
||||
Text::new(text)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,12 +392,12 @@ impl Rooms {
|
|||
let heading_text =
|
||||
Styled::new("Rooms", heading_style).then_plain(format!(" ({})", euph_rooms.len()));
|
||||
|
||||
let mut list = list.widget();
|
||||
Self::render_rows(&mut list, euph_rooms, order).await;
|
||||
let mut list_builder = ListBuilder::new();
|
||||
Self::render_rows(&mut list_builder, euph_rooms, order).await;
|
||||
|
||||
Join2::vertical(
|
||||
Text::new(heading_text).segment().with_fixed(true),
|
||||
list.segment(),
|
||||
list_builder.build(list).segment(),
|
||||
)
|
||||
.boxed_async()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,13 +211,6 @@ impl<Id: Clone> ListState<Id> {
|
|||
self.move_cursor_to(new_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn widget<W>(&mut self) -> List<'_, Id, W> {
|
||||
List {
|
||||
state: self,
|
||||
rows: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id: Clone + Eq> ListState<Id> {
|
||||
|
|
@ -248,39 +241,61 @@ impl<Id: Clone + Eq> ListState<Id> {
|
|||
}
|
||||
}
|
||||
|
||||
struct Row<Id, W> {
|
||||
struct UnrenderedRow<'a, Id, W> {
|
||||
id: Option<Id>,
|
||||
widget: W,
|
||||
widget: Box<dyn FnOnce(bool) -> W + 'a>,
|
||||
}
|
||||
|
||||
pub struct List<'a, Id, W> {
|
||||
state: &'a mut ListState<Id>,
|
||||
rows: Vec<Row<Id, W>>,
|
||||
pub struct ListBuilder<'a, Id, W> {
|
||||
rows: Vec<UnrenderedRow<'a, Id, W>>,
|
||||
}
|
||||
|
||||
impl<Id, W> List<'_, Id, W> {
|
||||
pub fn state(&self) -> &ListState<Id> {
|
||||
&self.state
|
||||
}
|
||||
|
||||
pub fn state_mut(&mut self) -> &mut ListState<Id> {
|
||||
&mut self.state
|
||||
impl<'a, Id, W> ListBuilder<'a, Id, W> {
|
||||
pub fn new() -> Self {
|
||||
Self { rows: vec![] }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.rows.is_empty()
|
||||
}
|
||||
|
||||
pub fn add_unsel(&mut self, widget: W) {
|
||||
self.rows.push(Row { id: None, widget });
|
||||
}
|
||||
|
||||
pub fn add_sel(&mut self, id: Id, widget: W) {
|
||||
self.rows.push(Row {
|
||||
id: Some(id),
|
||||
widget,
|
||||
pub fn add_unsel(&mut self, widget: W)
|
||||
where
|
||||
W: 'a,
|
||||
{
|
||||
self.rows.push(UnrenderedRow {
|
||||
id: None,
|
||||
widget: Box::new(|_| widget),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_sel(&mut self, id: Id, widget: impl FnOnce(bool) -> W + 'a) {
|
||||
self.rows.push(UnrenderedRow {
|
||||
id: Some(id),
|
||||
widget: Box::new(widget),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn build(self, state: &mut ListState<Id>) -> List<'_, Id, W>
|
||||
where
|
||||
Id: Clone + Eq,
|
||||
{
|
||||
state.last_rows = self.rows.iter().map(|row| row.id.clone()).collect();
|
||||
state.fix_cursor();
|
||||
|
||||
let selected = state.selected();
|
||||
let rows = self
|
||||
.rows
|
||||
.into_iter()
|
||||
.map(|row| (row.widget)(row.id.as_ref() == selected))
|
||||
.collect();
|
||||
List { state, rows }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct List<'a, Id, W> {
|
||||
state: &'a mut ListState<Id>,
|
||||
rows: Vec<W>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
|
@ -297,7 +312,7 @@ where
|
|||
) -> Result<Size, E> {
|
||||
let mut width = 0;
|
||||
for row in &self.rows {
|
||||
let size = row.widget.size(widthdb, max_width, Some(1)).await?;
|
||||
let size = row.size(widthdb, max_width, Some(1)).await?;
|
||||
width = width.max(size.width);
|
||||
}
|
||||
let height = self.rows.len().try_into().unwrap_or(u16::MAX);
|
||||
|
|
@ -307,9 +322,7 @@ where
|
|||
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||
let size = frame.size();
|
||||
|
||||
self.state.last_rows = self.rows.iter().map(|row| row.id.clone()).collect();
|
||||
self.state.last_height = size.height;
|
||||
self.state.fix_cursor();
|
||||
|
||||
for (y, row) in self
|
||||
.rows
|
||||
|
|
@ -319,7 +332,7 @@ where
|
|||
.enumerate()
|
||||
{
|
||||
frame.push(Pos::new(0, y as i32), Size::new(size.width, 1));
|
||||
row.widget.draw(frame).await?;
|
||||
row.draw(frame).await?;
|
||||
frame.pop();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue