Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
tui: Support property lists
Erik Kundt committed 3 years ago
commit 35c15d7ebadd5a7c1a65d0d1a7f40ec480dfa8f7
parent a4a43cdbe6c2b0ae00c6b4b45ae295a49e94b7c7
4 files changed +139 -10
modified radicle-tui/src/ui.rs
@@ -6,9 +6,13 @@ pub mod widget;
use tuirealm::props::Attribute;
use tuirealm::{MockComponent, StateValue};

-
use components::{GlobalListener, Label, Shortcut, ShortcutBar};
+
use components::{GlobalListener, Label, Property, PropertyList, Shortcut, ShortcutBar};
use widget::Widget;

+
pub fn global_listener() -> Widget<GlobalListener> {
+
    Widget::new(GlobalListener::default())
+
}
+

pub fn label(content: &str) -> Widget<Label> {
    // TODO: Remove when size constraints are implemented
    let width = content.chars().count() as u16;
@@ -41,6 +45,27 @@ pub fn shortcut_bar(theme: &theme::Theme, shortcuts: Vec<Widget<Shortcut>>) -> W
    Widget::new(shortcut_bar).height(1)
}

-
pub fn global_listener() -> Widget<GlobalListener> {
-
    Widget::new(GlobalListener::default())
+
pub fn property(theme: &theme::Theme, name: &str, value: &str) -> Widget<Property> {
+
    let name = label(name).foreground(theme.colors.property_name_fg);
+
    let divider = label(&format!(" {} ", theme.icons.property_divider));
+
    let value = label(value).foreground(theme.colors.default_fg);
+

+
    // TODO: Remove when size constraints are implemented
+
    let name_w = name.query(Attribute::Width).unwrap().unwrap_size();
+
    let divider_w = divider.query(Attribute::Width).unwrap().unwrap_size();
+
    let value_w = value.query(Attribute::Width).unwrap().unwrap_size();
+
    let width = name_w.saturating_add(divider_w).saturating_add(value_w);
+

+
    let property = Property::new(name, divider, value);
+

+
    Widget::new(property).height(1).width(width)
+
}
+

+
pub fn property_list(
+
    _theme: &theme::Theme,
+
    properties: Vec<Widget<Property>>,
+
) -> Widget<PropertyList> {
+
    let property_list = PropertyList::new(properties);
+

+
    Widget::new(property_list)
}
modified radicle-tui/src/ui/components.rs
@@ -1,3 +1,5 @@
+
use tui_realm_stdlib::Phantom;
+

use tuirealm::command::{Cmd, CmdResult};
use tuirealm::props::{AttrValue, Attribute, Color, Props};
use tuirealm::tui::layout::Rect;
@@ -24,6 +26,14 @@ impl WidgetComponent for GlobalListener {
    }
}

+
/// Some user events need to be handled globally (e.g. user presses key `q` to quit
+
/// the application). This component can be used in conjunction with SubEventClause
+
/// to handle those events.
+
#[derive(Default, MockComponent)]
+
pub struct GlobalPhantom {
+
    component: Phantom,
+
}
+

/// A label that can be styled using a foreground color and text modifiers.
/// Its height is fixed, its width depends on the length of the text it displays.
#[derive(Clone)]
@@ -167,3 +177,90 @@ impl WidgetComponent for ShortcutBar {
        CmdResult::None
    }
}
+

+
/// A component that displays a labeled property.
+
#[derive(Clone)]
+
pub struct Property {
+
    label: Widget<Label>,
+
    divider: Widget<Label>,
+
    property: Widget<Label>,
+
}
+

+
impl Property {
+
    pub fn new(label: Widget<Label>, divider: Widget<Label>, property: Widget<Label>) -> Self {
+
        Self {
+
            label,
+
            divider,
+
            property,
+
        }
+
    }
+
}
+

+
impl WidgetComponent for Property {
+
    fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
+
        let display = properties
+
            .get_or(Attribute::Display, AttrValue::Flag(true))
+
            .unwrap_flag();
+

+
        if display {
+
            let labels: Vec<Box<dyn MockComponent>> = vec![
+
                self.label.clone().to_boxed(),
+
                self.divider.clone().to_boxed(),
+
                self.property.clone().to_boxed(),
+
            ];
+

+
            let layout = layout::h_stack(labels, area);
+
            for (mut label, area) in layout {
+
                label.view(frame, area);
+
            }
+
        }
+
    }
+

+
    fn state(&self) -> State {
+
        State::None
+
    }
+

+
    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
+
        CmdResult::None
+
    }
+
}
+

+
/// A component that can display lists of labeled properties
+
pub struct PropertyList {
+
    properties: Vec<Widget<Property>>,
+
}
+

+
impl PropertyList {
+
    pub fn new(properties: Vec<Widget<Property>>) -> Self {
+
        Self { properties }
+
    }
+
}
+

+
impl WidgetComponent for PropertyList {
+
    fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
+
        let display = properties
+
            .get_or(Attribute::Display, AttrValue::Flag(true))
+
            .unwrap_flag();
+

+
        if display {
+
            let properties = self
+
                .properties
+
                .iter()
+
                .map(|property| property.clone().to_boxed() as Box<dyn MockComponent>)
+
                .collect();
+

+
            let layout = layout::v_stack(properties, area);
+
            for (mut property, area) in layout {
+
                property.view(frame, area);
+
            }
+
        }
+
    }
+

+
    fn state(&self) -> State {
+
        State::None
+
    }
+

+
    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
+
        CmdResult::None
+
    }
+
}
modified radicle-tui/src/ui/layout.rs
@@ -1,7 +1,5 @@
use tuirealm::props::{AttrValue, Attribute};
-
use tuirealm::tui::layout::Rect;
-

-
use tuirealm::tui::layout::{Constraint, Direction, Layout};
+
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
use tuirealm::MockComponent;

pub fn v_stack(
modified radicle-tui/src/ui/theme.rs
@@ -2,6 +2,9 @@ use tuirealm::props::Color;

#[derive(Debug)]
pub struct Colors {
+
    pub default_fg: Color,
+
    pub property_name_fg: Color,
+
    pub property_divider_fg: Color,
    pub shortcut_short_fg: Color,
    pub shortcut_long_fg: Color,
    pub shortcutbar_divider_fg: Color,
@@ -9,16 +12,18 @@ pub struct Colors {

#[derive(Debug)]
pub struct Icons {
-
    pub whitespace: char,
+
    pub property_divider: char,
    pub shortcutbar_divider: char,
+
    pub whitespace: char,
}

-
/// The Radicle TUI theme. Can be defined in a JSON config file. e.g.:
-
///
+
/// The Radicle TUI theme. Will be defined in a JSON config file in the
+
/// future. e.g.:
/// {
///     "name": "Radicle Dark",
///     "colors": {
///         "foreground": "#ffffff",
+
///         "propertyForeground": "#ffffff",
///         "highlightedBackground": "#000000",
///     },
///     "icons": {
@@ -37,13 +42,17 @@ pub fn default_dark() -> Theme {
    Theme {
        name: String::from("Radicle Dark"),
        colors: Colors {
+
            default_fg: Color::Rgb(200, 200, 200),
+
            property_name_fg: Color::Rgb(85, 85, 255),
+
            property_divider_fg: Color::Rgb(10, 206, 209),
            shortcut_short_fg: Color::Rgb(100, 100, 100),
            shortcut_long_fg: Color::Rgb(70, 70, 70),
            shortcutbar_divider_fg: Color::Rgb(70, 70, 70),
        },
        icons: Icons {
-
            whitespace: ' ',
+
            property_divider: '∙',
            shortcutbar_divider: '∙',
+
            whitespace: ' ',
        },
    }
}