Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
bin: Use improved widget base interface
Erik Kundt committed 2 years ago
commit f1df2ea2a92557e8846fa2ab3cf45dc3d2bef492
parent e5eda52f43b538d62b62b9cfd293a708bf904f49
3 files changed +208 -202
modified bin/commands/inbox/select/ui.rs
@@ -22,7 +22,7 @@ use tui::ui::widget::input::{TextField, TextFieldProps};
use tui::ui::widget::list::{Table, TableProps, TableUtils};
use tui::ui::widget::text::{Paragraph, ParagraphProps};
use tui::ui::widget::window::{Shortcuts, ShortcutsProps};
-
use tui::ui::widget::{BaseView, BoxedAny, Properties, RenderProps, Widget};
+
use tui::ui::widget::{BoxedAny, Properties, RenderProps, Widget, WidgetBase};

use tui::Selection;

@@ -113,7 +113,7 @@ impl<'a> BoxedAny for BrowserProps<'a> {}

pub struct Browser<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: BrowserProps<'a>,
    /// Notification widget
@@ -130,11 +130,7 @@ impl<'a: 'static> Widget for Browser<'a> {
        let props = BrowserProps::from(state);

        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: props.clone(),
            notifications: Container::new(state, action_tx.clone())
                .header(
@@ -155,8 +151,11 @@ impl<'a: 'static> Widget for Browser<'a> {
                            table
                                .downcast_mut::<Table<State, Action, NotificationItem, 9>>()
                                .and_then(|table| {
-
                                    let selected = table.selected();
-
                                    table.base_mut().send(Action::Select { selected }).ok()
+
                                    table
+
                                        .send(Action::Select {
+
                                            selected: table.selected(),
+
                                        })
+
                                        .ok()
                                });
                        })
                        .on_update(|state| {
@@ -196,7 +195,7 @@ impl<'a: 'static> Widget for Browser<'a> {
        } else {
            match key {
                Key::Char('/') => {
-
                    let _ = self.base.send(Action::OpenSearch);
+
                    let _ = self.send(Action::OpenSearch);
                }
                Key::Char('\n') => {
                    self.props
@@ -260,7 +259,11 @@ impl<'a: 'static> Widget for Browser<'a> {
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -302,7 +305,7 @@ impl<'a> BoxedAny for BrowserPageProps<'a> {}

pub struct BrowserPage<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: BrowserPageProps<'a>,
    /// Sections widget
@@ -319,11 +322,7 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
        let props = BrowserPageProps::from(state);

        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: props.clone(),
            sections: SectionGroup::new(state, action_tx.clone())
                .section(Browser::new(state, action_tx.clone()).to_boxed())
@@ -350,10 +349,10 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
        if self.props.handle_keys {
            match key {
                Key::Esc | Key::Ctrl('c') => {
-
                    let _ = self.base.send(Action::Exit { selection: None });
+
                    let _ = self.send(Action::Exit { selection: None });
                }
                Key::Char('?') => {
-
                    let _ = self.base.send(Action::OpenHelp);
+
                    let _ = self.send(Action::OpenHelp);
                }
                _ => {}
            }
@@ -384,11 +383,15 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
            .render(frame, RenderProps::from(shortcuts_area));

        if page_size != self.props.page_size {
-
            let _ = self.base.send(Action::BrowserPageSize(page_size));
+
            let _ = self.send(Action::BrowserPageSize(page_size));
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -399,7 +402,7 @@ impl Properties for SearchProps {}

pub struct Search {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    _props: SearchProps,
    /// Search input field
@@ -414,44 +417,39 @@ impl Widget for Search {
    where
        Self: Sized,
    {
-
        let input = TextField::new(state, action_tx.clone())
-
            .on_event(|field| {
-
                field
-
                    .downcast_mut::<TextField<State, Action>>()
-
                    .and_then(|field| {
-
                        let text = field.text().unwrap_or(&String::new()).to_string();
-
                        field
-
                            .base_mut()
-
                            .send(Action::UpdateSearch { value: text })
-
                            .ok()
-
                    });
-
            })
-
            .on_update(|state| {
-
                TextFieldProps::default()
-
                    .text(&state.browser.search.read().to_string())
-
                    .title("Search")
-
                    .inline(true)
-
                    .to_boxed()
-
            })
-
            .to_boxed();
        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            _props: SearchProps {},
-
            input,
+
            input: TextField::new(state, action_tx.clone())
+
                .on_event(|widget| {
+
                    widget
+
                        .downcast_mut::<TextField<State, Action>>()
+
                        .and_then(|field| {
+
                            field
+
                                .send(Action::UpdateSearch {
+
                                    value: field.text().unwrap_or(&String::new()).to_string(),
+
                                })
+
                                .ok()
+
                        });
+
                })
+
                .on_update(|state| {
+
                    TextFieldProps::default()
+
                        .text(&state.browser.search.read().to_string())
+
                        .title("Search")
+
                        .inline(true)
+
                        .to_boxed()
+
                })
+
                .to_boxed(),
        }
    }

    fn handle_event(&mut self, key: termion::event::Key) {
        match key {
            Key::Esc => {
-
                let _ = self.base.send(Action::CloseSearch);
+
                let _ = self.send(Action::CloseSearch);
            }
            Key::Char('\n') => {
-
                let _ = self.base.send(Action::ApplySearch);
+
                let _ = self.send(Action::ApplySearch);
            }
            _ => {
                self.input.handle_event(key);
@@ -471,7 +469,11 @@ impl Widget for Search {
        self.input.render(frame, RenderProps::from(layout[0]));
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -501,7 +503,7 @@ impl<'a> BoxedAny for HelpPageProps<'a> {}

pub struct HelpPage<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: HelpPageProps<'a>,
    /// Content widget
@@ -519,11 +521,7 @@ impl<'a: 'static> Widget for HelpPage<'a> {
        Self: Sized,
    {
        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: HelpPageProps::from(state),
            content: Container::new(state, action_tx.clone())
                .header(
@@ -541,10 +539,10 @@ impl<'a: 'static> Widget for HelpPage<'a> {
                            paragraph
                                .downcast_mut::<Paragraph<'_, State, Action>>()
                                .and_then(|paragraph| {
-
                                    let progress = paragraph.progress();
                                    paragraph
-
                                        .base_mut()
-
                                        .send(Action::ScrollHelp { progress })
+
                                        .send(Action::ScrollHelp {
+
                                            progress: paragraph.progress(),
+
                                        })
                                        .ok()
                                });
                        })
@@ -593,10 +591,10 @@ impl<'a: 'static> Widget for HelpPage<'a> {
    fn handle_event(&mut self, key: termion::event::Key) {
        match key {
            Key::Esc | Key::Ctrl('c') => {
-
                let _ = self.base.send(Action::Exit { selection: None });
+
                let _ = self.send(Action::Exit { selection: None });
            }
            Key::Char('?') => {
-
                let _ = self.base.send(Action::LeavePage);
+
                let _ = self.send(Action::LeavePage);
            }
            _ => {
                self.content.handle_event(key);
@@ -624,11 +622,15 @@ impl<'a: 'static> Widget for HelpPage<'a> {
            .render(frame, RenderProps::from(shortcuts_area));

        if page_size != self.props.page_size {
-
            let _ = self.base.send(Action::HelpPageSize(page_size));
+
            let _ = self.send(Action::HelpPageSize(page_size));
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
modified bin/commands/issue/select/ui.rs
@@ -24,7 +24,7 @@ use tui::ui::widget::input::{TextField, TextFieldProps};
use tui::ui::widget::list::{Table, TableProps, TableUtils};
use tui::ui::widget::text::{Paragraph, ParagraphProps};
use tui::ui::widget::window::{Shortcuts, ShortcutsProps};
-
use tui::ui::widget::{BaseView, BoxedAny, Properties, RenderProps, Widget};
+
use tui::ui::widget::{BoxedAny, Properties, RenderProps, Widget, WidgetBase};

use tui::Selection;

@@ -133,7 +133,7 @@ impl<'a> BoxedAny for BrowserProps<'a> {}

pub struct Browser<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: BrowserProps<'a>,
    /// Notifications widget
@@ -150,11 +150,7 @@ impl<'a: 'static> Widget for Browser<'a> {
        let props = BrowserProps::from(state);

        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: BrowserProps::from(state),
            issues: Container::new(state, action_tx.clone())
                .header(
@@ -169,8 +165,11 @@ impl<'a: 'static> Widget for Browser<'a> {
                            table
                                .downcast_mut::<Table<State, Action, IssueItem, 8>>()
                                .and_then(|table| {
-
                                    let selected = table.selected();
-
                                    table.base_mut().send(Action::Select { selected }).ok()
+
                                    table
+
                                        .send(Action::Select {
+
                                            selected: table.selected(),
+
                                        })
+
                                        .ok()
                                });
                        })
                        .on_update(|state| {
@@ -212,7 +211,7 @@ impl<'a: 'static> Widget for Browser<'a> {
        } else {
            match key {
                Key::Char('/') => {
-
                    let _ = self.base.send(Action::OpenSearch);
+
                    let _ = self.send(Action::OpenSearch);
                }
                Key::Char('\n') => {
                    let operation = match self.props.mode {
@@ -279,7 +278,11 @@ impl<'a: 'static> Widget for Browser<'a> {
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -321,7 +324,7 @@ impl<'a> BoxedAny for BrowserPageProps<'a> {}

pub struct BrowserPage<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: BrowserPageProps<'a>,
    /// Sections widget
@@ -338,11 +341,7 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
        let props = BrowserPageProps::from(state);

        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: props.clone(),
            sections: SectionGroup::new(state, action_tx.clone())
                .section(Browser::new(state, action_tx.clone()).to_boxed())
@@ -369,10 +368,10 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
        if self.props.handle_keys {
            match key {
                Key::Esc | Key::Ctrl('c') => {
-
                    let _ = self.base.send(Action::Exit { selection: None });
+
                    let _ = self.send(Action::Exit { selection: None });
                }
                Key::Char('?') => {
-
                    let _ = self.base.send(Action::OpenHelp);
+
                    let _ = self.send(Action::OpenHelp);
                }
                _ => {}
            }
@@ -403,11 +402,15 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
            .render(frame, RenderProps::from(shortcuts_area));

        if page_size != self.props.page_size {
-
            let _ = self.base.send(Action::BrowserPageSize(page_size));
+
            let _ = self.send(Action::BrowserPageSize(page_size));
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -418,7 +421,7 @@ impl Properties for SearchProps {}

pub struct Search {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    _props: SearchProps,
    /// Search input field
@@ -433,44 +436,39 @@ impl Widget for Search {
    where
        Self: Sized,
    {
-
        let input = TextField::new(state, action_tx.clone())
-
            .on_event(|field| {
-
                field
-
                    .downcast_mut::<TextField<State, Action>>()
-
                    .and_then(|field| {
-
                        let text = field.text().unwrap_or(&String::new()).to_string();
-
                        field
-
                            .base_mut()
-
                            .send(Action::UpdateSearch { value: text })
-
                            .ok()
-
                    });
-
            })
-
            .on_update(|state| {
-
                TextFieldProps::default()
-
                    .text(&state.browser.search.read().to_string())
-
                    .title("Search")
-
                    .inline(true)
-
                    .to_boxed()
-
            })
-
            .to_boxed();
        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            _props: SearchProps {},
-
            input,
+
            input: TextField::new(state, action_tx.clone())
+
                .on_event(|widget| {
+
                    widget
+
                        .downcast_mut::<TextField<State, Action>>()
+
                        .and_then(|field| {
+
                            field
+
                                .send(Action::UpdateSearch {
+
                                    value: field.text().unwrap_or(&String::new()).to_string(),
+
                                })
+
                                .ok()
+
                        });
+
                })
+
                .on_update(|state| {
+
                    TextFieldProps::default()
+
                        .text(&state.browser.search.read().to_string())
+
                        .title("Search")
+
                        .inline(true)
+
                        .to_boxed()
+
                })
+
                .to_boxed(),
        }
    }

    fn handle_event(&mut self, key: termion::event::Key) {
        match key {
            Key::Esc => {
-
                let _ = self.base.send(Action::CloseSearch);
+
                let _ = self.send(Action::CloseSearch);
            }
            Key::Char('\n') => {
-
                let _ = self.base.send(Action::ApplySearch);
+
                let _ = self.send(Action::ApplySearch);
            }
            _ => {
                self.input.handle_event(key);
@@ -490,7 +488,11 @@ impl Widget for Search {
        self.input.render(frame, RenderProps::from(layout[0]));
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -517,7 +519,7 @@ impl<'a> BoxedAny for HelpPageProps<'a> {}

pub struct HelpPage<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: HelpPageProps<'a>,
    /// Content widget
@@ -535,11 +537,7 @@ impl<'a: 'static> Widget for HelpPage<'a> {
        Self: Sized,
    {
        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: HelpPageProps::from(state),
            content: Container::new(state, action_tx.clone())
                .header(
@@ -553,25 +551,25 @@ impl<'a: 'static> Widget for HelpPage<'a> {
                )
                .content(
                    Paragraph::new(state, action_tx.clone())
-
                        .on_update(|state| {
-
                            let props = HelpPageProps::from(state);
-

-
                            ParagraphProps::default()
-
                                .text(&help_text())
-
                                .page_size(props.page_size)
-
                                .to_boxed()
-
                        })
                        .on_event(|paragraph| {
                            paragraph
                                .downcast_mut::<Paragraph<'_, State, Action>>()
                                .and_then(|paragraph| {
-
                                    let progress = paragraph.progress();
                                    paragraph
-
                                        .base_mut()
-
                                        .send(Action::ScrollHelp { progress })
+
                                        .send(Action::ScrollHelp {
+
                                            progress: paragraph.progress(),
+
                                        })
                                        .ok()
                                });
                        })
+
                        .on_update(|state| {
+
                            let props = HelpPageProps::from(state);
+

+
                            ParagraphProps::default()
+
                                .text(&help_text())
+
                                .page_size(props.page_size)
+
                                .to_boxed()
+
                        })
                        .to_boxed(),
                )
                .footer(
@@ -609,10 +607,10 @@ impl<'a: 'static> Widget for HelpPage<'a> {
    fn handle_event(&mut self, key: termion::event::Key) {
        match key {
            Key::Esc | Key::Ctrl('c') => {
-
                let _ = self.base.send(Action::Exit { selection: None });
+
                let _ = self.send(Action::Exit { selection: None });
            }
            Key::Char('?') => {
-
                let _ = self.base.send(Action::LeavePage);
+
                let _ = self.send(Action::LeavePage);
            }
            _ => {
                self.content.handle_event(key);
@@ -640,11 +638,15 @@ impl<'a: 'static> Widget for HelpPage<'a> {
            .render(frame, RenderProps::from(shortcuts_area));

        if page_size != self.props.page_size {
-
            let _ = self.base.send(Action::HelpPageSize(page_size));
+
            let _ = self.send(Action::HelpPageSize(page_size));
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
modified bin/commands/patch/select/ui.rs
@@ -25,7 +25,7 @@ use tui::ui::widget::input::{TextField, TextFieldProps};
use tui::ui::widget::list::{Table, TableProps, TableUtils};
use tui::ui::widget::text::{Paragraph, ParagraphProps};
use tui::ui::widget::window::{Shortcuts, ShortcutsProps};
-
use tui::ui::widget::{BaseView, BoxedAny, Properties, RenderProps, Widget};
+
use tui::ui::widget::{BoxedAny, Properties, RenderProps, Widget, WidgetBase};

use tui::Selection;

@@ -133,7 +133,7 @@ impl<'a: 'static> BoxedAny for BrowserProps<'a> {}

pub struct Browser<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: BrowserProps<'a>,
    /// Patches widget
@@ -150,11 +150,7 @@ impl<'a: 'static> Widget for Browser<'a> {
        let props = BrowserProps::from(state);

        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: props.clone(),
            patches: Container::new(state, action_tx.clone())
                .header(
@@ -169,8 +165,11 @@ impl<'a: 'static> Widget for Browser<'a> {
                            table
                                .downcast_mut::<Table<State, Action, PatchItem, 9>>()
                                .and_then(|table| {
-
                                    let selected = table.selected();
-
                                    table.base_mut().send(Action::Select { selected }).ok()
+
                                    table
+
                                        .send(Action::Select {
+
                                            selected: table.selected(),
+
                                        })
+
                                        .ok()
                                });
                        })
                        .on_update(|state| {
@@ -212,13 +211,13 @@ impl<'a: 'static> Widget for Browser<'a> {
        } else {
            match key {
                Key::Esc | Key::Ctrl('c') => {
-
                    let _ = self.base.send(Action::Exit { selection: None });
+
                    let _ = self.send(Action::Exit { selection: None });
                }
                Key::Char('?') => {
-
                    let _ = self.base.send(Action::OpenHelp);
+
                    let _ = self.send(Action::OpenHelp);
                }
                Key::Char('/') => {
-
                    let _ = self.base.send(Action::OpenSearch);
+
                    let _ = self.send(Action::OpenSearch);
                }
                Key::Char('\n') => {
                    let operation = match self.props.mode {
@@ -301,7 +300,11 @@ impl<'a: 'static> Widget for Browser<'a> {
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -344,7 +347,7 @@ impl<'a> BoxedAny for BrowserPageProps<'a> {}

pub struct BrowserPage<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: BrowserPageProps<'a>,
    /// Sections widget
@@ -361,11 +364,7 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
        let props = BrowserPageProps::from(state);

        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: props.clone(),
            sections: SectionGroup::new(state, action_tx.clone())
                .section(Browser::new(state, action_tx.clone()).to_boxed())
@@ -392,10 +391,10 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
        if self.props.handle_keys {
            match key {
                Key::Esc | Key::Ctrl('c') => {
-
                    let _ = self.base.send(Action::Exit { selection: None });
+
                    let _ = self.send(Action::Exit { selection: None });
                }
                Key::Char('?') => {
-
                    let _ = self.base.send(Action::OpenHelp);
+
                    let _ = self.send(Action::OpenHelp);
                }
                _ => {}
            }
@@ -426,11 +425,15 @@ impl<'a: 'static> Widget for BrowserPage<'a> {
            .render(frame, RenderProps::from(shortcuts_area));

        if page_size != self.props.page_size {
-
            let _ = self.base.send(Action::BrowserPageSize(page_size));
+
            let _ = self.send(Action::BrowserPageSize(page_size));
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -441,7 +444,7 @@ impl Properties for SearchProps {}

pub struct Search {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    _props: SearchProps,
    /// Search input field
@@ -456,44 +459,39 @@ impl Widget for Search {
    where
        Self: Sized,
    {
-
        let input = TextField::new(state, action_tx.clone())
-
            .on_event(|field| {
-
                field
-
                    .downcast_mut::<TextField<State, Action>>()
-
                    .and_then(|field| {
-
                        let text = field.text().unwrap_or(&String::new()).to_string();
-
                        field
-
                            .base_mut()
-
                            .send(Action::UpdateSearch { value: text })
-
                            .ok()
-
                    });
-
            })
-
            .on_update(|state| {
-
                TextFieldProps::default()
-
                    .text(&state.browser.search.read().to_string())
-
                    .title("Search")
-
                    .inline(true)
-
                    .to_boxed()
-
            })
-
            .to_boxed();
        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            _props: SearchProps {},
-
            input,
+
            input: TextField::new(state, action_tx.clone())
+
                .on_event(|widget| {
+
                    widget
+
                        .downcast_mut::<TextField<State, Action>>()
+
                        .and_then(|field| {
+
                            field
+
                                .send(Action::UpdateSearch {
+
                                    value: field.text().unwrap_or(&String::new()).to_string(),
+
                                })
+
                                .ok()
+
                        });
+
                })
+
                .on_update(|state| {
+
                    TextFieldProps::default()
+
                        .text(&state.browser.search.read().to_string())
+
                        .title("Search")
+
                        .inline(true)
+
                        .to_boxed()
+
                })
+
                .to_boxed(),
        }
    }

    fn handle_event(&mut self, key: termion::event::Key) {
        match key {
            Key::Esc => {
-
                let _ = self.base.send(Action::CloseSearch);
+
                let _ = self.send(Action::CloseSearch);
            }
            Key::Char('\n') => {
-
                let _ = self.base.send(Action::ApplySearch);
+
                let _ = self.send(Action::ApplySearch);
            }
            _ => {
                self.input.handle_event(key);
@@ -513,7 +511,11 @@ impl Widget for Search {
        self.input.render(frame, RenderProps::from(layout[0]));
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}
@@ -540,7 +542,7 @@ impl<'a> BoxedAny for HelpPageProps<'a> {}

pub struct HelpPage<'a> {
    /// Internal base
-
    base: BaseView<State, Action>,
+
    base: WidgetBase<State, Action>,
    /// Internal props
    props: HelpPageProps<'a>,
    /// Content widget
@@ -558,11 +560,7 @@ impl<'a: 'static> Widget for HelpPage<'a> {
        Self: Sized,
    {
        Self {
-
            base: BaseView {
-
                action_tx: action_tx.clone(),
-
                on_update: None,
-
                on_event: None,
-
            },
+
            base: WidgetBase::new(action_tx.clone()),
            props: HelpPageProps::from(state),
            content: Container::new(state, action_tx.clone())
                .header(
@@ -580,10 +578,10 @@ impl<'a: 'static> Widget for HelpPage<'a> {
                            paragraph
                                .downcast_mut::<Paragraph<'_, State, Action>>()
                                .and_then(|paragraph| {
-
                                    let progress = paragraph.progress();
                                    paragraph
-
                                        .base_mut()
-
                                        .send(Action::ScrollHelp { progress })
+
                                        .send(Action::ScrollHelp {
+
                                            progress: paragraph.progress(),
+
                                        })
                                        .ok()
                                });
                        })
@@ -632,10 +630,10 @@ impl<'a: 'static> Widget for HelpPage<'a> {
    fn handle_event(&mut self, key: termion::event::Key) {
        match key {
            Key::Esc | Key::Ctrl('c') => {
-
                let _ = self.base.send(Action::Exit { selection: None });
+
                let _ = self.send(Action::Exit { selection: None });
            }
            Key::Char('?') => {
-
                let _ = self.base.send(Action::LeavePage);
+
                let _ = self.send(Action::LeavePage);
            }
            _ => {
                self.content.handle_event(key);
@@ -662,11 +660,15 @@ impl<'a: 'static> Widget for HelpPage<'a> {
            .render(frame, RenderProps::from(shortcuts_area));

        if page_size != self.props.page_size {
-
            let _ = self.base.send(Action::HelpPageSize(page_size));
+
            let _ = self.send(Action::HelpPageSize(page_size));
        }
    }

-
    fn base_mut(&mut self) -> &mut BaseView<State, Action> {
+
    fn base(&self) -> &WidgetBase<State, Action> {
+
        &self.base
+
    }
+

+
    fn base_mut(&mut self) -> &mut WidgetBase<State, Action> {
        &mut self.base
    }
}