Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
tui: Show project payload
Erik Kundt committed 3 years ago
commit 1e0d561af97930a1634a8ba4f0693c8754155f88
parent 35c15d7ebadd5a7c1a65d0d1a7f40ec480dfa8f7
4 files changed +161 -4
modified radicle-tui/src/app.rs
@@ -9,7 +9,7 @@ use tuirealm::tui::layout::{Constraint, Direction, Layout};
use tuirealm::{Application, Component, Frame, NoUserEvent, Sub, SubClause, SubEventClause};

use radicle_tui::ui;
-
use radicle_tui::ui::components::{GlobalListener, ShortcutBar};
+
use radicle_tui::ui::components::{GlobalListener, LabeledContainer, PropertyList, ShortcutBar};
use radicle_tui::ui::theme;
use radicle_tui::ui::widget::Widget;

@@ -33,6 +33,7 @@ pub enum Message {
/// All components known to this application.
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum ComponentId {
+
    Workspaces,
    ShortcutBar,
    GlobalListener,
}
@@ -54,6 +55,25 @@ impl Tui<ComponentId, Message> for App {
        let theme = theme::default_dark();

        app.mount(
+
            ComponentId::Workspaces,
+
            ui::labeled_container(
+
                &theme,
+
                "about",
+
                ui::property_list(
+
                    &theme,
+
                    vec![
+
                        ui::property(&theme, "id", &self.id.to_string()),
+
                        ui::property(&theme, "name", self.project.name()),
+
                        ui::property(&theme, "description", self.project.description()),
+
                    ],
+
                )
+
                .to_boxed(),
+
            )
+
            .to_boxed(),
+
            vec![],
+
        )?;
+

+
        app.mount(
            ComponentId::ShortcutBar,
            ui::shortcut_bar(
                &theme,
@@ -114,6 +134,7 @@ impl Tui<ComponentId, Message> for App {
            )
            .split(area);

+
        app.view(&ComponentId::Workspaces, frame, layout[0]);
        app.view(&ComponentId::ShortcutBar, frame, layout[1]);
    }

@@ -147,6 +168,18 @@ impl Component<Message, NoUserEvent> for Widget<GlobalListener> {
    }
}

+
impl Component<Message, NoUserEvent> for Widget<LabeledContainer> {
+
    fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
+
        None
+
    }
+
}
+

+
impl Component<Message, NoUserEvent> for Widget<PropertyList> {
+
    fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
+
        None
+
    }
+
}
+

impl Component<Message, NoUserEvent> for Widget<ShortcutBar> {
    fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
        None
modified radicle-tui/src/ui.rs
@@ -6,7 +6,10 @@ pub mod widget;
use tuirealm::props::Attribute;
use tuirealm::{MockComponent, StateValue};

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

pub fn global_listener() -> Widget<GlobalListener> {
@@ -21,6 +24,21 @@ pub fn label(content: &str) -> Widget<Label> {
    Widget::new(label).height(1).width(width)
}

+
pub fn labeled_container(
+
    theme: &theme::Theme,
+
    title: &str,
+
    component: Box<dyn MockComponent>,
+
) -> Widget<LabeledContainer> {
+
    let title = label(&format!(" {} ", title))
+
        .foreground(theme.colors.default_fg)
+
        .background(theme.colors.labeled_container_bg);
+
    let spacer = label("");
+
    let header = Widget::new(ContainerHeader::new(title, spacer));
+
    let container = LabeledContainer::new(header, component);
+

+
    Widget::new(container).background(theme.colors.labeled_container_bg)
+
}
+

pub fn shortcut(theme: &theme::Theme, short: &str, long: &str) -> Widget<Shortcut> {
    let short = label(short).foreground(theme.colors.shortcut_short_fg);
    let divider = label(&theme.icons.whitespace.to_string());
modified radicle-tui/src/ui/components.rs
@@ -1,8 +1,9 @@
use tui_realm_stdlib::Phantom;

use tuirealm::command::{Cmd, CmdResult};
-
use tuirealm::props::{AttrValue, Attribute, Color, Props};
-
use tuirealm::tui::layout::Rect;
+
use tuirealm::props::{AttrValue, Attribute, Color, Props, Style};
+
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
+
use tuirealm::tui::widgets::Block;
use tuirealm::{Frame, MockComponent, State, StateValue};

use super::layout;
@@ -57,15 +58,20 @@ impl WidgetComponent for Label {
        let foreground = properties
            .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
            .unwrap_color();
+
        let background = properties
+
            .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
+
            .unwrap_color();

        if display {
            let mut label = match properties.get(Attribute::TextProps) {
                Some(modifiers) => Label::default()
                    .foreground(foreground)
+
                    .background(background)
                    .modifiers(modifiers.unwrap_text_modifiers())
                    .text(self.content.clone().unwrap_string()),
                None => Label::default()
                    .foreground(foreground)
+
                    .background(background)
                    .text(self.content.clone().unwrap_string()),
            };

@@ -82,6 +88,102 @@ impl WidgetComponent for Label {
    }
}

+
/// A labeled container header.
+
#[derive(Clone)]
+
pub struct ContainerHeader {
+
    content: Widget<Label>,
+
    spacer: Widget<Label>,
+
}
+

+
impl ContainerHeader {
+
    pub fn new(content: Widget<Label>, spacer: Widget<Label>) -> Self {
+
        Self { content, spacer }
+
    }
+
}
+

+
impl WidgetComponent for ContainerHeader {
+
    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.content.clone().to_boxed(),
+
                self.spacer.clone().to_boxed(),
+
            ];
+

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

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

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

+
pub struct LabeledContainer {
+
    header: Widget<ContainerHeader>,
+
    component: Box<dyn MockComponent>,
+
}
+

+
impl LabeledContainer {
+
    pub fn new(header: Widget<ContainerHeader>, component: Box<dyn MockComponent>) -> Self {
+
        Self { header, component }
+
    }
+
}
+

+
impl WidgetComponent for LabeledContainer {
+
    fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
+
        let display = properties
+
            .get_or(Attribute::Display, AttrValue::Flag(true))
+
            .unwrap_flag();
+
        let background = properties
+
            .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
+
            .unwrap_color();
+
        let header_height = self
+
            .header
+
            .query(Attribute::Height)
+
            .unwrap_or(AttrValue::Size(1))
+
            .unwrap_size();
+

+
        if display {
+
            let layout = Layout::default()
+
                .direction(Direction::Vertical)
+
                .constraints([Constraint::Length(header_height), Constraint::Length(0)].as_ref())
+
                .split(area);
+

+
            self.header.view(frame, layout[0]);
+

+
            // Make some space on the left
+
            let inner_layout = Layout::default()
+
                .direction(Direction::Horizontal)
+
                .constraints(vec![Constraint::Length(1), Constraint::Min(0)].as_ref())
+
                .split(layout[1]);
+
            // reverse draw order: child needs to be drawn first?
+
            self.component.view(frame, inner_layout[1]);
+

+
            let block = Block::default().style(Style::default().bg(background));
+
            frame.render_widget(block, layout[1]);
+
        }
+
    }
+

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

+
    fn perform(&mut self, cmd: Cmd) -> CmdResult {
+
        self.component.perform(cmd)
+
    }
+
}
+

/// A shortcut that consists of a label displaying the "hotkey", a label that displays
/// the action and a spacer between them.
#[derive(Clone)]
@@ -226,6 +328,8 @@ 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>>,
}
modified radicle-tui/src/ui/theme.rs
@@ -3,6 +3,7 @@ use tuirealm::props::Color;
#[derive(Debug)]
pub struct Colors {
    pub default_fg: Color,
+
    pub labeled_container_bg: Color,
    pub property_name_fg: Color,
    pub property_divider_fg: Color,
    pub shortcut_short_fg: Color,
@@ -43,6 +44,7 @@ pub fn default_dark() -> Theme {
        name: String::from("Radicle Dark"),
        colors: Colors {
            default_fg: Color::Rgb(200, 200, 200),
+
            labeled_container_bg: Color::Rgb(20, 20, 20),
            property_name_fg: Color::Rgb(85, 85, 255),
            property_divider_fg: Color::Rgb(10, 206, 209),
            shortcut_short_fg: Color::Rgb(100, 100, 100),