Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Pass around boxed properties
Erik Kundt committed 2 years ago
commit 4488123e9e2cacf8838ef1c466f3b5b28a93341c
parent 8068d146effc7cb5451c2b8deced9486622a4865
4 files changed +44 -53
modified src/ui/widget.rs
@@ -70,7 +70,7 @@ where
    /// Renders a widget to the given frame in the given area.
    ///
    /// Optional props take precedence over the internal ones.
-
    fn render(&self, frame: &mut Frame, area: Rect, props: Option<&dyn Any>);
+
    fn render(&self, frame: &mut Frame, area: Rect, props: Option<Box<dyn Any>>);
}

/// Needs to be implemented for items that are supposed to be rendering in tables.
@@ -86,6 +86,13 @@ pub trait Properties {
    {
        Box::new(self)
    }
+

+
    fn from_boxed_any(any: Box<dyn Any>) -> Option<Self>
+
    where
+
        Self: Sized + Clone + 'static,
+
    {
+
        any.downcast_ref::<Self>().cloned()
+
    }
}

#[derive(Clone)]
@@ -124,7 +131,7 @@ pub struct Shortcuts<S, A> {
    /// Internal properties
    props: ShortcutsProps,
    /// Message sender
-
    action_tx: UnboundedSender<A>,
+
    _action_tx: UnboundedSender<A>,
    /// Custom update handler
    on_update: Option<UpdateCallback<S>>,
    /// Additional custom event handler
@@ -151,7 +158,7 @@ impl<S, A> Shortcuts<S, A> {
impl<S, A> View<S, A> for Shortcuts<S, A> {
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
        Self {
-
            action_tx: action_tx.clone(),
+
            _action_tx: action_tx.clone(),
            props: ShortcutsProps::default(),
            on_update: None,
            on_change: None,
@@ -168,16 +175,12 @@ impl<S, A> View<S, A> for Shortcuts<S, A> {
        self
    }

-
    fn handle_key_event(&mut self, _key: Key) {
-
        if let Some(on_change) = self.on_change {
-
            (on_change)(&self.props, self.action_tx.clone());
-
        }
-
    }
+
    fn handle_key_event(&mut self, _key: Key) {}

    fn update(&mut self, state: &S) {
        self.props = self
            .on_update
-
            .and_then(|on_update| (on_update)(state).downcast_ref::<ShortcutsProps>().cloned())
+
            .and_then(|on_update| ShortcutsProps::from_boxed_any((on_update)(state)))
            .unwrap_or(self.props.clone())
    }
}
@@ -186,12 +189,12 @@ 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: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        use ratatui::widgets::Table;

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

        let mut shortcuts = props.shortcuts.iter().peekable();
        let mut row = vec![];
@@ -319,7 +322,7 @@ where
    }
}

-
impl<'a, R> Properties for TableProps<'a, R> where R: ToRow {}
+
impl<'a: 'static, R> Properties for TableProps<'a, R> where R: ToRow + 'static {}

pub struct Table<'a, S, A, R>
where
@@ -419,11 +422,7 @@ where
    fn update(&mut self, state: &S) {
        self.props = self
            .on_update
-
            .and_then(|on_update| {
-
                (on_update)(state)
-
                    .downcast_ref::<TableProps<'_, R>>()
-
                    .cloned()
-
            })
+
            .and_then(|on_update| TableProps::<'_, R>::from_boxed_any((on_update)(state)))
            .unwrap_or(self.props.clone());

        // TODO: Move to state reducer
@@ -470,10 +469,10 @@ where
    B: Backend,
    R: ToRow + Clone + Debug + 'static,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
-
            .and_then(|props| props.downcast_ref::<TableProps<'_, R>>())
-
            .unwrap_or(&self.props);
+
            .and_then(|props| TableProps::<'_, R>::from_boxed_any(props))
+
            .unwrap_or(self.props.clone());

        let widths: Vec<Constraint> = self
            .props
modified src/ui/widget/container.rs
@@ -50,7 +50,7 @@ impl<'a> Default for HeaderProps<'a> {
    }
}

-
impl<'a> Properties for HeaderProps<'a> {}
+
impl<'a: 'static> Properties for HeaderProps<'a> {}

pub struct Header<'a, S, A> {
    /// Internal props
@@ -104,7 +104,7 @@ impl<'a: 'static, S, A> View<S, A> for Header<'a, S, A> {
    fn update(&mut self, state: &S) {
        self.props = self
            .on_update
-
            .and_then(|on_update| (on_update)(state).downcast_ref::<HeaderProps>().cloned())
+
            .and_then(|on_update| HeaderProps::from_boxed_any((on_update)(state)))
            .unwrap_or(self.props.clone());
    }

@@ -119,10 +119,10 @@ 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: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
-
            .and_then(|props| props.downcast_ref::<HeaderProps>())
-
            .unwrap_or(&self.props);
+
            .and_then(|props| HeaderProps::from_boxed_any(props))
+
            .unwrap_or(self.props.clone());

        let widths: Vec<Constraint> = props
            .columns
@@ -214,7 +214,7 @@ impl<'a> Default for FooterProps<'a> {
    }
}

-
impl<'a> Properties for FooterProps<'a> {}
+
impl<'a: 'static> Properties for FooterProps<'a> {}

pub struct Footer<'a, S, A> {
    /// Internal properties
@@ -268,7 +268,7 @@ impl<'a: 'static, S, A> View<S, A> for Footer<'a, S, A> {
    fn update(&mut self, state: &S) {
        self.props = self
            .on_update
-
            .and_then(|on_update| (on_update)(state).downcast_ref::<FooterProps>().cloned())
+
            .and_then(|on_update| FooterProps::from_boxed_any((on_update)(state)))
            .unwrap_or(self.props.clone());
    }

@@ -307,10 +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: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
-
            .and_then(|props| props.downcast_ref::<FooterProps>())
-
            .unwrap_or(&self.props);
+
            .and_then(|props| FooterProps::from_boxed_any(props))
+
            .unwrap_or(self.props.clone());

        let widths = props
            .columns
@@ -436,7 +436,7 @@ where
    fn update(&mut self, state: &S) {
        self.props = self
            .on_update
-
            .and_then(|on_update| (on_update)(state).downcast_ref::<ContainerProps>().cloned())
+
            .and_then(|on_update| ContainerProps::from_boxed_any((on_update)(state)))
            .unwrap_or(self.props.clone());

        if let Some(header) = &mut self.header {
@@ -463,10 +463,10 @@ impl<'a: 'static, B, S, A> Widget<B, S, A> for Container<B, S, A>
where
    B: Backend,
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
-
            .and_then(|props| props.downcast_ref::<ContainerProps>())
-
            .unwrap_or(&self.props);
+
            .and_then(|props| ContainerProps::from_boxed_any(props))
+
            .unwrap_or(self.props.clone());

        let header_h = if self.header.is_some() { 3 } else { 0 };
        let footer_h = if self.footer.is_some() && !props.hide_footer {
modified src/ui/widget/input.rs
@@ -198,10 +198,10 @@ 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: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
-
            .and_then(|props| props.downcast_ref::<TextFieldProps>())
-
            .unwrap_or(&self.props);
+
            .and_then(|props| TextFieldProps::from_boxed_any(props))
+
            .unwrap_or(self.props.clone());

        let layout = Layout::vertical(Constraint::from_lengths([1, 1])).split(area);

modified src/ui/widget/text.rs
@@ -7,9 +7,6 @@ use termion::event::Key;
use ratatui::backend::Backend;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::text::Text;
-
use ratatui::widgets::{Block, BorderType, Borders};
-

-
use crate::ui::theme::style;

use super::{EventCallback, Properties, UpdateCallback, View, Widget};

@@ -53,7 +50,8 @@ impl<'a> Default for ParagraphProps<'a> {
    }
}

-
impl<'a> Properties for ParagraphProps<'a> {}
+
impl<'a: 'static> Properties for ParagraphProps<'a> {}
+

pub struct ParagraphState {
    /// Internal offset
    pub offset: usize,
@@ -178,7 +176,7 @@ impl<'a: 'static, S, A> View<S, A> for Paragraph<'a, S, A> {
    fn update(&mut self, state: &S) {
        self.props = self
            .on_update
-
            .and_then(|on_update| (on_update)(state).downcast_ref::<ParagraphProps>().cloned())
+
            .and_then(|on_update| ParagraphProps::from_boxed_any((on_update)(state)))
            .unwrap_or(self.props.clone());
    }

@@ -218,19 +216,13 @@ 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: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
-
            .and_then(|props| props.downcast_ref::<ParagraphProps>())
-
            .unwrap_or(&self.props);
-

-
        let block = Block::default()
-
            .borders(Borders::LEFT | Borders::RIGHT)
-
            .border_type(BorderType::Rounded)
-
            .border_style(style::border(props.focus));
-
        frame.render_widget(block, area);
+
            .and_then(|props| ParagraphProps::from_boxed_any(props))
+
            .unwrap_or(self.props.clone());

        let [content_area] = Layout::horizontal([Constraint::Min(1)])
-
            .horizontal_margin(2)
+
            .horizontal_margin(1)
            .areas(area);
        let content = ratatui::widgets::Paragraph::new(props.content.clone())
            .scroll((self.state.offset as u16, 0));