From 2c8ce4b00feb336e07a214513eb0faae81a1779e Mon Sep 17 00:00:00 2001 From: Idan Arye Date: Sun, 7 Feb 2021 20:02:44 +0200 Subject: [PATCH] Close #8 - introduce `BuilderSingalConnector` to replace old signal connection --- examples/example_actor_per_row.rs | 4 +- examples/example_events.rs | 2 +- examples/example_taggged_signals.rs | 8 +-- macros/src/builder_signal_derive.rs | 6 ++ src/builder.rs | 101 ++++++++++------------------ src/builder_signal.rs | 87 +++++++++++++++++++++++- src/lib.rs | 48 +++++++------ tests/basic.rs | 2 +- tests/no_signals.rs | 2 +- 9 files changed, 165 insertions(+), 95 deletions(-) diff --git a/examples/example_actor_per_row.rs b/examples/example_actor_per_row.rs index f3471a6314..57e6baa228 100644 --- a/examples/example_actor_per_row.rs +++ b/examples/example_actor_per_row.rs @@ -47,7 +47,7 @@ impl actix::StreamHandler for WindowActor { match signal { WindowSignal::ClickButton => { let addend = self.factories.row_addend.instantiate().actor() - .connect_signals::() + .connect_signals(AddendSignal::connector()) .create(|builder_ctx| { let widgets: AddendWidgets = builder_ctx.widgets().unwrap(); self.widgets.lst_addition.add(&widgets.row_addend); @@ -166,7 +166,7 @@ fn main() -> Result<(), Box> { woab::run_actix_inside_gtk_event_loop("example")?; factories.win_app.instantiate().actor() - .connect_signals::() + .connect_signals(WindowSignal::connector()) .create(|ctx| { WindowActor { widgets: ctx.widgets().unwrap(), diff --git a/examples/example_events.rs b/examples/example_events.rs index a47b71f742..6c588335c8 100644 --- a/examples/example_events.rs +++ b/examples/example_events.rs @@ -89,7 +89,7 @@ fn main() -> Result<(), Box> { woab::run_actix_inside_gtk_event_loop("example")?; factories.win_app.instantiate().actor() - .connect_signals::() + .connect_signals(WindowSignal::connector()) .create(|ctx| WindowActor { widgets: ctx.widgets().unwrap(), press_times: Default::default(), diff --git a/examples/example_taggged_signals.rs b/examples/example_taggged_signals.rs index 2be0dce7da..26bcd96a1e 100644 --- a/examples/example_taggged_signals.rs +++ b/examples/example_taggged_signals.rs @@ -46,9 +46,9 @@ impl actix::StreamHandler for WindowActor { WindowSignal::ClickButton => { let addend_id = self.next_addend_id; self.next_addend_id += 1; - let builder = self.factories.row_addend.instantiate(); - builder.connect_signals_tagged(addend_id, ctx); - let widgets = builder.widgets::().unwrap(); + let widgets: AddendWidgets = self.factories.row_addend.instantiate() + .connect_signals(ctx, AddendSignal::connector().tag(addend_id)) + .widgets().unwrap(); self.widgets.lst_addition.add(&widgets.row_addend); self.addends.insert(addend_id, (widgets, Some(0))); self.recalculate(); @@ -112,7 +112,7 @@ fn main() -> Result<(), Box> { woab::run_actix_inside_gtk_event_loop("example")?; factories.win_app.instantiate().actor() - .connect_signals::() + .connect_signals(WindowSignal::connector()) .create(|ctx| { WindowActor { widgets: ctx.widgets().unwrap(), diff --git a/macros/src/builder_signal_derive.rs b/macros/src/builder_signal_derive.rs index 91aa400739..8e43eef52f 100644 --- a/macros/src/builder_signal_derive.rs +++ b/macros/src/builder_signal_derive.rs @@ -131,5 +131,11 @@ pub fn impl_builder_signal_derive(ast: &syn::DeriveInput) -> Result woab::BuilderSingalConnector { + ::connector() + } + } }) } diff --git a/src/builder.rs b/src/builder.rs index 2c81336ce0..f889c5df1a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -4,7 +4,6 @@ use std::cell::RefCell; use gtk::Builder; use tokio::sync::mpsc; -use tokio::stream::StreamExt; /// Holds instructions for generating a GTK builder. /// @@ -64,8 +63,8 @@ impl BuilderFactory { /// # use gtk::prelude::*; /// #[derive(woab::Factories)] /// struct Factories { -/// window: woab::Factory, -/// row: woab::Factory, +/// window: woab::BuilderFactory, +/// row: woab::BuilderFactory, /// } /// /// struct WindowActor { @@ -115,16 +114,19 @@ impl BuilderFactory { /// } /// /// fn create_window_with_rows(factory: &Factories) { -/// factory.window.build().actor(|ctx, widgets| { -/// for row_number in 0..10 { -/// let row_widgets = factory.row.build() -/// .connect_tagged_builder_signals(ctx, row_number) -/// .widgets().unwrap(); -/// row_widgets.label.set_text(&format!("Roe number {}", row_number)); -/// widgets.list_box.add(&row_widgets.row); -/// } -/// WindowActor { widgets } -/// }).unwrap(); +/// factory.window.instantiate().actor() +/// .connect_signals(WindowSignal::connector()) +/// .create(|ctx| { +/// let widgets: WindowWidgets = ctx.widgets().unwrap(); +/// for row_number in 0..10 { +/// let row_widgets: RowWidgets = factory.row.instantiate() +/// .connect_signals(ctx, RowSignal::connector().tag(row_number)) +/// .widgets().unwrap(); +/// row_widgets.label.set_text(&format!("Roe number {}", row_number)); +/// widgets.list_box.add(&row_widgets.row); +/// } +/// WindowActor { widgets } +/// }); /// } /// ``` @@ -133,7 +135,7 @@ impl BuilderFactory { /// It wraps a `gtk::Builder` instance and provides methods to create actors that are /// connected to the widgets in that builder. /// -/// See [`woab::Factory`](struct.Factory.html) for usage example. +/// See [`woab::BuilderFactory`](struct.BuilderFactory.html) for usage example. pub struct BuilderConnector { builder: gtk::Builder, callbacks: RefCell>, @@ -169,52 +171,23 @@ impl BuilderConnector { } /// Create a widgets struct (as defined by the `W` generic parameter of - /// [`woab::Factory`](struct.Factory.html)) and map the builder's widgets to its fields. - pub fn widgets(&self) -> Result>::Error> + /// [`woab::BuilderFactory`](struct.BuilderFactory.html)) and map the builder's widgets to its fields. + pub fn widgets(&self) -> Result>::Error> where gtk::Builder: TryInto { self.builder.clone().try_into() } - pub fn connect_signals(&self, ctx: &mut actix::Context) - where - A: actix::Actor>, - A: actix::StreamHandler, - S: crate::BuilderSignal, - { - let (tx, rx) = mpsc::channel(16); - let mut callbacks = self.callbacks.borrow_mut(); - for signal in S::list_signals() { - callbacks.insert(signal, S::bridge_signal(signal, tx.clone()).unwrap()); - } - A::add_stream(rx, ctx); - } - - /// Stream the signals generated by the builder to an actor represented by `ctx`, together with - /// a tag. - /// - /// When using the same actor to handle multiple copies of the same set of widgets (e.g. - /// multiple `GtkListBoxRow`s) the tag can be used to determine which copy generated the - /// signal. - /// - /// **If multiple tagged signals are streamed to the same actor - which is the typical use case - /// for tagged signals - `StreamHandler::finished` should be overridden to avoid stopping the - /// actor when one instance of the widgets is removed!!!** - pub fn connect_signals_tagged(&self, tag: T, ctx: &mut actix::Context) + pub fn connect_signals(&self, ctx: &mut actix::Context, register_signal_handlers: R) -> &Self where - T: Clone + 'static, A: actix::Actor>, - A: actix::StreamHandler<(T, S)>, - S: crate::BuilderSignal, + R: crate::RegisterSignalHandlers, + R::MessageType: 'static, + A: actix::StreamHandler, { - let (tx, rx) = mpsc::channel(16); - let rx = rx.map(move |s| (tag.clone(), s)); let mut callbacks = self.callbacks.borrow_mut(); - for signal in S::list_signals() { - callbacks.insert(signal, S::bridge_signal(signal, tx.clone()).unwrap()); - } - use actix::AsyncContext; - ctx.add_stream(rx); + register_signal_handlers.register_signal_handlers::(ctx, &mut callbacks); + self } pub fn actor>>(&self) -> ActorBuilder { @@ -284,7 +257,7 @@ pub struct ActorBuilder<'a, A: actix::Actor>> { } impl<'a, A: actix::Actor>> ActorBuilder<'a, A> { - pub fn run(self, actor: A) -> actix::Addr { + pub fn start(self, actor: A) -> actix::Addr { self.actor_context.run(actor) } @@ -297,22 +270,22 @@ impl<'a, A: actix::Actor>> ActorBuilder<'a, A> { actor_builder_context.actor_context.run(actor) } - pub fn connect_signals(mut self) -> Self - where - A: actix::StreamHandler, - S: crate::BuilderSignal, - { - self.builder_connector.connect_signals(&mut self.actor_context); - self + pub fn try_create<'b, E>(self, dlg: impl FnOnce(&mut ActorBuilderContext<'a, A>) -> Result) -> Result, E> where 'a: 'b { + let mut actor_builder_context = ActorBuilderContext { + builder_connector: self.builder_connector, + actor_context: self.actor_context, + }; + let actor = dlg(&mut actor_builder_context)?; + Ok(actor_builder_context.actor_context.run(actor)) } - pub fn connect_signals_tagged(mut self, tag: T) -> Self + pub fn connect_signals(mut self, register_signal_handlers: R) -> Self where - T: Clone + 'static, - A: actix::StreamHandler<(T, S)>, - S: crate::BuilderSignal, + R: crate::RegisterSignalHandlers, + R::MessageType: 'static, + A: actix::StreamHandler, { - self.builder_connector.connect_signals_tagged(tag, &mut self.actor_context); + self.builder_connector.connect_signals(&mut self.actor_context, register_signal_handlers); self } } diff --git a/src/builder_signal.rs b/src/builder_signal.rs index 54e19021a8..d77b62165f 100644 --- a/src/builder_signal.rs +++ b/src/builder_signal.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use tokio::sync::mpsc; /// Type of a gtk signal callback function that operates on uncast glib values @@ -5,7 +7,6 @@ pub type RawSignalCallback = Box Option>; /// Represent a GTK signal that originates from a GTK builder. Refer to [the corresponding derive](derive.BuilderSignal.html). pub trait BuilderSignal: Sized + 'static { - /// Generate a signal handler function for GTK. /// /// The returned function should convert the signals it revceives to the signal type, and @@ -13,4 +14,88 @@ pub trait BuilderSignal: Sized + 'static { fn bridge_signal(signal: &str, tx: mpsc::Sender) -> Option; fn list_signals() -> &'static [&'static str]; + + fn connector() -> BuilderSingalConnector { + BuilderSingalConnector { + transformer: (), + _phantom_data: Default::default(), + } + } +} + +pub trait RegisterSignalHandlers { + type MessageType; + + fn register_signal_handlers(self, ctx: &mut A::Context, callbacks: &mut HashMap<&'static str, crate::RawSignalCallback>) + where + A: actix::Actor>, + A: actix::StreamHandler; +} + +pub trait SignalTransformer: Clone { + type Output: 'static; + + fn transform(&self, signal: S) -> Self::Output; +} + +impl SignalTransformer for () { + type Output = S; + + fn transform(&self, signal: S) -> Self::Output { + signal + } +} + +impl SignalTransformer for (T,) { + type Output = (T, S); + + fn transform(&self, signal: S) -> Self::Output { + (self.0.clone(), signal) + } +} + +pub struct BuilderSingalConnector +where + S: BuilderSignal, + T: Clone, +{ + transformer: T, + _phantom_data: core::marker::PhantomData, +} + +impl BuilderSingalConnector +where + S: BuilderSignal, +{ + pub fn tag(self, tag: T) -> BuilderSingalConnector { + BuilderSingalConnector { + transformer: (tag,), + _phantom_data: Default::default(), + } + } +} + +impl RegisterSignalHandlers for BuilderSingalConnector +where + S: 'static, + S: BuilderSignal, + T: 'static, + T: SignalTransformer, +{ + type MessageType = T::Output; + // type MessageType = S; + + fn register_signal_handlers(self, ctx: &mut A::Context, callbacks: &mut HashMap<&'static str, crate::RawSignalCallback>) + where + A: actix::Actor>, + A: actix::StreamHandler, + { + let (tx, rx) = mpsc::channel(16); + for signal in S::list_signals() { + callbacks.insert(signal, S::bridge_signal(signal, tx.clone()).unwrap()); + } + use tokio::stream::StreamExt; + let rx = rx.map(move|s| self.transformer.transform(s)); + A::add_stream(rx, ctx); + } } diff --git a/src/lib.rs b/src/lib.rs index ca2568fd68..5b298f13c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ //! //! To use WoAB one would typically create a factories struct using //! [`woab::Factories`](derive.Factories.html) and use it dissect the Glade XML file(s). Each field -//! of the factories struct will be a [`woab::Factory`](struct.Factory.html) that can create: +//! of the factories struct will be a [`woab::BuilderFactory`](struct.BuilderFactory.html) that can create: //! * An Actor (optional) //! * A widgets struct using [`woab::WidgetsFromBuilder`](derive.WidgetsFromBuilder.html) @@ -37,7 +37,7 @@ //! #[derive(woab::Factories)] //! struct Factories { //! // The field name must be the ID from the builder XML file: -//! main_window: woab::Factory, +//! main_window: woab::BuilderFactory, //! // Possibly other things from the builder XML file that need to be created during the program. //! } //! @@ -85,13 +85,15 @@ //! gtk::init()?; //! woab::run_actix_inside_gtk_event_loop("my-WoAB-app")?; // <===== IMPORTANT!!! //! -//! factories.main_window.build().actor(|_ctx, widgets| { -//! widgets.main_window.show_all(); // Could also be done inside the actor -//! AppActor { -//! widgets, -//! factories: factories, -//! } -//! })?; +//! factories.main_window.instantiate().actor() +//! .create(|ctx| { +//! let widgets: AppWidgets = ctx.widgets().unwrap(); +//! widgets.main_window.show_all(); // Could also be done inside the actor +//! AppActor { +//! widgets, +//! factories: factories, +//! } +//! }); //! //! gtk::main(); //! Ok(()) @@ -139,7 +141,8 @@ pub use woab_macros::BuilderSignal; /// `GtkListBox`) and see how they look together inside Glade, and then split the XML to multiple /// factories that create them separately during runtime. /// -/// Typically the fields of the struct will be of type [`woab::Factory`](struct.Factory.html), but +/// Typically the fields of the struct will be of type +/// [`woab::BuilderFactory`](struct.BuilderFactory.html), but /// anything `From` is allowed so [`woab::BuilderFactory`](struct.BuilderFactory.html) or /// even just `String`s are also okay, if they are needed. /// @@ -157,10 +160,10 @@ pub use woab_macros::BuilderSignal; /// # type SomeListBoxRowSignal = (); /// #[derive(woab::Factories)] /// struct Factories { -/// main_window: woab::Factory, +/// main_window: woab::BuilderFactory, /// #[factory(extra(some_text_buffer_used_by_a_text_box_in_sub_window))] -/// sub_window: woab::Factory, -/// some_list_box_row: woab::Factory<(), SomeListBoxRowWidgets, SomeListBoxRowSignal>, // doesn't have its own actor +/// sub_window: woab::BuilderFactory, +/// some_list_box_row: woab::BuilderFactory, // doesn't have its own actor /// } /// fn main() -> Result<(), Box> { /// # fn read_builder_xml() -> std::io::BufReader { @@ -183,7 +186,7 @@ pub use woab_macros::Factories; /// # /// # #[derive(woab::Factories)] /// # struct Factories { -/// # list_box_row: woab::Factory, +/// # list_box_row: woab::BuilderFactory, /// # } /// # /// # #[derive(woab::WidgetsFromBuilder)] @@ -209,12 +212,15 @@ pub use woab_macros::Factories; /// # } /// /// fn create_the_row(factories: &Factories, list_box: >k::ListBox) -> actix::Addr { -/// factories.list_box_row.build().actor(|_, widgets| { -/// list_box.add(&widgets.list_box_row); -/// RowActor { -/// widgets, -/// } -/// }).unwrap() +/// factories.list_box_row.instantiate().actor() +/// .connect_signals(RowSignal::connector()) +/// .create(|ctx| { +/// let widgets: RowWidgets = ctx.widgets().unwrap(); +/// list_box.add(&widgets.list_box_row); +/// RowActor { +/// widgets, +/// } +/// }) /// } /// /// fn remove_the_row(row: &actix::Addr) { @@ -225,7 +231,7 @@ pub use woab_macros::Removable; pub use event_loops_bridge::run_actix_inside_gtk_event_loop; pub use builder_dissect::dissect_builder_xml; -pub use builder_signal::{RawSignalCallback, BuilderSignal}; +pub use builder_signal::{RawSignalCallback, BuilderSignal, RegisterSignalHandlers, BuilderSingalConnector}; // pub use factories::{BuilderFactory, Factory, BuilderUtilizer, BuilderConnector, ActorBuilder, ActorWidgetsBuilder}; pub use builder::*; diff --git a/tests/basic.rs b/tests/basic.rs index 491d549355..3647afff82 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -61,7 +61,7 @@ fn test_basic() -> anyhow::Result<()> { woab::run_actix_inside_gtk_event_loop("test")?; let mut put_widgets_in = None; factories.win_test.instantiate().actor() - .connect_signals::() + .connect_signals(TestSignal::connector()) .create(|ctx| { let widgets = ctx.widgets::().unwrap(); put_widgets_in = Some(widgets.clone()); diff --git a/tests/no_signals.rs b/tests/no_signals.rs index b7c539aad3..3fb120087a 100644 --- a/tests/no_signals.rs +++ b/tests/no_signals.rs @@ -49,7 +49,7 @@ fn test_no_signals() -> anyhow::Result<()> { gtk::init()?; woab::run_actix_inside_gtk_event_loop("test")?; let output = Rc::new(RefCell::new(Vec::new())); - factories.win_test.instantiate().actor().run(TestActor { output: output.clone() }); + factories.win_test.instantiate().actor().start(TestActor { output: output.clone() }); wait_for!(*output.borrow() == [ "before spawned future", "inside spawned future",