Skip to content

Commit

Permalink
Close #8 - introduce BuilderSingalConnector to replace old signal c…
Browse files Browse the repository at this point in the history
…onnection
  • Loading branch information
idanarye committed Feb 7, 2021
1 parent 7a06199 commit 2c8ce4b
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 95 deletions.
4 changes: 2 additions & 2 deletions examples/example_actor_per_row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl actix::StreamHandler<WindowSignal> for WindowActor {
match signal {
WindowSignal::ClickButton => {
let addend = self.factories.row_addend.instantiate().actor()
.connect_signals::<AddendSignal>()
.connect_signals(AddendSignal::connector())
.create(|builder_ctx| {
let widgets: AddendWidgets = builder_ctx.widgets().unwrap();
self.widgets.lst_addition.add(&widgets.row_addend);
Expand Down Expand Up @@ -166,7 +166,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
woab::run_actix_inside_gtk_event_loop("example")?;

factories.win_app.instantiate().actor()
.connect_signals::<WindowSignal>()
.connect_signals(WindowSignal::connector())
.create(|ctx| {
WindowActor {
widgets: ctx.widgets().unwrap(),
Expand Down
2 changes: 1 addition & 1 deletion examples/example_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
woab::run_actix_inside_gtk_event_loop("example")?;

factories.win_app.instantiate().actor()
.connect_signals::<WindowSignal>()
.connect_signals(WindowSignal::connector())
.create(|ctx| WindowActor {
widgets: ctx.widgets().unwrap(),
press_times: Default::default(),
Expand Down
8 changes: 4 additions & 4 deletions examples/example_taggged_signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ impl actix::StreamHandler<WindowSignal> 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::<AddendWidgets>().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();
Expand Down Expand Up @@ -112,7 +112,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
woab::run_actix_inside_gtk_event_loop("example")?;

factories.win_app.instantiate().actor()
.connect_signals::<WindowSignal>()
.connect_signals(WindowSignal::connector())
.create(|ctx| {
WindowActor {
widgets: ctx.widgets().unwrap(),
Expand Down
6 changes: 6 additions & 0 deletions macros/src/builder_signal_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,11 @@ pub fn impl_builder_signal_derive(ast: &syn::DeriveInput) -> Result<proc_macro2:
]
}
}

impl #enum_ident {
fn connector() -> woab::BuilderSingalConnector<Self, ()> {
<Self as woab::BuilderSignal>::connector()
}
}
})
}
101 changes: 37 additions & 64 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 }
/// });
/// }
/// ```

Expand All @@ -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<HashMap<&'static str, crate::RawSignalCallback>>,
Expand Down Expand Up @@ -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<W>(&self) -> Result<W, <gtk::Builder as TryInto<W>>::Error>
/// [`woab::BuilderFactory`](struct.BuilderFactory.html)) and map the builder's widgets to its fields.
pub fn widgets<W>(&self) -> Result<W, <gtk::Builder as TryInto<W>>::Error>
where gtk::Builder: TryInto<W>
{
self.builder.clone().try_into()
}

pub fn connect_signals<A, S>(&self, ctx: &mut actix::Context<A>)
where
A: actix::Actor<Context = actix::Context<A>>,
A: actix::StreamHandler<S>,
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<A, S, T>(&self, tag: T, ctx: &mut actix::Context<A>)
pub fn connect_signals<A, R>(&self, ctx: &mut actix::Context<A>, register_signal_handlers: R) -> &Self
where
T: Clone + 'static,
A: actix::Actor<Context = actix::Context<A>>,
A: actix::StreamHandler<(T, S)>,
S: crate::BuilderSignal,
R: crate::RegisterSignalHandlers,
R::MessageType: 'static,
A: actix::StreamHandler<R::MessageType>,
{
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::<A>(ctx, &mut callbacks);
self
}

pub fn actor<A: actix::Actor<Context = actix::Context<A>>>(&self) -> ActorBuilder<A> {
Expand Down Expand Up @@ -284,7 +257,7 @@ pub struct ActorBuilder<'a, A: actix::Actor<Context = actix::Context<A>>> {
}

impl<'a, A: actix::Actor<Context = actix::Context<A>>> ActorBuilder<'a, A> {
pub fn run(self, actor: A) -> actix::Addr<A> {
pub fn start(self, actor: A) -> actix::Addr<A> {
self.actor_context.run(actor)
}

Expand All @@ -297,22 +270,22 @@ impl<'a, A: actix::Actor<Context = actix::Context<A>>> ActorBuilder<'a, A> {
actor_builder_context.actor_context.run(actor)
}

pub fn connect_signals<S>(mut self) -> Self
where
A: actix::StreamHandler<S>,
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<A, E>) -> Result<actix::Addr<A>, 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<S, T>(mut self, tag: T) -> Self
pub fn connect_signals<R>(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<R::MessageType>,
{
self.builder_connector.connect_signals_tagged(tag, &mut self.actor_context);
self.builder_connector.connect_signals(&mut self.actor_context, register_signal_handlers);
self
}
}
Expand Down
87 changes: 86 additions & 1 deletion src/builder_signal.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,101 @@
use std::collections::HashMap;

use tokio::sync::mpsc;

/// Type of a gtk signal callback function that operates on uncast glib values
pub type RawSignalCallback = Box<dyn Fn(&[glib::Value]) -> Option<glib::Value>>;

/// 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
/// transmit them over `tx`.
fn bridge_signal(signal: &str, tx: mpsc::Sender<Self>) -> Option<RawSignalCallback>;

fn list_signals() -> &'static [&'static str];

fn connector() -> BuilderSingalConnector<Self, ()> {
BuilderSingalConnector {
transformer: (),
_phantom_data: Default::default(),
}
}
}

pub trait RegisterSignalHandlers {
type MessageType;

fn register_signal_handlers<A>(self, ctx: &mut A::Context, callbacks: &mut HashMap<&'static str, crate::RawSignalCallback>)
where
A: actix::Actor<Context = actix::Context<A>>,
A: actix::StreamHandler<Self::MessageType>;
}

pub trait SignalTransformer<S>: Clone {
type Output: 'static;

fn transform(&self, signal: S) -> Self::Output;
}

impl<S: 'static> SignalTransformer<S> for () {
type Output = S;

fn transform(&self, signal: S) -> Self::Output {
signal
}
}

impl<S: 'static, T: 'static + Clone> SignalTransformer<S> for (T,) {
type Output = (T, S);

fn transform(&self, signal: S) -> Self::Output {
(self.0.clone(), signal)
}
}

pub struct BuilderSingalConnector<S, T>
where
S: BuilderSignal,
T: Clone,
{
transformer: T,
_phantom_data: core::marker::PhantomData<S>,
}

impl<S> BuilderSingalConnector<S, ()>
where
S: BuilderSignal,
{
pub fn tag<T: Clone>(self, tag: T) -> BuilderSingalConnector<S, (T,)> {
BuilderSingalConnector {
transformer: (tag,),
_phantom_data: Default::default(),
}
}
}

impl<S, T> RegisterSignalHandlers for BuilderSingalConnector<S, T>
where
S: 'static,
S: BuilderSignal,
T: 'static,
T: SignalTransformer<S>,
{
type MessageType = T::Output;
// type MessageType = S;

fn register_signal_handlers<A>(self, ctx: &mut A::Context, callbacks: &mut HashMap<&'static str, crate::RawSignalCallback>)
where
A: actix::Actor<Context = actix::Context<A>>,
A: actix::StreamHandler<Self::MessageType>,
{
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);
}
}
Loading

0 comments on commit 2c8ce4b

Please sign in to comment.