diff options
author | 2019-12-05 06:10:13 +0100 | |
---|---|---|
committer | 2019-12-05 06:10:13 +0100 | |
commit | d575f4541126e2ab25908fe55c6805f16716b2a5 (patch) | |
tree | 9d5221fe201b64e2ec649228ebae26be819dbf58 | |
parent | e92ea48e8814b42fc566017db085ca9bdaf3c272 (diff) | |
download | iced-d575f4541126e2ab25908fe55c6805f16716b2a5.tar.gz iced-d575f4541126e2ab25908fe55c6805f16716b2a5.tar.bz2 iced-d575f4541126e2ab25908fe55c6805f16716b2a5.zip |
Draft first version of event subscriptions :tada:
Diffstat (limited to '')
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | core/Cargo.toml | 2 | ||||
-rw-r--r-- | core/src/lib.rs | 8 | ||||
-rw-r--r-- | core/src/subscription.rs | 61 | ||||
-rw-r--r-- | native/Cargo.toml | 2 | ||||
-rw-r--r-- | native/src/lib.rs | 4 | ||||
-rw-r--r-- | native/src/widget/checkbox.rs | 15 | ||||
-rw-r--r-- | src/application.rs | 11 | ||||
-rw-r--r-- | src/native.rs | 4 | ||||
-rw-r--r-- | src/sandbox.rs | 6 | ||||
-rw-r--r-- | winit/src/application.rs | 79 |
11 files changed, 182 insertions, 12 deletions
@@ -41,6 +41,8 @@ serde_json = "1.0" directories = "2.0" reqwest = "0.9" rand = "0.7" +chrono = "0.4" +futures = "0.3" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen = "0.2.51" diff --git a/core/Cargo.toml b/core/Cargo.toml index c623ba78..0a8fd8ef 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,6 +10,8 @@ repository = "https://github.com/hecrj/iced" [features] # Exposes a future-based `Command` type command = ["futures"] +# Exposes a future-based `Subscription` type +subscription = ["futures"] [dependencies] futures = { version = "0.3", optional = true } diff --git a/core/src/lib.rs b/core/src/lib.rs index 65304e8b..6f13c310 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,7 +9,7 @@ //! [Iced]: https://github.com/hecrj/iced //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native //! [`iced_web`]: https://github.com/hecrj/iced/tree/master/web -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] @@ -38,3 +38,9 @@ mod command; #[cfg(feature = "command")] pub use command::Command; + +#[cfg(feature = "subscription")] +pub mod subscription; + +#[cfg(feature = "subscription")] +pub use subscription::Subscription; diff --git a/core/src/subscription.rs b/core/src/subscription.rs new file mode 100644 index 00000000..1e6695d6 --- /dev/null +++ b/core/src/subscription.rs @@ -0,0 +1,61 @@ +//! Generate events asynchronously for you application. + +/// An event subscription. +pub struct Subscription<T> { + definitions: Vec<Box<dyn Definition<Message = T>>>, +} + +impl<T> Subscription<T> { + pub fn none() -> Self { + Self { + definitions: Vec::new(), + } + } + + pub fn batch(subscriptions: impl Iterator<Item = Subscription<T>>) -> Self { + Self { + definitions: subscriptions + .flat_map(|subscription| subscription.definitions) + .collect(), + } + } + + pub fn definitions(self) -> Vec<Box<dyn Definition<Message = T>>> { + self.definitions + } +} + +impl<T, A> From<A> for Subscription<T> +where + A: Definition<Message = T> + 'static, +{ + fn from(definition: A) -> Self { + Self { + definitions: vec![Box::new(definition)], + } + } +} + +/// The definition of an event subscription. +pub trait Definition { + type Message; + + fn id(&self) -> u64; + + fn stream( + &self, + ) -> ( + futures::stream::BoxStream<'static, Self::Message>, + Box<dyn Handle>, + ); +} + +pub trait Handle { + fn cancel(&mut self); +} + +impl<T> std::fmt::Debug for Subscription<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Command").finish() + } +} diff --git a/native/Cargo.toml b/native/Cargo.toml index 7993676e..c5f9c7ac 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -8,6 +8,6 @@ license = "MIT" repository = "https://github.com/hecrj/iced" [dependencies] -iced_core = { version = "0.1.0", path = "../core", features = ["command"] } +iced_core = { version = "0.1.0", path = "../core", features = ["command", "subscription"] } twox-hash = "1.5" raw-window-handle = "0.3" diff --git a/native/src/lib.rs b/native/src/lib.rs index 45c3c699..9afb3bc9 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -52,8 +52,8 @@ mod size; mod user_interface; pub use iced_core::{ - Align, Background, Color, Command, Font, HorizontalAlignment, Length, - Point, Rectangle, Vector, VerticalAlignment, + subscription, Align, Background, Color, Command, Font, HorizontalAlignment, + Length, Point, Rectangle, Subscription, Vector, VerticalAlignment, }; pub use element::Element; diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 9563291c..159cba84 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -31,6 +31,7 @@ pub struct Checkbox<Message> { on_toggle: Box<dyn Fn(bool) -> Message>, label: String, label_color: Option<Color>, + width: Length, } impl<Message> Checkbox<Message> { @@ -53,6 +54,7 @@ impl<Message> Checkbox<Message> { on_toggle: Box::new(f), label: String::from(label), label_color: None, + width: Length::Fill, } } @@ -63,6 +65,14 @@ impl<Message> Checkbox<Message> { self.label_color = Some(color.into()); self } + + /// Sets the width of the [`Checkbox`]. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } } impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message> @@ -70,7 +80,7 @@ where Renderer: self::Renderer + text::Renderer + row::Renderer, { fn width(&self) -> Length { - Length::Fill + Length::Shrink } fn height(&self) -> Length { @@ -85,6 +95,7 @@ where let size = self::Renderer::default_size(renderer); Row::<(), Renderer>::new() + .width(self.width) .spacing(15) .align_items(Align::Center) .push( @@ -92,7 +103,7 @@ where .width(Length::Units(size as u16)) .height(Length::Units(size as u16)), ) - .push(Text::new(&self.label)) + .push(Text::new(&self.label).width(self.width)) .layout(renderer, limits) } diff --git a/src/application.rs b/src/application.rs index a4d20e68..95113344 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,4 @@ -use crate::{Command, Element, Settings}; +use crate::{Command, Element, Settings, Subscription}; /// An interactive cross-platform application. /// @@ -117,6 +117,11 @@ pub trait Application: Sized { /// [`Command`]: struct.Command.html fn update(&mut self, message: Self::Message) -> Command<Self::Message>; + /// TODO + fn subscriptions(&self) -> Subscription<Self::Message> { + Subscription::none() + } + /// Returns the widgets to display in the [`Application`]. /// /// These widgets can produce __messages__ based on user interaction. @@ -168,6 +173,10 @@ where self.0.update(message) } + fn subscriptions(&self) -> Subscription<Self::Message> { + self.0.subscriptions() + } + fn view(&mut self) -> Element<'_, Self::Message> { self.0.view() } diff --git a/src/native.rs b/src/native.rs index 3537dd52..e3733955 100644 --- a/src/native.rs +++ b/src/native.rs @@ -1,6 +1,6 @@ pub use iced_winit::{ - Align, Background, Color, Command, Font, HorizontalAlignment, Length, - VerticalAlignment, + subscription, Align, Background, Color, Command, Font, HorizontalAlignment, + Length, Subscription, VerticalAlignment, }; pub mod widget { diff --git a/src/sandbox.rs b/src/sandbox.rs index acf7f5e0..248aa152 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -1,4 +1,4 @@ -use crate::{Application, Command, Element, Settings}; +use crate::{Application, Command, Element, Settings, Subscription}; /// A sandboxed [`Application`]. /// @@ -149,6 +149,10 @@ where Command::none() } + fn subscriptions(&self) -> Subscription<T::Message> { + Subscription::none() + } + fn view(&mut self) -> Element<'_, T::Message> { T::view(self) } diff --git a/winit/src/application.rs b/winit/src/application.rs index 3772a667..959e142d 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -2,9 +2,10 @@ use crate::{ conversion, input::{keyboard, mouse}, renderer::{Target, Windowed}, - Cache, Command, Container, Debug, Element, Event, Length, MouseCursor, - Settings, UserInterface, + subscription, Cache, Command, Container, Debug, Element, Event, Length, + MouseCursor, Settings, Subscription, UserInterface, }; +use std::collections::HashMap; /// An interactive, native cross-platform application. /// @@ -57,6 +58,9 @@ pub trait Application: Sized { /// [`Command`]: struct.Command.html fn update(&mut self, message: Self::Message) -> Command<Self::Message>; + /// TODO + fn subscriptions(&self) -> Subscription<Self::Message>; + /// Returns the widgets to display in the [`Application`]. /// /// These widgets can produce __messages__ based on user interaction. @@ -89,11 +93,15 @@ pub trait Application: Sized { let proxy = event_loop.create_proxy(); let mut thread_pool = futures::executor::ThreadPool::new().expect("Create thread pool"); + let mut alive_subscriptions = Subscriptions::new(); let mut external_messages = Vec::new(); let (mut application, init_command) = Self::new(); spawn(init_command, &mut thread_pool, &proxy); + let subscriptions = application.subscriptions(); + alive_subscriptions.update(subscriptions, &mut thread_pool, &proxy); + let mut title = application.title(); let window = { @@ -204,6 +212,13 @@ pub trait Application: Sized { debug.update_finished(); } + let subscriptions = application.subscriptions(); + alive_subscriptions.update( + subscriptions, + &mut thread_pool, + &proxy, + ); + // Update window title let new_title = application.title(); @@ -404,6 +419,66 @@ fn spawn<Message: Send>( } } +pub struct Subscriptions { + alive: HashMap<u64, Box<dyn subscription::Handle>>, +} + +impl Subscriptions { + fn new() -> Self { + Self { + alive: HashMap::new(), + } + } + + fn update<Message: Send>( + &mut self, + subscriptions: Subscription<Message>, + thread_pool: &mut futures::executor::ThreadPool, + proxy: &winit::event_loop::EventLoopProxy<Message>, + ) { + use futures::stream::StreamExt; + + let definitions = subscriptions.definitions(); + let mut alive = std::collections::HashSet::new(); + + for definition in definitions { + let id = definition.id(); + let _ = alive.insert(id); + + if !self.alive.contains_key(&id) { + let (stream, handle) = definition.stream(); + + let proxy = + std::sync::Arc::new(std::sync::Mutex::new(proxy.clone())); + + let future = stream.for_each(move |message| { + proxy + .lock() + .expect("Acquire event loop proxy lock") + .send_event(message) + .expect("Send subscription result to event loop"); + + futures::future::ready(()) + }); + + thread_pool.spawn_ok(future); + + let _ = self.alive.insert(id, handle); + } + } + + self.alive.retain(|id, handle| { + let is_still_alive = alive.contains(&id); + + if !is_still_alive { + handle.cancel(); + } + + is_still_alive + }); + } +} + // As defined in: http://www.unicode.org/faq/private_use.html // TODO: Remove once https://github.com/rust-windowing/winit/pull/1254 lands fn is_private_use_character(c: char) -> bool { |