From 3fbd4639a2812a3522bb4b0dbbd902f8f5db262d Mon Sep 17 00:00:00 2001 From: Andrii Zymohliad Date: Sun, 28 Jul 2024 20:45:19 +0300 Subject: [PATCH 1/4] Add message_handler view --- xilem/examples/data_thread.rs | 51 +++++++++++++++ xilem/src/view/message_handler.rs | 105 ++++++++++++++++++++++++++++++ xilem/src/view/mod.rs | 3 + 3 files changed, 159 insertions(+) create mode 100644 xilem/examples/data_thread.rs create mode 100644 xilem/src/view/message_handler.rs diff --git a/xilem/examples/data_thread.rs b/xilem/examples/data_thread.rs new file mode 100644 index 000000000..1089bf068 --- /dev/null +++ b/xilem/examples/data_thread.rs @@ -0,0 +1,51 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::{sync::mpsc, thread, time}; + +use xilem::{ + core::{fork, MessageProxy}, + view::{label, message_handler}, + EventLoop, WidgetView, Xilem, +}; + +struct AppData { + proxy_sender: mpsc::SyncSender>, + number: i32, +} + +fn app_logic(data: &mut AppData) -> impl WidgetView { + fork( + label(format!("Number: {}", &data.number)), + message_handler( + |data: &mut AppData, proxy: MessageProxy| { + data.proxy_sender.send(proxy).unwrap(); + }, + |data: &mut AppData, msg: i32| { + data.number = msg; + }, + ), + ) +} + +fn data_thread(proxy_receiver: mpsc::Receiver>) { + if let Ok(proxy) = proxy_receiver.recv() { + let mut number = 0; + while let Ok(()) = proxy.message(number) { + number += 1; + thread::sleep(time::Duration::from_secs(1)) + } + } +} + +fn main() { + let (proxy_sender, proxy_receiver) = mpsc::sync_channel(1); + let data = AppData { + proxy_sender, + number: 0, + }; + thread::spawn(move || data_thread(proxy_receiver)); + Xilem::new(data, app_logic) + .run_windowed(EventLoop::with_user_event(), "Centered Flex".into()) + .unwrap() +} diff --git a/xilem/src/view/message_handler.rs b/xilem/src/view/message_handler.rs new file mode 100644 index 000000000..64f30ed23 --- /dev/null +++ b/xilem/src/view/message_handler.rs @@ -0,0 +1,105 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::{marker::PhantomData, ops::Deref, sync::Arc}; + +use xilem_core::{ + DynMessage, Message, MessageProxy, NoElement, RawProxy, View, ViewId, ViewPathTracker, +}; + +use crate::ViewCtx; + +/// No-element view which allows to update app-data in response to +/// asynchronous user messages. +/// +/// `store_proxy` serves as a way to obtain [`MessageProxy`], which can then +/// be used to send messages to self. +/// It is given a mutable reference to the app data and a proxy, so the proxy +/// can be saved to the app data here, or sent to another thread for example. +/// Note, it is always called only once, changes to the app data won't trigger +/// `store_proxy` to rerun. +/// +/// `handle_event` receives messages from the aforementioned `MessageProxy`, +/// along with a mutable reference to the app data. +pub fn message_handler( + store_proxy: F, + handle_event: H, +) -> MessageHandler +where + F: Fn(&mut State, MessageProxy) + 'static, + H: Fn(&mut State, M) -> Action + 'static, + M: Message + 'static, +{ + MessageHandler { + store_proxy, + handle_event, + message: PhantomData, + } +} + +#[derive(Debug)] +struct StoreProxyMessage; + +pub struct MessageHandler { + store_proxy: F, + handle_event: H, + message: PhantomData M>, +} + +impl View for MessageHandler +where + F: Fn(&mut State, MessageProxy) + 'static, + H: Fn(&mut State, M) -> Action + 'static, + M: Message + 'static, +{ + type Element = NoElement; + type ViewState = (Arc>, Arc<[ViewId]>); + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let path: Arc<[ViewId]> = ctx.view_path().into(); + ctx.proxy + .send_message(path.clone(), Box::new(StoreProxyMessage)) + .unwrap(); + (NoElement, (ctx.proxy.clone(), path.clone())) + } + + fn rebuild<'el>( + &self, + _: &Self, + _: &mut Self::ViewState, + _: &mut ViewCtx, + (): xilem_core::Mut<'el, Self::Element>, + ) -> xilem_core::Mut<'el, Self::Element> { + // Nothing to do + } + + fn teardown( + &self, + _: &mut Self::ViewState, + _: &mut ViewCtx, + _: xilem_core::Mut<'_, Self::Element>, + ) { + // Nothing to do + } + + fn message( + &self, + (raw_proxy, path): &mut Self::ViewState, + id_path: &[xilem_core::ViewId], + message: DynMessage, + app_state: &mut State, + ) -> xilem_core::MessageResult { + debug_assert!( + id_path.is_empty(), + "id path should be empty in MessageHandler::message" + ); + if message.deref().as_any().is::() { + let proxy = MessageProxy::new(raw_proxy.clone(), path.clone()); + (self.store_proxy)(app_state, proxy); + xilem_core::MessageResult::Nop + } else { + let message = message.downcast::().unwrap(); + xilem_core::MessageResult::Action((self.handle_event)(app_state, *message)) + } + } +} diff --git a/xilem/src/view/mod.rs b/xilem/src/view/mod.rs index b9e8170d7..bdb27d301 100644 --- a/xilem/src/view/mod.rs +++ b/xilem/src/view/mod.rs @@ -19,6 +19,9 @@ pub use sized_box::*; mod label; pub use label::*; +mod message_handler; +pub use message_handler::*; + mod prose; pub use prose::*; From 05e7e7554cac8ca1b520f860998f87b2afb9609e Mon Sep 17 00:00:00 2001 From: Andrii Zymohliad Date: Sun, 28 Jul 2024 21:10:33 +0300 Subject: [PATCH 2/4] Fix clippy errors --- xilem/examples/data_thread.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xilem/examples/data_thread.rs b/xilem/examples/data_thread.rs index 1089bf068..f9778687e 100644 --- a/xilem/examples/data_thread.rs +++ b/xilem/examples/data_thread.rs @@ -33,7 +33,7 @@ fn data_thread(proxy_receiver: mpsc::Receiver>) { let mut number = 0; while let Ok(()) = proxy.message(number) { number += 1; - thread::sleep(time::Duration::from_secs(1)) + thread::sleep(time::Duration::from_secs(1)); } } } @@ -47,5 +47,5 @@ fn main() { thread::spawn(move || data_thread(proxy_receiver)); Xilem::new(data, app_logic) .run_windowed(EventLoop::with_user_event(), "Centered Flex".into()) - .unwrap() + .unwrap(); } From 78eba7128d822bcd707fbdcbdeacc8ae628dc156 Mon Sep 17 00:00:00 2001 From: Andrii Zymohliad Date: Sun, 28 Jul 2024 21:28:46 +0300 Subject: [PATCH 3/4] Improve message_handler documentation --- xilem/examples/data_thread.rs | 7 ++++++- xilem/src/view/message_handler.rs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/xilem/examples/data_thread.rs b/xilem/examples/data_thread.rs index f9778687e..642e50361 100644 --- a/xilem/examples/data_thread.rs +++ b/xilem/examples/data_thread.rs @@ -10,8 +10,9 @@ use xilem::{ }; struct AppData { - proxy_sender: mpsc::SyncSender>, number: i32, + // Used to send MessageProxy from message_handler view to data_thread + proxy_sender: mpsc::SyncSender>, } fn app_logic(data: &mut AppData) -> impl WidgetView { @@ -19,9 +20,11 @@ fn app_logic(data: &mut AppData) -> impl WidgetView { label(format!("Number: {}", &data.number)), message_handler( |data: &mut AppData, proxy: MessageProxy| { + // Send message proxy to the data_thread data.proxy_sender.send(proxy).unwrap(); }, |data: &mut AppData, msg: i32| { + // Receive data from the data_thread data.number = msg; }, ), @@ -29,7 +32,9 @@ fn app_logic(data: &mut AppData) -> impl WidgetView { } fn data_thread(proxy_receiver: mpsc::Receiver>) { + // Wait for the MessageProxy if let Ok(proxy) = proxy_receiver.recv() { + // Generate data and send it to the message_handler view let mut number = 0; while let Ok(()) = proxy.message(number) { number += 1; diff --git a/xilem/src/view/message_handler.rs b/xilem/src/view/message_handler.rs index 64f30ed23..7299421e6 100644 --- a/xilem/src/view/message_handler.rs +++ b/xilem/src/view/message_handler.rs @@ -9,18 +9,18 @@ use xilem_core::{ use crate::ViewCtx; -/// No-element view which allows to update app-data in response to -/// asynchronous user messages. +/// No-element view which allows to update app state in response to +/// asynchronous user messages, for example from another thread. /// /// `store_proxy` serves as a way to obtain [`MessageProxy`], which can then -/// be used to send messages to self. -/// It is given a mutable reference to the app data and a proxy, so the proxy -/// can be saved to the app data here, or sent to another thread for example. -/// Note, it is always called only once, changes to the app data won't trigger -/// `store_proxy` to rerun. +/// be used to send messages to this view. +/// It is given a mutable reference to the app state and a message proxy, so +/// the proxy can be e.g. saved to the app state here, or sent to another thread. +/// Note, `store_proxy` is called once, shortly after the view is built. +/// Changes to the app state won't it to rerun. /// /// `handle_event` receives messages from the aforementioned `MessageProxy`, -/// along with a mutable reference to the app data. +/// along with a mutable reference to the app state. pub fn message_handler( store_proxy: F, handle_event: H, From 631326c79cc2db41e1da3fd4245ecb60606abc8a Mon Sep 17 00:00:00 2001 From: Andrii Zymohliad Date: Sun, 28 Jul 2024 21:29:08 +0300 Subject: [PATCH 4/4] Minor refactor --- xilem/src/view/message_handler.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/xilem/src/view/message_handler.rs b/xilem/src/view/message_handler.rs index 7299421e6..3e50af3f8 100644 --- a/xilem/src/view/message_handler.rs +++ b/xilem/src/view/message_handler.rs @@ -26,7 +26,7 @@ pub fn message_handler( handle_event: H, ) -> MessageHandler where - F: Fn(&mut State, MessageProxy) + 'static, + F: Fn(&mut State, MessageProxy) -> Action + 'static, H: Fn(&mut State, M) -> Action + 'static, M: Message + 'static, { @@ -48,7 +48,7 @@ pub struct MessageHandler { impl View for MessageHandler where - F: Fn(&mut State, MessageProxy) + 'static, + F: Fn(&mut State, MessageProxy) -> Action + 'static, H: Fn(&mut State, M) -> Action + 'static, M: Message + 'static, { @@ -95,11 +95,12 @@ where ); if message.deref().as_any().is::() { let proxy = MessageProxy::new(raw_proxy.clone(), path.clone()); - (self.store_proxy)(app_state, proxy); - xilem_core::MessageResult::Nop + let action = (self.store_proxy)(app_state, proxy); + xilem_core::MessageResult::Action(action) } else { let message = message.downcast::().unwrap(); - xilem_core::MessageResult::Action((self.handle_event)(app_state, *message)) + let action = (self.handle_event)(app_state, *message); + xilem_core::MessageResult::Action(action) } } }