lib: Use associated types in views

This commit is contained in:
Erik Kundt
2024-04-24 13:26:12 +02:00
parent 956ca70f4a
commit 166add7780
6 changed files with 60 additions and 31 deletions
+7 -5
View File
@@ -13,13 +13,15 @@ const STORE_TICK_RATE: Duration = Duration::from_millis(1000);
/// The `State` known to the application store. It handles user-defined
/// application messages as well as ticks.
pub trait State<A, P>
pub trait State<P>
where
P: Clone + Debug + Send + Sync,
{
type Action;
/// 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: A) -> Option<Exit<P>>;
fn handle_action(&mut self, action: Self::Action) -> Option<Exit<P>>;
/// Handle recurring tick.
fn tick(&self);
@@ -29,7 +31,7 @@ where
/// messages coming from the frontend and updates the state accordingly.
pub struct Store<A, S, P>
where
S: State<A, P> + Clone + Send + Sync,
S: State<P> + Clone + Send + Sync,
P: Clone + Debug + Send + Sync,
{
state_tx: UnboundedSender<S>,
@@ -38,7 +40,7 @@ where
impl<A, S, P> Store<A, S, P>
where
S: State<A, P> + Clone + Send + Sync,
S: State<P> + Clone + Send + Sync,
P: Clone + Debug + Send + Sync,
{
pub fn new() -> (Self, UnboundedReceiver<S>) {
@@ -56,7 +58,7 @@ where
impl<A, S, P> Store<A, S, P>
where
S: State<A, P> + Clone + Debug + Send + Sync + 'static,
S: State<P, Action = A> + Clone + Debug + Send + Sync + 'static,
P: Clone + Debug + Send + Sync + 'static,
{
/// By calling `main_loop`, the store will wait for new messages coming
+2 -2
View File
@@ -71,8 +71,8 @@ impl<A> Frontend<A> {
mut interrupt_rx: broadcast::Receiver<Interrupted<P>>,
) -> anyhow::Result<Interrupted<P>>
where
S: State<A, P>,
W: Widget<Backend, S, A>,
S: State<P>,
W: Widget<Backend, State = S, Action = A>,
P: Clone + Send + Sync + Debug,
{
let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);
+26 -14
View File
@@ -18,7 +18,7 @@ use ratatui::widgets::{Cell, Row, TableState};
use super::theme::style;
use super::{layout, span};
pub type BoxedWidget<B, S, A> = Box<dyn Widget<B, S, A>>;
pub type BoxedWidget<B, S, A> = Box<dyn Widget<B, State = S, Action = A>>;
pub type UpdateCallback<S> = fn(&S) -> Box<dyn Any>;
pub type EventCallback<A> = fn(&dyn Any, UnboundedSender<A>);
@@ -36,15 +36,18 @@ pub struct BaseView<S, A> {
/// Main trait defining a `View` behaviour.
///
/// This is the first trait that you should implement to define a custom `Widget`.
pub trait View<S, A> {
pub trait View {
type State;
type Action;
/// Should return a new view with props build from state (if type is known) and a
/// message sender set.
fn new(state: &S, action_tx: UnboundedSender<A>) -> Self
fn new(state: &Self::State, action_tx: UnboundedSender<Self::Action>) -> Self
where
Self: Sized;
/// Should set the optional custom event handler.
fn on_event(mut self, callback: EventCallback<A>) -> Self
fn on_event(mut self, callback: EventCallback<Self::Action>) -> Self
where
Self: Sized,
{
@@ -53,7 +56,7 @@ pub trait View<S, A> {
}
/// Should set the optional update handler.
fn on_update(mut self, callback: UpdateCallback<S>) -> Self
fn on_update(mut self, callback: UpdateCallback<Self::State>) -> Self
where
Self: Sized,
{
@@ -70,7 +73,7 @@ pub trait View<S, A> {
}
/// Return a mutable reference to this widgets' base view.
fn base_mut(&mut self) -> &mut BaseView<S, A>;
fn base_mut(&mut self) -> &mut BaseView<Self::State, Self::Action>;
/// Should handle key events and call `handle_event` on all children.
///
@@ -88,13 +91,13 @@ pub trait View<S, A> {
/// 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: &S);
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<B, S, A>: View<S, A>
pub trait Widget<B>: View
where
B: Backend,
{
@@ -179,11 +182,14 @@ where
}
}
impl<'a: 'static, B, S, A, Id> View<S, A> for Window<B, S, A, Id>
impl<'a: 'static, B, S, A, Id> View for Window<B, S, A, Id>
where
B: Backend + 'a,
Id: Clone + Hash + Eq + PartialEq + 'a,
{
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self
where
Self: Sized,
@@ -230,7 +236,7 @@ where
}
}
impl<'a: 'static, B, S, A, Id> Widget<B, S, A> for Window<B, S, A, Id>
impl<'a: 'static, B, S, A, Id> Widget<B> for Window<B, S, A, Id>
where
B: Backend + 'a,
Id: Clone + Hash + Eq + PartialEq + 'a,
@@ -310,7 +316,10 @@ impl<S, A> Shortcuts<S, A> {
}
}
impl<S, A> View<S, A> for Shortcuts<S, A> {
impl<S, A> View for Shortcuts<S, A> {
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
Self {
base: BaseView {
@@ -334,7 +343,7 @@ impl<S, A> View<S, A> for Shortcuts<S, A> {
}
}
impl<B, S, A> Widget<B, S, A> for Shortcuts<S, A>
impl<B, S, A> Widget<B> for Shortcuts<S, A>
where
B: Backend,
{
@@ -540,10 +549,13 @@ where
}
}
impl<'a: 'static, S, A, R> View<S, A> for Table<'a, S, A, R>
impl<'a: 'static, S, A, R> View for Table<'a, S, A, R>
where
R: ToRow + Clone + 'static,
{
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
Self {
base: BaseView {
@@ -603,7 +615,7 @@ where
}
}
impl<'a: 'static, B, S, A, R> Widget<B, S, A> for Table<'a, S, A, R>
impl<'a: 'static, B, S, A, R> Widget<B> for Table<'a, S, A, R>
where
B: Backend,
R: ToRow + Clone + Debug + 'static,
+15 -6
View File
@@ -77,7 +77,10 @@ impl<'a, S, A> Header<'a, S, A> {
}
}
impl<'a: 'static, S, A> View<S, A> for Header<'a, S, A> {
impl<'a: 'static, S, A> View for Header<'a, S, A> {
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
Self {
base: BaseView {
@@ -108,7 +111,7 @@ impl<'a: 'static, S, A> View<S, A> for Header<'a, S, A> {
}
}
impl<'a: 'static, B, S, A> Widget<B, S, A> for Header<'a, S, A>
impl<'a: 'static, B, S, A> Widget<B> for Header<'a, S, A>
where
B: Backend,
{
@@ -234,7 +237,10 @@ impl<'a, S, A> Footer<'a, S, A> {
}
}
impl<'a: 'static, S, A> View<S, A> for Footer<'a, S, A> {
impl<'a: 'static, S, A> View for Footer<'a, S, A> {
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
Self {
base: BaseView {
@@ -289,7 +295,7 @@ impl<'a, S, A> Footer<'a, S, A> {
}
}
impl<'a: 'static, B, S, A> Widget<B, S, A> for Footer<'a, S, A>
impl<'a: 'static, B, S, A> Widget<B> for Footer<'a, S, A>
where
B: Backend,
{
@@ -386,10 +392,13 @@ where
}
}
impl<B, S, A> View<S, A> for Container<B, S, A>
impl<B, S, A> View for Container<B, S, A>
where
B: Backend,
{
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self
where
Self: Sized,
@@ -436,7 +445,7 @@ where
}
}
impl<'a: 'static, B, S, A> Widget<B, S, A> for Container<B, S, A>
impl<'a: 'static, B, S, A> Widget<B> for Container<B, S, A>
where
B: Backend,
{
+5 -2
View File
@@ -126,7 +126,10 @@ impl<S, A> TextField<S, A> {
}
}
impl<S, A> View<S, A> for TextField<S, A> {
impl<S, A> View for TextField<S, A> {
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self {
Self {
base: BaseView {
@@ -186,7 +189,7 @@ impl<S, A> View<S, A> for TextField<S, A> {
}
}
impl<B, S, A> Widget<B, S, A> for TextField<S, A>
impl<B, S, A> Widget<B> for TextField<S, A>
where
B: Backend,
{
+5 -2
View File
@@ -142,7 +142,10 @@ impl<'a, S, A> Paragraph<'a, S, A> {
}
}
impl<'a: 'static, S, A> View<S, A> for Paragraph<'a, S, A> {
impl<'a: 'static, S, A> View for Paragraph<'a, S, A> {
type Action = A;
type State = S;
fn new(_state: &S, action_tx: UnboundedSender<A>) -> Self
where
Self: Sized,
@@ -202,7 +205,7 @@ impl<'a: 'static, S, A> View<S, A> for Paragraph<'a, S, A> {
}
}
impl<'a: 'static, B, S, A> Widget<B, S, A> for Paragraph<'a, S, A>
impl<'a: 'static, B, S, A> Widget<B> for Paragraph<'a, S, A>
where
B: Backend,
{