Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Rename action to message
Erik Kundt committed 2 years ago
commit 746e8eff5c90b93005a804b0ece1c9ef7f106702
parent 71452e7e305558b00c674e7cd86f06f429b5cc15
9 files changed +146 -142
modified src/lib.rs
@@ -105,9 +105,9 @@ impl<T> PageStack<T> {
}

/// A multi-producer, single-consumer message channel.
-
pub struct Channel<A> {
-
    pub tx: UnboundedSender<A>,
-
    pub rx: UnboundedReceiver<A>,
+
pub struct Channel<M> {
+
    pub tx: UnboundedSender<M>,
+
    pub rx: UnboundedReceiver<M>,
}

impl<A> Default for Channel<A> {
@@ -120,16 +120,16 @@ impl<A> Default for Channel<A> {
/// Initialize a `Store` with the `State` given and a `Frontend` with the `Widget` given,
/// and run their main loops concurrently. Connect them to the `Channel` and also to
/// an interrupt broadcast channel also initialized in this function.
-
pub async fn run<S, A, W, P>(channel: Channel<A>, state: S, root: W) -> Result<Option<P>>
+
pub async fn run<S, M, W, P>(channel: Channel<M>, state: S, root: W) -> Result<Option<P>>
where
-
    S: State<P, Action = A> + Clone + Debug + Send + Sync + 'static,
-
    W: Widget<State = S, Action = A>,
+
    S: State<P, Message = M> + Clone + Debug + Send + Sync + 'static,
+
    W: Widget<State = S, Message = M>,
    P: Clone + Debug + Send + Sync + 'static,
{
    let (terminator, mut interrupt_rx) = task::create_termination();

-
    let (store, state_rx) = store::Store::<A, S, P>::new();
-
    let frontend = Frontend::<A>::new(channel.tx.clone());
+
    let (store, state_rx) = store::Store::<S, M, P>::new();
+
    let frontend = Frontend::<M>::new(channel.tx.clone());

    tokio::try_join!(
        store.main_loop(state, terminator, channel.rx, interrupt_rx.resubscribe()),
modified src/store.rs
@@ -17,11 +17,11 @@ pub trait State<P>
where
    P: Clone + Debug + Send + Sync,
{
-
    type Action;
+
    type Message;

    /// Handle a user-defined application message and return an `Exit` object
    /// in case the received message requested the application to also quit.
-
    fn handle_action(&mut self, action: Self::Action) -> Option<Exit<P>>;
+
    fn update(&mut self, message: Self::Message) -> Option<Exit<P>>;

    /// Handle recurring tick.
    fn tick(&self);
@@ -29,16 +29,16 @@ where

/// The `Store` updates the applications' state concurrently. It handles
/// messages coming from the frontend and updates the state accordingly.
-
pub struct Store<A, S, P>
+
pub struct Store<S, M, P>
where
    S: State<P> + Clone + Send + Sync,
    P: Clone + Debug + Send + Sync,
{
    state_tx: UnboundedSender<S>,
-
    _phantom: PhantomData<(A, P)>,
+
    _phantom: PhantomData<(M, P)>,
}

-
impl<A, S, P> Store<A, S, P>
+
impl<S, M, P> Store<S, M, P>
where
    S: State<P> + Clone + Send + Sync,
    P: Clone + Debug + Send + Sync,
@@ -56,9 +56,9 @@ where
    }
}

-
impl<A, S, P> Store<A, S, P>
+
impl<S, M, P> Store<S, M, P>
where
-
    S: State<P, Action = A> + Clone + Debug + Send + Sync + 'static,
+
    S: State<P, Message = M> + Clone + Debug + Send + Sync + 'static,
    P: Clone + Debug + Send + Sync + 'static,
{
    /// By calling `main_loop`, the store will wait for new messages coming
@@ -69,7 +69,7 @@ where
        self,
        mut state: S,
        mut terminator: Terminator<P>,
-
        mut action_rx: UnboundedReceiver<A>,
+
        mut message_rx: UnboundedReceiver<M>,
        mut interrupt_rx: broadcast::Receiver<Interrupted<P>>,
    ) -> anyhow::Result<Interrupted<P>> {
        // Send the initial state once
@@ -79,10 +79,10 @@ where

        let result = loop {
            tokio::select! {
-
                // Handle the actions coming from the frontend
+
                // Handle the messages coming from the frontend
                // and process them to do async operations
-
                Some(action) = action_rx.recv() => {
-
                    if let Some(exit) = state.handle_action(action) {
+
                Some(message) = message_rx.recv() => {
+
                    if let Some(exit) = state.update(message) {
                        let interrupted = Interrupted::User { payload: exit.value };
                        let _ = terminator.terminate(interrupted.clone());

modified src/ui.rs
@@ -29,16 +29,14 @@ const INLINE_HEIGHT: usize = 20;
///
/// Once created and run with `main_loop`, the `Frontend` will wait for new messages
/// being sent on either the terminal event, the state or the interrupt message channel.
-
pub struct Frontend<A> {
-
    action_tx: mpsc::UnboundedSender<A>,
+
pub struct Frontend<M> {
+
    tx: mpsc::UnboundedSender<M>,
}

-
impl<A> Frontend<A> {
+
impl<M> Frontend<M> {
    /// Create a new `Frontend` storing the sending end of a message channel.
-
    pub fn new(action_tx: mpsc::UnboundedSender<A>) -> Self {
-
        Self {
-
            action_tx: action_tx.clone(),
-
        }
+
    pub fn new(tx: mpsc::UnboundedSender<M>) -> Self {
+
        Self { tx: tx.clone() }
    }

    /// By calling `main_loop`, the `Frontend` will wait for new messages being sent
@@ -63,7 +61,7 @@ impl<A> Frontend<A> {
    ) -> anyhow::Result<Interrupted<P>>
    where
        S: State<P>,
-
        W: Widget<State = S, Action = A>,
+
        W: Widget<State = S, Message = M>,
        P: Clone + Send + Sync + Debug,
    {
        let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);
@@ -79,7 +77,7 @@ impl<A> Frontend<A> {
                    root.update(&state);
                    root
                }
-
                None => W::new(&state, self.action_tx.clone()),
+
                None => W::new(&state, self.tx.clone()),
            }
        };

@@ -110,12 +108,4 @@ impl<A> Frontend<A> {

        result
    }
-

-
    // pub fn action_tx(&self) -> UnboundedSender<A> {
-
    //     self.action_tx.clone()
-
    // }
-

-
    // pub fn action_rx(&self) -> UnboundedReceiver<A> {
-
    //     self.action_rx.clone()
-
    // }
}
modified src/ui/widget.rs
@@ -14,35 +14,35 @@ use termion::event::Key;
use ratatui::prelude::*;
use ratatui::widgets::Cell;

-
pub type BoxedWidget<S, A> = Box<dyn Widget<State = S, Action = A>>;
+
pub type BoxedWidget<S, M> = Box<dyn Widget<State = S, Message = M>>;

pub type UpdateCallback<S> = fn(&S) -> Box<dyn Any>;
pub type EventCallback = fn(&mut dyn Any);

/// A `WidgetBase` provides common functionality to a `Widget`. It's used to store
/// event and update callbacks as well sending messages to the UI's message channel.
-
pub struct WidgetBase<S, A> {
+
pub struct WidgetBase<S, M> {
    /// Message sender
-
    pub action_tx: UnboundedSender<A>,
+
    pub tx: UnboundedSender<M>,
    /// Custom update handler
    pub on_update: Option<UpdateCallback<S>>,
    /// Additional custom event handler
    pub on_event: Option<EventCallback>,
}

-
impl<S, A> WidgetBase<S, A> {
+
impl<S, M> WidgetBase<S, M> {
    /// Create a new `WidgetBase` with no callbacks set.
-
    pub fn new(action_tx: UnboundedSender<A>) -> Self {
+
    pub fn new(tx: UnboundedSender<M>) -> Self {
        Self {
-
            action_tx: action_tx.clone(),
+
            tx: tx.clone(),
            on_update: None,
            on_event: None,
        }
    }

    /// Send a message to the internal channel.
-
    pub fn send(&self, action: A) -> Result<(), SendError<A>> {
-
        self.action_tx.send(action)
+
    pub fn send(&self, message: M) -> Result<(), SendError<M>> {
+
        self.tx.send(message)
    }
}

@@ -87,11 +87,11 @@ impl From<Rect> for RenderProps {
/// This is the trait that you should implement to define a custom `Widget`.
pub trait Widget {
    type State;
-
    type Action;
+
    type Message;

    /// Should return a new view with props build from state (if type is known) and a
    /// message sender set.
-
    fn new(state: &Self::State, action_tx: UnboundedSender<Self::Action>) -> Self
+
    fn new(state: &Self::State, tx: UnboundedSender<Self::Message>) -> Self
    where
        Self: Sized;

@@ -119,14 +119,14 @@ pub trait Widget {
    fn render(&self, frame: &mut Frame, props: RenderProps);

    /// Return a reference to this widgets' base.
-
    fn base(&self) -> &WidgetBase<Self::State, Self::Action>;
+
    fn base(&self) -> &WidgetBase<Self::State, Self::Message>;

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

    /// Send a message to the widgets' base channel.
-
    fn send(&self, action: Self::Action) -> Result<(), SendError<Self::Action>> {
-
        self.base().send(action)
+
    fn send(&self, message: Self::Message) -> Result<(), SendError<Self::Message>> {
+
        self.base().send(message)
    }

    /// Should set the optional custom event handler.
modified src/ui/widget/container.rs
@@ -67,11 +67,11 @@ impl<'a> Default for HeaderProps<'a> {
impl<'a: 'static> Properties for HeaderProps<'a> {}
impl<'a: 'static> BoxedAny for HeaderProps<'a> {}

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

impl<'a, S, A> Header<'a, S, A> {
@@ -87,13 +87,13 @@ impl<'a, S, A> Header<'a, S, A> {
    }
}

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

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: HeaderProps::default(),
        }
    }
@@ -166,11 +166,11 @@ impl<'a: 'static, S, A> Widget for Header<'a, S, A> {
        frame.render_widget(header, header_layout[0]);
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

-
    fn base_mut(&mut self) -> &mut WidgetBase<S, A> {
+
    fn base_mut(&mut self) -> &mut WidgetBase<S, M> {
        &mut self.base
    }
}
@@ -208,14 +208,14 @@ impl<'a> Default for FooterProps<'a> {
impl<'a: 'static> Properties for FooterProps<'a> {}
impl<'a: 'static> BoxedAny for FooterProps<'a> {}

-
pub struct Footer<'a, S, A> {
+
pub struct Footer<'a, S, M> {
    /// Internal props
    props: FooterProps<'a>,
    /// Internal base
-
    base: WidgetBase<S, A>,
+
    base: WidgetBase<S, M>,
}

-
impl<'a, S, A> Footer<'a, S, A> {
+
impl<'a, S, M> Footer<'a, S, M> {
    pub fn columns(mut self, columns: Vec<Column<'a>>) -> Self {
        self.props.columns = columns;
        self
@@ -250,13 +250,13 @@ impl<'a, S, A> Footer<'a, S, A> {
    }
}

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

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: FooterProps::default(),
        }
    }
@@ -305,11 +305,11 @@ impl<'a: 'static, S, A> Widget for Footer<'a, S, A> {
        }
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

-
    fn base_mut(&mut self) -> &mut WidgetBase<S, A> {
+
    fn base_mut(&mut self) -> &mut WidgetBase<S, M> {
        &mut self.base
    }
}
@@ -329,46 +329,46 @@ impl ContainerProps {
impl Properties for ContainerProps {}
impl BoxedAny for ContainerProps {}

-
pub struct Container<S, A> {
+
pub struct Container<S, M> {
    /// Internal base
-
    base: WidgetBase<S, A>,
+
    base: WidgetBase<S, M>,
    /// Internal props
    props: ContainerProps,
    /// Container header
-
    header: Option<BoxedWidget<S, A>>,
+
    header: Option<BoxedWidget<S, M>>,
    /// Content widget
-
    content: Option<BoxedWidget<S, A>>,
+
    content: Option<BoxedWidget<S, M>>,
    /// Container footer
-
    footer: Option<BoxedWidget<S, A>>,
+
    footer: Option<BoxedWidget<S, M>>,
}

-
impl<S, A> Container<S, A> {
-
    pub fn header(mut self, header: BoxedWidget<S, A>) -> Self {
+
impl<S, M> Container<S, M> {
+
    pub fn header(mut self, header: BoxedWidget<S, M>) -> Self {
        self.header = Some(header);
        self
    }

-
    pub fn content(mut self, content: BoxedWidget<S, A>) -> Self {
+
    pub fn content(mut self, content: BoxedWidget<S, M>) -> Self {
        self.content = Some(content);
        self
    }

-
    pub fn footer(mut self, footer: BoxedWidget<S, A>) -> Self {
+
    pub fn footer(mut self, footer: BoxedWidget<S, M>) -> Self {
        self.footer = Some(footer);
        self
    }
}

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

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self
    where
        Self: Sized,
    {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: ContainerProps::default(),
            header: None,
            content: None,
@@ -446,11 +446,11 @@ impl<S, A> Widget for Container<S, A> {
        }
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

-
    fn base_mut(&mut self) -> &mut WidgetBase<S, A> {
+
    fn base_mut(&mut self) -> &mut WidgetBase<S, M> {
        &mut self.base
    }
}
@@ -479,19 +479,19 @@ impl SectionGroupProps {
impl Properties for SectionGroupProps {}
impl BoxedAny for SectionGroupProps {}

-
pub struct SectionGroup<S, A> {
+
pub struct SectionGroup<S, M> {
    /// Internal base
-
    base: WidgetBase<S, A>,
+
    base: WidgetBase<S, M>,
    /// Internal table properties
    props: SectionGroupProps,
    /// All sections
-
    sections: Vec<BoxedWidget<S, A>>,
+
    sections: Vec<BoxedWidget<S, M>>,
    /// Internal selection and offset state
    state: SectionGroupState,
}

-
impl<S, A> SectionGroup<S, A> {
-
    pub fn section(mut self, section: BoxedWidget<S, A>) -> Self {
+
impl<S, M> SectionGroup<S, M> {
+
    pub fn section(mut self, section: BoxedWidget<S, M>) -> Self {
        self.sections.push(section);
        self
    }
@@ -515,13 +515,17 @@ impl<S, A> SectionGroup<S, A> {
    }
}

-
impl<S: 'static, A: 'static> Widget for SectionGroup<S, A> {
+
impl<S, M> Widget for SectionGroup<S, M>
+
where
+
    S: 'static,
+
    M: 'static,
+
{
    type State = S;
-
    type Action = A;
+
    type Message = M;

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: SectionGroupProps::default(),
            sections: vec![],
            state: SectionGroupState { focus: Some(0) },
@@ -579,11 +583,11 @@ impl<S: 'static, A: 'static> Widget for SectionGroup<S, A> {
        }
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

-
    fn base_mut(&mut self) -> &mut WidgetBase<S, A> {
+
    fn base_mut(&mut self) -> &mut WidgetBase<S, M> {
        &mut self.base
    }
}
modified src/ui/widget/input.rs
@@ -56,16 +56,16 @@ struct TextFieldState {

impl BoxedAny for TextFieldState {}

-
pub struct TextField<S, A> {
+
pub struct TextField<S, M> {
    /// Internal base
-
    base: WidgetBase<S, A>,
+
    base: WidgetBase<S, M>,
    /// Internal props
    props: TextFieldProps,
    /// Internal state
    state: TextFieldState,
}

-
impl<S, A> TextField<S, A> {
+
impl<S, M> TextField<S, M> {
    pub fn text(&self) -> Option<&String> {
        self.state.text.as_ref()
    }
@@ -131,13 +131,17 @@ impl<S, A> TextField<S, A> {
    }
}

-
impl<S: 'static, A: 'static> Widget for TextField<S, A> {
-
    type Action = A;
+
impl<S, M> Widget for TextField<S, M>
+
where
+
    S: 'static,
+
    M: 'static,
+
{
+
    type Message = M;
    type State = S;

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: TextFieldProps::default(),
            state: TextFieldState {
                text: None,
@@ -234,13 +238,13 @@ impl<S: 'static, A: 'static> Widget for TextField<S, A> {
        }
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

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

-
impl<S, A> BoxedAny for TextField<S, A> {}
+
impl<S, M> BoxedAny for TextField<S, M> {}
modified src/ui/widget/list.rs
@@ -88,19 +88,19 @@ impl<'a: 'static, R, const W: usize> BoxedAny for TableProps<'a, R, W> where R:

impl BoxedAny for TableState {}

-
pub struct Table<'a, S, A, R, const W: usize>
+
pub struct Table<'a, S, M, R, const W: usize>
where
    R: ToRow<W>,
{
    /// Internal base
-
    base: WidgetBase<S, A>,
+
    base: WidgetBase<S, M>,
    /// Internal table properties
    props: TableProps<'a, R, W>,
    /// Internal selection and offset state
    state: TableState,
}

-
impl<'a, S, A, R, const W: usize> Table<'a, S, A, R, W>
+
impl<'a, S, M, R, const W: usize> Table<'a, S, M, R, W>
where
    R: ToRow<W>,
{
@@ -159,16 +159,16 @@ where
    }
}

-
impl<'a: 'static, S: 'static, A: 'static, R, const W: usize> Widget for Table<'a, S, A, R, W>
+
impl<'a: 'static, S: 'a, M: 'a, R, const W: usize> Widget for Table<'a, S, M, R, W>
where
    R: ToRow<W> + Clone + 'static,
{
-
    type Action = A;
+
    type Message = M;
    type State = S;

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: TableProps::default(),
            state: TableState::default().with_selected(Some(0)),
        }
@@ -273,11 +273,11 @@ where
        }
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

-
    fn base_mut(&mut self) -> &mut WidgetBase<S, A> {
+
    fn base_mut(&mut self) -> &mut WidgetBase<S, M> {
        &mut self.base
    }
}
modified src/ui/widget/text.rs
@@ -53,16 +53,16 @@ struct ParagraphState {

impl BoxedAny for ParagraphState {}

-
pub struct Paragraph<'a, S, A> {
+
pub struct Paragraph<'a, S, M> {
    /// Internal base
-
    base: WidgetBase<S, A>,
+
    base: WidgetBase<S, M>,
    /// Internal props
    props: ParagraphProps<'a>,
    /// Internal state
    state: ParagraphState,
}

-
impl<'a, S, A> Paragraph<'a, S, A> {
+
impl<'a, S, M> Paragraph<'a, S, M> {
    pub fn scroll(&self) -> (u16, u16) {
        (self.state.offset as u16, 0)
    }
@@ -136,16 +136,21 @@ impl<'a, S, A> Paragraph<'a, S, A> {
    }
}

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

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self
    where
        Self: Sized,
    {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: ParagraphProps::default(),
            state: ParagraphState {
                offset: 0,
@@ -200,11 +205,11 @@ impl<'a: 'static, S: 'static, A: 'static> Widget for Paragraph<'a, S, A> {
        frame.render_widget(content, content_area);
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

-
    fn base_mut(&mut self) -> &mut WidgetBase<S, A> {
+
    fn base_mut(&mut self) -> &mut WidgetBase<S, M> {
        &mut self.base
    }
}
modified src/ui/widget/window.rs
@@ -35,38 +35,39 @@ impl<Id> Default for WindowProps<Id> {
impl<Id> Properties for WindowProps<Id> {}
impl<Id> BoxedAny for WindowProps<Id> {}

-
pub struct Window<S, A, Id> {
+
pub struct Window<S, M, Id> {
    /// Internal base
-
    base: WidgetBase<S, A>,
+
    base: WidgetBase<S, M>,
    /// Internal properties
    props: WindowProps<Id>,
    /// All pages known
-
    pages: HashMap<Id, BoxedWidget<S, A>>,
+
    pages: HashMap<Id, BoxedWidget<S, M>>,
}

-
impl<S, A, Id> Window<S, A, Id>
+
impl<S, M, Id> Window<S, M, Id>
where
    Id: Clone + Hash + Eq + PartialEq,
{
-
    pub fn page(mut self, id: Id, page: BoxedWidget<S, A>) -> Self {
+
    pub fn page(mut self, id: Id, page: BoxedWidget<S, M>) -> Self {
        self.pages.insert(id, page);
        self
    }
}

-
impl<'a: 'static, S, A, Id> Widget for Window<S, A, Id>
+
impl<'a, S, M, Id> Widget for Window<S, M, Id>
where
-
    Id: Clone + Hash + Eq + PartialEq + 'a,
+
    'a: 'static,
+
    Id: Clone + Hash + Eq + PartialEq + 'static,
{
-
    type Action = A;
+
    type Message = M;
    type State = S;

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self
    where
        Self: Sized,
    {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: WindowProps::default(),
            pages: HashMap::new(),
        }
@@ -113,11 +114,11 @@ where
        }
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

-
    fn base_mut(&mut self) -> &mut WidgetBase<S, A> {
+
    fn base_mut(&mut self) -> &mut WidgetBase<S, M> {
        &mut self.base
    }
}
@@ -155,14 +156,14 @@ impl Default for ShortcutsProps {
impl Properties for ShortcutsProps {}
impl BoxedAny for ShortcutsProps {}

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

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

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

-
    fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
+
    fn new(_state: &S, tx: UnboundedSender<M>) -> Self {
        Self {
-
            base: WidgetBase::new(action_tx.clone()),
+
            base: WidgetBase::new(tx.clone()),
            props: ShortcutsProps::default(),
        }
    }
@@ -235,11 +236,11 @@ impl<S, A> Widget for Shortcuts<S, A> {
        frame.render_widget(table, props.area);
    }

-
    fn base(&self) -> &WidgetBase<S, A> {
+
    fn base(&self) -> &WidgetBase<S, M> {
        &self.base
    }

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