From 166add7780136bcb0a1dc07f5bdbd4a2eb361075 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 24 Apr 2024 13:26:12 +0200 Subject: [PATCH] lib: Use associated types in views --- src/store.rs | 12 +++++++----- src/ui.rs | 4 ++-- src/ui/widget.rs | 40 +++++++++++++++++++++++++------------- src/ui/widget/container.rs | 21 ++++++++++++++------ src/ui/widget/input.rs | 7 +++++-- src/ui/widget/text.rs | 7 +++++-- 6 files changed, 60 insertions(+), 31 deletions(-) diff --git a/src/store.rs b/src/store.rs index 6c80dfc5..0b80b697 100644 --- a/src/store.rs +++ b/src/store.rs @@ -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 +pub trait State

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>; + fn handle_action(&mut self, action: Self::Action) -> Option>; /// Handle recurring tick. fn tick(&self); @@ -29,7 +31,7 @@ where /// messages coming from the frontend and updates the state accordingly. pub struct Store where - S: State + Clone + Send + Sync, + S: State

+ Clone + Send + Sync, P: Clone + Debug + Send + Sync, { state_tx: UnboundedSender, @@ -38,7 +40,7 @@ where impl Store where - S: State + Clone + Send + Sync, + S: State

+ Clone + Send + Sync, P: Clone + Debug + Send + Sync, { pub fn new() -> (Self, UnboundedReceiver) { @@ -56,7 +58,7 @@ where impl Store where - S: State + Clone + Debug + Send + Sync + 'static, + S: State + Clone + Debug + Send + Sync + 'static, P: Clone + Debug + Send + Sync + 'static, { /// By calling `main_loop`, the store will wait for new messages coming diff --git a/src/ui.rs b/src/ui.rs index 64cfeb70..18290984 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -71,8 +71,8 @@ impl Frontend { mut interrupt_rx: broadcast::Receiver>, ) -> anyhow::Result> where - S: State, - W: Widget, + S: State

, + W: Widget, P: Clone + Send + Sync + Debug, { let mut ticker = tokio::time::interval(RENDERING_TICK_RATE); diff --git a/src/ui/widget.rs b/src/ui/widget.rs index 9a08274b..1b19a2f7 100644 --- a/src/ui/widget.rs +++ b/src/ui/widget.rs @@ -18,7 +18,7 @@ use ratatui::widgets::{Cell, Row, TableState}; use super::theme::style; use super::{layout, span}; -pub type BoxedWidget = Box>; +pub type BoxedWidget = Box>; pub type UpdateCallback = fn(&S) -> Box; pub type EventCallback = fn(&dyn Any, UnboundedSender); @@ -36,15 +36,18 @@ pub struct BaseView { /// Main trait defining a `View` behaviour. /// /// This is the first trait that you should implement to define a custom `Widget`. -pub trait View { +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) -> Self + fn new(state: &Self::State, action_tx: UnboundedSender) -> Self where Self: Sized; /// Should set the optional custom event handler. - fn on_event(mut self, callback: EventCallback) -> Self + fn on_event(mut self, callback: EventCallback) -> Self where Self: Sized, { @@ -53,7 +56,7 @@ pub trait View { } /// Should set the optional update handler. - fn on_update(mut self, callback: UpdateCallback) -> Self + fn on_update(mut self, callback: UpdateCallback) -> Self where Self: Sized, { @@ -70,7 +73,7 @@ pub trait View { } /// Return a mutable reference to this widgets' base view. - fn base_mut(&mut self) -> &mut BaseView; + fn base_mut(&mut self) -> &mut BaseView; /// Should handle key events and call `handle_event` on all children. /// @@ -88,13 +91,13 @@ pub trait View { /// 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: View +pub trait Widget: View where B: Backend, { @@ -179,11 +182,14 @@ where } } -impl<'a: 'static, B, S, A, Id> View for Window +impl<'a: 'static, B, S, A, Id> View for Window where B: Backend + 'a, Id: Clone + Hash + Eq + PartialEq + 'a, { + type Action = A; + type State = S; + fn new(_state: &S, action_tx: UnboundedSender) -> Self where Self: Sized, @@ -230,7 +236,7 @@ where } } -impl<'a: 'static, B, S, A, Id> Widget for Window +impl<'a: 'static, B, S, A, Id> Widget for Window where B: Backend + 'a, Id: Clone + Hash + Eq + PartialEq + 'a, @@ -310,7 +316,10 @@ impl Shortcuts { } } -impl View for Shortcuts { +impl View for Shortcuts { + type Action = A; + type State = S; + fn new(_state: &S, action_tx: UnboundedSender) -> Self { Self { base: BaseView { @@ -334,7 +343,7 @@ impl View for Shortcuts { } } -impl Widget for Shortcuts +impl Widget for Shortcuts where B: Backend, { @@ -540,10 +549,13 @@ where } } -impl<'a: 'static, S, A, R> View 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) -> Self { Self { base: BaseView { @@ -603,7 +615,7 @@ where } } -impl<'a: 'static, B, S, A, R> Widget for Table<'a, S, A, R> +impl<'a: 'static, B, S, A, R> Widget for Table<'a, S, A, R> where B: Backend, R: ToRow + Clone + Debug + 'static, diff --git a/src/ui/widget/container.rs b/src/ui/widget/container.rs index 7bc266c9..f2c338f1 100644 --- a/src/ui/widget/container.rs +++ b/src/ui/widget/container.rs @@ -77,7 +77,10 @@ impl<'a, S, A> Header<'a, S, A> { } } -impl<'a: 'static, S, A> View 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) -> Self { Self { base: BaseView { @@ -108,7 +111,7 @@ impl<'a: 'static, S, A> View for Header<'a, S, A> { } } -impl<'a: 'static, B, S, A> Widget for Header<'a, S, A> +impl<'a: 'static, B, S, A> Widget 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 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) -> Self { Self { base: BaseView { @@ -289,7 +295,7 @@ impl<'a, S, A> Footer<'a, S, A> { } } -impl<'a: 'static, B, S, A> Widget for Footer<'a, S, A> +impl<'a: 'static, B, S, A> Widget for Footer<'a, S, A> where B: Backend, { @@ -386,10 +392,13 @@ where } } -impl View for Container +impl View for Container where B: Backend, { + type Action = A; + type State = S; + fn new(_state: &S, action_tx: UnboundedSender) -> Self where Self: Sized, @@ -436,7 +445,7 @@ where } } -impl<'a: 'static, B, S, A> Widget for Container +impl<'a: 'static, B, S, A> Widget for Container where B: Backend, { diff --git a/src/ui/widget/input.rs b/src/ui/widget/input.rs index addf273f..b3d53285 100644 --- a/src/ui/widget/input.rs +++ b/src/ui/widget/input.rs @@ -126,7 +126,10 @@ impl TextField { } } -impl View for TextField { +impl View for TextField { + type Action = A; + type State = S; + fn new(_state: &S, action_tx: UnboundedSender) -> Self { Self { base: BaseView { @@ -186,7 +189,7 @@ impl View for TextField { } } -impl Widget for TextField +impl Widget for TextField where B: Backend, { diff --git a/src/ui/widget/text.rs b/src/ui/widget/text.rs index ce60a21b..f204ce30 100644 --- a/src/ui/widget/text.rs +++ b/src/ui/widget/text.rs @@ -142,7 +142,10 @@ impl<'a, S, A> Paragraph<'a, S, A> { } } -impl<'a: 'static, S, A> View 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) -> Self where Self: Sized, @@ -202,7 +205,7 @@ impl<'a: 'static, S, A> View for Paragraph<'a, S, A> { } } -impl<'a: 'static, B, S, A> Widget for Paragraph<'a, S, A> +impl<'a: 'static, B, S, A> Widget for Paragraph<'a, S, A> where B: Backend, {