Skip to content

Commit

Permalink
Convert Callback to be an enum to support Fn and FnOnce. (#1125)
Browse files Browse the repository at this point in the history
* Convert Callback to be an enum to support Fn and FnOnce.

* Run cargo fmt

* Update macro_test stderr

* Use type alias to simplify CallbackOnce

* Add Callback::callback_once

* Add Scope::callback_once

* Run cargo fmt

* Rename to Callback::once
  • Loading branch information
lukerandall committed Apr 25, 2020
1 parent 3b669fa commit d799368
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 15 deletions.
10 changes: 5 additions & 5 deletions yew-macro/tests/macro/html-tag-fail.stderr
Expand Up @@ -204,10 +204,10 @@ error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:32:20
|
32 | html! { <input onclick=1 /> };
| ^^^^^^^ expected struct `yew::callback::Callback`, found integer
| ^^^^^^^ expected enum `yew::callback::Callback`, found integer
|
= note: expected struct `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found type `{integer}`
= note: expected enum `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found type `{integer}`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
Expand All @@ -216,8 +216,8 @@ error[E0308]: mismatched types
33 | html! { <input onclick=Callback::from(|a: String| ()) /> };
| ^^^^^^^ expected struct `web_sys::features::gen_MouseEvent::MouseEvent`, found struct `std::string::String`
|
= note: expected struct `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found struct `yew::callback::Callback<std::string::String>`
= note: expected enum `yew::callback::Callback<web_sys::features::gen_MouseEvent::MouseEvent>`
found enum `yew::callback::Callback<std::string::String>`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: no method named `to_string` found for struct `NotToString` in the current scope
Expand Down
52 changes: 45 additions & 7 deletions yew/src/callback.rs
@@ -1,5 +1,6 @@
//! This module contains structs to interact with `Scope`s.
//! This module contains data types for interacting with `Scope`s.

use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;

Expand All @@ -10,36 +11,73 @@ use std::rc::Rc;
/// Callbacks should be used from JS callbacks or `setTimeout` calls.
/// </aside>
/// `Rc` wrapper used to make it clonable.
pub struct Callback<IN>(Rc<dyn Fn(IN)>);
pub enum Callback<IN> {
/// A callback that can be called multiple times
Callback(Rc<dyn Fn(IN)>),
/// A callback that will only be called once. Panics if it is called again
CallbackOnce(Rc<CallbackOnce<IN>>),
}

type CallbackOnce<IN> = RefCell<Option<Box<dyn FnOnce(IN)>>>;

impl<IN, F: Fn(IN) + 'static> From<F> for Callback<IN> {
fn from(func: F) -> Self {
Callback(Rc::new(func))
Callback::Callback(Rc::new(func))
}
}

impl<IN> Clone for Callback<IN> {
fn clone(&self) -> Self {
Callback(self.0.clone())
match self {
Callback::Callback(cb) => Callback::Callback(cb.clone()),
Callback::CallbackOnce(cb) => Callback::CallbackOnce(cb.clone()),
}
}
}

impl<IN> PartialEq for Callback<IN> {
fn eq(&self, other: &Callback<IN>) -> bool {
Rc::ptr_eq(&self.0, &other.0)
match (&self, &other) {
(Callback::Callback(cb), Callback::Callback(other_cb)) => Rc::ptr_eq(cb, other_cb),
(Callback::CallbackOnce(cb), Callback::CallbackOnce(other_cb)) => {
Rc::ptr_eq(cb, other_cb)
}
_ => false,
}
}
}

impl<IN> fmt::Debug for Callback<IN> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Callback<_>")
let data = match self {
Callback::Callback(_) => "Callback<_>",
Callback::CallbackOnce(_) => "CallbackOnce<_>",
};

f.write_str(data)
}
}

impl<IN> Callback<IN> {
/// This method calls the actual callback.
pub fn emit(&self, value: IN) {
(self.0)(value);
match self {
Callback::Callback(cb) => cb(value),
Callback::CallbackOnce(rc) => {
let cb = rc.replace(None);
let f = cb.expect("callback in CallbackOnce has already been used");
f(value)
}
};
}

/// Creates a callback from a FnOnce. You are responsible for ensuring
/// the callback is only called once otherwise it will panic.
pub fn once<F>(func: F) -> Self
where
F: FnOnce(IN) + 'static,
{
Callback::CallbackOnce(Rc::new(RefCell::new(Some(Box::new(func)))))
}

/// Creates a no-op callback which can be used when it is not suitable to use an
Expand Down
21 changes: 18 additions & 3 deletions yew/src/html/scope.rs
Expand Up @@ -119,7 +119,7 @@ impl<COMP: Component> Scope<COMP> {
self.update(ComponentUpdate::MessageBatch(messages));
}

/// This method creates a `Callback` which will send a message to the linked component's
/// Creates a `Callback` which will send a message to the linked component's
/// update method when invoked.
pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
where
Expand All @@ -134,8 +134,23 @@ impl<COMP: Component> Scope<COMP> {
closure.into()
}

/// This method creates a `Callback` which will send a batch of messages back to the linked
/// component's update method when called.
/// Creates a `Callback` from a FnOnce which will send a message to the linked
/// component's update method when invoked.
pub fn callback_once<F, IN, M>(&self, function: F) -> Callback<IN>
where
M: Into<COMP::Message>,
F: FnOnce(IN) -> M + 'static,
{
let scope = self.clone();
let closure = move |input| {
let output = function(input);
scope.send_message(output);
};
Callback::once(closure)
}

/// Creates a `Callback` which will send a batch of messages back to the linked
/// component's update method when invoked.
pub fn batch_callback<F, IN>(&self, function: F) -> Callback<IN>
where
F: Fn(IN) -> Vec<COMP::Message> + 'static,
Expand Down

0 comments on commit d799368

Please sign in to comment.