Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
tui: Implement generic table component
Erik Kundt committed 3 years ago
commit 46e05e84694a01298adbb841ba558e2c6b157695
parent 2c74fb7e8f475b9eefa46d7722ccdc5cfcd8a555
4 files changed +178 -6
modified radicle-tui/src/ui/components/label.rs
@@ -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))
+
    }
+
}
modified radicle-tui/src/ui/components/list.rs
@@ -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,
+
        }
+
    }
+
}
modified radicle-tui/src/ui/theme.rs
@@ -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),
modified radicle-tui/src/ui/widget.rs
@@ -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)
    }