Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Cleanup base and props usage
Erik Kundt committed 2 years ago
commit 6b7f94611890ae44421ab11ac51bec0c1977a961
parent 18a35f3afab62aaca6fb863f9de647c9e1d8327e
4 files changed +120 -89
modified src/ui/widget.rs
@@ -23,12 +23,7 @@ pub type BoxedWidget<B, S, A> = Box<dyn Widget<B, S, A>>;
pub type UpdateCallback<S> = fn(&S) -> Box<dyn Any>;
pub type EventCallback<A> = fn(&dyn Any, UnboundedSender<A>);

-
pub struct BaseView<S, A, P>
-
where
-
    P: Properties,
-
{
-
    /// Internal properties
-
    pub props: P,
+
pub struct BaseView<S, A> {
    /// Message sender
    pub action_tx: UnboundedSender<A>,
    /// Custom update handler
@@ -65,6 +60,8 @@ pub trait View<S, A> {
        Box::new(self)
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A>;
+

    /// Should handle key events and call `handle_key_event` on all children.
    ///
    /// After key events have been handled, the custom event handler `on_event` should
@@ -153,7 +150,9 @@ where
    B: Backend,
{
    /// Internal base
-
    base: BaseView<S, A, WindowProps<Id>>,
+
    base: BaseView<S, A>,
+
    /// Internal properties
+
    props: WindowProps<Id>,
    /// All pages known
    pages: HashMap<Id, BoxedWidget<B, S, A>>,
}
@@ -182,14 +181,18 @@ where
        Self {
            base: BaseView {
                action_tx: action_tx.clone(),
-
                props: WindowProps::default(),
                on_update: None,
                on_event: None,
            },
+
            props: WindowProps::default(),
            pages: HashMap::new(),
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_update(mut self, callback: UpdateCallback<S>) -> Self {
        self.base.on_update = Some(callback);
        self
@@ -201,11 +204,10 @@ where
    }

    fn update(&mut self, state: &S) {
-
        self.base.props = WindowProps::from_callback(self.base.on_update, state)
-
            .unwrap_or(self.base.props.clone());
+
        self.props =
+
            WindowProps::from_callback(self.base.on_update, state).unwrap_or(self.props.clone());

        let page = self
-
            .base
            .props
            .current_page
            .as_ref()
@@ -218,7 +220,6 @@ where

    fn handle_key_event(&mut self, key: termion::event::Key) {
        let page = self
-
            .base
            .props
            .current_page
            .as_ref()
@@ -238,12 +239,11 @@ where
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, props: Option<Box<dyn Any>>) {
        let _props = props
            .and_then(WindowProps::from_boxed_any)
-
            .unwrap_or(self.base.props.clone());
+
            .unwrap_or(self.props.clone());

        let area = frame.size();

        let page = self
-
            .base
            .props
            .current_page
            .as_ref()
@@ -288,21 +288,22 @@ impl Default for ShortcutsProps {
impl Properties for ShortcutsProps {}

pub struct Shortcuts<S, A> {
+
    /// Internal properties
+
    props: ShortcutsProps,
    /// Internal base
-
    base: BaseView<S, A, ShortcutsProps>,
+
    base: BaseView<S, A>,
}

impl<S, A> Shortcuts<S, A> {
    pub fn divider(mut self, divider: char) -> Self {
-
        self.base.props.divider = divider;
+
        self.props.divider = divider;
        self
    }

    pub fn shortcuts(mut self, shortcuts: &[(&str, &str)]) -> Self {
-
        self.base.props.shortcuts.clear();
+
        self.props.shortcuts.clear();
        for (short, long) in shortcuts {
-
            self.base
-
                .props
+
            self.props
                .shortcuts
                .push((short.to_string(), long.to_string()));
        }
@@ -315,13 +316,17 @@ impl<S, A> View<S, A> for Shortcuts<S, A> {
        Self {
            base: BaseView {
                action_tx: action_tx.clone(),
-
                props: ShortcutsProps::default(),
                on_update: None,
                on_event: None,
            },
+
            props: ShortcutsProps::default(),
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_event(mut self, callback: EventCallback<A>) -> Self {
        self.base.on_event = Some(callback);
        self
@@ -335,8 +340,8 @@ impl<S, A> View<S, A> for Shortcuts<S, A> {
    fn handle_key_event(&mut self, _key: Key) {}

    fn update(&mut self, state: &S) {
-
        self.base.props = ShortcutsProps::from_callback(self.base.on_update, state)
-
            .unwrap_or(self.base.props.clone());
+
        self.props =
+
            ShortcutsProps::from_callback(self.base.on_update, state).unwrap_or(self.props.clone());
    }
}

@@ -349,7 +354,7 @@ where

        let props = props
            .and_then(ShortcutsProps::from_boxed_any)
-
            .unwrap_or(self.base.props.clone());
+
            .unwrap_or(self.props.clone());

        let mut shortcuts = props.shortcuts.iter().peekable();
        let mut row = vec![];
@@ -483,14 +488,10 @@ pub struct Table<'a, S, A, R>
where
    R: ToRow,
{
+
    /// Internal base
+
    base: BaseView<S, A>,
    /// Internal table properties
    props: TableProps<'a, R>,
-
    /// Message sender
-
    action_tx: UnboundedSender<A>,
-
    /// Custom update handler
-
    on_update: Option<UpdateCallback<S>>,
-
    /// Additional custom event handler
-
    on_event: Option<EventCallback<A>>,
    /// Internal selection and offset state
    state: TableState,
}
@@ -556,27 +557,33 @@ where
{
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
        Self {
-
            action_tx: action_tx.clone(),
+
            base: BaseView {
+
                action_tx: action_tx.clone(),
+
                on_update: None,
+
                on_event: None,
+
            },
            props: TableProps::default(),
            state: TableState::default().with_selected(Some(0)),
-
            on_update: None,
-
            on_event: None,
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_update(mut self, callback: UpdateCallback<S>) -> Self {
-
        self.on_update = Some(callback);
+
        self.base.on_update = Some(callback);
        self
    }

    fn on_event(mut self, callback: EventCallback<A>) -> Self {
-
        self.on_event = Some(callback);
+
        self.base.on_event = Some(callback);
        self
    }

    fn update(&mut self, state: &S) {
-
        self.props =
-
            TableProps::<'_, R>::from_callback(self.on_update, state).unwrap_or(self.props.clone());
+
        self.props = TableProps::<'_, R>::from_callback(self.base.on_update, state)
+
            .unwrap_or(self.props.clone());

        // TODO: Move to state reducer
        if let Some(selected) = self.state.selected() {
@@ -611,8 +618,8 @@ where

        self.props.selected = self.state.selected();

-
        if let Some(on_event) = self.on_event {
-
            (on_event)(&self.state, self.action_tx.clone());
+
        if let Some(on_event) = self.base.on_event {
+
            (on_event)(&self.state, self.base.action_tx.clone());
        }
    }
}
modified src/ui/widget/container.rs
@@ -55,24 +55,26 @@ impl<'a> Default for HeaderProps<'a> {
impl<'a: 'static> Properties for HeaderProps<'a> {}

pub struct Header<'a: 'static, S, A> {
+
    /// Internal props
+
    props: HeaderProps<'a>,
    /// Internal base
-
    base: BaseView<S, A, HeaderProps<'a>>,
+
    base: BaseView<S, A>,
}

impl<'a, S, A> Header<'a, S, A> {
    pub fn columns(mut self, columns: Vec<Column<'a>>) -> Self {
-
        self.base.props.columns = columns;
+
        self.props.columns = columns;
        self
    }

    pub fn focus(mut self, focus: bool) -> Self {
-
        self.base.props.focus = focus;
+
        self.props.focus = focus;
        self
    }

    pub fn cutoff(mut self, cutoff: usize, cutoff_after: usize) -> Self {
-
        self.base.props.cutoff = cutoff;
-
        self.base.props.cutoff_after = cutoff_after;
+
        self.props.cutoff = cutoff;
+
        self.props.cutoff_after = cutoff_after;
        self
    }
}
@@ -82,13 +84,17 @@ impl<'a: 'static, S, A> View<S, A> for Header<'a, S, A> {
        Self {
            base: BaseView {
                action_tx: action_tx.clone(),
-
                props: HeaderProps::default(),
                on_update: None,
                on_event: None,
            },
+
            props: HeaderProps::default(),
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_update(mut self, callback: UpdateCallback<S>) -> Self {
        self.base.on_update = Some(callback);
        self
@@ -100,16 +106,16 @@ impl<'a: 'static, S, A> View<S, A> for Header<'a, S, A> {
    }

    fn update(&mut self, state: &S) {
-
        self.base.props = self
+
        self.props = self
            .base
            .on_update
            .and_then(|on_update| HeaderProps::from_boxed_any((on_update)(state)))
-
            .unwrap_or(self.base.props.clone());
+
            .unwrap_or(self.props.clone());
    }

    fn handle_key_event(&mut self, _key: Key) {
        if let Some(on_event) = self.base.on_event {
-
            (on_event)(&self.base.props, self.base.action_tx.clone());
+
            (on_event)(&self.props, self.base.action_tx.clone());
        }
    }
}
@@ -121,7 +127,7 @@ where
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(HeaderProps::from_boxed_any)
-
            .unwrap_or(self.base.props.clone());
+
            .unwrap_or(self.props.clone());

        let widths: Vec<Constraint> = props
            .columns
@@ -216,14 +222,10 @@ impl<'a> Default for FooterProps<'a> {
impl<'a: 'static> Properties for FooterProps<'a> {}

pub struct Footer<'a, S, A> {
-
    /// Internal properties
+
    /// Internal props
    props: FooterProps<'a>,
-
    /// Message sender
-
    action_tx: UnboundedSender<A>,
-
    /// Custom update handler
-
    on_update: Option<UpdateCallback<S>>,
-
    /// Additional custom event handler
-
    on_event: Option<EventCallback<A>>,
+
    /// Internal base
+
    base: BaseView<S, A>,
}

impl<'a, S, A> Footer<'a, S, A> {
@@ -247,33 +249,40 @@ impl<'a, S, A> Footer<'a, S, A> {
impl<'a: 'static, S, A> View<S, A> for Footer<'a, S, A> {
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
        Self {
-
            action_tx: action_tx.clone(),
+
            base: BaseView {
+
                action_tx: action_tx.clone(),
+
                on_update: None,
+
                on_event: None,
+
            },
            props: FooterProps::default(),
-
            on_update: None,
-
            on_event: None,
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_update(mut self, callback: UpdateCallback<S>) -> Self {
-
        self.on_update = Some(callback);
+
        self.base.on_update = Some(callback);
        self
    }

    fn on_event(mut self, callback: EventCallback<A>) -> Self {
-
        self.on_event = Some(callback);
+
        self.base.on_event = Some(callback);
        self
    }

    fn update(&mut self, state: &S) {
        self.props = self
+
            .base
            .on_update
            .and_then(|on_update| FooterProps::from_boxed_any((on_update)(state)))
            .unwrap_or(self.props.clone());
    }

    fn handle_key_event(&mut self, _key: Key) {
-
        if let Some(on_event) = self.on_event {
-
            (on_event)(&self.props, self.action_tx.clone());
+
        if let Some(on_event) = self.base.on_event {
+
            (on_event)(&self.props, self.base.action_tx.clone());
        }
    }
}
@@ -367,14 +376,10 @@ pub struct Container<B, S, A>
where
    B: Backend,
{
-
    /// Internal properties
+
    /// Internal base
+
    base: BaseView<S, A>,
+
    /// Internal props
    props: ContainerProps,
-
    /// Message sender
-
    _action_tx: UnboundedSender<A>,
-
    /// Custom update handler
-
    on_update: Option<UpdateCallback<S>>,
-
    /// Additional custom event handler
-
    on_event: Option<EventCallback<A>>,
    /// Container header
    header: Option<BoxedWidget<B, S, A>>,
    /// Content widget
@@ -412,29 +417,36 @@ where
        Self: Sized,
    {
        Self {
-
            _action_tx: action_tx.clone(),
+
            base: BaseView {
+
                action_tx: action_tx.clone(),
+

+
                on_update: None,
+
                on_event: None,
+
            },
            props: ContainerProps::default(),
            header: None,
            content: None,
            footer: None,
-
            on_update: None,
-
            on_event: None,
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_update(mut self, callback: UpdateCallback<S>) -> Self {
-
        self.on_update = Some(callback);
+
        self.base.on_update = Some(callback);
        self
    }

    fn on_event(mut self, callback: EventCallback<A>) -> Self {
-
        self.on_event = Some(callback);
+
        self.base.on_event = Some(callback);
        self
    }

    fn update(&mut self, state: &S) {
        self.props =
-
            ContainerProps::from_callback(self.on_update, state).unwrap_or(self.props.clone());
+
            ContainerProps::from_callback(self.base.on_update, state).unwrap_or(self.props.clone());

        if let Some(header) = &mut self.header {
            header.update(state);
modified src/ui/widget/input.rs
@@ -57,7 +57,9 @@ pub struct TextFieldState {

pub struct TextField<S, A> {
    /// Internal base
-
    base: BaseView<S, A, TextFieldProps>,
+
    base: BaseView<S, A>,
+
    /// Internal props
+
    props: TextFieldProps,
    /// Internal state
    state: TextFieldState,
}
@@ -129,10 +131,10 @@ impl<S, A> View<S, A> for TextField<S, A> {
        Self {
            base: BaseView {
                action_tx: action_tx.clone(),
-
                props: TextFieldProps::default(),
                on_update: None,
                on_event: None,
            },
+
            props: TextFieldProps::default(),
            state: TextFieldState {
                text: None,
                cursor_position: 0,
@@ -140,6 +142,10 @@ impl<S, A> View<S, A> for TextField<S, A> {
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_update(mut self, callback: UpdateCallback<S>) -> Self {
        self.base.on_update = Some(callback);
        self
@@ -153,7 +159,7 @@ impl<S, A> View<S, A> for TextField<S, A> {
    fn update(&mut self, state: &S) {
        if let Some(on_update) = self.base.on_update {
            if let Some(props) = (on_update)(state).downcast_ref::<TextFieldProps>() {
-
                self.base.props = props.clone();
+
                self.props = props.clone();

                if self.state.text.is_none() {
                    self.state.cursor_position = props.text.len().saturating_sub(1);
@@ -197,7 +203,7 @@ where
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(TextFieldProps::from_boxed_any)
-
            .unwrap_or(self.base.props.clone());
+
            .unwrap_or(self.props.clone());

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

modified src/ui/widget/text.rs
@@ -59,9 +59,11 @@ pub struct ParagraphState {
    pub progress: usize,
}

-
pub struct Paragraph<'a: 'static, S, A> {
+
pub struct Paragraph<'a, S, A> {
    /// Internal base
-
    base: BaseView<S, A, ParagraphProps<'a>>,
+
    base: BaseView<S, A>,
+
    /// Internal props
+
    props: ParagraphProps<'a>,
    /// Internal state
    state: ParagraphState,
}
@@ -72,12 +74,12 @@ impl<'a, S, A> Paragraph<'a, S, A> {
    }

    pub fn page_size(mut self, page_size: usize) -> Self {
-
        self.base.props.page_size = page_size;
+
        self.props.page_size = page_size;
        self
    }

    pub fn text(mut self, text: &Text<'a>) -> Self {
-
        self.base.props.content = text.clone();
+
        self.props.content = text.clone();
        self
    }

@@ -148,10 +150,10 @@ impl<'a: 'static, S, A> View<S, A> for Paragraph<'a, S, A> {
        Self {
            base: BaseView {
                action_tx: action_tx.clone(),
-
                props: ParagraphProps::default(),
                on_update: None,
                on_event: None,
            },
+
            props: ParagraphProps::default(),
            state: ParagraphState {
                offset: 0,
                progress: 0,
@@ -159,6 +161,10 @@ impl<'a: 'static, S, A> View<S, A> for Paragraph<'a, S, A> {
        }
    }

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
+

    fn on_event(mut self, callback: EventCallback<A>) -> Self {
        self.base.on_event = Some(callback);
        self
@@ -170,13 +176,13 @@ impl<'a: 'static, S, A> View<S, A> for Paragraph<'a, S, A> {
    }

    fn update(&mut self, state: &S) {
-
        self.base.props = ParagraphProps::from_callback(self.base.on_update, state)
-
            .unwrap_or(self.base.props.clone());
+
        self.props =
+
            ParagraphProps::from_callback(self.base.on_update, state).unwrap_or(self.props.clone());
    }

    fn handle_key_event(&mut self, key: Key) {
-
        let len = self.base.props.content.lines.len() + 1;
-
        let page_size = self.base.props.page_size;
+
        let len = self.props.content.lines.len() + 1;
+
        let page_size = self.props.page_size;

        match key {
            Key::Up | Key::Char('k') => {
@@ -213,7 +219,7 @@ where
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(ParagraphProps::from_boxed_any)
-
            .unwrap_or(self.base.props.clone());
+
            .unwrap_or(self.props.clone());

        let [content_area] = Layout::horizontal([Constraint::Min(1)])
            .horizontal_margin(1)