Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Make render properties optional
Erik Kundt committed 2 years ago
commit db78340fb604d961ddca2d02095405088b2aa515
parent 4c7327c320c55e35778a9172cf45ad53233fd9f2
8 files changed +151 -122
modified bin/commands/inbox/select/ui.rs
@@ -17,7 +17,7 @@ use radicle_tui as tui;
use tui::ui::items::{NotificationItem, NotificationItemFilter, NotificationState};
use tui::ui::span;
use tui::ui::widget::container::{Footer, FooterProps, Header, HeaderProps};
-
use tui::ui::widget::input::{TextField, TextFieldProps};
+
use tui::ui::widget::input::{TextField, TextFieldProps, TextFieldState};
use tui::ui::widget::text::{Paragraph, ParagraphProps, ParagraphState};
use tui::ui::widget::{self, TableUtils};
use tui::ui::widget::{
@@ -135,7 +135,7 @@ impl<'a: 'static, B> Widget<B, State, Action> for ListPage<B>
where
    B: Backend + 'a,
{
-
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, _props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, _props: Option<&dyn Any>) {
        let area = frame.size();
        let layout = tui::ui::layout::default_page(area, 0u16, 1u16);

@@ -143,15 +143,15 @@ where
            let component_layout = Layout::vertical([Constraint::Min(1), Constraint::Length(2)])
                .split(layout.component);

-
            self.notifications.render(frame, component_layout[0], &());
-
            self.search.render(frame, component_layout[1], &());
+
            self.notifications.render(frame, component_layout[0], None);
+
            self.search.render(frame, component_layout[1], None);
        } else if self.props.show_help {
-
            self.help.render(frame, layout.component, &());
+
            self.help.render(frame, layout.component, None);
        } else {
-
            self.notifications.render(frame, layout.component, &());
+
            self.notifications.render(frame, layout.component, None);
        }

-
        self.shortcuts.render(frame, layout.shortcuts, &());
+
        self.shortcuts.render(frame, layout.shortcuts, None);
    }
}

@@ -430,25 +430,25 @@ impl<'a: 'static, B> Widget<B, State, Action> for Notifications<'a, B>
where
    B: Backend + 'a,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
        let props = props
-
            .downcast_ref::<NotificationsProps>()
+
            .and_then(|props| props.downcast_ref::<NotificationsProps>())
            .unwrap_or(&self.props);

        let header_height = 3_usize;

        let page_size = if props.show_search {
-
            self.table.render(frame, area, &());
+
            self.table.render(frame, area, None);

            (area.height as usize).saturating_sub(header_height)
        } else {
            let layout = Layout::vertical([Constraint::Min(1), Constraint::Length(3)]).split(area);

-
            self.table.render(frame, layout[0], &());
+
            self.table.render(frame, layout[0], None);
            self.footer.render(
                frame,
                layout[1],
-
                &FooterProps::default().columns(Self::build_footer(props, props.selected)),
+
                Some(&FooterProps::default().columns(Self::build_footer(props, props.selected))),
            );

            (area.height as usize).saturating_sub(header_height)
@@ -535,12 +535,12 @@ impl<B> Widget<B, State, Action> for Search<B>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, _props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, _props: Option<&dyn Any>) {
        let layout = Layout::horizontal(Constraint::from_mins([0]))
            .horizontal_margin(1)
            .split(area);

-
        self.input.render(frame, layout[0], &());
+
        self.input.render(frame, layout[0], None);
    }
}

@@ -782,8 +782,10 @@ impl<'a: 'static, B> Widget<B, State, Action> for Help<'a, B>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
-
        let props = props.downcast_ref::<HelpProps>().unwrap_or(&self.props);
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
        let props = props
+
            .and_then(|props| props.downcast_ref::<HelpProps>())
+
            .unwrap_or(&self.props);

        let [header_area, content_area, footer_area] = Layout::vertical([
            Constraint::Length(3),
@@ -796,32 +798,38 @@ where
        self.header.render(
            frame,
            header_area,
-
            &HeaderProps::default()
-
                .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
-
                .focus(props.focus),
+
            Some(
+
                &HeaderProps::default()
+
                    .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
+
                    .focus(props.focus),
+
            ),
        );

        self.content.render(
            frame,
            content_area,
-
            &ParagraphProps::default()
-
                .text(&props.content)
-
                .page_size(props.page_size)
-
                .focus(props.focus),
+
            Some(
+
                &ParagraphProps::default()
+
                    .text(&props.content)
+
                    .page_size(props.page_size)
+
                    .focus(props.focus),
+
            ),
        );

        self.footer.render(
            frame,
            footer_area,
-
            &FooterProps::default()
-
                .columns(
-
                    [
-
                        Column::new(Text::raw(""), Constraint::Fill(1)),
-
                        Column::new(progress, Constraint::Min(4)),
-
                    ]
-
                    .to_vec(),
-
                )
-
                .focus(props.focus),
+
            Some(
+
                &FooterProps::default()
+
                    .columns(
+
                        [
+
                            Column::new(Text::raw(""), Constraint::Fill(1)),
+
                            Column::new(progress, Constraint::Min(4)),
+
                        ]
+
                        .to_vec(),
+
                    )
+
                    .focus(props.focus),
+
            ),
        );

        let page_size = content_area.height as usize;
modified bin/commands/issue/select/ui.rs
@@ -20,7 +20,7 @@ use tui::ui::items::{IssueItem, IssueItemFilter};
use tui::ui::span;
use tui::ui::widget;
use tui::ui::widget::container::{Footer, FooterProps, Header, HeaderProps};
-
use tui::ui::widget::input::{TextField, TextFieldProps};
+
use tui::ui::widget::input::{TextField, TextFieldProps, TextFieldState};
use tui::ui::widget::text::{Paragraph, ParagraphProps, ParagraphState};
use tui::ui::widget::{
    Column, EventCallback, Properties, Shortcuts, ShortcutsProps, Table, TableProps, TableUtils,
@@ -141,7 +141,7 @@ impl<'a: 'static, B> Widget<B, State, Action> for ListPage<B>
where
    B: Backend + 'a,
{
-
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, _props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, _props: Option<&dyn Any>) {
        let area = frame.size();
        let layout = tui::ui::layout::default_page(area, 0u16, 1u16);

@@ -149,15 +149,15 @@ where
            let component_layout = Layout::vertical([Constraint::Min(1), Constraint::Length(2)])
                .split(layout.component);

-
            self.issues.render(frame, component_layout[0], &());
-
            self.search.render(frame, component_layout[1], &());
+
            self.issues.render(frame, component_layout[0], None);
+
            self.search.render(frame, component_layout[1], None);
        } else if self.props.show_help {
-
            self.help.render(frame, layout.component, &());
+
            self.help.render(frame, layout.component, None);
        } else {
-
            self.issues.render(frame, layout.component, &());
+
            self.issues.render(frame, layout.component, None);
        }

-
        self.shortcuts.render(frame, layout.shortcuts, &());
+
        self.shortcuts.render(frame, layout.shortcuts, None);
    }
}

@@ -457,23 +457,25 @@ impl<'a: 'static, B> Widget<B, State, Action> for Issues<'a, B>
where
    B: Backend + 'a,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
-
        let props = props.downcast_ref::<IssuesProps>().unwrap_or(&self.props);
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
        let props = props
+
            .and_then(|props| props.downcast_ref::<IssuesProps>())
+
            .unwrap_or(&self.props);

        let header_height = 3_usize;

        let page_size = if props.show_search {
-
            self.table.render(frame, area, &());
+
            self.table.render(frame, area, None);

            (area.height as usize).saturating_sub(header_height)
        } else {
            let layout = Layout::vertical([Constraint::Min(1), Constraint::Length(3)]).split(area);

-
            self.table.render(frame, layout[0], &());
+
            self.table.render(frame, layout[0], None);
            self.footer.render(
                frame,
                layout[1],
-
                &FooterProps::default().columns(Self::build_footer(props, props.selected)),
+
                Some(&FooterProps::default().columns(Self::build_footer(props, props.selected))),
            );

            (area.height as usize).saturating_sub(header_height)
@@ -560,12 +562,12 @@ impl<B> Widget<B, State, Action> for Search<B>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, _props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, _props: Option<&dyn Any>) {
        let layout = Layout::horizontal(Constraint::from_mins([0]))
            .horizontal_margin(1)
            .split(area);

-
        self.input.render(frame, layout[0], &());
+
        self.input.render(frame, layout[0], None);
    }
}

@@ -805,8 +807,10 @@ impl<'a: 'static, B> Widget<B, State, Action> for Help<'a, B>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
-
        let props = props.downcast_ref::<HelpProps>().unwrap_or(&self.props);
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
        let props = props
+
            .and_then(|props| props.downcast_ref::<HelpProps>())
+
            .unwrap_or(&self.props);

        let [header_area, content_area, footer_area] = Layout::vertical([
            Constraint::Length(3),
@@ -819,32 +823,38 @@ where
        self.header.render(
            frame,
            header_area,
-
            &HeaderProps::default()
-
                .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
-
                .focus(props.focus),
+
            Some(
+
                &HeaderProps::default()
+
                    .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
+
                    .focus(props.focus),
+
            ),
        );

        self.content.render(
            frame,
            content_area,
-
            &ParagraphProps::default()
-
                .text(&props.content)
-
                .page_size(props.page_size)
-
                .focus(props.focus),
+
            Some(
+
                &ParagraphProps::default()
+
                    .text(&props.content)
+
                    .page_size(props.page_size)
+
                    .focus(props.focus),
+
            ),
        );

        self.footer.render(
            frame,
            footer_area,
-
            &FooterProps::default()
-
                .columns(
-
                    [
-
                        Column::new(Text::raw(""), Constraint::Fill(1)),
-
                        Column::new(progress, Constraint::Min(4)),
-
                    ]
-
                    .to_vec(),
-
                )
-
                .focus(props.focus),
+
            Some(
+
                &FooterProps::default()
+
                    .columns(
+
                        [
+
                            Column::new(Text::raw(""), Constraint::Fill(1)),
+
                            Column::new(progress, Constraint::Min(4)),
+
                        ]
+
                        .to_vec(),
+
                    )
+
                    .focus(props.focus),
+
            ),
        );

        let page_size = content_area.height as usize;
modified bin/commands/patch/select/ui.rs
@@ -20,7 +20,7 @@ use radicle_tui as tui;
use tui::ui::items::{PatchItem, PatchItemFilter};
use tui::ui::span;
use tui::ui::widget::container::{Footer, FooterProps, Header, HeaderProps};
-
use tui::ui::widget::input::{TextField, TextFieldProps};
+
use tui::ui::widget::input::{TextField, TextFieldProps, TextFieldState};
use tui::ui::widget::text::{Paragraph, ParagraphProps, ParagraphState};
use tui::ui::widget::{self, TableUtils};
use tui::ui::widget::{
@@ -139,7 +139,7 @@ impl<'a: 'static, B> Widget<B, State, Action> for ListPage<B>
where
    B: Backend + 'a,
{
-
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, __props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, __props: Option<&dyn Any>) {
        let area = frame.size();
        let layout = tui::ui::layout::default_page(area, 0u16, 1u16);

@@ -147,15 +147,15 @@ where
            let component_layout = Layout::vertical([Constraint::Min(1), Constraint::Length(2)])
                .split(layout.component);

-
            self.patches.render(frame, component_layout[0], &());
-
            self.search.render(frame, component_layout[1], &());
+
            self.patches.render(frame, component_layout[0], None);
+
            self.search.render(frame, component_layout[1], None);
        } else if self.props.show_help {
-
            self.help.render(frame, layout.component, &());
+
            self.help.render(frame, layout.component, None);
        } else {
-
            self.patches.render(frame, layout.component, &());
+
            self.patches.render(frame, layout.component, None);
        }

-
        self.shortcuts.render(frame, layout.shortcuts, &());
+
        self.shortcuts.render(frame, layout.shortcuts, None);
    }
}

@@ -486,23 +486,25 @@ impl<'a: 'static, B> Widget<B, State, Action> for Patches<'a, B>
where
    B: Backend + 'a,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
-
        let props = props.downcast_ref::<PatchesProps>().unwrap_or(&self.props);
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
        let props = props
+
            .and_then(|props| props.downcast_ref::<PatchesProps>())
+
            .unwrap_or(&self.props);

        let header_height = 3_usize;

        let page_size = if props.show_search {
-
            self.table.render(frame, area, &());
+
            self.table.render(frame, area, None);

            (area.height as usize).saturating_sub(header_height)
        } else {
            let layout = Layout::vertical([Constraint::Min(1), Constraint::Length(3)]).split(area);

-
            self.table.render(frame, layout[0], &());
+
            self.table.render(frame, layout[0], None);
            self.footer.render(
                frame,
                layout[1],
-
                &FooterProps::default().columns(Self::build_footer(props, props.selected)),
+
                Some(&FooterProps::default().columns(Self::build_footer(props, props.selected))),
            );

            (area.height as usize).saturating_sub(header_height)
@@ -589,12 +591,12 @@ impl<B> Widget<B, State, Action> for Search<B>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, _props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, _props: Option<&dyn Any>) {
        let layout = Layout::horizontal(Constraint::from_mins([0]))
            .horizontal_margin(1)
            .split(area);

-
        self.input.render(frame, layout[0], &());
+
        self.input.render(frame, layout[0], None);
    }
}

@@ -853,8 +855,10 @@ impl<'a: 'static, B> Widget<B, State, Action> for Help<'a, B>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
-
        let props = props.downcast_ref::<HelpProps<'_>>().unwrap_or(&self.props);
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
        let props = props
+
            .and_then(|props| props.downcast_ref::<HelpProps>())
+
            .unwrap_or(&self.props);

        let [header_area, content_area, footer_area] = Layout::vertical([
            Constraint::Length(3),
@@ -867,32 +871,38 @@ where
        self.header.render(
            frame,
            header_area,
-
            &HeaderProps::default()
-
                .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
-
                .focus(props.focus),
+
            Some(
+
                &HeaderProps::default()
+
                    .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
+
                    .focus(props.focus),
+
            ),
        );

        self.content.render(
            frame,
            content_area,
-
            &ParagraphProps::default()
-
                .text(&props.content)
-
                .page_size(props.page_size)
-
                .focus(props.focus),
+
            Some(
+
                &ParagraphProps::default()
+
                    .text(&props.content)
+
                    .page_size(props.page_size)
+
                    .focus(props.focus),
+
            ),
        );

        self.footer.render(
            frame,
            footer_area,
-
            &FooterProps::default()
-
                .columns(
-
                    [
-
                        Column::new(Text::raw(""), Constraint::Fill(1)),
-
                        Column::new(progress, Constraint::Min(4)),
-
                    ]
-
                    .to_vec(),
-
                )
-
                .focus(props.focus),
+
            Some(
+
                &FooterProps::default()
+
                    .columns(
+
                        [
+
                            Column::new(Text::raw(""), Constraint::Fill(1)),
+
                            Column::new(progress, Constraint::Min(4)),
+
                        ]
+
                        .to_vec(),
+
                    )
+
                    .focus(props.focus),
+
            ),
        );

        let page_size = content_area.height as usize;
modified src/ui.rs
@@ -79,7 +79,7 @@ impl<A> Frontend<A> {
                    break Ok(interrupted);
                }
            }
-
            terminal.draw(|frame| root.render(frame, frame.size(), &()))?;
+
            terminal.draw(|frame| root.render(frame, frame.size(), None))?;
        };

        terminal::restore(&mut terminal)?;
modified src/ui/widget.rs
@@ -41,6 +41,14 @@ pub trait View<S, A> {
    where
        Self: Sized;

+
    /// Returns a boxed `View`
+
    fn to_boxed(self) -> Box<Self>
+
    where
+
        Self: Sized,
+
    {
+
        Box::new(self)
+
    }
+

    /// Should handle key events and call `handle_key_event` on all children.
    ///
    /// After key events have been handled, the custom event handler `on_change` should
@@ -50,14 +58,6 @@ pub trait View<S, A> {
    /// Should update internal props by calling the custom update handler `on_update`
    /// and call `update` on all children.
    fn update(&mut self, state: &S);
-

-
    /// Returns a boxed `View`
-
    fn to_boxed(self) -> Box<Self>
-
    where
-
        Self: Sized,
-
    {
-
        Box::new(self)
-
    }
}

/// A `Widget` is a `View` that can be rendered using a specific backend.
@@ -69,9 +69,8 @@ where
{
    /// Renders a widget to the given frame in the given area.
    ///
-
    /// Will try to downcast the given props object to the internally used one.
-
    /// If successful, these props take precedence over the internal ones.
-
    fn render(&self, frame: &mut Frame, area: Rect, props: &dyn Any);
+
    /// Optional props take precedence over the internal ones.
+
    fn render(&self, frame: &mut Frame, area: Rect, props: Option<&dyn Any>);
}

/// Needs to be implemented for items that are supposed to be rendering in tables.
@@ -187,11 +186,11 @@ impl<B, S, A> Widget<B, S, A> for Shortcuts<S, A>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
        use ratatui::widgets::Table;

        let props = props
-
            .downcast_ref::<ShortcutsProps>()
+
            .and_then(|props| props.downcast_ref::<ShortcutsProps>())
            .unwrap_or(&self.props);

        let mut shortcuts = props.shortcuts.iter().peekable();
@@ -482,9 +481,9 @@ where
    B: Backend,
    R: ToRow + Clone + Debug + 'static,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
        let props = props
-
            .downcast_ref::<TableProps<'_, R>>()
+
            .and_then(|props| props.downcast_ref::<TableProps<'_, R>>())
            .unwrap_or(&self.props);

        let header_height = if self.header.is_some() { 3 } else { 0 };
@@ -544,7 +543,7 @@ where
                .highlight_style(style::highlight());

            if let Some(header) = &self.header {
-
                header.render(frame, header_area, &());
+
                header.render(frame, header_area, None);
            }

            frame.render_stateful_widget(rows, table_area, &mut self.state.clone());
@@ -555,7 +554,7 @@ where
                .borders(borders);

            if let Some(header) = &self.header {
-
                header.render(frame, header_area, &());
+
                header.render(frame, header_area, None);
            }
            frame.render_widget(block, table_area);

modified src/ui/widget/container.rs
@@ -119,9 +119,9 @@ impl<'a: 'static, B, S, A> Widget<B, S, A> for Header<'a, S, A>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
        let props = props
-
            .downcast_ref::<HeaderProps<'_>>()
+
            .and_then(|props| props.downcast_ref::<HeaderProps>())
            .unwrap_or(&self.props);

        let widths: Vec<Constraint> = props
@@ -307,8 +307,10 @@ impl<'a: 'static, B, S, A> Widget<B, S, A> for Footer<'a, S, A>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
-
        let props = props.downcast_ref::<FooterProps>().unwrap_or(&self.props);
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
        let props = props
+
            .and_then(|props| props.downcast_ref::<FooterProps>())
+
            .unwrap_or(&self.props);

        let widths = props
            .columns
modified src/ui/widget/input.rs
@@ -176,9 +176,9 @@ impl<B, S, A> Widget<B, S, A> for TextField<S, A>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
        let props = props
-
            .downcast_ref::<TextFieldProps>()
+
            .and_then(|props| props.downcast_ref::<TextFieldProps>())
            .unwrap_or(&self.props);

        let layout = Layout::vertical(Constraint::from_lengths([1, 1])).split(area);
modified src/ui/widget/text.rs
@@ -219,9 +219,9 @@ impl<'a: 'static, B, S, A> Widget<B, S, A> for Paragraph<'a, S, A>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: &dyn Any) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
        let props = props
-
            .downcast_ref::<ParagraphProps>()
+
            .and_then(|props| props.downcast_ref::<ParagraphProps>())
            .unwrap_or(&self.props);

        let block = Block::default()