From fc1cfb860b66a6f03e33baa6a48934dbdbea3011 Mon Sep 17 00:00:00 2001 From: Ludwig Stecher <4567.angel@gmail.com> Date: Sat, 30 Mar 2019 20:16:46 +0100 Subject: [PATCH 1/4] Added notifications crate --- Cargo.toml | 3 +- crates/notifications/Cargo.toml | 36 +++++ crates/notifications/src/builder.rs | 115 +++++++++++++++ crates/notifications/src/future.rs | 34 +++++ crates/notifications/src/lib.rs | 208 ++++++++++++++++++++++++++++ src/lib.rs | 1 + 6 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 crates/notifications/Cargo.toml create mode 100644 crates/notifications/src/builder.rs create mode 100644 crates/notifications/src/future.rs create mode 100644 crates/notifications/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 8f4e7e55..80b01902 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,10 @@ version = "0.1.0" [dependencies] gloo-timers = { version = "0.1.0", path = "crates/timers" } gloo-console-timer = { version = "0.1.0", path = "crates/console-timer" } +gloo-notifications = { version = "0.1.0", path = "crates/notifications" } [features] default = [] -futures = ["gloo-timers/futures"] +futures = ["gloo-timers/futures", "gloo-notifications/futures"] [workspace] diff --git a/crates/notifications/Cargo.toml b/crates/notifications/Cargo.toml new file mode 100644 index 00000000..8627c896 --- /dev/null +++ b/crates/notifications/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "gloo-notifications" +version = "0.1.0" +authors = ["Rust and WebAssembly Working Group"] +edition = "2018" + +[dependencies] +wasm-bindgen = "0.2.40" +js-sys = "0.3.17" + +[dependencies.futures_rs] +package = "futures" +version = "0.1.25" +optional = true + +[dependencies.wasm-bindgen-futures] +version = "0.3.17" +optional = true + +[dependencies.web-sys] +version = "0.3.17" +features = [ + "Notification", + "NotificationOptions", + "NotificationDirection", + "NotificationPermission", + "GetNotificationOptions", +] + +[features] +default = [] +futures = ["futures_rs", "wasm-bindgen-futures"] + + +[dev-dependencies] +wasm-bindgen-test = "0.2.40" diff --git a/crates/notifications/src/builder.rs b/crates/notifications/src/builder.rs new file mode 100644 index 00000000..172c0f25 --- /dev/null +++ b/crates/notifications/src/builder.rs @@ -0,0 +1,115 @@ +use crate::Notification; +use wasm_bindgen::JsValue; +use web_sys::{NotificationDirection, NotificationOptions}; + +/// A builder for a `Notification`. +/// +/// The builder is turned into a `Notification` by calling `.show()`, +/// which displays the notifcation on the screen. +/// +/// Example: +/// +/// ```rust +/// use gloo_notifications::Notification; +/// +/// Notification::request_permission() +/// .map(|mut builder| { +/// let _notification = builder +/// .title("Notification title") +/// .body("Notification body") +/// .show(); +/// }) +/// ``` +#[derive(Debug)] +pub struct NotificationBuilder<'a> { + title: &'a str, + sys_builder: NotificationOptions, +} + +impl<'a> NotificationBuilder<'a> { + #[inline] + pub(crate) fn new() -> Self { + NotificationBuilder { + title: "", + sys_builder: NotificationOptions::new(), + } + } + + #[inline] + pub(crate) fn get_inner(&self) -> (&str, &NotificationOptions) { + (self.title, &self.sys_builder) + } + + /// Sets the title of the notification + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn title(&mut self, title: &'a str) -> &mut Self { + self.title = title; + self + } + + /// Sets the body text of the notification + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn body(&mut self, body: &str) -> &mut Self { + self.sys_builder.body(body); + self + } + + /// Sets the data of the notification + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn data(&mut self, val: &JsValue) -> &mut Self { + self.sys_builder.data(val); + self + } + + /// Sets the direction of the notification, which is either Auto, Ltr or Rtl. + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn dir(&mut self, dir: NotificationDirection) -> &mut Self { + self.sys_builder.dir(dir); + self + } + + /// Sets the icon of the notification + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn icon(&mut self, val: &str) -> &mut Self { + self.sys_builder.icon(val); + self + } + + /// Sets the language of the notification + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn lang(&mut self, val: &str) -> &mut Self { + self.sys_builder.lang(val); + self + } + + /// Sets the requireInteraction property. + /// + /// If set to `true`, the notification stays visible until the user activates or closes it. + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn require_interaction(&mut self, val: bool) -> &mut Self { + self.sys_builder.require_interaction(val); + self + } + + /// Sets the tag of the notification + #[inline] + #[must_use = "You have to call .show() to display the notification"] + pub fn tag(&mut self, val: &str) -> &mut Self { + self.sys_builder.tag(val); + self + } + + /// Returns a new Notification from this builder + /// and displays it in the browser, if the permission is granted + #[inline] + pub fn show(&self) -> Notification { + Notification::new(self) + } +} diff --git a/crates/notifications/src/future.rs b/crates/notifications/src/future.rs new file mode 100644 index 00000000..b3315ac2 --- /dev/null +++ b/crates/notifications/src/future.rs @@ -0,0 +1,34 @@ +//! This module provides the `Notification::request_permission()` function, +//! which returns a `futures_rs::Future`. + +extern crate futures_rs as futures; + +use futures::Future; +use wasm_bindgen::{JsValue, UnwrapThrowExt}; +use wasm_bindgen_futures::JsFuture; + +use crate::{Notification, NotificationBuilder}; + +impl Notification { + /// ```rust + /// use gloo_notifications::Notification; + /// + /// Notification::request_permission() + /// .map(|mut builder| { + /// let _notification = builder + /// .title("Hello World") + /// .show(); + /// }) + /// .map_err(|_| { + /// // in case the permission is denied + /// }) + /// ``` + /// + #[must_use = "futures do nothing unless polled"] + pub fn request_permission<'a>() -> impl Future, Error = JsValue> + { + let promise = web_sys::Notification::request_permission().unwrap_throw(); + + JsFuture::from(promise).map(|_| NotificationBuilder::new()) + } +} diff --git a/crates/notifications/src/lib.rs b/crates/notifications/src/lib.rs new file mode 100644 index 00000000..ace99b60 --- /dev/null +++ b/crates/notifications/src/lib.rs @@ -0,0 +1,208 @@ +//! Displaying notifications on the web. +//! +//! This API comes in two flavors: +//! +//! 1. a callback style (that more directly mimics the JavaScript APIs), and +//! 2. a `Future`s API. +//! +//! Before a notification can be displayed, the user of the browser has to give his permission. +//! +//! Because the permission can also be withdrawn, permission *must* be checked +//! every time a notification is displayed. + +#![cfg_attr(feature = "futures", doc = "```no_run")] +#![cfg_attr(not(feature = "futures"), doc = "```ignore")] +#![deny(missing_docs, missing_debug_implementations)] + +#[cfg(feature = "futures")] +extern crate futures_rs as futures; + +use wasm_bindgen::{closure::Closure, JsCast, JsValue, UnwrapThrowExt}; +pub use web_sys::{NotificationDirection, NotificationOptions, NotificationPermission}; + +#[cfg(feature = "futures")] +pub mod future; + +mod builder; +pub use builder::NotificationBuilder; + +/// A notification. This struct can not be created directly, +/// because you might not have permission to show a notification. +/// +/// ## 1. Callback API +/// +/// ```rust +/// use gloo_notifications::Notification; +/// +/// Notification::request_permission_map(|mut builder| { +/// let _notification = builder +/// .title("Notification title") +/// .show(); +/// }); +/// +/// // or +/// +/// Notification::request_permission_map_or(|mut builder| { +/// let _notification = builder +/// .title("Notification title") +/// .show(); +/// }, |_| { +/// // in case the permission is denied +/// }); +/// ``` +/// +/// ## 2. `Future` API: +/// +/// ```rust +/// use gloo_notifications::Notification; +/// +/// Notification::request_permission() +/// .map(|mut builder| { +/// let _notification = builder +/// .title("Notification title") +/// .show(); +/// }) +/// .map_err(|_| { +/// // in case the permission is denied +/// }) +/// ``` +/// +/// ## Adding event listeners +/// +/// ```rust +/// use gloo_notifications::Notification; +/// +/// Notification::request_permission() +/// .map(|mut builder| { +/// let notification = builder +/// .title("Notification title") +/// .show(); +/// +/// on(¬ification, |e: ClickEvent| {}); +/// on(¬ification, |e: ShowEvent | {}); +/// on(¬ification, |e: ErrorEvent| {}); +/// on(¬ification, |e: CloseEvent| {}); +/// }) +/// ``` +#[repr(transparent)] +#[derive(Debug)] +pub struct Notification { + sys_notification: web_sys::Notification, +} + +impl Notification { + fn new<'a>(builder: &'a NotificationBuilder) -> Notification { + let (title, sys_builder) = builder.get_inner(); + let sys_notification = + web_sys::Notification::new_with_options(title, sys_builder).unwrap_throw(); + Notification { sys_notification } + } + + fn with_title<'a>(title: &'a str) -> Notification { + let sys_builder = &NotificationOptions::new(); + let sys_notification = + web_sys::Notification::new_with_options(title, sys_builder).unwrap_throw(); + Notification { sys_notification } + } + + /// This returns the permission to display notifications, which is one of the following values: + /// + /// - `default`: The user has neither granted, nor denied his permission. + /// Calling `Notification::request_permission()` displays a dialog window. + /// - `granted`: You are allowed to display notifications. + /// Calling `Notification::request_permission()` succeeds immediately. + /// - `denied`: You are forbidden to display notifications. + /// Calling `Notification::request_permission()` fails immediately. + #[inline] + pub fn permission() -> NotificationPermission { + web_sys::Notification::permission() + } + + /// Requests permission to display notifications, and asynchronously calls `f` + /// with a new `NotificationBuilder`, when the permission is granted. + /// + /// If the permission is denied, nothing happens. + pub fn request_permission_map(mut f: F) + where + F: FnMut(NotificationBuilder) + 'static, + { + let resolve = Closure::once(move |_| f(NotificationBuilder::new())); + + web_sys::Notification::request_permission() + .unwrap_throw() + .then(&resolve); + } + + /// Requests permission to display notifications, and asynchronously calls + /// + /// - `f(NotificationBuilder)`, if the permission is granted + /// - `g()`, if the permission is denied + pub fn request_permission_map_or(mut f: Ok, g: Err) + where + Ok: FnMut(NotificationBuilder) + 'static, + Err: FnMut(JsValue) + 'static, + { + let resolve = Closure::once(move |_| f(NotificationBuilder::new())); + let reject = Closure::once(g); + + web_sys::Notification::request_permission() + .unwrap_throw() + .then2(&resolve, &reject); + } + + + /// Requests permission to display notifications, + /// and displays a notification with a title, if the permission is granted. + /// + /// If the permission is denied, nothing happens. + pub fn request_permission_with_title(title: &'static str) { + let resolve = Closure::once(move |_| { + Notification::with_title(title); + }); + + web_sys::Notification::request_permission() + .unwrap_throw() + .then(&resolve); + } + + /// Requests permission to display notifications, + /// and displays a notification with a title, if the permission is granted. + /// + /// If the permission is denied, `f()` is called. + pub fn request_permission_with_title_or(title: &'static str, f: Err) + where + Err: FnMut(JsValue) + 'static + { + let resolve = Closure::once(move |_| { + Notification::with_title(title); + }); + let reject = Closure::once(f); + + web_sys::Notification::request_permission() + .unwrap_throw() + .then2(&resolve, &reject); + } + + + /// Sets the "click" event listener + pub fn onclick(&self, listener: Option) -> &Self + where + F: Fn(JsValue) + 'static, + { + match listener { + Some(f) => { + let boxed: Box = Box::new(f); + self.sys_notification + .set_onclick(Some(Closure::wrap(boxed).as_ref().unchecked_ref())); + } + None => self.sys_notification.set_onclick(None), + } + self + } + + /// Closes the notification + #[inline] + pub fn close(&self) { + self.sys_notification.close() + } +} diff --git a/src/lib.rs b/src/lib.rs index 52c3710b..a0cdcaff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,4 +5,5 @@ // Re-exports of toolkit crates. pub use gloo_console_timer as console_timer; +pub use gloo_notifications as notifications; pub use gloo_timers as timers; From aa943bf4ef35d83baeb97de9a620aa4b23f7c8bd Mon Sep 17 00:00:00 2001 From: Ludwig Stecher Date: Sat, 30 Mar 2019 21:52:08 +0100 Subject: [PATCH 2/4] Update docs --- crates/notifications/src/lib.rs | 182 +++++++++++++++++++------------- 1 file changed, 106 insertions(+), 76 deletions(-) diff --git a/crates/notifications/src/lib.rs b/crates/notifications/src/lib.rs index ace99b60..58c50072 100644 --- a/crates/notifications/src/lib.rs +++ b/crates/notifications/src/lib.rs @@ -1,14 +1,74 @@ //! Displaying notifications on the web. //! -//! This API comes in two flavors: -//! -//! 1. a callback style (that more directly mimics the JavaScript APIs), and -//! 2. a `Future`s API. +//! This API comes in two flavors: A callback style and `Future`s API. //! //! Before a notification can be displayed, the user of the browser has to give his permission. //! -//! Because the permission can also be withdrawn, permission *must* be checked -//! every time a notification is displayed. +//! ## 1. Callback style +//! +//! ```rust +//! use gloo_notifications::Notification; +//! +//! Notification::request_permission_map(|mut builder| { +//! builder.title("Notification title").show(); +//! }); +//! +//! Notification::request_permission_map_or(|mut builder| { +//! builder.title("Notification title") +//! .body("Notification body") +//! .show(); +//! }, |_| { +//! // in case the permission is denied +//! }); +//! +//! // short form, if you only need a title +//! Notification::request_permission_with_title("Notification title"); +//! ``` +//! +//! ## 2. `Future` API: +//! +//! ```rust +//! use gloo_notifications::Notification; +//! +//! Notification::request_permission() +//! .map(|mut builder| { +//! builder.title("Notification title").show(); +//! }) +//! .map_err(|_| { +//! // in case the permission is denied +//! }) +//! ``` +//! +//! ## Adding event listeners +//! +//! This part of the API is **unstable**! +//! +//! ```rust +//! use gloo_notifications::Notification; +//! +//! Notification::request_permission_map(|mut builder| { +//! let notification = builder +//! .title("Notification title") +//! .show(); +//! +//! notification.onclick(|_| { ... }); +//! }) +//! ``` +//! +//! ## Macro +//! +//! ```rust +//! use gloo_notifications::{Notification, notification}; +//! +//! // requests permission, then displays the notification +//! // and adds a "click" event listener +//! notification! { +//! title: "Hello World", +//! body: "Foo", +//! icon: "/assets/notification.png"; +//! onclick: |_| {} +//! } +//! ``` #![cfg_attr(feature = "futures", doc = "```no_run")] #![cfg_attr(not(feature = "futures"), doc = "```ignore")] @@ -26,68 +86,56 @@ pub mod future; mod builder; pub use builder::NotificationBuilder; -/// A notification. This struct can not be created directly, -/// because you might not have permission to show a notification. -/// -/// ## 1. Callback API -/// -/// ```rust -/// use gloo_notifications::Notification; -/// -/// Notification::request_permission_map(|mut builder| { -/// let _notification = builder -/// .title("Notification title") -/// .show(); -/// }); +/// A notification. /// -/// // or -/// -/// Notification::request_permission_map_or(|mut builder| { -/// let _notification = builder -/// .title("Notification title") -/// .show(); -/// }, |_| { -/// // in case the permission is denied -/// }); -/// ``` +/// This struct can not be created directly, you have to request permission first. +#[repr(transparent)] +#[derive(Debug)] +pub struct Notification { + sys_notification: web_sys::Notification, +} + +/// Requests permission, then displays the notification if the permission was granted. /// -/// ## 2. `Future` API: +/// ### Example /// /// ```rust -/// use gloo_notifications::Notification; -/// -/// Notification::request_permission() -/// .map(|mut builder| { -/// let _notification = builder -/// .title("Notification title") -/// .show(); -/// }) -/// .map_err(|_| { -/// // in case the permission is denied -/// }) +/// notification! { +/// title: "Hello World", +/// body: "Foo", +/// } /// ``` /// -/// ## Adding event listeners +/// The identifiers are the same as the setter methods of `NotificationBuilder`. /// -/// ```rust -/// use gloo_notifications::Notification; +/// The macro can also add a "click" event listener. Simply add a semicolon `;` after the +/// properties and add a `onclick` property: /// -/// Notification::request_permission() -/// .map(|mut builder| { -/// let notification = builder -/// .title("Notification title") -/// .show(); /// -/// on(¬ification, |e: ClickEvent| {}); -/// on(¬ification, |e: ShowEvent | {}); -/// on(¬ification, |e: ErrorEvent| {}); -/// on(¬ification, |e: CloseEvent| {}); -/// }) +/// ```rust +/// notification! { +/// title: "Hello World", +/// body: "Foo" ; +/// onclick: |_| { ... } +/// } /// ``` -#[repr(transparent)] -#[derive(Debug)] -pub struct Notification { - sys_notification: web_sys::Notification, +#[macro_export] +macro_rules! notification { + ( $($k:ident : $v:expr),* $(,)? ) => ( + Notification::request_permission_map(|mut builder| { + builder + $( .$k($v) )* + .show(); + }); + ); + ( $($k:ident : $v:expr),* ; onclick: $e:expr $(,)? ) => ( + Notification::request_permission_map(|mut builder| { + let o = builder + $( .$k($v) )* + .show(); + o.onclick(Some($e)); + }); + ); } impl Notification { @@ -165,24 +213,6 @@ impl Notification { .then(&resolve); } - /// Requests permission to display notifications, - /// and displays a notification with a title, if the permission is granted. - /// - /// If the permission is denied, `f()` is called. - pub fn request_permission_with_title_or(title: &'static str, f: Err) - where - Err: FnMut(JsValue) + 'static - { - let resolve = Closure::once(move |_| { - Notification::with_title(title); - }); - let reject = Closure::once(f); - - web_sys::Notification::request_permission() - .unwrap_throw() - .then2(&resolve, &reject); - } - /// Sets the "click" event listener pub fn onclick(&self, listener: Option) -> &Self From 103e836acb74843c5cecc6b115a5cfd68ea03eca Mon Sep 17 00:00:00 2001 From: Ludwig Stecher <4567.angel@gmail.com> Date: Mon, 1 Apr 2019 23:13:40 +0200 Subject: [PATCH 3/4] first try --- Cargo.toml | 1 + crates/ani-frames/Cargo.toml | 20 ++++ crates/ani-frames/src/callbacks.rs | 185 +++++++++++++++++++++++++++++ crates/ani-frames/src/lib.rs | 5 + crates/ani-frames/tests/web.rs | 42 +++++++ src/lib.rs | 1 + 6 files changed, 254 insertions(+) create mode 100644 crates/ani-frames/Cargo.toml create mode 100644 crates/ani-frames/src/callbacks.rs create mode 100644 crates/ani-frames/src/lib.rs create mode 100644 crates/ani-frames/tests/web.rs diff --git a/Cargo.toml b/Cargo.toml index 8f4e7e55..154f7a43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ version = "0.1.0" [dependencies] gloo-timers = { version = "0.1.0", path = "crates/timers" } gloo-console-timer = { version = "0.1.0", path = "crates/console-timer" } +gloo-ani-frames = { version = "0.1.0", path = "crates/ani-frames" } [features] default = [] diff --git a/crates/ani-frames/Cargo.toml b/crates/ani-frames/Cargo.toml new file mode 100644 index 00000000..7d1f3d64 --- /dev/null +++ b/crates/ani-frames/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "gloo-ani-frames" +version = "0.1.0" +authors = ["Rust and WebAssembly Working Group"] +edition = "2018" + +[dependencies] +wasm-bindgen = "0.2.40" +js-sys = "0.3.17" + +[dependencies.web-sys] +version = "0.3.17" +features = [ + "console", + "Window", +] + +[dev-dependencies] +wasm-bindgen-test = "0.2.40" +futures = "0.1.25" \ No newline at end of file diff --git a/crates/ani-frames/src/callbacks.rs b/crates/ani-frames/src/callbacks.rs new file mode 100644 index 00000000..59c4ec6d --- /dev/null +++ b/crates/ani-frames/src/callbacks.rs @@ -0,0 +1,185 @@ +use std::marker::PhantomData; +use std::mem::{replace}; +use std::rc::Rc; +use std::sync::Mutex; + +use wasm_bindgen::{JsValue, JsCast, UnwrapThrowExt, closure::Closure}; +use std::fmt::Debug; + +#[inline] +fn request_af(cls: F) -> i32 { + web_sys::window().unwrap_throw() + .request_animation_frame(Closure::once(cls).as_ref().unchecked_ref()) + .unwrap_throw() +} + +#[inline] +fn cancel_af(index: i32) -> Result<(), JsValue> { + web_sys::window().unwrap_throw() + .cancel_animation_frame(index) +} + + +pub struct Animation(Mutex>); + +impl Animation<()> { + pub fn request_frame(callback: F) -> AniIndex { + AniIndex::new(request_af(callback)) + } + + pub fn cancel_frame(index: AniIndex) { + cancel_af(index.inner).unwrap_throw() + } +} + +impl Animation { + fn from_ani(ani: Ani) -> Self { + Animation(Mutex::new(ani)) + } + + pub fn new() -> Rc { + let ani = Rc::new(Animation::from_ani(Ani::paused())); + ani.start(); + ani + } + + pub fn paused() -> Rc { + Rc::new(Animation::from_ani(Ani::paused())) + } + + pub fn add(&mut self, cb: &'static F) -> AniIndex + where F: Fn(&AniState) + 'static + { + let mut guard = self.0.lock().unwrap_throw(); + + let new_ix = AniIndex::new(guard.next_index.inner + 1); + let old_ix = replace(&mut guard.next_index, new_ix); + + guard.callbacks.push((AniIndex::new(old_ix.inner), cb)); + return old_ix + } + + pub fn remove(&mut self, index: AniIndex) -> Result<(), ()> { + let mut guard = self.0.lock().unwrap_throw(); + + let pos = guard.callbacks.iter() + .position(|(ix, _)| ix.inner == index.inner) + .ok_or(())?; + guard.callbacks.remove(pos); + Ok(()) + } + + + fn do_loop(ani: Rc) { + let cloned = ani.clone(); + let num = request_af(|| Self::do_loop(cloned)); + + let mut guard = ani.0.lock().unwrap_throw(); + guard.state = AniState::Running(num); + + for (_, cb) in &guard.callbacks { + cb(&guard.state) + } + } +} + +pub trait AnimationRc { + fn start(&self); + fn pause(&self); + fn once(&self); +} + +impl AnimationRc for Rc> { + fn start(&self) { + let mut guard = self.0.lock().unwrap_throw(); + match guard.state { + AniState::Paused => { + let cloned = self.clone(); + let num = request_af(move || Animation::do_loop(cloned)); + guard.state = AniState::Running(num); + } + AniState::Running(_) => (), + AniState::RunningOnce(ix) => { + cancel_af(ix).unwrap_throw(); + + let cloned = self.clone(); + let num = request_af(|| Animation::do_loop(cloned)); + guard.state = AniState::Running(num); + } + } + } + + fn pause(&self) { + let mut guard = self.0.lock().unwrap_throw(); + match guard.state { + AniState::Paused => (), + AniState::Running(ix) => { + cancel_af(ix).unwrap_throw(); + guard.state = AniState::Paused; + } + AniState::RunningOnce(ix) => { + cancel_af(ix).unwrap_throw(); + guard.state = AniState::Paused; + } + } + } + + fn once(&self) { + let mut guard = self.0.lock().unwrap_throw(); + match guard.state { + AniState::Paused => { + let cloned = self.clone(); + let num = request_af(move || { + let guard = cloned.0.lock().unwrap_throw(); + + for (_, cb) in &guard.callbacks { + cb(&guard.state) + } + }); + guard.state = AniState::RunningOnce(num); + } + AniState::Running(_) | AniState::RunningOnce(_) => () + } + } +} + + +struct Ani { + state: AniState, + next_index: AniIndex, + callbacks: Vec<(AniIndex, &'static dyn Fn(&AniState))>, +} + +impl Ani { + fn paused() -> Self { + Ani { + state: AniState::Paused, + next_index: AniIndex::new(1), + callbacks: vec![] + } + } +} + +#[derive(Copy, Clone, Debug)] +pub enum AniState { + Paused, + Running(i32), + RunningOnce(i32), +} + +pub struct AniIndex { + inner: i32, + _marker: PhantomData, +} + +impl Debug for AniIndex { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!(f, "AniIndex({})", self.inner) + } +} + +impl AniIndex { + fn new(index: i32) -> Self { + AniIndex { inner: index, _marker: PhantomData } + } +} \ No newline at end of file diff --git a/crates/ani-frames/src/lib.rs b/crates/ani-frames/src/lib.rs new file mode 100644 index 00000000..a71db095 --- /dev/null +++ b/crates/ani-frames/src/lib.rs @@ -0,0 +1,5 @@ +//! +//! + +mod callbacks; +pub use callbacks::{Animation as Animation, AniIndex, AniState}; \ No newline at end of file diff --git a/crates/ani-frames/tests/web.rs b/crates/ani-frames/tests/web.rs new file mode 100644 index 00000000..07088df6 --- /dev/null +++ b/crates/ani-frames/tests/web.rs @@ -0,0 +1,42 @@ +#![cfg(target_arch = "wasm32")] + +use gloo_ani_frames::*; +use futures::prelude::*; + +use wasm_bindgen_test::*; +use std::future::Future; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test(async)] +pub fn test() -> impl Future<> { + let mut is_executed = false; + let index1 = Animation::request_frame(|| { + is_executed = true; + }); + let index2 = Animation::request_frame(|| { + unreachable!() + }); + Animation::cancel_frame(index2); + + Animation::request_frame(|| { + Animation::request_frame(|| { + assert_eq!(is_executed, true); + + let mut ani = Animation::<()>::new(); + + let mut is_executed = false; + let ix1 = ani.add(|s| { + unreachable!(); + }); + let ix2 = ani.add(|s| { + is_executed = true + }); + ani.remove(ix1); + ani.add(|s| { + assert_eq!(); + }); + }); + }); + ; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 52c3710b..82d1cabe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,4 @@ // Re-exports of toolkit crates. pub use gloo_console_timer as console_timer; pub use gloo_timers as timers; +pub use gloo_ani_frames as ani_frames; From 413c3a450af1f28eebe79567dc7d84c7a43aee48 Mon Sep 17 00:00:00 2001 From: Ludwig Stecher <4567.angel@gmail.com> Date: Wed, 3 Apr 2019 22:56:30 +0200 Subject: [PATCH 4/4] Revert "first try" This reverts commit 103e836a --- Cargo.toml | 1 - crates/ani-frames/Cargo.toml | 20 ---- crates/ani-frames/src/callbacks.rs | 185 ----------------------------- crates/ani-frames/src/lib.rs | 5 - crates/ani-frames/tests/web.rs | 42 ------- src/lib.rs | 1 - 6 files changed, 254 deletions(-) delete mode 100644 crates/ani-frames/Cargo.toml delete mode 100644 crates/ani-frames/src/callbacks.rs delete mode 100644 crates/ani-frames/src/lib.rs delete mode 100644 crates/ani-frames/tests/web.rs diff --git a/Cargo.toml b/Cargo.toml index 154f7a43..8f4e7e55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ version = "0.1.0" [dependencies] gloo-timers = { version = "0.1.0", path = "crates/timers" } gloo-console-timer = { version = "0.1.0", path = "crates/console-timer" } -gloo-ani-frames = { version = "0.1.0", path = "crates/ani-frames" } [features] default = [] diff --git a/crates/ani-frames/Cargo.toml b/crates/ani-frames/Cargo.toml deleted file mode 100644 index 7d1f3d64..00000000 --- a/crates/ani-frames/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "gloo-ani-frames" -version = "0.1.0" -authors = ["Rust and WebAssembly Working Group"] -edition = "2018" - -[dependencies] -wasm-bindgen = "0.2.40" -js-sys = "0.3.17" - -[dependencies.web-sys] -version = "0.3.17" -features = [ - "console", - "Window", -] - -[dev-dependencies] -wasm-bindgen-test = "0.2.40" -futures = "0.1.25" \ No newline at end of file diff --git a/crates/ani-frames/src/callbacks.rs b/crates/ani-frames/src/callbacks.rs deleted file mode 100644 index 59c4ec6d..00000000 --- a/crates/ani-frames/src/callbacks.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::marker::PhantomData; -use std::mem::{replace}; -use std::rc::Rc; -use std::sync::Mutex; - -use wasm_bindgen::{JsValue, JsCast, UnwrapThrowExt, closure::Closure}; -use std::fmt::Debug; - -#[inline] -fn request_af(cls: F) -> i32 { - web_sys::window().unwrap_throw() - .request_animation_frame(Closure::once(cls).as_ref().unchecked_ref()) - .unwrap_throw() -} - -#[inline] -fn cancel_af(index: i32) -> Result<(), JsValue> { - web_sys::window().unwrap_throw() - .cancel_animation_frame(index) -} - - -pub struct Animation(Mutex>); - -impl Animation<()> { - pub fn request_frame(callback: F) -> AniIndex { - AniIndex::new(request_af(callback)) - } - - pub fn cancel_frame(index: AniIndex) { - cancel_af(index.inner).unwrap_throw() - } -} - -impl Animation { - fn from_ani(ani: Ani) -> Self { - Animation(Mutex::new(ani)) - } - - pub fn new() -> Rc { - let ani = Rc::new(Animation::from_ani(Ani::paused())); - ani.start(); - ani - } - - pub fn paused() -> Rc { - Rc::new(Animation::from_ani(Ani::paused())) - } - - pub fn add(&mut self, cb: &'static F) -> AniIndex - where F: Fn(&AniState) + 'static - { - let mut guard = self.0.lock().unwrap_throw(); - - let new_ix = AniIndex::new(guard.next_index.inner + 1); - let old_ix = replace(&mut guard.next_index, new_ix); - - guard.callbacks.push((AniIndex::new(old_ix.inner), cb)); - return old_ix - } - - pub fn remove(&mut self, index: AniIndex) -> Result<(), ()> { - let mut guard = self.0.lock().unwrap_throw(); - - let pos = guard.callbacks.iter() - .position(|(ix, _)| ix.inner == index.inner) - .ok_or(())?; - guard.callbacks.remove(pos); - Ok(()) - } - - - fn do_loop(ani: Rc) { - let cloned = ani.clone(); - let num = request_af(|| Self::do_loop(cloned)); - - let mut guard = ani.0.lock().unwrap_throw(); - guard.state = AniState::Running(num); - - for (_, cb) in &guard.callbacks { - cb(&guard.state) - } - } -} - -pub trait AnimationRc { - fn start(&self); - fn pause(&self); - fn once(&self); -} - -impl AnimationRc for Rc> { - fn start(&self) { - let mut guard = self.0.lock().unwrap_throw(); - match guard.state { - AniState::Paused => { - let cloned = self.clone(); - let num = request_af(move || Animation::do_loop(cloned)); - guard.state = AniState::Running(num); - } - AniState::Running(_) => (), - AniState::RunningOnce(ix) => { - cancel_af(ix).unwrap_throw(); - - let cloned = self.clone(); - let num = request_af(|| Animation::do_loop(cloned)); - guard.state = AniState::Running(num); - } - } - } - - fn pause(&self) { - let mut guard = self.0.lock().unwrap_throw(); - match guard.state { - AniState::Paused => (), - AniState::Running(ix) => { - cancel_af(ix).unwrap_throw(); - guard.state = AniState::Paused; - } - AniState::RunningOnce(ix) => { - cancel_af(ix).unwrap_throw(); - guard.state = AniState::Paused; - } - } - } - - fn once(&self) { - let mut guard = self.0.lock().unwrap_throw(); - match guard.state { - AniState::Paused => { - let cloned = self.clone(); - let num = request_af(move || { - let guard = cloned.0.lock().unwrap_throw(); - - for (_, cb) in &guard.callbacks { - cb(&guard.state) - } - }); - guard.state = AniState::RunningOnce(num); - } - AniState::Running(_) | AniState::RunningOnce(_) => () - } - } -} - - -struct Ani { - state: AniState, - next_index: AniIndex, - callbacks: Vec<(AniIndex, &'static dyn Fn(&AniState))>, -} - -impl Ani { - fn paused() -> Self { - Ani { - state: AniState::Paused, - next_index: AniIndex::new(1), - callbacks: vec![] - } - } -} - -#[derive(Copy, Clone, Debug)] -pub enum AniState { - Paused, - Running(i32), - RunningOnce(i32), -} - -pub struct AniIndex { - inner: i32, - _marker: PhantomData, -} - -impl Debug for AniIndex { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!(f, "AniIndex({})", self.inner) - } -} - -impl AniIndex { - fn new(index: i32) -> Self { - AniIndex { inner: index, _marker: PhantomData } - } -} \ No newline at end of file diff --git a/crates/ani-frames/src/lib.rs b/crates/ani-frames/src/lib.rs deleted file mode 100644 index a71db095..00000000 --- a/crates/ani-frames/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! -//! - -mod callbacks; -pub use callbacks::{Animation as Animation, AniIndex, AniState}; \ No newline at end of file diff --git a/crates/ani-frames/tests/web.rs b/crates/ani-frames/tests/web.rs deleted file mode 100644 index 07088df6..00000000 --- a/crates/ani-frames/tests/web.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![cfg(target_arch = "wasm32")] - -use gloo_ani_frames::*; -use futures::prelude::*; - -use wasm_bindgen_test::*; -use std::future::Future; - -wasm_bindgen_test_configure!(run_in_browser); - -#[wasm_bindgen_test(async)] -pub fn test() -> impl Future<> { - let mut is_executed = false; - let index1 = Animation::request_frame(|| { - is_executed = true; - }); - let index2 = Animation::request_frame(|| { - unreachable!() - }); - Animation::cancel_frame(index2); - - Animation::request_frame(|| { - Animation::request_frame(|| { - assert_eq!(is_executed, true); - - let mut ani = Animation::<()>::new(); - - let mut is_executed = false; - let ix1 = ani.add(|s| { - unreachable!(); - }); - let ix2 = ani.add(|s| { - is_executed = true - }); - ani.remove(ix1); - ani.add(|s| { - assert_eq!(); - }); - }); - }); - ; -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 82d1cabe..52c3710b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,3 @@ // Re-exports of toolkit crates. pub use gloo_console_timer as console_timer; pub use gloo_timers as timers; -pub use gloo_ani_frames as ani_frames;