Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Make widgets mutable in render function
Erik Kundt committed 1 year ago
commit b01826bd5776dd210318b8d98e9ec1f6f19bcbcc
parent 17e197bdaa303c17bfdac3a6a3f6401bf20fcca8
7 files changed +114 -23
added bin/ui/widget.rs
@@ -0,0 +1,91 @@
+
use std::marker::PhantomData;
+

+
use ratatui::layout::Constraint;
+
use ratatui::style::Stylize;
+
use ratatui::text::{Line, Text};
+
use ratatui::widgets::Row;
+
use ratatui::Frame;
+

+
use radicle_tui as tui;
+

+
use tui::ui::span;
+
use tui::ui::widget::{RenderProps, View, ViewProps};
+

+
use super::items::IssueItem;
+

+
#[derive(Clone, Default)]
+
pub struct IssueDetailsProps {
+
    issue: Option<IssueItem>,
+
}
+

+
impl IssueDetailsProps {
+
    pub fn issue(mut self, issue: Option<IssueItem>) -> Self {
+
        self.issue = issue;
+
        self
+
    }
+
}
+

+
pub struct IssueDetails<S, M> {
+
    /// Phantom
+
    phantom: PhantomData<(S, M)>,
+
}
+

+
impl<S, M> Default for IssueDetails<S, M> {
+
    fn default() -> Self {
+
        Self {
+
            phantom: PhantomData,
+
        }
+
    }
+
}
+

+
impl<S, M> View for IssueDetails<S, M> {
+
    type State = S;
+
    type Message = M;
+

+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
        let default = IssueDetailsProps::default();
+
        let props = props
+
            .and_then(|props| props.inner_ref::<IssueDetailsProps>())
+
            .unwrap_or(&default);
+

+
        if let Some(issue) = props.issue.as_ref() {
+
            let author = match &issue.author.alias {
+
                Some(alias) => {
+
                    if issue.author.you {
+
                        span::alias(&format!("{} (you)", alias))
+
                    } else {
+
                        span::alias(alias)
+
                    }
+
                }
+
                None => match &issue.author.human_nid {
+
                    Some(nid) => span::alias(nid).dim(),
+
                    None => span::blank(),
+
                },
+
            };
+
            let did = match &issue.author.human_nid {
+
                Some(nid) => span::alias(nid).dim(),
+
                None => span::blank(),
+
            };
+

+
            let table = ratatui::widgets::Table::new(
+
                [
+
                    Row::new([
+
                        Text::raw("Title").cyan(),
+
                        Text::raw(issue.title.clone()).bold(),
+
                    ]),
+
                    Row::new([
+
                        Text::raw("Issue").cyan(),
+
                        Text::raw(issue.id.to_string()).bold(),
+
                    ]),
+
                    Row::new([
+
                        Text::raw("Author").cyan(),
+
                        Line::from([author, " ".into(), did].to_vec()).into(),
+
                    ]),
+
                    Row::new([Text::raw("Status").cyan(), Text::raw("???").magenta()]),
+
                ],
+
                [Constraint::Length(8), Constraint::Fill(1)],
+
            );
+
            frame.render_widget(table, render.area);
+
        }
+
    }
+
}
modified src/ui/widget.rs
@@ -177,7 +177,7 @@ pub trait View {
    fn update(&mut self, _props: Option<&ViewProps>, _state: &Self::State) {}

    /// Should render the view using the given `RenderProps`.
-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame);
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame);
}

/// A `View` needs to wrapped into a `Widget` before being able to use with the
@@ -243,7 +243,7 @@ impl<S: 'static, M: 'static> Widget<S, M> {
    }

    /// Renders the wrapped view.
-
    pub fn render(&self, render: RenderProps, frame: &mut Frame) {
+
    pub fn render(&mut self, render: RenderProps, frame: &mut Frame) {
        self.view.render(self.props.as_ref(), render.clone(), frame);

        if let Some(on_render) = self.on_render {
modified src/ui/widget/container.rs
@@ -80,7 +80,7 @@ impl<'a: 'static, S, M> View for Header<S, M> {
    type Message = M;
    type State = S;

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = HeaderProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<HeaderProps>())
@@ -210,7 +210,7 @@ impl<'a: 'static, S, M> View for Footer<S, M> {
    type Message = M;
    type State = S;

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = FooterProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<FooterProps>())
@@ -326,7 +326,7 @@ where
        }
    }

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = ContainerProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<ContainerProps>())
@@ -362,18 +362,18 @@ where
            .borders(borders);
        frame.render_widget(block.clone(), content_area);

-
        if let Some(header) = &self.header {
+
        if let Some(header) = self.header.as_mut() {
            header.render(RenderProps::from(header_area).focus(render.focus), frame);
        }

-
        if let Some(content) = &self.content {
+
        if let Some(content) = self.content.as_mut() {
            content.render(
                RenderProps::from(block.inner(content_area)).focus(render.focus),
                frame,
            );
        }

-
        if let Some(footer) = &self.footer {
+
        if let Some(footer) = self.footer.as_mut() {
            footer.render(RenderProps::from(footer_area).focus(render.focus), frame);
        }
    }
@@ -472,7 +472,7 @@ where
        }
    }

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = SplitContainerProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<SplitContainerProps>())
@@ -492,7 +492,7 @@ where

        let [top_area, bottom_area] = Layout::vertical(heights).areas(render.area);

-
        if let Some(top) = self.top.as_ref() {
+
        if let Some(top) = self.top.as_mut() {
            let block = HeaderBlock::default()
                .borders(Borders::ALL)
                .border_style(style::border(render.focus))
@@ -509,7 +509,7 @@ where
            top.render(RenderProps::from(top_area).focus(render.focus), frame)
        }

-
        if let Some(bottom) = self.bottom.as_ref() {
+
        if let Some(bottom) = self.bottom.as_mut() {
            let block = Block::default()
                .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
                .border_style(style::border(render.focus))
@@ -637,7 +637,7 @@ where
        }
    }

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = SectionGroupProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<SectionGroupProps>())
@@ -646,7 +646,7 @@ where
        let areas = props.layout.split(render.area);

        for (index, area) in areas.iter().enumerate() {
-
            if let Some(section) = self.sections.get(index) {
+
            if let Some(section) = self.sections.get_mut(index) {
                let focus = self
                    .state
                    .focus
modified src/ui/widget/input.rs
@@ -227,7 +227,7 @@ where
        self.state.text = Some(props.text.clone());
    }

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = TextFieldProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<TextFieldProps>())
modified src/ui/widget/list.rs
@@ -216,7 +216,7 @@ where
        }
    }

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = TableProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<TableProps<R, W>>())
@@ -261,7 +261,7 @@ where
                .column_spacing(1)
                .highlight_style(style::highlight());

-
            frame.render_stateful_widget(rows, render.area, &mut self.state.clone());
+
            frame.render_stateful_widget(rows, render.area, &mut self.state);
        } else {
            let center = layout::centered_rect(render.area, 50, 10);
            let hint = Text::from(span::default("Nothing to show"))
modified src/ui/widget/text.rs
@@ -168,7 +168,7 @@ where
        None
    }

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let default = TextAreaProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<TextAreaProps>())
modified src/ui/widget/window.rs
@@ -98,7 +98,7 @@ where
        }
    }

-
    fn render(&self, props: Option<&ViewProps>, _render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, _render: RenderProps, frame: &mut Frame) {
        let default = WindowProps::default();
        let props = props
            .and_then(|props| props.inner_ref::<WindowProps<Id>>())
@@ -109,7 +109,7 @@ where
        let page = props
            .current_page
            .as_ref()
-
            .and_then(|id| self.pages.get(id));
+
            .and_then(|id| self.pages.get_mut(id));

        if let Some(page) = page {
            page.render(RenderProps::from(area).focus(true), frame);
@@ -190,11 +190,11 @@ where
        }
    }

-
    fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        let [content_area, shortcuts_area] =
            Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area);

-
        if let Some(content) = self.content.as_ref() {
+
        if let Some(content) = self.content.as_mut() {
            content.render(
                RenderProps::from(content_area)
                    .layout(Layout::horizontal([Constraint::Min(1)]))
@@ -203,7 +203,7 @@ where
            );
        }

-
        if let Some(shortcuts) = self.shortcuts.as_ref() {
+
        if let Some(shortcuts) = self.shortcuts.as_mut() {
            shortcuts.render(RenderProps::from(shortcuts_area), frame);
        }
    }
@@ -256,7 +256,7 @@ impl<S, M> View for Shortcuts<S, M> {
    type Message = M;
    type State = S;

-
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
        use ratatui::widgets::Table;

        let default = ShortcutsProps::default();