Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Merge widget into view trait
Erik Kundt committed 2 years ago
commit eab2c8bd05e331861f7962f7c33854048bf41313
parent 3c12ba0965af2470cba81038d613d607e8be11be
4 files changed +144 -172
modified src/ui/widget.rs
@@ -33,10 +33,10 @@ pub struct BaseView<S, A> {
    pub on_event: Option<EventCallback<A>>,
}

-
/// Main trait defining a `View` behaviour.
+
/// Main trait defining a `Widget` behaviour.
///
-
/// This is the first trait that you should implement to define a custom `Widget`.
-
pub trait View {
+
/// This is the trait that you should implement to define a custom `Widget`.
+
pub trait Widget {
    type State;
    type Action;

@@ -46,6 +46,32 @@ pub trait View {
    where
        Self: Sized;

+
    /// Should handle key events and call `handle_event` on all children.
+
    ///
+
    /// After key events have been handled, the custom event handler `on_event` should
+
    /// be called
+
    fn handle_event(&mut self, key: Key);
+

+
    /// Should update the internal props of this and all children.
+
    ///
+
    /// Applications are usually defined by app-specific widgets that do know
+
    /// the type of `state`. These can use widgets from the library that do not know the
+
    /// type of `state`.
+
    ///
+
    /// If `on_update` is set, implementations of this function should call it to
+
    /// construct and update the internal props. If it is not set, app widgets can construct
+
    /// props directly via their state converters, whereas library widgets can just fallback
+
    /// to their current props.
+
    fn update(&mut self, state: &Self::State);
+

+
    /// 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<Box<dyn Any>>);
+

+
    /// Return a mutable reference to this widgets' base view.
+
    fn base_mut(&mut self) -> &mut BaseView<Self::State, Self::Action>;
+

    /// Should set the optional custom event handler.
    fn on_event(mut self, callback: EventCallback<Self::Action>) -> Self
    where
@@ -64,44 +90,13 @@ pub trait View {
        self
    }

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

-
    /// Return a mutable reference to this widgets' base view.
-
    fn base_mut(&mut self) -> &mut BaseView<Self::State, Self::Action>;
-

-
    /// Should handle key events and call `handle_event` on all children.
-
    ///
-
    /// After key events have been handled, the custom event handler `on_event` should
-
    /// be called
-
    fn handle_event(&mut self, key: Key);
-

-
    /// Should update the internal props of this and all children.
-
    ///
-
    /// Applications are usually defined by app-specific widgets that do know
-
    /// the type of `state`. These can use widgets from the library that do not know the
-
    /// type of `state`.
-
    ///
-
    /// If `on_update` is set, implementations of this function should call it to
-
    /// construct and update the internal props. If it is not set, app widgets can construct
-
    /// props directly via their state converters, whereas library widgets can just fallback
-
    /// to their current props.
-
    fn update(&mut self, state: &Self::State);
-
}
-

-
/// A `Widget` is a `View` that can be rendered using a specific backend.
-
///
-
/// This is the second trait that you should implement to define a custom `Widget`.
-
pub trait Widget: View {
-
    /// 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<Box<dyn Any>>);
}

/// Needs to be implemented for items that are supposed to be rendered in tables.
@@ -174,7 +169,7 @@ where
    }
}

-
impl<'a: 'static, S, A, Id> View for Window<S, A, Id>
+
impl<'a: 'static, S, A, Id> Widget for Window<S, A, Id>
where
    Id: Clone + Hash + Eq + PartialEq + 'a,
{
@@ -196,13 +191,7 @@ where
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
-
        &mut self.base
-
    }
-
    fn update(&mut self, state: &S) {
-
        self.props =
-
            WindowProps::from_callback(self.base.on_update, state).unwrap_or(self.props.clone());
-

+
    fn handle_event(&mut self, key: termion::event::Key) {
        let page = self
            .props
            .current_page
@@ -210,11 +199,14 @@ where
            .and_then(|id| self.pages.get_mut(id));

        if let Some(page) = page {
-
            page.update(state);
+
            page.handle_event(key);
        }
    }

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

        let page = self
            .props
            .current_page
@@ -222,15 +214,10 @@ where
            .and_then(|id| self.pages.get_mut(id));

        if let Some(page) = page {
-
            page.handle_event(key);
+
            page.update(state);
        }
    }
-
}

-
impl<'a: 'static, S, A, Id> Widget for Window<S, A, Id>
-
where
-
    Id: Clone + Hash + Eq + PartialEq + 'a,
-
{
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, props: Option<Box<dyn Any>>) {
        let _props = props
            .and_then(WindowProps::from_boxed_any)
@@ -248,6 +235,10 @@ where
            page.render(frame, area, None);
        }
    }
+

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

#[derive(Clone)]
@@ -306,7 +297,7 @@ impl<S, A> Shortcuts<S, A> {
    }
}

-
impl<S, A> View for Shortcuts<S, A> {
+
impl<S, A> Widget for Shortcuts<S, A> {
    type Action = A;
    type State = S;

@@ -321,19 +312,13 @@ impl<S, A> View for Shortcuts<S, A> {
        }
    }

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

    fn handle_event(&mut self, _key: Key) {}

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

-
impl<S, A> Widget for Shortcuts<S, A> {
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        use ratatui::widgets::Table;

@@ -374,6 +359,10 @@ impl<S, A> Widget for Shortcuts<S, A> {
        let table = Table::new([Row::new(row)], widths).column_spacing(0);
        frame.render_widget(table, area);
    }
+

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

#[derive(Clone, Debug)]
@@ -536,7 +525,7 @@ where
    }
}

-
impl<'a: 'static, S, A, R> View for Table<'a, S, A, R>
+
impl<'a: 'static, S, A, R> Widget for Table<'a, S, A, R>
where
    R: ToRow + Clone + 'static,
{
@@ -555,22 +544,6 @@ where
        }
    }

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

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

-
        // TODO: Move to state reducer
-
        if let Some(selected) = self.state.selected() {
-
            if selected > self.props.items.len() {
-
                self.begin();
-
            }
-
        }
-
    }
-

    fn handle_event(&mut self, key: Key) {
        match key {
            Key::Up | Key::Char('k') => {
@@ -600,12 +573,19 @@ where
            (on_event)(&self.state, self.base.action_tx.clone());
        }
    }
-
}

-
impl<'a: 'static, S, A, R> Widget for Table<'a, S, A, R>
-
where
-
    R: ToRow + Clone + Debug + 'static,
-
{
+
    fn update(&mut self, state: &S) {
+
        self.props =
+
            TableProps::from_callback(self.base.on_update, state).unwrap_or(self.props.clone());
+

+
        // TODO: Move to state reducer
+
        if let Some(selected) = self.state.selected() {
+
            if selected > self.props.items.len() {
+
                self.begin();
+
            }
+
        }
+
    }
+

    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(TableProps::from_boxed_any)
@@ -662,6 +642,10 @@ where
            frame.render_widget(hint, center);
        }
    }
+

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

pub struct TableUtils {}
modified src/ui/widget/container.rs
@@ -11,7 +11,7 @@ use ratatui::widgets::{Block, BorderType, Borders, Row};
use crate::ui::ext::{FooterBlock, FooterBlockType, HeaderBlock};
use crate::ui::theme::style;

-
use super::{BaseView, BoxedWidget, Column, Properties, View, Widget};
+
use super::{BaseView, BoxedWidget, Column, Properties, Widget};

#[derive(Clone, Debug)]
pub struct HeaderProps<'a> {
@@ -77,7 +77,7 @@ impl<'a, S, A> Header<'a, S, A> {
    }
}

-
impl<'a: 'static, S, A> View for Header<'a, S, A> {
+
impl<'a: 'static, S, A> Widget for Header<'a, S, A> {
    type Action = A;
    type State = S;

@@ -92,8 +92,10 @@ impl<'a: 'static, S, A> View for Header<'a, S, A> {
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
-
        &mut self.base
+
    fn handle_event(&mut self, _key: Key) {
+
        if let Some(on_event) = self.base.on_event {
+
            (on_event)(&self.props, self.base.action_tx.clone());
+
        }
    }

    fn update(&mut self, state: &S) {
@@ -104,14 +106,6 @@ impl<'a: 'static, S, A> View for Header<'a, S, A> {
            .unwrap_or(self.props.clone());
    }

-
    fn handle_event(&mut self, _key: Key) {
-
        if let Some(on_event) = self.base.on_event {
-
            (on_event)(&self.props, self.base.action_tx.clone());
-
        }
-
    }
-
}
-

-
impl<'a: 'static, S, A> Widget for Header<'a, S, A> {
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(HeaderProps::from_boxed_any)
@@ -168,6 +162,10 @@ impl<'a: 'static, S, A> Widget for Header<'a, S, A> {
        frame.render_widget(block, area);
        frame.render_widget(header, header_layout[0]);
    }
+

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

#[derive(Clone, Debug)]
@@ -232,9 +230,31 @@ impl<'a, S, A> Footer<'a, S, A> {
        self.props.focus = focus;
        self
    }
+

+
    fn render_cell(
+
        &self,
+
        frame: &mut ratatui::Frame,
+
        area: Rect,
+
        block_type: FooterBlockType,
+
        text: impl Into<Text<'a>>,
+
        focus: bool,
+
    ) {
+
        let footer_layout = Layout::default()
+
            .direction(Direction::Vertical)
+
            .constraints(vec![Constraint::Min(1)])
+
            .vertical_margin(1)
+
            .horizontal_margin(1)
+
            .split(area);
+

+
        let footer_block = FooterBlock::default()
+
            .border_style(style::border(focus))
+
            .block_type(block_type);
+
        frame.render_widget(footer_block, area);
+
        frame.render_widget(text.into(), footer_layout[0]);
+
    }
}

-
impl<'a: 'static, S, A> View for Footer<'a, S, A> {
+
impl<'a: 'static, S, A> Widget for Footer<'a, S, A> {
    type Action = A;
    type State = S;

@@ -249,8 +269,10 @@ impl<'a: 'static, S, A> View for Footer<'a, S, A> {
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
-
        &mut self.base
+
    fn handle_event(&mut self, _key: Key) {
+
        if let Some(on_event) = self.base.on_event {
+
            (on_event)(&self.props, self.base.action_tx.clone());
+
        }
    }

    fn update(&mut self, state: &S) {
@@ -261,38 +283,6 @@ impl<'a: 'static, S, A> View for Footer<'a, S, A> {
            .unwrap_or(self.props.clone());
    }

-
    fn handle_event(&mut self, _key: Key) {
-
        if let Some(on_event) = self.base.on_event {
-
            (on_event)(&self.props, self.base.action_tx.clone());
-
        }
-
    }
-
}
-

-
impl<'a, S, A> Footer<'a, S, A> {
-
    fn render_cell(
-
        &self,
-
        frame: &mut ratatui::Frame,
-
        area: Rect,
-
        block_type: FooterBlockType,
-
        text: impl Into<Text<'a>>,
-
        focus: bool,
-
    ) {
-
        let footer_layout = Layout::default()
-
            .direction(Direction::Vertical)
-
            .constraints(vec![Constraint::Min(1)])
-
            .vertical_margin(1)
-
            .horizontal_margin(1)
-
            .split(area);
-

-
        let footer_block = FooterBlock::default()
-
            .border_style(style::border(focus))
-
            .block_type(block_type);
-
        frame.render_widget(footer_block, area);
-
        frame.render_widget(text.into(), footer_layout[0]);
-
    }
-
}
-

-
impl<'a: 'static, S, A> Widget for Footer<'a, S, A> {
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(FooterProps::from_boxed_any)
@@ -328,6 +318,10 @@ impl<'a: 'static, S, A> Widget for Footer<'a, S, A> {
            self.render_cell(frame, *area, block_type, cell.clone(), props.focus);
        }
    }
+

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

#[derive(Clone, Default)]
@@ -380,7 +374,7 @@ impl<S, A> Container<S, A> {
    }
}

-
impl<S, A> View for Container<S, A> {
+
impl<S, A> Widget for Container<S, A> {
    type Action = A;
    type State = S;

@@ -402,8 +396,10 @@ impl<S, A> View for Container<S, A> {
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
-
        &mut self.base
+
    fn handle_event(&mut self, key: termion::event::Key) {
+
        if let Some(content) = &mut self.content {
+
            content.handle_event(key);
+
        }
    }

    fn update(&mut self, state: &S) {
@@ -423,14 +419,6 @@ impl<S, A> View for Container<S, A> {
        }
    }

-
    fn handle_event(&mut self, key: termion::event::Key) {
-
        if let Some(content) = &mut self.content {
-
            content.handle_event(key);
-
        }
-
    }
-
}
-

-
impl<'a: 'static, S, A> Widget for Container<S, A> {
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(ContainerProps::from_boxed_any)
@@ -478,4 +466,8 @@ impl<'a: 'static, S, A> Widget for Container<S, A> {
            footer.render(frame, footer_area, None);
        }
    }
+

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
}
modified src/ui/widget/input.rs
@@ -9,7 +9,7 @@ use ratatui::prelude::Rect;
use ratatui::style::Stylize;
use ratatui::text::{Line, Span};

-
use super::{BaseView, Properties, View, Widget};
+
use super::{BaseView, Properties, Widget};

#[derive(Clone)]
pub struct TextFieldProps {
@@ -126,7 +126,7 @@ impl<S, A> TextField<S, A> {
    }
}

-
impl<S, A> View for TextField<S, A> {
+
impl<S, A> Widget for TextField<S, A> {
    type Action = A;
    type State = S;

@@ -145,23 +145,6 @@ impl<S, A> View for TextField<S, A> {
        }
    }

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

-
    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.props = props.clone();
-

-
                if self.state.text.is_none() {
-
                    self.state.cursor_position = props.text.len().saturating_sub(1);
-
                }
-
                self.state.text = Some(props.text.clone());
-
            }
-
        }
-
    }
-

    fn handle_event(&mut self, key: Key) {
        match key {
            Key::Char(to_insert)
@@ -187,9 +170,20 @@ impl<S, A> View for TextField<S, A> {
            (on_event)(&self.state, self.base.action_tx.clone());
        }
    }
-
}

-
impl<S, A> Widget 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.props = props.clone();
+

+
                if self.state.text.is_none() {
+
                    self.state.cursor_position = props.text.len().saturating_sub(1);
+
                }
+
                self.state.text = Some(props.text.clone());
+
            }
+
        }
+
    }
+

    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(TextFieldProps::from_boxed_any)
@@ -241,4 +235,8 @@ impl<S, A> Widget for TextField<S, A> {
            }
        }
    }
+

+
    fn base_mut(&mut self) -> &mut BaseView<S, A> {
+
        &mut self.base
+
    }
}
modified src/ui/widget/text.rs
@@ -7,7 +7,7 @@ use termion::event::Key;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::text::Text;

-
use super::{BaseView, Properties, View, Widget};
+
use super::{BaseView, Properties, Widget};

#[derive(Clone)]
pub struct ParagraphProps<'a> {
@@ -141,7 +141,7 @@ impl<'a, S, A> Paragraph<'a, S, A> {
    }
}

-
impl<'a: 'static, S, A> View for Paragraph<'a, S, A> {
+
impl<'a: 'static, S, A> Widget for Paragraph<'a, S, A> {
    type Action = A;
    type State = S;

@@ -163,15 +163,6 @@ impl<'a: 'static, S, A> View for Paragraph<'a, S, A> {
        }
    }

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

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

    fn handle_event(&mut self, key: Key) {
        let len = self.props.content.lines.len() + 1;
        let page_size = self.props.page_size;
@@ -202,9 +193,12 @@ impl<'a: 'static, S, A> View for Paragraph<'a, S, A> {
            (on_event)(&self.state, self.base.action_tx.clone());
        }
    }
-
}

-
impl<'a: 'static, S, A> Widget for Paragraph<'a, S, A> {
+
    fn update(&mut self, state: &S) {
+
        self.props =
+
            ParagraphProps::from_callback(self.base.on_update, state).unwrap_or(self.props.clone());
+
    }
+

    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<Box<dyn Any>>) {
        let props = props
            .and_then(ParagraphProps::from_boxed_any)
@@ -218,4 +212,8 @@ impl<'a: 'static, S, A> Widget for Paragraph<'a, S, A> {

        frame.render_widget(content, content_area);
    }
+

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