Use widgets for List rows instead of Styleds
This commit is contained in:
parent
204eb95fa5
commit
2f60b0390e
3 changed files with 74 additions and 61 deletions
|
|
@ -14,7 +14,10 @@ use crate::euph::{self, Joined, Status};
|
||||||
use crate::vault::{EuphMsg, EuphVault};
|
use crate::vault::{EuphMsg, EuphVault};
|
||||||
|
|
||||||
use super::chat::Chat;
|
use super::chat::Chat;
|
||||||
|
use super::widgets::background::Background;
|
||||||
|
use super::widgets::empty::Empty;
|
||||||
use super::widgets::list::{List, ListState};
|
use super::widgets::list::{List, ListState};
|
||||||
|
use super::widgets::text::Text;
|
||||||
use super::widgets::Widget;
|
use super::widgets::Widget;
|
||||||
use super::{util, UiEvent};
|
use super::{util, UiEvent};
|
||||||
|
|
||||||
|
|
@ -188,7 +191,11 @@ impl EuphRoom {
|
||||||
|
|
||||||
let normal = Styled::new(owner).then(perms).then((name, style));
|
let normal = Styled::new(owner).then(perms).then((name, style));
|
||||||
let selected = Styled::new(owner).then(perms).then((name, style_inv));
|
let selected = Styled::new(owner).then(perms).then((name, style_inv));
|
||||||
list.add_sel(id, normal, style, selected, style_inv);
|
list.add_sel(
|
||||||
|
id,
|
||||||
|
Text::new(normal),
|
||||||
|
Background::new(Text::new(selected), style_inv),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_section(
|
fn render_section(
|
||||||
|
|
@ -204,11 +211,11 @@ impl EuphRoom {
|
||||||
let heading_style = ContentStyle::new().bold();
|
let heading_style = ContentStyle::new().bold();
|
||||||
|
|
||||||
if !list.is_empty() {
|
if !list.is_empty() {
|
||||||
list.add_unsel("");
|
list.add_unsel(Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = Styled::new((name, heading_style)).then(format!(" ({})", sessions.len()));
|
let row = Styled::new((name, heading_style)).then(format!(" ({})", sessions.len()));
|
||||||
list.add_unsel(row);
|
list.add_unsel(Text::new(row));
|
||||||
|
|
||||||
for session in sessions {
|
for session in sessions {
|
||||||
Self::render_row(list, session, own_session);
|
Self::render_row(list, session, own_session);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ use crate::euph::{Joined, Status};
|
||||||
use crate::vault::Vault;
|
use crate::vault::Vault;
|
||||||
|
|
||||||
use super::room::EuphRoom;
|
use super::room::EuphRoom;
|
||||||
|
use super::widgets::background::Background;
|
||||||
use super::widgets::list::{List, ListState};
|
use super::widgets::list::{List, ListState};
|
||||||
|
use super::widgets::text::Text;
|
||||||
use super::widgets::Widget;
|
use super::widgets::Widget;
|
||||||
use super::{util, UiEvent};
|
use super::{util, UiEvent};
|
||||||
|
|
||||||
|
|
@ -120,7 +122,7 @@ impl Rooms {
|
||||||
async fn render_rows(&self, list: &mut List<String>, rooms: Vec<String>) {
|
async fn render_rows(&self, list: &mut List<String>, rooms: Vec<String>) {
|
||||||
let heading_style = ContentStyle::default().bold();
|
let heading_style = ContentStyle::default().bold();
|
||||||
let heading = Styled::new(("Rooms", heading_style)).then(format!(" ({})", rooms.len()));
|
let heading = Styled::new(("Rooms", heading_style)).then(format!(" ({})", rooms.len()));
|
||||||
list.add_unsel(heading);
|
list.add_unsel(Text::new(heading));
|
||||||
|
|
||||||
for room in rooms {
|
for room in rooms {
|
||||||
let bg_style = ContentStyle::default();
|
let bg_style = ContentStyle::default();
|
||||||
|
|
@ -138,7 +140,11 @@ impl Rooms {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
list.add_sel(room, normal, bg_style, selected, bg_sel_style);
|
list.add_sel(
|
||||||
|
room,
|
||||||
|
Text::new(normal),
|
||||||
|
Background::new(Text::new(selected), bg_sel_style),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use crossterm::style::ContentStyle;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use toss::frame::{Frame, Pos, Size};
|
use toss::frame::{Frame, Pos, Size};
|
||||||
use toss::styled::Styled;
|
|
||||||
|
|
||||||
use super::Widget;
|
use super::Widget;
|
||||||
|
|
||||||
|
|
@ -12,7 +10,7 @@ use super::Widget;
|
||||||
// State //
|
// State //
|
||||||
///////////
|
///////////
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
struct Cursor<Id> {
|
struct Cursor<Id> {
|
||||||
/// Id of the element the cursor is pointing to.
|
/// Id of the element the cursor is pointing to.
|
||||||
///
|
///
|
||||||
|
|
@ -123,16 +121,6 @@ impl<Id: Clone> InnerListState<Id> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Id: Eq> InnerListState<Id> {
|
|
||||||
fn focusing(&self, id: &Id) -> bool {
|
|
||||||
if let Some(cursor) = &self.cursor {
|
|
||||||
cursor.id == *id
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Id: Clone + Eq> InnerListState<Id> {
|
impl<Id: Clone + Eq> InnerListState<Id> {
|
||||||
fn selectable_of_id(&self, id: &Id) -> Option<Cursor<Id>> {
|
fn selectable_of_id(&self, id: &Id) -> Option<Cursor<Id>> {
|
||||||
self.rows.iter().enumerate().find_map(|(i, r)| match r {
|
self.rows.iter().enumerate().find_map(|(i, r)| match r {
|
||||||
|
|
@ -217,31 +205,38 @@ impl<Id: Clone> ListState<Id> {
|
||||||
// Widget //
|
// Widget //
|
||||||
////////////
|
////////////
|
||||||
|
|
||||||
// TODO Use widgets for rows
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Row<Id> {
|
enum Row<Id> {
|
||||||
Unselectable(Styled),
|
Unselectable {
|
||||||
|
normal: Box<dyn Widget + Send>,
|
||||||
|
},
|
||||||
Selectable {
|
Selectable {
|
||||||
id: Id,
|
id: Id,
|
||||||
normal: Styled,
|
normal: Box<dyn Widget + Send>,
|
||||||
normal_bg: ContentStyle,
|
selected: Box<dyn Widget + Send>,
|
||||||
selected: Styled,
|
|
||||||
selected_bg: ContentStyle,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Id> Row<Id> {
|
impl<Id> Row<Id> {
|
||||||
fn id(&self) -> Option<&Id> {
|
fn id(&self) -> Option<&Id> {
|
||||||
match self {
|
match self {
|
||||||
Row::Unselectable(_) => None,
|
Row::Unselectable { .. } => None,
|
||||||
Row::Selectable { id, .. } => Some(id),
|
Row::Selectable { id, .. } => Some(id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled(&self) -> &Styled {
|
fn size(&self, frame: &mut Frame, max_width: Option<u16>, max_height: Option<u16>) -> Size {
|
||||||
match self {
|
match self {
|
||||||
Row::Unselectable(styled) => styled,
|
Row::Unselectable { normal } => normal.size(frame, max_width, max_height),
|
||||||
Row::Selectable { normal, .. } => normal,
|
Row::Selectable {
|
||||||
|
normal, selected, ..
|
||||||
|
} => {
|
||||||
|
let normal_size = normal.size(frame, max_width, max_height);
|
||||||
|
let selected_size = selected.size(frame, max_width, max_height);
|
||||||
|
Size::new(
|
||||||
|
normal_size.width.max(selected_size.width),
|
||||||
|
normal_size.height.max(selected_size.height),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -270,70 +265,75 @@ impl<Id> List<Id> {
|
||||||
self.rows.is_empty()
|
self.rows.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_unsel<S: Into<Styled>>(&mut self, styled: S) {
|
pub fn add_unsel<W: 'static + Widget + Send>(&mut self, normal: W) {
|
||||||
self.rows.push(Row::Unselectable(styled.into()));
|
self.rows.push(Row::Unselectable {
|
||||||
|
normal: Box::new(normal),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_sel<S1, S2>(
|
pub fn add_sel<W1, W2>(&mut self, id: Id, normal: W1, selected: W2)
|
||||||
&mut self,
|
where
|
||||||
id: Id,
|
W1: 'static + Widget + Send,
|
||||||
normal: S1,
|
W2: 'static + Widget + Send,
|
||||||
normal_bg: ContentStyle,
|
|
||||||
selected: S2,
|
|
||||||
selected_bg: ContentStyle,
|
|
||||||
) where
|
|
||||||
S1: Into<Styled>,
|
|
||||||
S2: Into<Styled>,
|
|
||||||
{
|
{
|
||||||
self.rows.push(Row::Selectable {
|
self.rows.push(Row::Selectable {
|
||||||
id,
|
id,
|
||||||
normal: normal.into(),
|
normal: Box::new(normal),
|
||||||
normal_bg,
|
selected: Box::new(selected),
|
||||||
selected: selected.into(),
|
|
||||||
selected_bg,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<Id: Clone + Eq + Send> Widget for List<Id> {
|
impl<Id: Clone + Eq + Send> Widget for List<Id> {
|
||||||
fn size(&self, frame: &mut Frame, _max_width: Option<u16>, _max_height: Option<u16>) -> Size {
|
fn size(&self, frame: &mut Frame, max_width: Option<u16>, _max_height: Option<u16>) -> Size {
|
||||||
let width = self
|
let width = self
|
||||||
.rows
|
.rows
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| frame.width_styled(r.styled()))
|
.map(|r| r.size(frame, max_width, Some(1)).width)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let height = self.rows.len();
|
let height = self.rows.len();
|
||||||
Size::new(width as u16, height as u16)
|
Size::new(width, height as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(self: Box<Self>, frame: &mut Frame, pos: Pos, size: Size) {
|
async fn render(self: Box<Self>, frame: &mut Frame, pos: Pos, size: Size) {
|
||||||
|
// Guard acquisition and dropping must be inside its own block or the
|
||||||
|
// compiler complains that "future created by async block is not
|
||||||
|
// `Send`", pointing to the function body.
|
||||||
|
//
|
||||||
|
// I assume this is because I'm using the parking lot mutex whose guard
|
||||||
|
// is not Send, and even though I was explicitly dropping it with
|
||||||
|
// drop(), rustc couldn't figure this out without some help.
|
||||||
|
let (offset, cursor) = {
|
||||||
let mut guard = self.state.lock();
|
let mut guard = self.state.lock();
|
||||||
guard.stabilize(&self.rows, size.height.into());
|
guard.stabilize(&self.rows, size.height.into());
|
||||||
|
(guard.offset as i32, guard.cursor.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
let row_size = Size::new(size.width, 1);
|
||||||
for (i, row) in self.rows.into_iter().enumerate() {
|
for (i, row) in self.rows.into_iter().enumerate() {
|
||||||
let dy = i as i32 - guard.offset as i32;
|
let dy = i as i32 - offset;
|
||||||
if dy < 0 || dy >= size.height as i32 {
|
if dy < 0 || dy >= size.height as i32 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pos = pos + Pos::new(0, dy);
|
let pos = pos + Pos::new(0, dy);
|
||||||
match row {
|
match row {
|
||||||
Row::Unselectable(styled) => frame.write(pos, styled),
|
Row::Unselectable { normal } => normal.render(frame, pos, row_size).await,
|
||||||
Row::Selectable {
|
Row::Selectable {
|
||||||
id,
|
id,
|
||||||
normal,
|
normal,
|
||||||
normal_bg,
|
|
||||||
selected,
|
selected,
|
||||||
selected_bg,
|
|
||||||
} => {
|
} => {
|
||||||
let (fg, bg) = if self.focus && guard.focusing(&id) {
|
let focusing = self.focus
|
||||||
(selected, selected_bg)
|
&& if let Some(cursor) = &cursor {
|
||||||
|
cursor.id == id
|
||||||
} else {
|
} else {
|
||||||
(normal, normal_bg)
|
false
|
||||||
};
|
};
|
||||||
frame.write(pos, (" ".repeat(size.width.into()), bg));
|
let widget = if focusing { selected } else { normal };
|
||||||
frame.write(pos, fg);
|
widget.render(frame, pos, row_size).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue