tui: Implement generic table component
Signed-off-by: Erik Kundt <erik@zirkular.io>
This commit is contained in:
parent
ceb1f8e700
commit
85fad7aee0
|
|
@ -1,7 +1,7 @@
|
|||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::props::{AttrValue, Attribute, Color, Props, Style};
|
||||
use tuirealm::tui::layout::Rect;
|
||||
use tuirealm::tui::text::Span;
|
||||
use tuirealm::tui::text::{Span, Text};
|
||||
use tuirealm::{Frame, MockComponent, State};
|
||||
|
||||
use crate::ui::widget::{Widget, WidgetComponent};
|
||||
|
|
@ -64,3 +64,18 @@ impl From<&Widget<Label>> for Span<'_> {
|
|||
Span::styled(content, Style::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Widget<Label>> for Text<'_> {
|
||||
fn from(label: &Widget<Label>) -> Self {
|
||||
let content = label
|
||||
.query(Attribute::Content)
|
||||
.unwrap_or(AttrValue::String(String::default()))
|
||||
.unwrap_string();
|
||||
let foreground = label
|
||||
.query(Attribute::Foreground)
|
||||
.unwrap_or(AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
|
||||
Text::styled(content, Style::default().fg(foreground))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::props::{AttrValue, Attribute, Props};
|
||||
use tuirealm::tui::layout::Rect;
|
||||
use tuirealm::{Frame, MockComponent, State};
|
||||
use radicle::Profile;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::props::{
|
||||
AttrValue, Attribute, Color, PropPayload, PropValue, Props, Style, TextModifiers, TextSpan,
|
||||
};
|
||||
use tuirealm::tui::layout::{Constraint, Rect};
|
||||
use tuirealm::tui::widgets::{Cell, Row, TableState};
|
||||
use tuirealm::{Frame, MockComponent, State, StateValue};
|
||||
|
||||
use crate::ui::components::label::Label;
|
||||
use crate::ui::layout;
|
||||
use crate::ui::theme::Theme;
|
||||
use crate::ui::widget::{Widget, WidgetComponent};
|
||||
|
||||
pub trait List {
|
||||
fn row(&self, theme: &Theme, profile: &Profile) -> Vec<TextSpan>;
|
||||
}
|
||||
|
||||
/// A component that displays a labeled property.
|
||||
#[derive(Clone)]
|
||||
pub struct Property {
|
||||
|
|
@ -56,7 +65,6 @@ impl WidgetComponent for Property {
|
|||
|
||||
/// A component that can display lists of labeled properties
|
||||
#[derive(Default)]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub struct PropertyList {
|
||||
properties: Vec<Widget<Property>>,
|
||||
}
|
||||
|
|
@ -95,3 +103,145 @@ impl WidgetComponent for PropertyList {
|
|||
CmdResult::None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Table {
|
||||
state: TableState,
|
||||
}
|
||||
|
||||
impl Default for Table {
|
||||
fn default() -> Self {
|
||||
let mut state = TableState::default();
|
||||
state.select(Some(0));
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
impl Table {
|
||||
fn select_previous(&mut self) {
|
||||
let index = match self.state.selected() {
|
||||
Some(selected) if selected == 0 => 0,
|
||||
Some(selected) => selected.saturating_sub(1),
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(index));
|
||||
}
|
||||
|
||||
fn select_next(&mut self, len: usize) {
|
||||
let index = match self.state.selected() {
|
||||
Some(selected) if selected >= len.saturating_sub(1) => len.saturating_sub(1),
|
||||
Some(selected) => selected.saturating_add(1),
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(index));
|
||||
}
|
||||
|
||||
fn header<'a>(spans: Vec<PropValue>) -> Row<'a> {
|
||||
Row::new(
|
||||
spans
|
||||
.iter()
|
||||
.map(|span| {
|
||||
Cell::from(span.clone().unwrap_text_span().content)
|
||||
.style(Style::default().add_modifier(TextModifiers::BOLD))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn rows<'a>(spans: Vec<Vec<TextSpan>>) -> Vec<Row<'a>> {
|
||||
spans
|
||||
.iter()
|
||||
.map(|spans| {
|
||||
let cells = spans.iter().map(|span| {
|
||||
let style = Style::default().fg(span.fg);
|
||||
Cell::from(span.content.clone()).style(style)
|
||||
});
|
||||
Row::new(cells).height(1)
|
||||
})
|
||||
.collect::<Vec<Row>>()
|
||||
}
|
||||
|
||||
fn widths(widths: Vec<PropValue>) -> Vec<Constraint> {
|
||||
widths
|
||||
.iter()
|
||||
.map(|prop| Constraint::Percentage(prop.clone().unwrap_u16()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetComponent for Table {
|
||||
fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
|
||||
let content = properties
|
||||
.get_or(Attribute::Content, AttrValue::Table(vec![]))
|
||||
.unwrap_table();
|
||||
let background = properties
|
||||
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
let highlight = properties
|
||||
.get_or(Attribute::HighlightedColor, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
let header = properties
|
||||
.get_or(
|
||||
Attribute::Custom("header"),
|
||||
AttrValue::Payload(PropPayload::Vec(vec![])),
|
||||
)
|
||||
.unwrap_payload()
|
||||
.unwrap_vec();
|
||||
let widths = properties
|
||||
.get_or(
|
||||
Attribute::Custom("widths"),
|
||||
AttrValue::Payload(PropPayload::Vec(vec![])),
|
||||
)
|
||||
.unwrap_payload()
|
||||
.unwrap_vec();
|
||||
|
||||
let header = Self::header(header);
|
||||
let rows = Self::rows(content);
|
||||
let widths = Self::widths(widths);
|
||||
|
||||
let table = tuirealm::tui::widgets::Table::new(rows)
|
||||
.highlight_style(Style::default().bg(highlight))
|
||||
.style(Style::default().bg(background))
|
||||
.column_spacing(3u16)
|
||||
.header(header)
|
||||
.widths(&widths);
|
||||
|
||||
frame.render_stateful_widget(table, area, &mut self.state);
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::None
|
||||
}
|
||||
|
||||
fn perform(&mut self, properties: &Props, cmd: Cmd) -> CmdResult {
|
||||
let content = properties
|
||||
.get_or(Attribute::Content, AttrValue::Table(vec![]))
|
||||
.unwrap_table();
|
||||
|
||||
match cmd {
|
||||
Cmd::Move(Direction::Up) => {
|
||||
self.select_previous();
|
||||
if let Some(selected) = self.state.selected() {
|
||||
CmdResult::Changed(State::One(StateValue::Usize(selected)))
|
||||
} else {
|
||||
CmdResult::None
|
||||
}
|
||||
}
|
||||
Cmd::Move(Direction::Down) => {
|
||||
self.select_next(content.len());
|
||||
if let Some(selected) = self.state.selected() {
|
||||
CmdResult::Changed(State::One(StateValue::Usize(selected)))
|
||||
} else {
|
||||
CmdResult::None
|
||||
}
|
||||
}
|
||||
Cmd::Submit => {
|
||||
if let Some(selected) = self.state.selected() {
|
||||
CmdResult::Submit(State::One(StateValue::Usize(selected)))
|
||||
} else {
|
||||
CmdResult::None
|
||||
}
|
||||
}
|
||||
_ => CmdResult::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub struct Colors {
|
|||
pub tabs_highlighted_fg: Color,
|
||||
pub workspaces_info_fg: Color,
|
||||
pub labeled_container_bg: Color,
|
||||
pub item_list_highlighted_bg: Color,
|
||||
pub property_name_fg: Color,
|
||||
pub property_divider_fg: Color,
|
||||
pub shortcut_short_fg: Color,
|
||||
|
|
@ -52,6 +53,7 @@ pub fn default_dark() -> Theme {
|
|||
tabs_highlighted_fg: Color::Rgb(38, 162, 105),
|
||||
workspaces_info_fg: Color::Rgb(220, 140, 40),
|
||||
labeled_container_bg: Color::Rgb(20, 20, 20),
|
||||
item_list_highlighted_bg: Color::Rgb(40, 40, 40),
|
||||
property_name_fg: Color::Rgb(85, 85, 255),
|
||||
property_divider_fg: Color::Rgb(10, 206, 209),
|
||||
shortcut_short_fg: Color::Rgb(100, 100, 100),
|
||||
|
|
|
|||
|
|
@ -67,6 +67,11 @@ impl<T: WidgetComponent> Widget<T> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn custom(mut self, key: &'static str, value: AttrValue) -> Self {
|
||||
self.attr(Attribute::Custom(key), value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_boxed(self) -> Box<Self> {
|
||||
Box::new(self)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue