From bdca20fc4a3e8f6bd8ffb59de75e6ca0f8a94b6a Mon Sep 17 00:00:00 2001 From: Vladyslav Nikonov Date: Thu, 21 Oct 2021 22:42:14 +0300 Subject: Experimental wgpu WebGL backend support - Added missing `draw_cache_align_4x4` call for `brush_glyph` on wasm32 target - Added WebGL support to `integratio_wgpu` example - Fixed test.yml CI workflow - Removed spir-v shader in `integration_wgpu`; Fixed formatting - Removed redundant `BoxStream` typedef --- .github/workflows/test.yml | 2 + examples/integration_wgpu/.gitignore | 2 + examples/integration_wgpu/Cargo.toml | 13 ++- examples/integration_wgpu/README.md | 17 ++++ examples/integration_wgpu/index.html | 21 +++++ examples/integration_wgpu/src/controls.rs | 19 ++++- examples/integration_wgpu/src/main.rs | 94 ++++++++++++++++------ examples/integration_wgpu/src/scene.rs | 23 +++--- examples/integration_wgpu/src/shader/frag.spv | Bin 352 -> 0 bytes examples/integration_wgpu/src/shader/frag.wgsl | 4 + examples/integration_wgpu/src/shader/vert.spv | Bin 904 -> 0 bytes examples/integration_wgpu/src/shader/vert.wgsl | 6 ++ futures/src/runtime.rs | 49 +++++++++++- futures/src/subscription/tracker.rs | 52 ++++++++++-- glow/src/text.rs | 12 ++- native/src/subscription.rs | 42 +++++++++- wgpu/Cargo.toml | 1 + wgpu/src/text.rs | 10 ++- winit/Cargo.toml | 4 +- winit/src/application.rs | 106 +++++++++++++++++-------- winit/src/clipboard.rs | 25 ++++++ 21 files changed, 415 insertions(+), 87 deletions(-) create mode 100644 examples/integration_wgpu/.gitignore create mode 100644 examples/integration_wgpu/index.html delete mode 100644 examples/integration_wgpu/src/shader/frag.spv create mode 100644 examples/integration_wgpu/src/shader/frag.wgsl delete mode 100644 examples/integration_wgpu/src/shader/vert.spv create mode 100644 examples/integration_wgpu/src/shader/vert.wgsl diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0450f13d..433afadc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,3 +37,5 @@ jobs: run: cargo build --package tour --target wasm32-unknown-unknown - name: Check compilation of `todos` example run: cargo build --package todos --target wasm32-unknown-unknown + - name: Check compilation of `integration_wgpu` example + run: cargo build --package integration_wgpu --target wasm32-unknown-unknown diff --git a/examples/integration_wgpu/.gitignore b/examples/integration_wgpu/.gitignore new file mode 100644 index 00000000..e188dc28 --- /dev/null +++ b/examples/integration_wgpu/.gitignore @@ -0,0 +1,2 @@ +*.wasm +*.js diff --git a/examples/integration_wgpu/Cargo.toml b/examples/integration_wgpu/Cargo.toml index a088dd1b..0ded1a56 100644 --- a/examples/integration_wgpu/Cargo.toml +++ b/examples/integration_wgpu/Cargo.toml @@ -7,5 +7,16 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu", features = ["spirv"] } +iced_wgpu = { path = "../../wgpu", features = ["webgl"] } env_logger = "0.8" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1.7" +console_log = "0.2.0" +log = "0.4" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } +# This dependency a little bit quirky, it is deep in the tree and without `js` feature it +# refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch +# to make it work +getrandom = { version = "0.2", features = ["js"] } diff --git a/examples/integration_wgpu/README.md b/examples/integration_wgpu/README.md index c51c2c65..faefa153 100644 --- a/examples/integration_wgpu/README.md +++ b/examples/integration_wgpu/README.md @@ -15,5 +15,22 @@ You can run it with `cargo run`: cargo run --package integration ``` +### How to run this example with WebGL backend +NOTE: Currently, WebGL backend is is still experimental, so expect bugs. + +```sh +# 0. Install prerequisites +cargo install wasm-bindgen-cli https +# 1. cd to the current folder +# 2. Compile wasm module +cargo build -p integration_wgpu --target wasm32-unknown-unknown +# 3. Invoke wasm-bindgen +wasm-bindgen ../../target/wasm32-unknown-unknown/debug/integration_wgpu.wasm --out-dir . --target web --no-typescript +# 4. run http server +http +# 5. Open 127.0.0.1:8000 in browser +``` + + [`main`]: src/main.rs [`wgpu`]: https://github.com/gfx-rs/wgpu diff --git a/examples/integration_wgpu/index.html b/examples/integration_wgpu/index.html new file mode 100644 index 00000000..461e67a4 --- /dev/null +++ b/examples/integration_wgpu/index.html @@ -0,0 +1,21 @@ + + + + + Iced - wgpu + WebGL integration + + +

integration_wgpu

+ + + + + diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs index 4f110bd2..9bca40eb 100644 --- a/examples/integration_wgpu/src/controls.rs +++ b/examples/integration_wgpu/src/controls.rs @@ -1,23 +1,29 @@ use iced_wgpu::Renderer; use iced_winit::widget::slider::{self, Slider}; +use iced_winit::widget::text_input::{self, TextInput}; use iced_winit::widget::{Column, Row, Text}; use iced_winit::{Alignment, Color, Command, Element, Length, Program}; pub struct Controls { background_color: Color, + text: String, sliders: [slider::State; 3], + text_input: text_input::State, } #[derive(Debug, Clone)] pub enum Message { BackgroundColorChanged(Color), + TextChanged(String), } impl Controls { pub fn new() -> Controls { Controls { background_color: Color::BLACK, + text: Default::default(), sliders: Default::default(), + text_input: Default::default(), } } @@ -35,6 +41,9 @@ impl Program for Controls { Message::BackgroundColorChanged(color) => { self.background_color = color; } + Message::TextChanged(text) => { + self.text = text; + } } Command::none() @@ -42,7 +51,9 @@ impl Program for Controls { fn view(&mut self) -> Element { let [r, g, b] = &mut self.sliders; + let t = &mut self.text_input; let background_color = self.background_color; + let text = &self.text; let sliders = Row::new() .width(Length::Units(500)) @@ -96,7 +107,13 @@ impl Program for Controls { Text::new(format!("{:?}", background_color)) .size(14) .color(Color::WHITE), - ), + ) + .push(TextInput::new( + t, + "Placeholder", + text, + move |text| Message::TextChanged(text), + )), ), ) .into() diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 35a69a7d..045ee0d3 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -14,11 +14,39 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] +use web_sys::HtmlCanvasElement; +#[cfg(target_arch = "wasm32")] +use winit::platform::web::WindowBuilderExtWebSys; + pub fn main() { + #[cfg(target_arch = "wasm32")] + let canvas_element = { + console_log::init_with_level(log::Level::Debug) + .expect("could not initialize logger"); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.get_element_by_id("iced_canvas")) + .and_then(|element| element.dyn_into::().ok()) + .expect("Canvas with id `iced_canvas` is missing") + }; + #[cfg(not(target_arch = "wasm32"))] env_logger::init(); // Initialize winit let event_loop = EventLoop::new(); + + #[cfg(target_arch = "wasm32")] + let window = winit::window::WindowBuilder::new() + .with_canvas(Some(canvas_element)) + .build(&event_loop) + .expect("Failed to build winit window"); + + #[cfg(not(target_arch = "wasm32"))] let window = winit::window::Window::new(&event_loop).unwrap(); let physical_size = window.inner_size(); @@ -31,18 +59,35 @@ pub fn main() { let mut clipboard = Clipboard::connect(&window); // Initialize wgpu - let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY); + + #[cfg(target_arch = "wasm32")] + let default_backend = wgpu::Backends::GL; + #[cfg(not(target_arch = "wasm32"))] + let default_backend = wgpu::Backends::PRIMARY; + + let backend = + wgpu::util::backend_bits_from_env().unwrap_or(default_backend); + + let instance = wgpu::Instance::new(backend); let surface = unsafe { instance.create_surface(&window) }; let (format, (mut device, queue)) = futures::executor::block_on(async { - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .expect("Request adapter"); + let adapter = wgpu::util::initialize_adapter_from_env_or_default( + &instance, + backend, + Some(&surface), + ) + .await + .expect("No suitable GPU adapters found on the system!"); + + let adapter_features = adapter.features(); + + #[cfg(target_arch = "wasm32")] + let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()); + + #[cfg(not(target_arch = "wasm32"))] + let needed_limits = wgpu::Limits::default(); ( surface @@ -52,8 +97,8 @@ pub fn main() { .request_device( &wgpu::DeviceDescriptor { label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), + features: adapter_features & wgpu::Features::default(), + limits: needed_limits, }, None, ) @@ -62,20 +107,17 @@ pub fn main() { ) }); - { - let size = window.inner_size(); - - surface.configure( - &device, - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Mailbox, - }, - ) - }; + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: physical_size.width, + height: physical_size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ); + let mut resized = false; // Initialize staging belt and local pool @@ -83,7 +125,7 @@ pub fn main() { let mut local_pool = futures::executor::LocalPool::new(); // Initialize scene and GUI controls - let scene = Scene::new(&mut device); + let scene = Scene::new(&mut device, format); let controls = Controls::new(); // Initialize iced diff --git a/examples/integration_wgpu/src/scene.rs b/examples/integration_wgpu/src/scene.rs index 910d8d8c..fbda1326 100644 --- a/examples/integration_wgpu/src/scene.rs +++ b/examples/integration_wgpu/src/scene.rs @@ -6,8 +6,11 @@ pub struct Scene { } impl Scene { - pub fn new(device: &wgpu::Device) -> Scene { - let pipeline = build_pipeline(device); + pub fn new( + device: &wgpu::Device, + texture_format: wgpu::TextureFormat, + ) -> Scene { + let pipeline = build_pipeline(device, texture_format); Scene { pipeline } } @@ -47,12 +50,14 @@ impl Scene { } } -fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { - let vs_module = - device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv")); - - let fs_module = - device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv")); +fn build_pipeline( + device: &wgpu::Device, + texture_format: wgpu::TextureFormat, +) -> wgpu::RenderPipeline { + let (vs_module, fs_module) = ( + device.create_shader_module(&wgpu::include_wgsl!("shader/vert.wgsl")), + device.create_shader_module(&wgpu::include_wgsl!("shader/frag.wgsl")), + ); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -74,7 +79,7 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { module: &fs_module, entry_point: "main", targets: &[wgpu::ColorTargetState { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: texture_format, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent::REPLACE, alpha: wgpu::BlendComponent::REPLACE, diff --git a/examples/integration_wgpu/src/shader/frag.spv b/examples/integration_wgpu/src/shader/frag.spv deleted file mode 100644 index 9d6807c9..00000000 Binary files a/examples/integration_wgpu/src/shader/frag.spv and /dev/null differ diff --git a/examples/integration_wgpu/src/shader/frag.wgsl b/examples/integration_wgpu/src/shader/frag.wgsl new file mode 100644 index 00000000..a6f61336 --- /dev/null +++ b/examples/integration_wgpu/src/shader/frag.wgsl @@ -0,0 +1,4 @@ +[[stage(fragment)]] +fn main() -> [[location(0)]] vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/examples/integration_wgpu/src/shader/vert.spv b/examples/integration_wgpu/src/shader/vert.spv deleted file mode 100644 index 0cabc9c0..00000000 Binary files a/examples/integration_wgpu/src/shader/vert.spv and /dev/null differ diff --git a/examples/integration_wgpu/src/shader/vert.wgsl b/examples/integration_wgpu/src/shader/vert.wgsl new file mode 100644 index 00000000..7ef47fb2 --- /dev/null +++ b/examples/integration_wgpu/src/shader/vert.wgsl @@ -0,0 +1,6 @@ +[[stage(vertex)]] +fn main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4 { + let x = f32(1 - i32(in_vertex_index)) * 0.5; + let y = f32(1 - i32(in_vertex_index & 1u) * 2) * 0.5; + return vec4(x, y, 0.0, 1.0); +} diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 7779e235..96104cd9 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -5,6 +5,50 @@ use crate::{subscription, Executor, Subscription}; use futures::{channel::mpsc, Sink}; use std::marker::PhantomData; +#[cfg(not(target_arch = "wasm32"))] +mod trait_aliases { + use super::*; + + pub trait RuntimeMessage: Send + 'static {} + + impl RuntimeMessage for T where T: Send + 'static {} + + pub trait RuntimeMessageSender: + Sink + Unpin + Send + Clone + 'static + { + } + + impl RuntimeMessageSender for T where + T: Sink + + Unpin + + Send + + Clone + + 'static + { + } +} + +#[cfg(target_arch = "wasm32")] +mod trait_aliases { + use super::*; + + pub trait RuntimeMessage: 'static {} + + impl RuntimeMessage for T where T: 'static {} + + pub trait RuntimeMessageSender: + Sink + Unpin + Clone + 'static + { + } + + impl RuntimeMessageSender for T where + T: Sink + Unpin + Clone + 'static + { + } +} + +pub use trait_aliases::{RuntimeMessage, RuntimeMessageSender}; + /// A batteries-included runtime of commands and subscriptions. /// /// If you have an [`Executor`], a [`Runtime`] can be leveraged to run any @@ -23,9 +67,8 @@ where Hasher: std::hash::Hasher + Default, Event: Send + Clone + 'static, Executor: self::Executor, - Sender: - Sink + Unpin + Send + Clone + 'static, - Message: Send + 'static, + Sender: RuntimeMessageSender, + Message: RuntimeMessage, { /// Creates a new empty [`Runtime`]. /// diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 3a8d4a87..01e0c105 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -3,6 +3,50 @@ use crate::{BoxFuture, Subscription}; use futures::{channel::mpsc, sink::Sink}; use std::{collections::HashMap, marker::PhantomData}; +#[cfg(not(target_arch = "wasm32"))] +mod trait_aliases { + use super::*; + + pub trait TrackerMessage: Send + 'static {} + + impl TrackerMessage for T where T: Send + 'static {} + + pub trait TrackerMessageReceiver: + Sink + Unpin + Send + Clone + 'static + { + } + + impl TrackerMessageReceiver for T where + T: Sink + + Unpin + + Send + + Clone + + 'static + { + } +} + +#[cfg(target_arch = "wasm32")] +mod trait_aliases { + use super::*; + + pub trait TrackerMessage: 'static {} + + impl TrackerMessage for T where T: 'static {} + + pub trait TrackerMessageReceiver: + Sink + Unpin + Clone + 'static + { + } + + impl TrackerMessageReceiver for T where + T: Sink + Unpin + Clone + 'static + { + } +} + +pub use trait_aliases::{TrackerMessage, TrackerMessageReceiver}; + /// A registry of subscription streams. /// /// If you have an application that continuously returns a [`Subscription`], @@ -57,12 +101,8 @@ where receiver: Receiver, ) -> Vec> where - Message: 'static + Send, - Receiver: 'static - + Sink - + Unpin - + Send - + Clone, + Message: TrackerMessage, + Receiver: TrackerMessageReceiver, { use futures::{future::FutureExt, stream::StreamExt}; diff --git a/glow/src/text.rs b/glow/src/text.rs index 3b6f3bf5..834e7902 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -46,11 +46,15 @@ impl Pipeline { .expect("Load fallback font") }); - let draw_brush = - glow_glyph::GlyphBrushBuilder::using_font(font.clone()) + let draw_brush_builder = + wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading) - .build(&gl); + .draw_cache_multithread(multithreading); + + #[cfg(target_arch = "wasm32")] + let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); + + let draw_brush = draw_brush_builder.build(&gl); let measure_brush = glyph_brush::GlyphBrushBuilder::using_font(font).build(); diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 63834654..4fb7e760 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -7,6 +7,37 @@ use iced_futures::BoxStream; use std::hash::Hash; +#[cfg(not(target_arch = "wasm32"))] +mod trait_aliases { + use super::*; + + /// Wrapper type + pub trait RunnerStream: + Stream + Send + 'static + { + } + + impl RunnerStream for T where + T: Stream + Send + 'static + { + } +} + +#[cfg(target_arch = "wasm32")] +mod trait_aliases { + use super::*; + + /// Wrapper type + pub trait RunnerStream: Stream + 'static {} + + impl RunnerStream for T where + T: Stream + 'static + { + } +} + +pub use trait_aliases::RunnerStream; + /// A request to listen to external events. /// /// Besides performing async actions on demand with [`Command`], most @@ -191,7 +222,7 @@ impl Recipe where I: Hash + 'static, F: FnOnce(EventStream) -> S, - S: Stream + Send + 'static, + S: RunnerStream, { type Output = Message; @@ -203,6 +234,13 @@ where fn stream(self: Box, input: EventStream) -> BoxStream { use futures::stream::StreamExt; - (self.spawn)(input).boxed() + #[cfg(target_arch = "wasm32")] + { + (self.spawn)(input).boxed_local() + } + #[cfg(not(target_arch = "wasm32"))] + { + (self.spawn)(input).boxed() + } } } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f4c4fa2c..46012ea7 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -25,6 +25,7 @@ canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] spirv = ["wgpu/spirv"] +webgl = ["wgpu/webgl"] [dependencies] wgpu = "0.12" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 336696ee..45f1f2de 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -48,11 +48,15 @@ impl Pipeline { .expect("Load fallback font") }); - let draw_brush = + let draw_brush_builder = wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading) - .build(device, format); + .draw_cache_multithread(multithreading); + + #[cfg(target_arch = "wasm32")] + let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); + + let draw_brush = draw_brush_builder.build(device, format); let measure_brush = glyph_brush::GlyphBrushBuilder::using_font(font).build(); diff --git a/winit/Cargo.toml b/winit/Cargo.toml index bfcfacbc..e0581b00 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,7 +14,6 @@ categories = ["gui"] debug = ["iced_native/debug"] [dependencies] -window_clipboard = "0.2" log = "0.4" thiserror = "1.0" @@ -37,3 +36,6 @@ path = "../futures" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.window_clipboard] +version = "0.2" diff --git a/winit/src/application.rs b/winit/src/application.rs index e9212b5e..a3715b04 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -115,12 +115,16 @@ where use futures::task; use futures::Future; use winit::event_loop::EventLoop; + #[cfg(not(target_arch = "wasm32"))] use winit::platform::run_return::EventLoopExtRunReturn; let mut debug = Debug::new(); debug.startup_started(); + #[cfg(not(target_arch = "wasm32"))] let mut event_loop = EventLoop::with_user_event(); + #[cfg(target_arch = "wasm32")] + let event_loop = EventLoop::with_user_event(); let mut proxy = event_loop.create_proxy(); let mut runtime = { @@ -179,41 +183,81 @@ where let mut context = task::Context::from_waker(task::noop_waker_ref()); - event_loop.run_return(move |event, _, control_flow| { - use winit::event_loop::ControlFlow; + #[cfg(not(target_arch = "wasm32"))] + { + event_loop.run_return(move |event, _, control_flow| { + use winit::event_loop::ControlFlow; - if let ControlFlow::Exit = control_flow { - return; - } + if let ControlFlow::Exit = control_flow { + return; + } - let event = match event { - winit::event::Event::WindowEvent { - event: - winit::event::WindowEvent::ScaleFactorChanged { - new_inner_size, - .. - }, - window_id, - } => Some(winit::event::Event::WindowEvent { - event: winit::event::WindowEvent::Resized(*new_inner_size), - window_id, - }), - _ => event.to_static(), - }; - - if let Some(event) = event { - sender.start_send(event).expect("Send event"); - - let poll = instance.as_mut().poll(&mut context); - - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, + let event = match event { + winit::event::Event::WindowEvent { + event: + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + }, + window_id, + } => Some(winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::Resized(*new_inner_size), + window_id, + }), + _ => event.to_static(), }; - } - }); - Ok(()) + if let Some(event) = event { + sender.start_send(event).expect("Send event"); + + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); + + Ok(()) + } + + #[cfg(target_arch = "wasm32")] + { + event_loop.run(move |event, _, control_flow| { + use winit::event_loop::ControlFlow; + + if let ControlFlow::Exit = control_flow { + return; + } + + let event = match event { + winit::event::Event::WindowEvent { + event: + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + }, + window_id, + } => Some(winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::Resized(*new_inner_size), + window_id, + }), + _ => event.to_static(), + }; + + if let Some(event) = event { + sender.start_send(event).expect("Send event"); + + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, + }; + } + }); + } } async fn run_instance( diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 1b92b28d..197d32b3 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -6,15 +6,40 @@ use crate::command::{self, Command}; /// A buffer for short-term storage and transfer within and between /// applications. #[allow(missing_debug_implementations)] +#[cfg(target_arch = "wasm32")] +pub struct Clipboard; + +#[cfg(target_arch = "wasm32")] +impl Clipboard { + /// Creates a new [`Clipboard`] for the given window. + pub fn connect(_window: &winit::window::Window) -> Clipboard { + Clipboard + } + + /// Reads the current content of the [`Clipboard`] as text. + pub fn read(&self) -> Option { + None + } + + /// Writes the given text contents to the [`Clipboard`]. + pub fn write(&mut self, _contents: String) {} +} + +/// A buffer for short-term storage and transfer within and between +/// applications. +#[allow(missing_debug_implementations)] +#[cfg(not(target_arch = "wasm32"))] pub struct Clipboard { state: State, } +#[cfg(not(target_arch = "wasm32"))] enum State { Connected(window_clipboard::Clipboard), Unavailable, } +#[cfg(not(target_arch = "wasm32"))] impl Clipboard { /// Creates a new [`Clipboard`] for the given window. pub fn connect(window: &winit::window::Window) -> Clipboard { -- cgit From 9f47ac8d3158ba8a54730dddea5b58a9926b6e1b Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Sat, 22 Jan 2022 18:39:59 -0600 Subject: Use instant instead of std::instant --- native/Cargo.toml | 1 + native/src/mouse/click.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/native/Cargo.toml b/native/Cargo.toml index 8f0aea6a..d8e75a4e 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -14,6 +14,7 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" +instant = { version="0.1", features=["wasm-bindgen"] } [dependencies.iced_core] version = "0.4" diff --git a/native/src/mouse/click.rs b/native/src/mouse/click.rs index 6c8b61a5..58cfda61 100644 --- a/native/src/mouse/click.rs +++ b/native/src/mouse/click.rs @@ -1,6 +1,6 @@ //! Track mouse clicks. use crate::Point; -use std::time::Instant; +use instant::Instant; /// A mouse click. #[derive(Debug, Clone, Copy)] -- cgit From 908259663de60ae62723a73c3e93159c4bd22586 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 27 Jan 2022 11:27:12 -0600 Subject: Remove wasm-specific clipboard --- winit/Cargo.toml | 4 +--- winit/src/clipboard.rs | 28 +--------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index e0581b00..984a9316 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -16,6 +16,7 @@ debug = ["iced_native/debug"] [dependencies] log = "0.4" thiserror = "1.0" +window_clipboard = "0.2" [dependencies.winit] version = "0.26" @@ -36,6 +37,3 @@ path = "../futures" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.window_clipboard] -version = "0.2" diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 197d32b3..559b3521 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -3,43 +3,17 @@ pub use iced_native::clipboard::Action; use crate::command::{self, Command}; -/// A buffer for short-term storage and transfer within and between -/// applications. +#[allow(missing_docs)] #[allow(missing_debug_implementations)] -#[cfg(target_arch = "wasm32")] -pub struct Clipboard; - -#[cfg(target_arch = "wasm32")] -impl Clipboard { - /// Creates a new [`Clipboard`] for the given window. - pub fn connect(_window: &winit::window::Window) -> Clipboard { - Clipboard - } - - /// Reads the current content of the [`Clipboard`] as text. - pub fn read(&self) -> Option { - None - } - - /// Writes the given text contents to the [`Clipboard`]. - pub fn write(&mut self, _contents: String) {} -} - -/// A buffer for short-term storage and transfer within and between -/// applications. -#[allow(missing_debug_implementations)] -#[cfg(not(target_arch = "wasm32"))] pub struct Clipboard { state: State, } -#[cfg(not(target_arch = "wasm32"))] enum State { Connected(window_clipboard::Clipboard), Unavailable, } -#[cfg(not(target_arch = "wasm32"))] impl Clipboard { /// Creates a new [`Clipboard`] for the given window. pub fn connect(window: &winit::window::Window) -> Clipboard { -- cgit From beebf25c5f2fe2a2e6bc860cebfa7e8d2c862cdc Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 27 Jan 2022 11:28:55 -0600 Subject: Re-add docs for clipboard --- winit/src/clipboard.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 559b3521..1b92b28d 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -3,7 +3,8 @@ pub use iced_native::clipboard::Action; use crate::command::{self, Command}; -#[allow(missing_docs)] +/// A buffer for short-term storage and transfer within and between +/// applications. #[allow(missing_debug_implementations)] pub struct Clipboard { state: State, -- cgit From f3fb00f838ff94c7dd38d6ffac1a159ba460398c Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 27 Jan 2022 14:38:13 -0600 Subject: Use glow_glyp in opengl_integration example --- glow/src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glow/src/text.rs b/glow/src/text.rs index 834e7902..0d45d61b 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -47,7 +47,7 @@ impl Pipeline { }); let draw_brush_builder = - wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) + glow_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) .draw_cache_multithread(multithreading); -- cgit From f39147d3822037955dc27c38cea873bb65c594e3 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 27 Jan 2022 14:52:53 -0600 Subject: Always use event_loop.run --- winit/src/application.rs | 107 +++++++++++++---------------------------------- 1 file changed, 30 insertions(+), 77 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index a3715b04..53b7a6c7 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -115,15 +115,10 @@ where use futures::task; use futures::Future; use winit::event_loop::EventLoop; - #[cfg(not(target_arch = "wasm32"))] - use winit::platform::run_return::EventLoopExtRunReturn; let mut debug = Debug::new(); debug.startup_started(); - #[cfg(not(target_arch = "wasm32"))] - let mut event_loop = EventLoop::with_user_event(); - #[cfg(target_arch = "wasm32")] let event_loop = EventLoop::with_user_event(); let mut proxy = event_loop.create_proxy(); @@ -183,81 +178,39 @@ where let mut context = task::Context::from_waker(task::noop_waker_ref()); - #[cfg(not(target_arch = "wasm32"))] - { - event_loop.run_return(move |event, _, control_flow| { - use winit::event_loop::ControlFlow; + event_loop.run(move |event, _, control_flow| { + use winit::event_loop::ControlFlow; - if let ControlFlow::Exit = control_flow { - return; - } - - let event = match event { - winit::event::Event::WindowEvent { - event: - winit::event::WindowEvent::ScaleFactorChanged { - new_inner_size, - .. - }, - window_id, - } => Some(winit::event::Event::WindowEvent { - event: winit::event::WindowEvent::Resized(*new_inner_size), - window_id, - }), - _ => event.to_static(), - }; - - if let Some(event) = event { - sender.start_send(event).expect("Send event"); - - let poll = instance.as_mut().poll(&mut context); - - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, - }; - } - }); - - Ok(()) - } - - #[cfg(target_arch = "wasm32")] - { - event_loop.run(move |event, _, control_flow| { - use winit::event_loop::ControlFlow; - - if let ControlFlow::Exit = control_flow { - return; - } + if let ControlFlow::Exit = control_flow { + return; + } - let event = match event { - winit::event::Event::WindowEvent { - event: - winit::event::WindowEvent::ScaleFactorChanged { - new_inner_size, - .. - }, - window_id, - } => Some(winit::event::Event::WindowEvent { - event: winit::event::WindowEvent::Resized(*new_inner_size), - window_id, - }), - _ => event.to_static(), + let event = match event { + winit::event::Event::WindowEvent { + event: + winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size, + .. + }, + window_id, + } => Some(winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::Resized(*new_inner_size), + window_id, + }), + _ => event.to_static(), + }; + + if let Some(event) = event { + sender.start_send(event).expect("Send event"); + + let poll = instance.as_mut().poll(&mut context); + + *control_flow = match poll { + task::Poll::Pending => ControlFlow::Wait, + task::Poll::Ready(_) => ControlFlow::Exit, }; - - if let Some(event) = event { - sender.start_send(event).expect("Send event"); - - let poll = instance.as_mut().poll(&mut context); - - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, - }; - } - }); - } + } + }); } async fn run_instance( -- cgit From 1e3feee3a36f25d7e2eda231c3e6b895858952c5 Mon Sep 17 00:00:00 2001 From: Kai Mast Date: Thu, 27 Jan 2022 14:56:28 -0600 Subject: Reduce diff --- winit/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 984a9316..bfcfacbc 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,9 +14,9 @@ categories = ["gui"] debug = ["iced_native/debug"] [dependencies] +window_clipboard = "0.2" log = "0.4" thiserror = "1.0" -window_clipboard = "0.2" [dependencies.winit] version = "0.26" -- cgit From 825c7749ff364cf1f7ae5cab0c25f27ed85c7d82 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Jan 2022 16:47:50 +0700 Subject: Replace `iced_web` with WebGL support in `wgpu` :tada: --- Cargo.toml | 11 +- src/application.rs | 78 ++------ src/element.rs | 4 - src/error.rs | 1 - src/lib.rs | 19 +- src/settings.rs | 1 - src/widget.rs | 96 ++++----- src/window/icon.rs | 19 -- src/window/position.rs | 1 - src/window/settings.rs | 1 - web/Cargo.toml | 48 ----- web/README.md | 68 ------- web/src/bus.rs | 53 ----- web/src/command.rs | 72 ------- web/src/command/action.rs | 28 --- web/src/css.rs | 217 --------------------- web/src/element.rs | 90 --------- web/src/hasher.rs | 21 -- web/src/lib.rs | 431 ----------------------------------------- web/src/subscription.rs | 18 -- web/src/widget.rs | 68 ------- web/src/widget/button.rs | 192 ------------------ web/src/widget/checkbox.rs | 150 -------------- web/src/widget/column.rs | 148 -------------- web/src/widget/container.rs | 153 --------------- web/src/widget/image.rs | 186 ------------------ web/src/widget/progress_bar.rs | 116 ----------- web/src/widget/radio.rs | 155 --------------- web/src/widget/row.rs | 148 -------------- web/src/widget/scrollable.rs | 152 --------------- web/src/widget/slider.rs | 183 ----------------- web/src/widget/space.rs | 63 ------ web/src/widget/text.rs | 148 -------------- web/src/widget/text_input.rs | 234 ---------------------- web/src/widget/toggler.rs | 171 ---------------- 35 files changed, 69 insertions(+), 3475 deletions(-) delete mode 100644 web/Cargo.toml delete mode 100644 web/README.md delete mode 100644 web/src/bus.rs delete mode 100644 web/src/command.rs delete mode 100644 web/src/command/action.rs delete mode 100644 web/src/css.rs delete mode 100644 web/src/element.rs delete mode 100644 web/src/hasher.rs delete mode 100644 web/src/lib.rs delete mode 100644 web/src/subscription.rs delete mode 100644 web/src/widget.rs delete mode 100644 web/src/widget/button.rs delete mode 100644 web/src/widget/checkbox.rs delete mode 100644 web/src/widget/column.rs delete mode 100644 web/src/widget/container.rs delete mode 100644 web/src/widget/image.rs delete mode 100644 web/src/widget/progress_bar.rs delete mode 100644 web/src/widget/radio.rs delete mode 100644 web/src/widget/row.rs delete mode 100644 web/src/widget/scrollable.rs delete mode 100644 web/src/widget/slider.rs delete mode 100644 web/src/widget/space.rs delete mode 100644 web/src/widget/text.rs delete mode 100644 web/src/widget/text_input.rs delete mode 100644 web/src/widget/toggler.rs diff --git a/Cargo.toml b/Cargo.toml index e553c78f..cdce2ae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ categories = ["gui"] resolver = "2" [features] -default = ["wgpu", "default_system_font"] +default = ["wgpu"] # Enables the `iced_wgpu` renderer wgpu = ["iced_wgpu"] # Enables the `Image` widget @@ -58,7 +58,6 @@ members = [ "lazy", "native", "style", - "web", "wgpu", "winit", "examples/bezier_tool", @@ -94,16 +93,16 @@ members = [ [dependencies] iced_core = { version = "0.4", path = "core" } iced_futures = { version = "0.3", path = "futures" } +iced_winit = { version = "0.3", path = "winit" } +iced_glutin = { version = "0.2", path = "glutin", optional = true } +iced_glow = { version = "0.2", path = "glow", optional = true } thiserror = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_winit = { version = "0.3", path = "winit" } -iced_glutin = { version = "0.2", path = "glutin", optional = true } iced_wgpu = { version = "0.4", path = "wgpu", optional = true } -iced_glow = { version = "0.2", path = "glow", optional = true} [target.'cfg(target_arch = "wasm32")'.dependencies] -iced_web = { version = "0.4", path = "web" } +iced_wgpu = { version = "0.4", path = "wgpu", features = ["webgl"], optional = true } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/application.rs b/src/application.rs index b722c8a3..14a16d61 100644 --- a/src/application.rs +++ b/src/application.rs @@ -198,39 +198,28 @@ pub trait Application: Sized { where Self: 'static, { - #[cfg(not(target_arch = "wasm32"))] - { - let renderer_settings = crate::renderer::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - text_multithreading: settings.text_multithreading, - antialiasing: if settings.antialiasing { - Some(crate::renderer::settings::Antialiasing::MSAAx4) - } else { - None - }, - ..crate::renderer::Settings::from_env() - }; - - Ok(crate::runtime::application::run::< - Instance, - Self::Executor, - crate::renderer::window::Compositor, - >(settings.into(), renderer_settings)?) - } - - #[cfg(target_arch = "wasm32")] - { - as iced_web::Application>::run(settings.flags); - - Ok(()) - } + let renderer_settings = crate::renderer::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, + antialiasing: if settings.antialiasing { + Some(crate::renderer::settings::Antialiasing::MSAAx4) + } else { + None + }, + ..crate::renderer::Settings::from_env() + }; + + Ok(crate::runtime::application::run::< + Instance, + Self::Executor, + crate::renderer::window::Compositor, + >(settings.into(), renderer_settings)?) } } struct Instance(A); -#[cfg(not(target_arch = "wasm32"))] impl iced_winit::Program for Instance where A: Application, @@ -247,7 +236,6 @@ where } } -#[cfg(not(target_arch = "wasm32"))] impl crate::runtime::Application for Instance where A: Application, @@ -288,35 +276,3 @@ where self.0.should_exit() } } - -#[cfg(target_arch = "wasm32")] -impl iced_web::Application for Instance -where - A: Application, -{ - type Executor = A::Executor; - type Message = A::Message; - type Flags = A::Flags; - - fn new(flags: Self::Flags) -> (Self, Command) { - let (app, command) = A::new(flags); - - (Instance(app), command) - } - - fn title(&self) -> String { - self.0.title() - } - - fn update(&mut self, message: Self::Message) -> Command { - self.0.update(message) - } - - fn subscription(&self) -> Subscription { - self.0.subscription() - } - - fn view(&mut self) -> Element<'_, Self::Message> { - self.0.view() - } -} diff --git a/src/element.rs b/src/element.rs index 6f47c701..8bad18c1 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,9 +1,5 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. -#[cfg(not(target_arch = "wasm32"))] pub type Element<'a, Message> = crate::runtime::Element<'a, Message, crate::renderer::Renderer>; - -#[cfg(target_arch = "wasm32")] -pub use iced_web::Element; diff --git a/src/error.rs b/src/error.rs index c8fa6636..17479c60 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,6 @@ pub enum Error { GraphicsAdapterNotFound, } -#[cfg(not(target_arch = "wasm32"))] impl From for Error { fn from(error: iced_winit::Error) -> Error { match error { diff --git a/src/lib.rs b/src/lib.rs index 6c0e03e8..41fb3a8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,29 +208,18 @@ pub mod window; )] pub mod time; -#[cfg(all( - not(target_arch = "wasm32"), - not(feature = "glow"), - feature = "wgpu" -))] +#[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_winit as runtime; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(feature = "glow")] use iced_glutin as runtime; -#[cfg(all( - not(target_arch = "wasm32"), - not(feature = "glow"), - feature = "wgpu" -))] +#[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_wgpu as renderer; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(feature = "glow")] use iced_glow as renderer; -#[cfg(target_arch = "wasm32")] -use iced_web as runtime; - #[doc(no_inline)] pub use widget::*; diff --git a/src/settings.rs b/src/settings.rs index c521a62a..d31448fb 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -106,7 +106,6 @@ where } } -#[cfg(not(target_arch = "wasm32"))] impl From> for iced_winit::Settings { fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { diff --git a/src/widget.rs b/src/widget.rs index 0f0b0325..c619bcfa 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -13,63 +13,53 @@ //! //! These widgets have their own module with a `State` type. For instance, a //! [`TextInput`] has some [`text_input::State`]. -#[cfg(not(target_arch = "wasm32"))] -mod platform { - pub use crate::renderer::widget::{ - button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, - Space, Text, - }; +pub use crate::renderer::widget::{ + button, checkbox, container, pane_grid, pick_list, progress_bar, radio, + rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, Space, + Text, +}; - #[cfg(any(feature = "canvas", feature = "glow_canvas"))] - #[cfg_attr( - docsrs, - doc(cfg(any(feature = "canvas", feature = "glow_canvas"))) - )] - pub use crate::renderer::widget::canvas; +#[cfg(any(feature = "canvas", feature = "glow_canvas"))] +#[cfg_attr( + docsrs, + doc(cfg(any(feature = "canvas", feature = "glow_canvas"))) +)] +pub use crate::renderer::widget::canvas; - #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] - #[cfg_attr( - docsrs, - doc(cfg(any(feature = "qr_code", feature = "glow_qr_code"))) - )] - pub use crate::renderer::widget::qr_code; +#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] +#[cfg_attr( + docsrs, + doc(cfg(any(feature = "qr_code", feature = "glow_qr_code"))) +)] +pub use crate::renderer::widget::qr_code; - #[cfg_attr(docsrs, doc(cfg(feature = "image")))] - pub mod image { - //! Display images in your user interface. - pub use crate::runtime::image::Handle; - pub use crate::runtime::widget::image::viewer; - pub use crate::runtime::widget::image::{Image, Viewer}; - } - - #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] - pub mod svg { - //! Display vector graphics in your user interface. - pub use crate::runtime::svg::Handle; - pub use crate::runtime::widget::svg::Svg; - } - - #[doc(no_inline)] - pub use { - button::Button, checkbox::Checkbox, container::Container, image::Image, - pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, - radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip, - }; - - #[cfg(any(feature = "canvas", feature = "glow_canvas"))] - #[doc(no_inline)] - pub use canvas::Canvas; - - #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] - #[doc(no_inline)] - pub use qr_code::QRCode; +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub mod image { + //! Display images in your user interface. + pub use crate::runtime::image::Handle; + pub use crate::runtime::widget::image::viewer; + pub use crate::runtime::widget::image::{Image, Viewer}; } -#[cfg(target_arch = "wasm32")] -mod platform { - pub use iced_web::widget::*; +#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] +pub mod svg { + //! Display vector graphics in your user interface. + pub use crate::runtime::svg::Handle; + pub use crate::runtime::widget::svg::Svg; } -pub use platform::*; +#[doc(no_inline)] +pub use { + button::Button, checkbox::Checkbox, container::Container, image::Image, + pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, + radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, toggler::Toggler, tooltip::Tooltip, +}; + +#[cfg(any(feature = "canvas", feature = "glow_canvas"))] +#[doc(no_inline)] +pub use canvas::Canvas; + +#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] +#[doc(no_inline)] +pub use qr_code::QRCode; diff --git a/src/window/icon.rs b/src/window/icon.rs index 287538b1..aacadfca 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -3,18 +3,11 @@ use std::fmt; use std::io; /// The icon of a window. -#[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Clone)] pub struct Icon(iced_winit::winit::window::Icon); -/// The icon of a window. -#[cfg(target_arch = "wasm32")] -#[derive(Debug, Clone)] -pub struct Icon; - impl Icon { /// Creates an icon from 32bpp RGBA data. - #[cfg(not(target_arch = "wasm32"))] pub fn from_rgba( rgba: Vec, width: u32, @@ -25,16 +18,6 @@ impl Icon { Ok(Icon(raw)) } - - /// Creates an icon from 32bpp RGBA data. - #[cfg(target_arch = "wasm32")] - pub fn from_rgba( - _rgba: Vec, - _width: u32, - _height: u32, - ) -> Result { - Ok(Icon) - } } /// An error produced when using `Icon::from_rgba` with invalid arguments. @@ -62,7 +45,6 @@ pub enum Error { OsError(io::Error), } -#[cfg(not(target_arch = "wasm32"))] impl From for Error { fn from(error: iced_winit::winit::window::BadIcon) -> Self { use iced_winit::winit::window::BadIcon; @@ -86,7 +68,6 @@ impl From for Error { } } -#[cfg(not(target_arch = "wasm32"))] impl From for iced_winit::winit::window::Icon { fn from(icon: Icon) -> Self { icon.0 diff --git a/src/window/position.rs b/src/window/position.rs index 8535ef6a..6b9fac41 100644 --- a/src/window/position.rs +++ b/src/window/position.rs @@ -21,7 +21,6 @@ impl Default for Position { } } -#[cfg(not(target_arch = "wasm32"))] impl From for iced_winit::Position { fn from(position: Position) -> Self { match position { diff --git a/src/window/settings.rs b/src/window/settings.rs index ec6c3071..8e32f4fb 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -47,7 +47,6 @@ impl Default for Settings { } } -#[cfg(not(target_arch = "wasm32"))] impl From for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { diff --git a/web/Cargo.toml b/web/Cargo.toml deleted file mode 100644 index 01a19e7c..00000000 --- a/web/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "iced_web" -version = "0.4.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2018" -description = "A web backend for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_web" -readme = "README.md" -keywords = ["gui", "ui", "web", "interface", "widgets"] -categories = ["web-programming"] - -[badges] -maintenance = { status = "actively-developed" } - -[dependencies] -dodrio = "0.2" -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" -url = "2.0" -num-traits = "0.2" -base64 = "0.13" - -[dependencies.iced_core] -version = "0.4" -path = "../core" - -[dependencies.iced_futures] -version = "0.3" -path = "../futures" - -[dependencies.iced_style] -version = "0.3" -path = "../style" - -[dependencies.web-sys] -version = "0.3.27" -features = [ - "console", - "Document", - "HtmlElement", - "HtmlInputElement", - "Event", - "EventTarget", - "InputEvent", - "KeyboardEvent", -] diff --git a/web/README.md b/web/README.md deleted file mode 100644 index b797385e..00000000 --- a/web/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# `iced_web` -[![Documentation](https://docs.rs/iced_web/badge.svg)][documentation] -[![Crates.io](https://img.shields.io/crates/v/iced_web.svg)](https://crates.io/crates/iced_web) -[![License](https://img.shields.io/crates/l/iced_web.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) -[![project chat](https://img.shields.io/badge/chat-on_zulip-brightgreen.svg)](https://iced.zulipchat.com) - -`iced_web` takes [`iced_core`] and builds a WebAssembly runtime on top. It achieves this by introducing a `Widget` trait that can be used to produce VDOM nodes. - -The crate is currently a __very experimental__, simple abstraction layer over [`dodrio`]. - -[documentation]: https://docs.rs/iced_web -[`iced_core`]: ../core -[`dodrio`]: https://github.com/fitzgen/dodrio - -## Installation -Add `iced_web` as a dependency in your `Cargo.toml`: - -```toml -iced_web = "0.4" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases - -## Usage -The current build process is a bit involved, as [`wasm-pack`] does not currently [support building binary crates](https://github.com/rustwasm/wasm-pack/issues/734). - -Therefore, we instead build using the `wasm32-unknown-unknown` target and use the [`wasm-bindgen`] CLI to generate appropriate bindings. - -For instance, let's say we want to build the [`tour` example]: - -``` -cd examples -cargo build --package tour --target wasm32-unknown-unknown -wasm-bindgen ../target/wasm32-unknown-unknown/debug/tour.wasm --out-dir tour --web -``` - -*__Note:__ Keep in mind that Iced is still in early exploration stages and most of the work needs to happen on the native side of the ecosystem. At this stage, it is important to be able to batch work without having to constantly jump back and forth. Because of this, there is currently no requirement for the `master` branch to contain a cross-platform API at all times. If you hit an issue when building an example and want to help, it may be a good way to [start contributing]!* - -[start contributing]: ../CONTRIBUTING.md - -Once the example is compiled, we need to create an `.html` file to load our application: - -```html - - - - - - Tour - Iced - - - - - -``` - -Finally, we serve it using an HTTP server and access it with our browser. - -[`wasm-pack`]: https://github.com/rustwasm/wasm-pack -[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen -[`tour` example]: ../examples/README.md#tour diff --git a/web/src/bus.rs b/web/src/bus.rs deleted file mode 100644 index 5ce8e810..00000000 --- a/web/src/bus.rs +++ /dev/null @@ -1,53 +0,0 @@ -use iced_futures::futures::channel::mpsc; -use std::rc::Rc; - -/// A publisher of messages. -/// -/// It can be used to route messages back to the [`Application`]. -/// -/// [`Application`]: crate::Application -#[allow(missing_debug_implementations)] -pub struct Bus { - publish: Rc ()>>, -} - -impl Clone for Bus { - fn clone(&self) -> Self { - Bus { - publish: self.publish.clone(), - } - } -} - -impl Bus -where - Message: 'static, -{ - pub(crate) fn new(publish: mpsc::UnboundedSender) -> Self { - Self { - publish: Rc::new(Box::new(move |message| { - publish.unbounded_send(message).expect("Send message"); - })), - } - } - - /// Publishes a new message for the [`Application`]. - /// - /// [`Application`]: crate::Application - pub fn publish(&self, message: Message) { - (self.publish)(message) - } - - /// Creates a new [`Bus`] that applies the given function to the messages - /// before publishing. - pub fn map(&self, mapper: Rc Message>>) -> Bus - where - B: 'static, - { - let publish = self.publish.clone(); - - Bus { - publish: Rc::new(Box::new(move |message| publish(mapper(message)))), - } - } -} diff --git a/web/src/command.rs b/web/src/command.rs deleted file mode 100644 index 33e49e70..00000000 --- a/web/src/command.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod action; - -pub use action::Action; - -use std::fmt; - -#[cfg(target_arch = "wasm32")] -use std::future::Future; - -/// A set of asynchronous actions to be performed by some runtime. -pub struct Command(iced_futures::Command>); - -impl Command { - /// Creates an empty [`Command`]. - /// - /// In other words, a [`Command`] that does nothing. - pub const fn none() -> Self { - Self(iced_futures::Command::none()) - } - - /// Creates a [`Command`] that performs a single [`Action`]. - pub const fn single(action: Action) -> Self { - Self(iced_futures::Command::single(action)) - } - - /// Creates a [`Command`] that performs the action of the given future. - #[cfg(target_arch = "wasm32")] - pub fn perform( - future: impl Future + 'static, - f: impl Fn(T) -> A + 'static + Send, - ) -> Command { - use iced_futures::futures::FutureExt; - - Command::single(Action::Future(Box::pin(future.map(f)))) - } - - /// Creates a [`Command`] that performs the actions of all the given - /// commands. - /// - /// Once this command is run, all the commands will be executed at once. - pub fn batch(commands: impl IntoIterator>) -> Self { - Self(iced_futures::Command::batch( - commands.into_iter().map(|Command(command)| command), - )) - } - - /// Applies a transformation to the result of a [`Command`]. - #[cfg(target_arch = "wasm32")] - pub fn map(self, f: impl Fn(T) -> A + 'static + Clone) -> Command - where - T: 'static, - { - let Command(command) = self; - - Command(command.map(move |action| action.map(f.clone()))) - } - - /// Returns all of the actions of the [`Command`]. - pub fn actions(self) -> Vec> { - let Command(command) = self; - - command.actions() - } -} - -impl fmt::Debug for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Command(command) = self; - - command.fmt(f) - } -} diff --git a/web/src/command/action.rs b/web/src/command/action.rs deleted file mode 100644 index c0223e50..00000000 --- a/web/src/command/action.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub enum Action { - Future(iced_futures::BoxFuture), -} - -use std::fmt; - -impl Action { - /// Applies a transformation to the result of a [`Command`]. - #[cfg(target_arch = "wasm32")] - pub fn map(self, f: impl Fn(T) -> A + 'static) -> Action - where - T: 'static, - { - use iced_futures::futures::FutureExt; - - match self { - Self::Future(future) => Action::Future(Box::pin(future.map(f))), - } - } -} - -impl fmt::Debug for Action { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Future(_) => write!(f, "Action::Future"), - } - } -} diff --git a/web/src/css.rs b/web/src/css.rs deleted file mode 100644 index 07589150..00000000 --- a/web/src/css.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! Style your widgets. -use crate::bumpalo; -use crate::{Alignment, Background, Color, Length, Padding}; - -use std::collections::BTreeMap; - -/// A CSS rule of a VDOM node. -#[derive(Debug)] -pub enum Rule { - /// Container with vertical distribution - Column, - - /// Container with horizonal distribution - Row, - - /// Spacing between elements - Spacing(u16), - - /// Toggler input for a specific size - Toggler(u16), -} - -impl Rule { - /// Returns the class name of the [`Rule`]. - pub fn class<'a>(&self) -> String { - match self { - Rule::Column => String::from("c"), - Rule::Row => String::from("r"), - Rule::Spacing(spacing) => format!("s-{}", spacing), - Rule::Toggler(size) => format!("toggler-{}", size), - } - } - - /// Returns the declaration of the [`Rule`]. - pub fn declaration<'a>(&self, bump: &'a bumpalo::Bump) -> &'a str { - let class = self.class(); - - match self { - Rule::Column => { - let body = "{ display: flex; flex-direction: column; }"; - - bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str() - } - Rule::Row => { - let body = "{ display: flex; flex-direction: row; }"; - - bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str() - } - Rule::Spacing(spacing) => bumpalo::format!( - in bump, - ".c.{} > * {{ margin-bottom: {}px }} \ - .r.{} > * {{ margin-right: {}px }} \ - .c.{} > *:last-child {{ margin-bottom: 0 }} \ - .r.{} > *:last-child {{ margin-right: 0 }}", - class, - spacing, - class, - spacing, - class, - class - ) - .into_bump_str(), - Rule::Toggler(size) => bumpalo::format!( - in bump, - ".toggler-{} {{ display: flex; cursor: pointer; justify-content: space-between; }} \ - .toggler-{} input {{ display:none; }} \ - .toggler-{} span {{ background-color: #b1b1b1; position: relative; display: inline-flex; width:{}px; height: {}px; border-radius: {}px;}} \ - .toggler-{} span > span {{ background-color: #FFFFFF; width: {}px; height: {}px; border-radius: 50%; top: 1px; left: 1px;}} \ - .toggler-{}:hover span > span {{ background-color: #f1f1f1 !important; }} \ - .toggler-{} input:checked + span {{ background-color: #00FF00; }} \ - .toggler-{} input:checked + span > span {{ -webkit-transform: translateX({}px); -ms-transform:translateX({}px); transform: translateX({}px); }} - ", - // toggler - size, - - // toggler input - size, - - // toggler span - size, - size*2, - size, - size, - - // toggler span > span - size, - size-2, - size-2, - - // toggler: hover + span > span - size, - - // toggler input:checked + span - size, - - // toggler input:checked + span > span - size, - size, - size, - size - ) - .into_bump_str(), - } - } -} - -/// A cascading style sheet. -#[derive(Debug)] -pub struct Css<'a> { - rules: BTreeMap, -} - -impl<'a> Css<'a> { - /// Creates an empty [`Css`]. - pub fn new() -> Self { - Css { - rules: BTreeMap::new(), - } - } - - /// Inserts the [`Rule`] in the [`Css`], if it was not previously - /// inserted. - /// - /// It returns the class name of the provided [`Rule`]. - pub fn insert(&mut self, bump: &'a bumpalo::Bump, rule: Rule) -> String { - let class = rule.class(); - - if !self.rules.contains_key(&class) { - let _ = self.rules.insert(class.clone(), rule.declaration(bump)); - } - - class - } - - /// Produces the VDOM node of the [`Css`]. - pub fn node(self, bump: &'a bumpalo::Bump) -> dodrio::Node<'a> { - use dodrio::builder::*; - - let mut declarations = bumpalo::collections::Vec::new_in(bump); - - declarations.push(text("html { height: 100% }")); - declarations.push(text( - "body { height: 100%; margin: 0; padding: 0; font-family: sans-serif }", - )); - declarations.push(text("* { margin: 0; padding: 0 }")); - declarations.push(text( - "button { border: none; cursor: pointer; outline: none }", - )); - - for declaration in self.rules.values() { - declarations.push(text(*declaration)); - } - - style(bump).children(declarations).finish() - } -} - -/// Returns the style value for the given [`Length`]. -pub fn length(length: Length) -> String { - match length { - Length::Shrink => String::from("auto"), - Length::Units(px) => format!("{}px", px), - Length::Fill | Length::FillPortion(_) => String::from("100%"), - } -} - -/// Returns the style value for the given maximum length in units. -pub fn max_length(units: u32) -> String { - use std::u32; - - if units == u32::MAX { - String::from("initial") - } else { - format!("{}px", units) - } -} - -/// Returns the style value for the given minimum length in units. -pub fn min_length(units: u32) -> String { - if units == 0 { - String::from("initial") - } else { - format!("{}px", units) - } -} - -/// Returns the style value for the given [`Color`]. -pub fn color(Color { r, g, b, a }: Color) -> String { - format!("rgba({}, {}, {}, {})", 255.0 * r, 255.0 * g, 255.0 * b, a) -} - -/// Returns the style value for the given [`Background`]. -pub fn background(background: Background) -> String { - match background { - Background::Color(c) => color(c), - } -} - -/// Returns the style value for the given [`Alignment`]. -pub fn alignment(alignment: Alignment) -> &'static str { - match alignment { - Alignment::Start => "flex-start", - Alignment::Center => "center", - Alignment::End => "flex-end", - Alignment::Fill => "stretch", - } -} - -/// Returns the style value for the given [`Padding`]. -/// -/// [`Padding`]: struct.Padding.html -pub fn padding(padding: Padding) -> String { - format!( - "{}px {}px {}px {}px", - padding.top, padding.right, padding.bottom, padding.left - ) -} diff --git a/web/src/element.rs b/web/src/element.rs deleted file mode 100644 index 6bb90177..00000000 --- a/web/src/element.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{Bus, Color, Css, Widget}; - -use dodrio::bumpalo; -use std::rc::Rc; - -/// A generic [`Widget`]. -/// -/// It is useful to build composable user interfaces that do not leak -/// implementation details in their __view logic__. -/// -/// If you have a [built-in widget], you should be able to use `Into` -/// to turn it into an [`Element`]. -/// -/// [built-in widget]: mod@crate::widget -#[allow(missing_debug_implementations)] -pub struct Element<'a, Message> { - pub(crate) widget: Box + 'a>, -} - -impl<'a, Message> Element<'a, Message> { - /// Create a new [`Element`] containing the given [`Widget`]. - pub fn new(widget: impl Widget + 'a) -> Self { - Self { - widget: Box::new(widget), - } - } - - /// Applies a transformation to the produced message of the [`Element`]. - /// - /// This method is useful when you want to decouple different parts of your - /// UI and make them __composable__. - pub fn map(self, f: F) -> Element<'a, B> - where - Message: 'static, - B: 'static, - F: 'static + Fn(Message) -> B, - { - Element { - widget: Box::new(Map::new(self.widget, f)), - } - } - - /// Marks the [`Element`] as _to-be-explained_. - pub fn explain(self, _color: Color) -> Element<'a, Message> { - self - } - - /// Produces a VDOM node for the [`Element`]. - pub fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - self.widget.node(bump, bus, style_sheet) - } -} - -struct Map<'a, A, B> { - widget: Box + 'a>, - mapper: Rc B>>, -} - -impl<'a, A, B> Map<'a, A, B> { - pub fn new(widget: Box + 'a>, mapper: F) -> Map<'a, A, B> - where - F: 'static + Fn(A) -> B, - { - Map { - widget, - mapper: Rc::new(Box::new(mapper)), - } - } -} - -impl<'a, A, B> Widget for Map<'a, A, B> -where - A: 'static, - B: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - self.widget - .node(bump, &bus.map(self.mapper.clone()), style_sheet) - } -} diff --git a/web/src/hasher.rs b/web/src/hasher.rs deleted file mode 100644 index 1a28a2f9..00000000 --- a/web/src/hasher.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::collections::hash_map::DefaultHasher; - -/// The hasher used to compare subscriptions. -#[derive(Debug)] -pub struct Hasher(DefaultHasher); - -impl Default for Hasher { - fn default() -> Self { - Hasher(DefaultHasher::default()) - } -} - -impl core::hash::Hasher for Hasher { - fn write(&mut self, bytes: &[u8]) { - self.0.write(bytes) - } - - fn finish(&self) -> u64 { - self.0.finish() - } -} diff --git a/web/src/lib.rs b/web/src/lib.rs deleted file mode 100644 index 6311dd12..00000000 --- a/web/src/lib.rs +++ /dev/null @@ -1,431 +0,0 @@ -//! A web runtime for Iced, targetting the DOM. -//! -//! `iced_web` takes [`iced_core`] and builds a WebAssembly runtime on top. It -//! achieves this by introducing a `Widget` trait that can be used to produce -//! VDOM nodes. -//! -//! The crate is currently a __very experimental__, simple abstraction layer -//! over [`dodrio`]. -//! -//! [`iced_core`]: https://github.com/iced-rs/iced/tree/master/core -//! [`dodrio`]: https://github.com/fitzgen/dodrio -//! -//! # Usage -//! The current build process is a bit involved, as [`wasm-pack`] does not -//! currently [support building binary crates](https://github.com/rustwasm/wasm-pack/issues/734). -//! -//! Therefore, we instead build using the `wasm32-unknown-unknown` target and -//! use the [`wasm-bindgen`] CLI to generate appropriate bindings. -//! -//! For instance, let's say we want to build the [`tour` example]: -//! -//! ```bash -//! cd examples -//! cargo build --package tour --target wasm32-unknown-unknown -//! wasm-bindgen ../target/wasm32-unknown-unknown/debug/tour.wasm --out-dir tour --web -//! ``` -//! -//! Then, we need to create an `.html` file to load our application: -//! -//! ```html -//! -//! -//! -//! -//! -//! Tour - Iced -//! -//! -//! -//! -//! -//! ``` -//! -//! Finally, we serve it using an HTTP server and access it with our browser. -//! -//! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack -//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen -//! [`tour` example]: https://github.com/iced-rs/iced/tree/0.3/examples/tour -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] -#![deny(unused_results)] -#![forbid(unsafe_code)] -#![forbid(rust_2018_idioms)] -use dodrio::bumpalo; -use std::{cell::RefCell, rc::Rc}; - -mod bus; -mod command; -mod element; -mod hasher; - -pub mod css; -pub mod subscription; -pub mod widget; - -pub use bus::Bus; -pub use command::Command; -pub use css::Css; -pub use dodrio; -pub use element::Element; -pub use hasher::Hasher; -pub use subscription::Subscription; - -pub use iced_core::alignment; -pub use iced_core::keyboard; -pub use iced_core::mouse; -pub use iced_futures::executor; -pub use iced_futures::futures; - -pub use iced_core::{ - Alignment, Background, Color, Font, Length, Padding, Point, Rectangle, - Size, Vector, -}; - -#[doc(no_inline)] -pub use widget::*; - -#[doc(no_inline)] -pub use executor::Executor; - -/// An interactive web application. -/// -/// This trait is the main entrypoint of Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`](#method.run). It will take -/// control of the `` and the `<body>` of the document. -/// -/// An [`Application`](trait.Application.html) can execute asynchronous actions -/// by returning a [`Command`](struct.Command.html) in some of its methods. -pub trait Application { - /// The [`Executor`] that will run commands and subscriptions. - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - type Message: Send; - - /// The data needed to initialize your [`Application`]. - type Flags; - - /// Initializes the [`Application`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// Additionally, you can return a [`Command`] if you need to perform some - /// async action in the background on startup. This is useful if you want to - /// load state from a file, perform an initial HTTP request, etc. - fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) - where - Self: Sized; - - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self) -> String; - - /// Handles a __message__ and updates the state of the [`Application`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view(&mut self) -> Element<'_, Self::Message>; - - /// Returns the event [`Subscription`] for the current state of the - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - fn subscription(&self) -> Subscription<Self::Message> { - Subscription::none() - } - - /// Runs the [`Application`]. - fn run(flags: Self::Flags) - where - Self: 'static + Sized, - { - use futures::stream::StreamExt; - - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); - - let (sender, receiver) = - iced_futures::futures::channel::mpsc::unbounded(); - - let mut runtime = iced_futures::Runtime::new( - Self::Executor::new().expect("Create executor"), - sender.clone(), - ); - - let (app, command) = runtime.enter(|| Self::new(flags)); - - let mut title = app.title(); - document.set_title(&title); - - run_command(command, &mut runtime); - - let application = Rc::new(RefCell::new(app)); - - let instance = Instance { - application: application.clone(), - bus: Bus::new(sender), - }; - - let vdom = dodrio::Vdom::new(&body, instance); - - let event_loop = receiver.for_each(move |message| { - let (command, subscription) = runtime.enter(|| { - let command = application.borrow_mut().update(message); - let subscription = application.borrow().subscription(); - - (command, subscription) - }); - - let new_title = application.borrow().title(); - - run_command(command, &mut runtime); - runtime.track(subscription); - - if title != new_title { - document.set_title(&new_title); - - title = new_title; - } - - vdom.weak().schedule_render(); - - futures::future::ready(()) - }); - - wasm_bindgen_futures::spawn_local(event_loop); - } -} - -struct Instance<A: Application> { - application: Rc<RefCell<A>>, - bus: Bus<A::Message>, -} - -impl<'a, A> dodrio::Render<'a> for Instance<A> -where - A: Application, -{ - fn render( - &self, - context: &mut dodrio::RenderContext<'a>, - ) -> dodrio::Node<'a> { - use dodrio::builder::*; - - let mut ui = self.application.borrow_mut(); - let element = ui.view(); - let mut css = Css::new(); - - let node = element.widget.node(context.bump, &self.bus, &mut css); - - div(context.bump) - .attr("style", "width: 100%; height: 100%") - .children(vec![css.node(context.bump), node]) - .finish() - } -} - -/// An interactive embedded web application. -/// -/// This trait is the main entrypoint of Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`](#method.run). It will either -/// take control of the `<body>' or of an HTML element of the document specified -/// by `container_id`. -/// -/// An [`Embedded`](trait.Embedded.html) can execute asynchronous actions -/// by returning a [`Command`](struct.Command.html) in some of its methods. -pub trait Embedded { - /// The [`Executor`] that will run commands and subscriptions. - /// - /// The [`executor::WasmBindgen`] can be a good choice for the Web. - /// - /// [`Executor`]: trait.Executor.html - /// [`executor::Default`]: executor/struct.Default.html - type Executor: Executor; - - /// The type of __messages__ your [`Embedded`] application will produce. - /// - /// [`Embedded`]: trait.Embedded.html - type Message: Send; - - /// The data needed to initialize your [`Embedded`] application. - /// - /// [`Embedded`]: trait.Embedded.html - type Flags; - - /// Initializes the [`Embedded`] application. - /// - /// Here is where you should return the initial state of your app. - /// - /// Additionally, you can return a [`Command`](struct.Command.html) if you - /// need to perform some async action in the background on startup. This is - /// useful if you want to load state from a file, perform an initial HTTP - /// request, etc. - /// - /// [`Embedded`]: trait.Embedded.html - fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) - where - Self: Sized; - - /// Handles a __message__ and updates the state of the [`Embedded`] - /// application. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - /// - /// [`Embedded`]: trait.Embedded.html - /// [`Command`]: struct.Command.html - fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - - /// Returns the widgets to display in the [`Embedded`] application. - /// - /// These widgets can produce __messages__ based on user interaction. - /// - /// [`Embedded`]: trait.Embedded.html - fn view(&mut self) -> Element<'_, Self::Message>; - - /// Returns the event [`Subscription`] for the current state of the embedded - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - /// - /// [`Subscription`]: struct.Subscription.html - fn subscription(&self) -> Subscription<Self::Message> { - Subscription::none() - } - - /// Runs the [`Embedded`] application. - /// - /// [`Embedded`]: trait.Embedded.html - fn run(flags: Self::Flags, container_id: Option<String>) - where - Self: 'static + Sized, - { - use futures::stream::StreamExt; - use wasm_bindgen::JsCast; - use web_sys::HtmlElement; - - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let container: HtmlElement = container_id - .map(|id| document.get_element_by_id(&id).unwrap()) - .map(|container| { - container.dyn_ref::<HtmlElement>().unwrap().to_owned() - }) - .unwrap_or_else(|| document.body().unwrap()); - - let (sender, receiver) = - iced_futures::futures::channel::mpsc::unbounded(); - - let mut runtime = iced_futures::Runtime::new( - Self::Executor::new().expect("Create executor"), - sender.clone(), - ); - - let (app, command) = runtime.enter(|| Self::new(flags)); - run_command(command, &mut runtime); - - let application = Rc::new(RefCell::new(app)); - - let instance = EmbeddedInstance { - application: application.clone(), - bus: Bus::new(sender), - }; - - let vdom = dodrio::Vdom::new(&container, instance); - - let event_loop = receiver.for_each(move |message| { - let (command, subscription) = runtime.enter(|| { - let command = application.borrow_mut().update(message); - let subscription = application.borrow().subscription(); - - (command, subscription) - }); - - run_command(command, &mut runtime); - runtime.track(subscription); - - vdom.weak().schedule_render(); - - futures::future::ready(()) - }); - - wasm_bindgen_futures::spawn_local(event_loop); - } -} - -fn run_command<Message: 'static + Send, E: Executor>( - command: Command<Message>, - runtime: &mut iced_futures::Runtime< - Hasher, - (), - E, - iced_futures::futures::channel::mpsc::UnboundedSender<Message>, - Message, - >, -) { - for action in command.actions() { - match action { - command::Action::Future(future) => { - runtime.spawn(future); - } - } - } -} - -struct EmbeddedInstance<A: Embedded> { - application: Rc<RefCell<A>>, - bus: Bus<A::Message>, -} - -impl<'a, A> dodrio::Render<'a> for EmbeddedInstance<A> -where - A: Embedded, -{ - fn render( - &self, - context: &mut dodrio::RenderContext<'a>, - ) -> dodrio::Node<'a> { - use dodrio::builder::*; - - let mut ui = self.application.borrow_mut(); - let element = ui.view(); - let mut css = Css::new(); - - let node = element.widget.node(context.bump, &self.bus, &mut css); - - div(context.bump) - .attr("style", "width: 100%; height: 100%") - .children(vec![css.node(context.bump), node]) - .finish() - } -} diff --git a/web/src/subscription.rs b/web/src/subscription.rs deleted file mode 100644 index fb54f7e3..00000000 --- a/web/src/subscription.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Listen to external events in your application. -use crate::Hasher; - -/// A request to listen to external events. -/// -/// Besides performing async actions on demand with [`Command`], most -/// applications also need to listen to external events passively. -/// -/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], -/// and it will generate events as long as the user keeps requesting it. -/// -/// For instance, you can use a [`Subscription`] to listen to a WebSocket -/// connection, keyboard presses, mouse events, time ticks, etc. -/// -/// [`Command`]: crate::Command -pub type Subscription<T> = iced_futures::Subscription<Hasher, (), T>; - -pub use iced_futures::subscription::Recipe; diff --git a/web/src/widget.rs b/web/src/widget.rs deleted file mode 100644 index 4cb0a9cc..00000000 --- a/web/src/widget.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Use the built-in widgets or create your own. -//! -//! # Custom widgets -//! If you want to implement a custom widget, you simply need to implement the -//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or -//! source of inspiration. -//! -//! # Re-exports -//! For convenience, the contents of this module are available at the root -//! module. Therefore, you can directly type: -//! -//! ``` -//! use iced_web::{button, Button, Widget}; -//! ``` -use crate::{Bus, Css}; -use dodrio::bumpalo; - -pub mod button; -pub mod checkbox; -pub mod container; -pub mod image; -pub mod progress_bar; -pub mod radio; -pub mod scrollable; -pub mod slider; -pub mod text_input; -pub mod toggler; - -mod column; -mod row; -mod space; -mod text; - -#[doc(no_inline)] -pub use button::Button; -#[doc(no_inline)] -pub use scrollable::Scrollable; -#[doc(no_inline)] -pub use slider::Slider; -#[doc(no_inline)] -pub use text::Text; -#[doc(no_inline)] -pub use text_input::TextInput; -#[doc(no_inline)] -pub use toggler::Toggler; - -pub use checkbox::Checkbox; -pub use column::Column; -pub use container::Container; -pub use image::Image; -pub use progress_bar::ProgressBar; -pub use radio::Radio; -pub use row::Row; -pub use space::Space; - -/// A component that displays information and allows interaction. -/// -/// If you want to build your own widgets, you will need to implement this -/// trait. -pub trait Widget<Message> { - /// Produces a VDOM node for the [`Widget`]. - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b>; -} diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs deleted file mode 100644 index 88137607..00000000 --- a/web/src/widget/button.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`]. -use crate::{css, Background, Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::button::{Style, StyleSheet}; - -use dodrio::bumpalo; - -/// A generic widget that produces a message when pressed. -/// -/// ``` -/// # use iced_web::{button, Button, Text}; -/// # -/// enum Message { -/// ButtonPressed, -/// } -/// -/// let mut state = button::State::new(); -/// let button = Button::new(&mut state, Text::new("Press me!")) -/// .on_press(Message::ButtonPressed); -/// ``` -/// -/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will -/// be disabled: -/// -/// ``` -/// # use iced_web::{button, Button, Text}; -/// # -/// #[derive(Clone)] -/// enum Message { -/// ButtonPressed, -/// } -/// -/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> { -/// Button::new(state, Text::new("I'm disabled!")) -/// } -/// -/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> { -/// disabled_button(state).on_press(Message::ButtonPressed) -/// } -/// ``` -#[allow(missing_debug_implementations)] -pub struct Button<'a, Message> { - content: Element<'a, Message>, - on_press: Option<Message>, - width: Length, - #[allow(dead_code)] - height: Length, - min_width: u32, - #[allow(dead_code)] - min_height: u32, - padding: Padding, - style: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Button<'a, Message> { - /// Creates a new [`Button`] with some local [`State`] and the given - /// content. - pub fn new<E>(_state: &'a mut State, content: E) -> Self - where - E: Into<Element<'a, Message>>, - { - Button { - content: content.into(), - on_press: None, - width: Length::Shrink, - height: Length::Shrink, - min_width: 0, - min_height: 0, - padding: Padding::new(5), - style: Default::default(), - } - } - - /// Sets the width of the [`Button`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Button`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the minimum width of the [`Button`]. - pub fn min_width(mut self, min_width: u32) -> Self { - self.min_width = min_width; - self - } - - /// Sets the minimum height of the [`Button`]. - pub fn min_height(mut self, min_height: u32) -> Self { - self.min_height = min_height; - self - } - - /// Sets the [`Padding`] of the [`Button`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the style of the [`Button`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { - self.style = style.into(); - self - } - - /// Sets the message that will be produced when the [`Button`] is pressed. - /// If on_press isn't set, button will be disabled. - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); - self - } -} - -/// The local state of a [`Button`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} - -impl<'a, Message> Widget<Message> for Button<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - // TODO: State-based styling - let style = self.style.active(); - - let background = match style.background { - None => String::from("none"), - Some(background) => match background { - Background::Color(color) => css::color(color), - }, - }; - - let mut node = button(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "background: {}; border-radius: {}px; width:{}; \ - min-width: {}; color: {}; padding: {}", - background, - style.border_radius, - css::length(self.width), - css::min_length(self.min_width), - css::color(style.text_color), - css::padding(self.padding) - ) - .into_bump_str(), - ) - .children(vec![self.content.node(bump, bus, style_sheet)]); - - if let Some(on_press) = self.on_press.clone() { - let event_bus = bus.clone(); - - node = node.on("click", move |_root, _vdom, _event| { - event_bus.publish(on_press.clone()); - }); - } else { - node = node.attr("disabled", ""); - } - - node.finish() - } -} - -impl<'a, Message> From<Button<'a, Message>> for Element<'a, Message> -where - Message: 'static + Clone, -{ - fn from(button: Button<'a, Message>) -> Element<'a, Message> { - Element::new(button) - } -} diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs deleted file mode 100644 index 844bf862..00000000 --- a/web/src/widget/checkbox.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! Show toggle controls using checkboxes. -use crate::{css, Bus, Css, Element, Length, Widget}; - -pub use iced_style::checkbox::{Style, StyleSheet}; - -use dodrio::bumpalo; -use std::rc::Rc; - -/// A box that can be checked. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Checkbox; -/// -/// pub enum Message { -/// CheckboxToggled(bool), -/// } -/// -/// let is_checked = true; -/// -/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); -/// ``` -/// -/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true) -#[allow(missing_debug_implementations)] -pub struct Checkbox<'a, Message> { - is_checked: bool, - on_toggle: Rc<dyn Fn(bool) -> Message>, - label: String, - id: Option<String>, - width: Length, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Checkbox<'a, Message> { - /// Creates a new [`Checkbox`]. - /// - /// It expects: - /// * a boolean describing whether the [`Checkbox`] is checked or not - /// * the label of the [`Checkbox`] - /// * a function that will be called when the [`Checkbox`] is toggled. It - /// will receive the new state of the [`Checkbox`] and must produce a - /// `Message`. - pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Checkbox { - is_checked, - on_toggle: Rc::new(f), - label: label.into(), - id: None, - width: Length::Shrink, - style_sheet: Default::default(), - } - } - - /// Sets the width of the [`Checkbox`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the id of the [`Checkbox`]. - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Checkbox<'a, Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let checkbox_label = - String::from_str_in(&self.label, bump).into_bump_str(); - - let event_bus = bus.clone(); - let on_toggle = self.on_toggle.clone(); - let is_checked = self.is_checked; - - let row_class = style_sheet.insert(bump, css::Rule::Row); - - let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - label - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, spacing_class) - .into_bump_str(), - ) - .attr( - "style", - bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) - .into_bump_str(), - ) - .children(vec![ - // TODO: Checkbox styling - input - .attr("type", "checkbox") - .bool_attr("checked", self.is_checked) - .on("click", move |_root, vdom, _event| { - let msg = on_toggle(!is_checked); - event_bus.publish(msg); - - vdom.schedule_render(); - }) - .finish(), - text(checkbox_label), - ]) - .finish() - } -} - -impl<'a, Message> From<Checkbox<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(checkbox: Checkbox<'a, Message>) -> Element<'a, Message> { - Element::new(checkbox) - } -} diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs deleted file mode 100644 index 30a57c41..00000000 --- a/web/src/widget/column.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::css; -use crate::{Alignment, Bus, Css, Element, Length, Padding, Widget}; - -use dodrio::bumpalo; -use std::u32; - -/// A container that distributes its contents vertically. -/// -/// A [`Column`] will try to fill the horizontal space of its container. -#[allow(missing_debug_implementations)] -pub struct Column<'a, Message> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - align_items: Alignment, - children: Vec<Element<'a, Message>>, -} - -impl<'a, Message> Column<'a, Message> { - /// Creates an empty [`Column`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Column`] with the given elements. - pub fn with_children(children: Vec<Element<'a, Message>>) -> Self { - Column { - spacing: 0, - padding: Padding::ZERO, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Alignment::Start, - children, - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.spacing = units; - self - } - - /// Sets the [`Padding`] of the [`Column`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Column`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Column`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Column`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Column`] in pixels. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the horizontal alignment of the contents of the [`Column`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an element to the [`Column`]. - pub fn push<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Column<'a, Message> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - publish: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let children: Vec<_> = self - .children - .iter() - .map(|element| element.widget.node(bump, publish, style_sheet)) - .collect(); - - let column_class = style_sheet.insert(bump, css::Rule::Column); - - let spacing_class = - style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - - // TODO: Complete styling - div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{} {}", column_class, spacing_class) - .into_bump_str(), - ) - .attr("style", bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::max_length(self.max_height), - css::padding(self.padding), - css::alignment(self.align_items) - ).into_bump_str() - ) - .children(children) - .finish() - } -} - -impl<'a, Message> From<Column<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(column: Column<'a, Message>) -> Element<'a, Message> { - Element::new(column) - } -} diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs deleted file mode 100644 index 8e345b9a..00000000 --- a/web/src/widget/container.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Decorate content and apply alignment. -use crate::alignment::{self, Alignment}; -use crate::bumpalo; -use crate::css; -use crate::{Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::container::{Style, StyleSheet}; - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[allow(missing_debug_implementations)] -pub struct Container<'a, Message> { - padding: Padding, - width: Length, - height: Length, - max_width: u32, - #[allow(dead_code)] - max_height: u32, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - style_sheet: Box<dyn StyleSheet + 'a>, - content: Element<'a, Message>, -} - -impl<'a, Message> Container<'a, Message> { - /// Creates an empty [`Container`]. - pub fn new<T>(content: T) -> Self - where - T: Into<Element<'a, Message>>, - { - use std::u32; - - Container { - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - style_sheet: Default::default(), - content: content.into(), - } - } - - /// Sets the [`Padding`] of the [`Container`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Container`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Container`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Container`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Container`] in pixels. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Centers the contents in the horizontal axis of the [`Container`]. - pub fn center_x(mut self) -> Self { - self.horizontal_alignment = alignment::Horizontal::Center; - - self - } - - /// Centers the contents in the vertical axis of the [`Container`]. - pub fn center_y(mut self) -> Self { - self.vertical_alignment = alignment::Vertical::Center; - - self - } - - /// Sets the style of the [`Container`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { - self.style_sheet = style.into(); - self - } -} - -impl<'a, Message> Widget<Message> for Container<'a, Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let column_class = style_sheet.insert(bump, css::Rule::Column); - - let style = self.style_sheet.style(); - - let node = div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{}", column_class).into_bump_str(), - ) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; padding: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::padding(self.padding), - css::alignment(Alignment::from(self.horizontal_alignment)), - css::alignment(Alignment::from(self.vertical_alignment)), - style.background.map(css::background).unwrap_or(String::from("initial")), - style.text_color.map(css::color).unwrap_or(String::from("inherit")), - style.border_width, - css::color(style.border_color), - style.border_radius - ) - .into_bump_str(), - ) - .children(vec![self.content.node(bump, bus, style_sheet)]); - - // TODO: Complete styling - - node.finish() - } -} - -impl<'a, Message> From<Container<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(container: Container<'a, Message>) -> Element<'a, Message> { - Element::new(container) - } -} diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs deleted file mode 100644 index 28435f4f..00000000 --- a/web/src/widget/image.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Display images in your user interface. -use crate::{Bus, Css, Element, Hasher, Length, Widget}; - -use dodrio::bumpalo; -use std::{ - hash::{Hash, Hasher as _}, - path::PathBuf, - sync::Arc, -}; - -/// A frame that displays an image while keeping aspect ratio. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Image; -/// -/// let image = Image::new("resources/ferris.png"); -/// ``` -#[derive(Debug)] -pub struct Image { - /// The image path - pub handle: Handle, - - /// The alt text of the image - pub alt: String, - - /// The width of the image - pub width: Length, - - /// The height of the image - pub height: Length, -} - -impl Image { - /// Creates a new [`Image`] with the given path. - pub fn new<T: Into<Handle>>(handle: T) -> Self { - Image { - handle: handle.into(), - alt: Default::default(), - width: Length::Shrink, - height: Length::Shrink, - } - } - - /// Sets the width of the [`Image`] boundaries. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Image`] boundaries. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the alt text of the [`Image`]. - pub fn alt(mut self, alt: impl Into<String>) -> Self { - self.alt = alt.into(); - self - } -} - -impl<Message> Widget<Message> for Image { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let src = match self.handle.data.as_ref() { - Data::Path(path) => { - String::from_str_in(path.to_str().unwrap_or(""), bump) - } - Data::Bytes(bytes) => { - // The web is able to infer the kind of image, so we don't have to add a dependency on image-rs to guess the mime type. - bumpalo::format!(in bump, "data:;base64,{}", base64::encode(bytes)) - }, - } - .into_bump_str(); - - let alt = String::from_str_in(&self.alt, bump).into_bump_str(); - - let mut image = img(bump).attr("src", src).attr("alt", alt); - - match self.width { - Length::Shrink => {} - Length::Fill | Length::FillPortion(_) => { - image = image.attr("width", "100%"); - } - Length::Units(px) => { - image = image.attr( - "width", - bumpalo::format!(in bump, "{}px", px).into_bump_str(), - ); - } - } - - // TODO: Complete styling - - image.finish() - } -} - -impl<'a, Message> From<Image> for Element<'a, Message> { - fn from(image: Image) -> Element<'a, Message> { - Element::new(image) - } -} - -/// An [`Image`] handle. -#[derive(Debug, Clone)] -pub struct Handle { - id: u64, - data: Arc<Data>, -} - -impl Handle { - /// Creates an image [`Handle`] pointing to the image of the given path. - pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { - Self::from_data(Data::Path(path.into())) - } - - /// Creates an image [`Handle`] containing the image data directly. - /// - /// This is useful if you already have your image loaded in-memory, maybe - /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: Vec<u8>) -> Handle { - Self::from_data(Data::Bytes(bytes)) - } - - fn from_data(data: Data) -> Handle { - let mut hasher = Hasher::default(); - data.hash(&mut hasher); - - Handle { - id: hasher.finish(), - data: Arc::new(data), - } - } - - /// Returns the unique identifier of the [`Handle`]. - pub fn id(&self) -> u64 { - self.id - } - - /// Returns a reference to the image [`Data`]. - pub fn data(&self) -> &Data { - &self.data - } -} - -impl From<String> for Handle { - fn from(path: String) -> Handle { - Handle::from_path(path) - } -} - -impl From<&str> for Handle { - fn from(path: &str) -> Handle { - Handle::from_path(path) - } -} - -/// The data of an [`Image`]. -#[derive(Clone, Hash)] -pub enum Data { - /// A remote image - Path(PathBuf), - - /// In-memory data - Bytes(Vec<u8>), -} - -impl std::fmt::Debug for Data { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Data::Path(path) => write!(f, "Path({:?})", path), - Data::Bytes(_) => write!(f, "Bytes(...)"), - } - } -} diff --git a/web/src/widget/progress_bar.rs b/web/src/widget/progress_bar.rs deleted file mode 100644 index 01f412f8..00000000 --- a/web/src/widget/progress_bar.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Provide progress feedback to your users. -use crate::{bumpalo, css, Bus, Css, Element, Length, Widget}; - -pub use iced_style::progress_bar::{Style, StyleSheet}; - -use std::ops::RangeInclusive; - -/// A bar that displays progress. -/// -/// # Example -/// ``` -/// use iced_web::ProgressBar; -/// -/// let value = 50.0; -/// -/// ProgressBar::new(0.0..=100.0, value); -/// ``` -/// -/// ![Progress bar](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png) -#[allow(missing_debug_implementations)] -pub struct ProgressBar<'a> { - range: RangeInclusive<f32>, - value: f32, - width: Length, - height: Option<Length>, - style: Box<dyn StyleSheet + 'a>, -} - -impl<'a> ProgressBar<'a> { - /// Creates a new [`ProgressBar`]. - /// - /// It expects: - /// * an inclusive range of possible values - /// * the current value of the [`ProgressBar`] - pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { - ProgressBar { - value: value.max(*range.start()).min(*range.end()), - range, - width: Length::Fill, - height: None, - style: Default::default(), - } - } - - /// Sets the width of the [`ProgressBar`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`ProgressBar`]. - pub fn height(mut self, height: Length) -> Self { - self.height = Some(height); - self - } - - /// Sets the style of the [`ProgressBar`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message> Widget<Message> for ProgressBar<'a> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let (range_start, range_end) = self.range.clone().into_inner(); - let amount_filled = - (self.value - range_start) / (range_end - range_start).max(1.0); - - let style = self.style.style(); - - let bar = div(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}%; height: 100%; background: {}", - amount_filled * 100.0, - css::background(style.bar) - ) - .into_bump_str(), - ) - .finish(); - - let node = div(bump).attr( - "style", - bumpalo::format!( - in bump, - "width: {}; height: {}; background: {}; border-radius: {}px; overflow: hidden;", - css::length(self.width), - css::length(self.height.unwrap_or(Length::Units(30))), - css::background(style.background), - style.border_radius - ) - .into_bump_str(), - ).children(vec![bar]); - - node.finish() - } -} - -impl<'a, Message> From<ProgressBar<'a>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(container: ProgressBar<'a>) -> Element<'a, Message> { - Element::new(container) - } -} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs deleted file mode 100644 index 03b2922b..00000000 --- a/web/src/widget/radio.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Create choices using radio buttons. -use crate::{Bus, Css, Element, Widget}; - -pub use iced_style::radio::{Style, StyleSheet}; - -use dodrio::bumpalo; - -/// A circular button representing a choice. -/// -/// # Example -/// ``` -/// # use iced_web::Radio; -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// pub enum Choice { -/// A, -/// B, -/// } -/// -/// #[derive(Debug, Clone, Copy)] -/// pub enum Message { -/// RadioSelected(Choice), -/// } -/// -/// let selected_choice = Some(Choice::A); -/// -/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); -/// -/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); -/// ``` -/// -/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true) -#[allow(missing_debug_implementations)] -pub struct Radio<'a, Message> { - is_selected: bool, - on_click: Message, - label: String, - id: Option<String>, - name: Option<String>, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Radio<'a, Message> { - /// Creates a new [`Radio`] button. - /// - /// It expects: - /// * the value related to the [`Radio`] button - /// * the label of the [`Radio`] button - /// * the current selected value - /// * a function that will be called when the [`Radio`] is selected. It - /// receives the value of the radio and must produce a `Message`. - pub fn new<F, V>( - value: V, - label: impl Into<String>, - selected: Option<V>, - f: F, - ) -> Self - where - V: Eq + Copy, - F: 'static + Fn(V) -> Message, - { - Radio { - is_selected: Some(value) == selected, - on_click: f(value), - label: label.into(), - id: None, - name: None, - style_sheet: Default::default(), - } - } - - /// Sets the style of the [`Radio`] button. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the name attribute of the [`Radio`] button. - pub fn name(mut self, name: impl Into<String>) -> Self { - self.name = Some(name.into()); - self - } - - /// Sets the id of the [`Radio`] button. - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Radio<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let radio_label = - String::from_str_in(&self.label, bump).into_bump_str(); - - let event_bus = bus.clone(); - let on_click = self.on_click.clone(); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - let input = if let Some(name) = &self.name { - let name = String::from_str_in(name, bump).into_bump_str(); - - dodrio::builder::input(bump).attr("name", name) - } else { - input - }; - - // TODO: Complete styling - label - .attr("style", "display: block; font-size: 20px") - .children(vec![ - input - .attr("type", "radio") - .attr("style", "margin-right: 10px") - .bool_attr("checked", self.is_selected) - .on("click", move |_root, _vdom, _event| { - event_bus.publish(on_click.clone()); - }) - .finish(), - text(radio_label), - ]) - .finish() - } -} - -impl<'a, Message> From<Radio<'a, Message>> for Element<'a, Message> -where - Message: 'static + Clone, -{ - fn from(radio: Radio<'a, Message>) -> Element<'a, Message> { - Element::new(radio) - } -} diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs deleted file mode 100644 index 13eab27d..00000000 --- a/web/src/widget/row.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::css; -use crate::{Alignment, Bus, Css, Element, Length, Padding, Widget}; - -use dodrio::bumpalo; -use std::u32; - -/// A container that distributes its contents horizontally. -/// -/// A [`Row`] will try to fill the horizontal space of its container. -#[allow(missing_debug_implementations)] -pub struct Row<'a, Message> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - align_items: Alignment, - children: Vec<Element<'a, Message>>, -} - -impl<'a, Message> Row<'a, Message> { - /// Creates an empty [`Row`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Row`] with the given elements. - pub fn with_children(children: Vec<Element<'a, Message>>) -> Self { - Row { - spacing: 0, - padding: Padding::ZERO, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Alignment::Start, - children, - } - } - - /// Sets the horizontal spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.spacing = units; - self - } - - /// Sets the [`Padding`] of the [`Row`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Row`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Row`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Row`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Row`]. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the vertical alignment of the contents of the [`Row`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an [`Element`] to the [`Row`]. - pub fn push<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Row<'a, Message> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - publish: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let children: Vec<_> = self - .children - .iter() - .map(|element| element.widget.node(bump, publish, style_sheet)) - .collect(); - - let row_class = style_sheet.insert(bump, css::Rule::Row); - - let spacing_class = - style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - - // TODO: Complete styling - div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, spacing_class) - .into_bump_str(), - ) - .attr("style", bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::max_length(self.max_height), - css::padding(self.padding), - css::alignment(self.align_items) - ).into_bump_str() - ) - .children(children) - .finish() - } -} - -impl<'a, Message> From<Row<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(column: Row<'a, Message>) -> Element<'a, Message> { - Element::new(column) - } -} diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs deleted file mode 100644 index 22cb61be..00000000 --- a/web/src/widget/scrollable.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Navigate an endless amount of content with a scrollbar. -use crate::bumpalo; -use crate::css; -use crate::{Alignment, Bus, Column, Css, Element, Length, Padding, Widget}; - -pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; - -/// A widget that can vertically display an infinite amount of content with a -/// scrollbar. -#[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message> { - width: Length, - height: Length, - max_height: u32, - content: Column<'a, Message>, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Scrollable<'a, Message> { - /// Creates a new [`Scrollable`] with the given [`State`]. - pub fn new(_state: &'a mut State) -> Self { - use std::u32; - - Scrollable { - width: Length::Fill, - height: Length::Shrink, - max_height: u32::MAX, - content: Column::new(), - style_sheet: Default::default(), - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.content = self.content.spacing(units); - self - } - - /// Sets the [`Padding`] of the [`Scrollable`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.content = self.content.padding(padding); - self - } - - /// Sets the width of the [`Scrollable`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Scrollable`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Scrollable`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.content = self.content.max_width(max_width); - self - } - - /// Sets the maximum height of the [`Scrollable`] in pixels. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the horizontal alignment of the contents of the [`Scrollable`] . - pub fn align_items(mut self, align_items: Alignment) -> Self { - self.content = self.content.align_items(align_items); - self - } - - /// Sets the style of the [`Scrollable`] . - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Adds an element to the [`Scrollable`]. - pub fn push<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.content = self.content.push(child); - self - } -} - -impl<'a, Message> Widget<Message> for Scrollable<'a, Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let width = css::length(self.width); - let height = css::length(self.height); - - // TODO: Scrollbar styling - - let node = div(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}; height: {}; max-height: {}px; overflow: auto", - width, - height, - self.max_height - ) - .into_bump_str(), - ) - .children(vec![self.content.node(bump, bus, style_sheet)]); - - node.finish() - } -} - -impl<'a, Message> From<Scrollable<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(scrollable: Scrollable<'a, Message>) -> Element<'a, Message> { - Element::new(scrollable) - } -} - -/// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Copy, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`] with the scrollbar located at the top. - pub fn new() -> Self { - State::default() - } -} diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs deleted file mode 100644 index 8cbf5bd0..00000000 --- a/web/src/widget/slider.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. -use crate::{Bus, Css, Element, Length, Widget}; - -pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; - -use dodrio::bumpalo; -use std::{ops::RangeInclusive, rc::Rc}; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// The [`Slider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_web::{slider, Slider}; -/// # -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let state = &mut slider::State::new(); -/// let value = 50.0; -/// -/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); -/// ``` -/// -/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) -#[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message> { - _state: &'a mut State, - range: RangeInclusive<T>, - step: T, - value: T, - on_change: Rc<Box<dyn Fn(T) -> Message>>, - #[allow(dead_code)] - width: Length, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, T, Message> Slider<'a, T, Message> -where - T: Copy + From<u8> + std::cmp::PartialOrd, -{ - /// Creates a new [`Slider`]. - /// - /// It expects: - /// * the local [`State`] of the [`Slider`] - /// * an inclusive range of possible values - /// * the current value of the [`Slider`] - /// * a function that will be called when the [`Slider`] is dragged. - /// It receives the new value of the [`Slider`] and must produce a - /// `Message`. - pub fn new<F>( - state: &'a mut State, - range: RangeInclusive<T>, - value: T, - on_change: F, - ) -> Self - where - F: 'static + Fn(T) -> Message, - { - let value = if value >= *range.start() { - value - } else { - *range.start() - }; - - let value = if value <= *range.end() { - value - } else { - *range.end() - }; - - Slider { - _state: state, - value, - range, - step: T::from(1), - on_change: Rc::new(Box::new(on_change)), - width: Length::Fill, - style_sheet: Default::default(), - } - } - - /// Sets the width of the [`Slider`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Slider`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the step size of the [`Slider`]. - pub fn step(mut self, step: T) -> Self { - self.step = step; - self - } -} - -impl<'a, T, Message> Widget<Message> for Slider<'a, T, Message> -where - T: 'static + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use wasm_bindgen::JsCast; - - let (start, end) = self.range.clone().into_inner(); - - let min = bumpalo::format!(in bump, "{}", start.into()); - let max = bumpalo::format!(in bump, "{}", end.into()); - let value = bumpalo::format!(in bump, "{}", self.value.into()); - let step = bumpalo::format!(in bump, "{}", self.step.into()); - - let on_change = self.on_change.clone(); - let event_bus = bus.clone(); - - // TODO: Styling - input(bump) - .attr("type", "range") - .attr("step", step.into_bump_str()) - .attr("min", min.into_bump_str()) - .attr("max", max.into_bump_str()) - .attr("value", value.into_bump_str()) - .attr("style", "width: 100%") - .on("input", move |_root, _vdom, event| { - let slider = match event.target().and_then(|t| { - t.dyn_into::<web_sys::HtmlInputElement>().ok() - }) { - None => return, - Some(slider) => slider, - }; - - if let Ok(value) = slider.value().parse::<f64>() { - if let Some(value) = T::from_f64(value) { - event_bus.publish(on_change(value)); - } - } - }) - .finish() - } -} - -impl<'a, T, Message> From<Slider<'a, T, Message>> for Element<'a, Message> -where - T: 'static + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'static, -{ - fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message> { - Element::new(slider) - } -} - -/// The local state of a [`Slider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`]. - pub fn new() -> Self { - Self - } -} diff --git a/web/src/widget/space.rs b/web/src/widget/space.rs deleted file mode 100644 index a8571fdb..00000000 --- a/web/src/widget/space.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::{css, Bus, Css, Element, Length, Widget}; -use dodrio::bumpalo; - -/// An amount of empty space. -/// -/// It can be useful if you want to fill some space with nothing. -#[derive(Debug)] -pub struct Space { - width: Length, - height: Length, -} - -impl Space { - /// Creates an amount of empty [`Space`] with the given width and height. - pub fn new(width: Length, height: Length) -> Self { - Space { width, height } - } - - /// Creates an amount of horizontal [`Space`]. - pub fn with_width(width: Length) -> Self { - Space { - width, - height: Length::Shrink, - } - } - - /// Creates an amount of vertical [`Space`]. - pub fn with_height(height: Length) -> Self { - Space { - width: Length::Shrink, - height, - } - } -} - -impl<'a, Message> Widget<Message> for Space { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _publish: &Bus<Message>, - _css: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let width = css::length(self.width); - let height = css::length(self.height); - - let style = bumpalo::format!( - in bump, - "width: {}; height: {};", - width, - height - ); - - div(bump).attr("style", style.into_bump_str()).finish() - } -} - -impl<'a, Message> From<Space> for Element<'a, Message> { - fn from(space: Space) -> Element<'a, Message> { - Element::new(space) - } -} diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs deleted file mode 100644 index 53d57bfd..00000000 --- a/web/src/widget/text.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::alignment; -use crate::css; -use crate::{Bus, Color, Css, Element, Font, Length, Widget}; -use dodrio::bumpalo; - -/// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Text; -/// -/// Text::new("I <3 iced!") -/// .size(40); -/// ``` -#[derive(Debug, Clone)] -pub struct Text { - content: String, - size: Option<u16>, - color: Option<Color>, - font: Font, - width: Length, - height: Length, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, -} - -impl Text { - /// Create a new fragment of [`Text`] with the given contents. - pub fn new<T: Into<String>>(label: T) -> Self { - Text { - content: label.into(), - size: None, - color: None, - font: Font::Default, - width: Length::Shrink, - height: Length::Shrink, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - } - } - - /// Sets the size of the [`Text`]. - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the [`Color`] of the [`Text`]. - pub fn color<C: Into<Color>>(mut self, color: C) -> Self { - self.color = Some(color.into()); - self - } - - /// Sets the [`Font`] of the [`Text`]. - pub fn font(mut self, font: Font) -> Self { - self.font = font; - self - } - - /// Sets the width of the [`Text`] boundaries. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Text`] boundaries. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the [`HorizontalAlignment`] of the [`Text`]. - pub fn horizontal_alignment( - mut self, - alignment: alignment::Horizontal, - ) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the [`VerticalAlignment`] of the [`Text`]. - pub fn vertical_alignment( - mut self, - alignment: alignment::Vertical, - ) -> Self { - self.vertical_alignment = alignment; - self - } -} - -impl<'a, Message> Widget<Message> for Text { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _publish: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let content = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.content, bump) - }; - - let color = self - .color - .map(css::color) - .unwrap_or(String::from("inherit")); - - let width = css::length(self.width); - let height = css::length(self.height); - - let text_align = match self.horizontal_alignment { - alignment::Horizontal::Left => "left", - alignment::Horizontal::Center => "center", - alignment::Horizontal::Right => "right", - }; - - let style = bumpalo::format!( - in bump, - "width: {}; height: {}; font-size: {}px; color: {}; \ - text-align: {}; font-family: {}", - width, - height, - self.size.unwrap_or(20), - color, - text_align, - match self.font { - Font::Default => "inherit", - Font::External { name, .. } => name, - } - ); - - // TODO: Complete styling - p(bump) - .attr("style", style.into_bump_str()) - .children(vec![text(content.into_bump_str())]) - .finish() - } -} - -impl<'a, Message> From<Text> for Element<'a, Message> { - fn from(text: Text) -> Element<'a, Message> { - Element::new(text) - } -} diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs deleted file mode 100644 index c5874485..00000000 --- a/web/src/widget/text_input.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! Display fields that can be filled with text. -//! -//! A [`TextInput`] has some local [`State`]. -use crate::{bumpalo, css, Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::text_input::{Style, StyleSheet}; - -use std::{rc::Rc, u32}; - -/// A field that can be filled with text. -/// -/// # Example -/// ``` -/// # use iced_web::{text_input, TextInput}; -/// # -/// enum Message { -/// TextInputChanged(String), -/// } -/// -/// let mut state = text_input::State::new(); -/// let value = "Some text"; -/// -/// let input = TextInput::new( -/// &mut state, -/// "This is the placeholder...", -/// value, -/// Message::TextInputChanged, -/// ); -/// ``` -#[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message> { - _state: &'a mut State, - placeholder: String, - value: String, - is_secure: bool, - width: Length, - max_width: u32, - padding: Padding, - size: Option<u16>, - on_change: Rc<Box<dyn Fn(String) -> Message>>, - on_submit: Option<Message>, - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> TextInput<'a, Message> { - /// Creates a new [`TextInput`]. - /// - /// It expects: - /// - some [`State`] - /// - a placeholder - /// - the current value - /// - a function that produces a message when the [`TextInput`] changes - pub fn new<F>( - state: &'a mut State, - placeholder: &str, - value: &str, - on_change: F, - ) -> Self - where - F: 'static + Fn(String) -> Message, - { - Self { - _state: state, - placeholder: String::from(placeholder), - value: String::from(value), - is_secure: false, - width: Length::Fill, - max_width: u32::MAX, - padding: Padding::ZERO, - size: None, - on_change: Rc::new(Box::new(on_change)), - on_submit: None, - style_sheet: Default::default(), - } - } - - /// Converts the [`TextInput`] into a secure password input. - pub fn password(mut self) -> Self { - self.is_secure = true; - self - } - - /// Sets the width of the [`TextInput`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the maximum width of the [`TextInput`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the [`Padding`] of the [`TextInput`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`TextInput`]. - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the message that should be produced when the [`TextInput`] is - /// focused and the enter key is pressed. - pub fn on_submit(mut self, message: Message) -> Self { - self.on_submit = Some(message); - self - } - - /// Sets the style of the [`TextInput`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } -} - -impl<'a, Message> Widget<Message> for TextInput<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use wasm_bindgen::JsCast; - - let placeholder = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.placeholder, bump).into_bump_str() - }; - - let value = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.value, bump).into_bump_str() - }; - - let on_change = self.on_change.clone(); - let on_submit = self.on_submit.clone(); - let input_event_bus = bus.clone(); - let submit_event_bus = bus.clone(); - let style = self.style_sheet.active(); - - input(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}; max-width: {}; padding: {}; font-size: {}px; \ - background: {}; border-width: {}px; border-color: {}; \ - border-radius: {}px; color: {}", - css::length(self.width), - css::max_length(self.max_width), - css::padding(self.padding), - self.size.unwrap_or(20), - css::background(style.background), - style.border_width, - css::color(style.border_color), - style.border_radius, - css::color(self.style_sheet.value_color()) - ) - .into_bump_str(), - ) - .attr("placeholder", placeholder) - .attr("value", value) - .attr("type", if self.is_secure { "password" } else { "text" }) - .on("input", move |_root, _vdom, event| { - let text_input = match event.target().and_then(|t| { - t.dyn_into::<web_sys::HtmlInputElement>().ok() - }) { - None => return, - Some(text_input) => text_input, - }; - - input_event_bus.publish(on_change(text_input.value())); - }) - .on("keypress", move |_root, _vdom, event| { - if let Some(on_submit) = on_submit.clone() { - let event = - event.unchecked_into::<web_sys::KeyboardEvent>(); - - match event.key_code() { - 13 => { - submit_event_bus.publish(on_submit); - } - _ => {} - } - } - }) - .finish() - } -} - -impl<'a, Message> From<TextInput<'a, Message>> for Element<'a, Message> -where - Message: 'static + Clone, -{ - fn from(text_input: TextInput<'a, Message>) -> Element<'a, Message> { - Element::new(text_input) - } -} - -/// The state of a [`TextInput`]. -#[derive(Debug, Clone, Copy, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`], representing an unfocused [`TextInput`]. - pub fn new() -> Self { - Self::default() - } - - /// Creates a new [`State`], representing a focused [`TextInput`]. - pub fn focused() -> Self { - // TODO - Self::default() - } - - /// Selects all the content of the [`TextInput`]. - pub fn select_all(&mut self) { - // TODO - } -} diff --git a/web/src/widget/toggler.rs b/web/src/widget/toggler.rs deleted file mode 100644 index 0a198079..00000000 --- a/web/src/widget/toggler.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Show toggle controls using togglers. -use crate::{css, Bus, Css, Element, Length, Widget}; - -pub use iced_style::toggler::{Style, StyleSheet}; - -use dodrio::bumpalo; -use std::rc::Rc; - -/// A toggler that can be toggled. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Toggler; -/// -/// pub enum Message { -/// TogglerToggled(bool), -/// } -/// -/// let is_active = true; -/// -/// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled); -/// ``` -/// -#[allow(missing_debug_implementations)] -pub struct Toggler<Message> { - is_active: bool, - on_toggle: Rc<dyn Fn(bool) -> Message>, - label: Option<String>, - id: Option<String>, - width: Length, - style: Box<dyn StyleSheet>, -} - -impl<Message> Toggler<Message> { - /// Creates a new [`Toggler`]. - /// - /// It expects: - /// * a boolean describing whether the [`Toggler`] is active or not - /// * An optional label for the [`Toggler`] - /// * a function that will be called when the [`Toggler`] is toggled. It - /// will receive the new state of the [`Toggler`] and must produce a - /// `Message`. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn new<F>( - is_active: bool, - label: impl Into<Option<String>>, - f: F, - ) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Toggler { - is_active, - on_toggle: Rc::new(f), - label: label.into(), - id: None, - width: Length::Shrink, - style: Default::default(), - } - } - - /// Sets the width of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { - self.style = style.into(); - self - } - - /// Sets the id of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<Message> Widget<Message> for Toggler<Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let toggler_label = &self - .label - .as_ref() - .map(|label| String::from_str_in(&label, bump).into_bump_str()); - - let event_bus = bus.clone(); - let on_toggle = self.on_toggle.clone(); - let is_active = self.is_active; - - let row_class = style_sheet.insert(bump, css::Rule::Row); - let toggler_class = style_sheet.insert(bump, css::Rule::Toggler(16)); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - let checkbox = input - .attr("type", "checkbox") - .bool_attr("checked", self.is_active) - .on("click", move |_root, vdom, _event| { - let msg = on_toggle(!is_active); - event_bus.publish(msg); - - vdom.schedule_render(); - }) - .finish(); - - let toggler = span(bump).children(vec![span(bump).finish()]).finish(); - - label - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, toggler_class) - .into_bump_str(), - ) - .attr( - "style", - bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) - .into_bump_str() - ) - .children( - if let Some(label) = toggler_label { - vec![ - text(label), - checkbox, - toggler, - ] - } else { - vec![ - checkbox, - toggler, - ] - } - ) - .finish() - } -} - -impl<'a, Message> From<Toggler<Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(toggler: Toggler<Message>) -> Element<'a, Message> { - Element::new(toggler) - } -} -- cgit From 26d95fdc4b7b6a66431d45f49edd0cc3ef19823f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 16:55:16 +0700 Subject: Append `Canvas` to `<body>` when targetting Wasm in `iced_winit` --- winit/Cargo.toml | 4 ++++ winit/src/application.rs | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index bfcfacbc..46f0cdb1 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -37,3 +37,7 @@ path = "../futures" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" + +[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] +version = "0.3" +features = ["Document", "Window"] diff --git a/winit/src/application.rs b/winit/src/application.rs index 53b7a6c7..7ddb9947 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -148,6 +148,21 @@ where .build(&event_loop) .map_err(Error::WindowCreationFailed)?; + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + + let canvas = window.canvas(); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + let _ = body + .append_child(&canvas) + .expect("Append canvas to HTML body"); + } + let mut clipboard = Clipboard::connect(&window); run_command( -- cgit From 776724162aa4351310de9f88c7bb8d61b0abca04 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 16:58:11 +0700 Subject: Use WebGL2 limits for `Compositor` in `iced_wgpu` --- wgpu/src/window/compositor.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 3b264475..6feb795b 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -48,6 +48,13 @@ impl Compositor { .as_ref() .and_then(|surface| surface.get_preferred_format(&adapter))?; + #[cfg(target_arch = "wasm32")] + let limits = wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()); + + #[cfg(not(target_arch = "wasm32"))] + let limits = wgpu::Limits::default(); + let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { @@ -57,7 +64,7 @@ impl Compositor { features: wgpu::Features::empty(), limits: wgpu::Limits { max_bind_groups: 2, - ..wgpu::Limits::default() + ..limits }, }, None, -- cgit From 87b3e03d187237f665b376018ea5af0cc5f05814 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 17:05:09 +0700 Subject: Enable `instant` only for `wasm32` targets ... and hide the dependency under a `time` module in `iced_native` --- native/Cargo.toml | 5 ++++- native/src/debug/basic.rs | 4 +++- native/src/lib.rs | 1 + native/src/mouse/click.rs | 2 +- native/src/time.rs | 7 +++++++ src/time.rs | 6 +++--- 6 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 native/src/time.rs diff --git a/native/Cargo.toml b/native/Cargo.toml index d8e75a4e..499ad29e 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -14,7 +14,6 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" -instant = { version="0.1", features=["wasm-bindgen"] } [dependencies.iced_core] version = "0.4" @@ -28,3 +27,7 @@ features = ["thread-pool"] [dependencies.iced_style] version = "0.3" path = "../style" + +[target.'cfg(target_arch = "wasm32")'.dependencies.instant] +version = "0.1" +features = ["wasm-bindgen"] diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs index a42f66ea..d706bb00 100644 --- a/native/src/debug/basic.rs +++ b/native/src/debug/basic.rs @@ -1,5 +1,7 @@ #![allow(missing_docs)] -use std::{collections::VecDeque, time}; +use crate::time; + +use std::collections::VecDeque; /// A bunch of time measurements for debugging purposes. #[derive(Debug)] diff --git a/native/src/lib.rs b/native/src/lib.rs index a5526e6d..bd50c6b2 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -49,6 +49,7 @@ pub mod renderer; pub mod subscription; pub mod svg; pub mod text; +pub mod time; pub mod touch; pub mod user_interface; pub mod widget; diff --git a/native/src/mouse/click.rs b/native/src/mouse/click.rs index 58cfda61..ec321387 100644 --- a/native/src/mouse/click.rs +++ b/native/src/mouse/click.rs @@ -1,6 +1,6 @@ //! Track mouse clicks. +use crate::time::Instant; use crate::Point; -use instant::Instant; /// A mouse click. #[derive(Debug, Clone, Copy)] diff --git a/native/src/time.rs b/native/src/time.rs new file mode 100644 index 00000000..5f95ee86 --- /dev/null +++ b/native/src/time.rs @@ -0,0 +1,7 @@ +//! Keep track of time, both in native and web platforms! + +#[cfg(target_arch = "wasm32")] +pub use instant::{Duration, Instant}; + +#[cfg(not(target_arch = "wasm32"))] +pub use std::time::{Duration, Instant}; diff --git a/src/time.rs b/src/time.rs index b8432895..943aa42f 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,12 +1,12 @@ //! Listen and react to time. +pub use crate::runtime::time::{Duration, Instant}; + use crate::Subscription; /// Returns a [`Subscription`] that produces messages at a set interval. /// /// The first message is produced after a `duration`, and then continues to /// produce more messages every `duration` after that. -pub fn every( - duration: std::time::Duration, -) -> Subscription<std::time::Instant> { +pub fn every(duration: Duration) -> Subscription<Instant> { iced_futures::time::every(duration) } -- cgit From 83c649b574d90667d23c8430baaebcd0ef933055 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 17:20:40 +0700 Subject: Move `time` module from `iced_native` to `iced_core` --- core/Cargo.toml | 4 ++++ core/src/lib.rs | 1 + core/src/time.rs | 7 +++++++ native/Cargo.toml | 4 ---- native/src/lib.rs | 2 +- native/src/time.rs | 7 ------- 6 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 core/src/time.rs delete mode 100644 native/src/time.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index ca28c308..83458ac0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -13,3 +13,7 @@ bitflags = "1.2" [dependencies.palette] version = "0.5" optional = true + +[target.'cfg(target_arch = "wasm32")'.dependencies.instant] +version = "0.1" +features = ["wasm-bindgen"] diff --git a/core/src/lib.rs b/core/src/lib.rs index b76c6d20..2a4e6158 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -17,6 +17,7 @@ pub mod alignment; pub mod keyboard; pub mod mouse; +pub mod time; mod background; mod color; diff --git a/core/src/time.rs b/core/src/time.rs new file mode 100644 index 00000000..5f95ee86 --- /dev/null +++ b/core/src/time.rs @@ -0,0 +1,7 @@ +//! Keep track of time, both in native and web platforms! + +#[cfg(target_arch = "wasm32")] +pub use instant::{Duration, Instant}; + +#[cfg(not(target_arch = "wasm32"))] +pub use std::time::{Duration, Instant}; diff --git a/native/Cargo.toml b/native/Cargo.toml index 499ad29e..8f0aea6a 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -27,7 +27,3 @@ features = ["thread-pool"] [dependencies.iced_style] version = "0.3" path = "../style" - -[target.'cfg(target_arch = "wasm32")'.dependencies.instant] -version = "0.1" -features = ["wasm-bindgen"] diff --git a/native/src/lib.rs b/native/src/lib.rs index bd50c6b2..6d98f7d1 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -49,7 +49,6 @@ pub mod renderer; pub mod subscription; pub mod svg; pub mod text; -pub mod time; pub mod touch; pub mod user_interface; pub mod widget; @@ -70,6 +69,7 @@ mod debug; mod debug; pub use iced_core::alignment; +pub use iced_core::time; pub use iced_core::{ Alignment, Background, Color, Font, Length, Padding, Point, Rectangle, Size, Vector, diff --git a/native/src/time.rs b/native/src/time.rs deleted file mode 100644 index 5f95ee86..00000000 --- a/native/src/time.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Keep track of time, both in native and web platforms! - -#[cfg(target_arch = "wasm32")] -pub use instant::{Duration, Instant}; - -#[cfg(not(target_arch = "wasm32"))] -pub use std::time::{Duration, Instant}; -- cgit From 5dab5a327ef643ee38ac3e42ab35212fff445631 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 17:35:47 +0700 Subject: Introduce `MaybeSend` trait in `iced_futures` It allows to clean up all the `trait_aliases` modules! --- futures/src/executor.rs | 8 ++--- futures/src/lib.rs | 2 ++ futures/src/maybe_send.rs | 21 ++++++++++++++ futures/src/runtime.rs | 56 +++++------------------------------ futures/src/subscription/tracker.rs | 54 +++++----------------------------- native/src/subscription.rs | 58 ++++++------------------------------- 6 files changed, 49 insertions(+), 150 deletions(-) create mode 100644 futures/src/maybe_send.rs diff --git a/futures/src/executor.rs b/futures/src/executor.rs index 23682f32..34e329a0 100644 --- a/futures/src/executor.rs +++ b/futures/src/executor.rs @@ -33,6 +33,7 @@ pub use self::smol::Smol; #[cfg(target_arch = "wasm32")] pub use wasm_bindgen::WasmBindgen; +use crate::MaybeSend; use futures::Future; /// A type that can run futures. @@ -43,12 +44,7 @@ pub trait Executor: Sized { Self: Sized; /// Spawns a future in the [`Executor`]. - #[cfg(not(target_arch = "wasm32"))] - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static); - - /// Spawns a local future in the [`Executor`]. - #[cfg(target_arch = "wasm32")] - fn spawn(&self, future: impl Future<Output = ()> + 'static); + fn spawn(&self, future: impl Future<Output = ()> + MaybeSend + 'static); /// Runs the given closure inside the [`Executor`]. /// diff --git a/futures/src/lib.rs b/futures/src/lib.rs index dbcb8aca..10ade9ed 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -14,6 +14,7 @@ pub use futures; mod command; +mod maybe_send; mod runtime; pub mod executor; @@ -35,6 +36,7 @@ pub mod time; pub use command::Command; pub use executor::Executor; +pub use maybe_send::MaybeSend; pub use platform::*; pub use runtime::Runtime; pub use subscription::Subscription; diff --git a/futures/src/maybe_send.rs b/futures/src/maybe_send.rs new file mode 100644 index 00000000..a6670f0e --- /dev/null +++ b/futures/src/maybe_send.rs @@ -0,0 +1,21 @@ +#[cfg(not(target_arch = "wasm32"))] +mod platform { + /// An extension trait that enforces `Send` only on native platforms. + /// + /// Useful to write cross-platform async code! + pub trait MaybeSend: Send {} + + impl<T> MaybeSend for T where T: Send {} +} + +#[cfg(target_arch = "wasm32")] +mod platform { + /// An extension trait that enforces `Send` only on native platforms. + /// + /// Useful to write cross-platform async code! + pub trait MaybeSend {} + + impl<T> MaybeSend for T {} +} + +pub use platform::MaybeSend; diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 96104cd9..2034ed6c 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,54 +1,10 @@ //! Run commands and keep track of subscriptions. -use crate::BoxFuture; -use crate::{subscription, Executor, Subscription}; +use crate::subscription; +use crate::{BoxFuture, Executor, MaybeSend, Subscription}; use futures::{channel::mpsc, Sink}; use std::marker::PhantomData; -#[cfg(not(target_arch = "wasm32"))] -mod trait_aliases { - use super::*; - - pub trait RuntimeMessage: Send + 'static {} - - impl<T> RuntimeMessage for T where T: Send + 'static {} - - pub trait RuntimeMessageSender<Message: RuntimeMessage>: - Sink<Message, Error = mpsc::SendError> + Unpin + Send + Clone + 'static - { - } - - impl<Message: RuntimeMessage, T> RuntimeMessageSender<Message> for T where - T: Sink<Message, Error = mpsc::SendError> - + Unpin - + Send - + Clone - + 'static - { - } -} - -#[cfg(target_arch = "wasm32")] -mod trait_aliases { - use super::*; - - pub trait RuntimeMessage: 'static {} - - impl<T> RuntimeMessage for T where T: 'static {} - - pub trait RuntimeMessageSender<Message: RuntimeMessage>: - Sink<Message, Error = mpsc::SendError> + Unpin + Clone + 'static - { - } - - impl<Message: RuntimeMessage, T> RuntimeMessageSender<Message> for T where - T: Sink<Message, Error = mpsc::SendError> + Unpin + Clone + 'static - { - } -} - -pub use trait_aliases::{RuntimeMessage, RuntimeMessageSender}; - /// A batteries-included runtime of commands and subscriptions. /// /// If you have an [`Executor`], a [`Runtime`] can be leveraged to run any @@ -67,8 +23,12 @@ where Hasher: std::hash::Hasher + Default, Event: Send + Clone + 'static, Executor: self::Executor, - Sender: RuntimeMessageSender<Message>, - Message: RuntimeMessage, + Sender: Sink<Message, Error = mpsc::SendError> + + Unpin + + MaybeSend + + Clone + + 'static, + Message: MaybeSend + 'static, { /// Creates a new empty [`Runtime`]. /// diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 01e0c105..421fb917 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -1,52 +1,8 @@ -use crate::{BoxFuture, Subscription}; +use crate::{BoxFuture, MaybeSend, Subscription}; use futures::{channel::mpsc, sink::Sink}; use std::{collections::HashMap, marker::PhantomData}; -#[cfg(not(target_arch = "wasm32"))] -mod trait_aliases { - use super::*; - - pub trait TrackerMessage: Send + 'static {} - - impl<T> TrackerMessage for T where T: Send + 'static {} - - pub trait TrackerMessageReceiver<Message: TrackerMessage>: - Sink<Message, Error = mpsc::SendError> + Unpin + Send + Clone + 'static - { - } - - impl<Message: TrackerMessage, T> TrackerMessageReceiver<Message> for T where - T: Sink<Message, Error = mpsc::SendError> - + Unpin - + Send - + Clone - + 'static - { - } -} - -#[cfg(target_arch = "wasm32")] -mod trait_aliases { - use super::*; - - pub trait TrackerMessage: 'static {} - - impl<T> TrackerMessage for T where T: 'static {} - - pub trait TrackerMessageReceiver<Message: TrackerMessage>: - Sink<Message, Error = mpsc::SendError> + Unpin + Clone + 'static - { - } - - impl<Message: TrackerMessage, T> TrackerMessageReceiver<Message> for T where - T: Sink<Message, Error = mpsc::SendError> + Unpin + Clone + 'static - { - } -} - -pub use trait_aliases::{TrackerMessage, TrackerMessageReceiver}; - /// A registry of subscription streams. /// /// If you have an application that continuously returns a [`Subscription`], @@ -101,8 +57,12 @@ where receiver: Receiver, ) -> Vec<BoxFuture<()>> where - Message: TrackerMessage, - Receiver: TrackerMessageReceiver<Message>, + Message: 'static + MaybeSend, + Receiver: 'static + + Sink<Message, Error = mpsc::SendError> + + Unpin + + MaybeSend + + Clone, { use futures::{future::FutureExt, stream::StreamExt}; diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 4fb7e760..9775c84b 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -3,41 +3,10 @@ use crate::event::{self, Event}; use crate::Hasher; use iced_futures::futures::{self, Future, Stream}; -use iced_futures::BoxStream; +use iced_futures::{BoxStream, MaybeSend}; use std::hash::Hash; -#[cfg(not(target_arch = "wasm32"))] -mod trait_aliases { - use super::*; - - /// Wrapper type - pub trait RunnerStream<Message>: - Stream<Item = Message> + Send + 'static - { - } - - impl<T, Message> RunnerStream<Message> for T where - T: Stream<Item = Message> + Send + 'static - { - } -} - -#[cfg(target_arch = "wasm32")] -mod trait_aliases { - use super::*; - - /// Wrapper type - pub trait RunnerStream<Message>: Stream<Item = Message> + 'static {} - - impl<T, Message> RunnerStream<Message> for T where - T: Stream<Item = Message> + 'static - { - } -} - -pub use trait_aliases::RunnerStream; - /// A request to listen to external events. /// /// Besides performing async actions on demand with [`Command`], most @@ -87,7 +56,7 @@ pub fn events_with<Message>( f: fn(Event, event::Status) -> Option<Message>, ) -> Subscription<Message> where - Message: 'static + Send, + Message: 'static + MaybeSend, { Subscription::from_recipe(Runner { id: f, @@ -109,7 +78,7 @@ where pub fn run<I, S, Message>(id: I, stream: S) -> Subscription<Message> where I: Hash + 'static, - S: Stream<Item = Message> + Send + 'static, + S: Stream<Item = Message> + MaybeSend + 'static, Message: 'static, { Subscription::from_recipe(Runner { @@ -190,13 +159,13 @@ where pub fn unfold<I, T, Fut, Message>( id: I, initial: T, - mut f: impl FnMut(T) -> Fut + Send + Sync + 'static, + mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static, ) -> Subscription<Message> where I: Hash + 'static, - T: Send + 'static, - Fut: Future<Output = (Option<Message>, T)> + Send + 'static, - Message: 'static + Send, + T: MaybeSend + 'static, + Fut: Future<Output = (Option<Message>, T)> + MaybeSend + 'static, + Message: 'static + MaybeSend, { use futures::future::{self, FutureExt}; use futures::stream::StreamExt; @@ -222,7 +191,7 @@ impl<I, S, F, Message> Recipe<Hasher, (Event, event::Status)> where I: Hash + 'static, F: FnOnce(EventStream) -> S, - S: RunnerStream<Message>, + S: Stream<Item = Message> + MaybeSend + 'static, { type Output = Message; @@ -232,15 +201,6 @@ where } fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> { - use futures::stream::StreamExt; - - #[cfg(target_arch = "wasm32")] - { - (self.spawn)(input).boxed_local() - } - #[cfg(not(target_arch = "wasm32"))] - { - (self.spawn)(input).boxed() - } + iced_futures::boxed_stream((self.spawn)(input)) } } -- cgit From 167be45a7db7c1f60a79116766bdf38300429c6a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 18:24:07 +0700 Subject: Split `iced_futures` into different `backend` implementations --- futures/src/backend.rs | 10 +++ futures/src/backend/default.rs | 38 +++++++++++ futures/src/backend/native.rs | 16 +++++ futures/src/backend/native/async_std.rs | 59 +++++++++++++++++ futures/src/backend/native/smol.rs | 59 +++++++++++++++++ futures/src/backend/native/thread_pool.rs | 16 +++++ futures/src/backend/native/tokio.rs | 72 ++++++++++++++++++++ futures/src/backend/null.rs | 18 +++++ futures/src/backend/wasm.rs | 2 + futures/src/backend/wasm/wasm_bindgen.rs | 15 +++++ futures/src/executor.rs | 34 ---------- futures/src/executor/async_std.rs | 18 ----- futures/src/executor/null.rs | 19 ------ futures/src/executor/smol.rs | 18 ----- futures/src/executor/thread_pool.rs | 17 ----- futures/src/executor/tokio.rs | 22 ------- futures/src/executor/wasm_bindgen.rs | 15 ----- futures/src/lib.rs | 15 +---- futures/src/time.rs | 105 ------------------------------ src/executor.rs | 94 ++++---------------------- src/sandbox.rs | 2 +- src/time.rs | 12 +--- 22 files changed, 319 insertions(+), 357 deletions(-) create mode 100644 futures/src/backend.rs create mode 100644 futures/src/backend/default.rs create mode 100644 futures/src/backend/native.rs create mode 100644 futures/src/backend/native/async_std.rs create mode 100644 futures/src/backend/native/smol.rs create mode 100644 futures/src/backend/native/thread_pool.rs create mode 100644 futures/src/backend/native/tokio.rs create mode 100644 futures/src/backend/null.rs create mode 100644 futures/src/backend/wasm.rs create mode 100644 futures/src/backend/wasm/wasm_bindgen.rs delete mode 100644 futures/src/executor/async_std.rs delete mode 100644 futures/src/executor/null.rs delete mode 100644 futures/src/executor/smol.rs delete mode 100644 futures/src/executor/thread_pool.rs delete mode 100644 futures/src/executor/tokio.rs delete mode 100644 futures/src/executor/wasm_bindgen.rs delete mode 100644 futures/src/time.rs diff --git a/futures/src/backend.rs b/futures/src/backend.rs new file mode 100644 index 00000000..1cc4af80 --- /dev/null +++ b/futures/src/backend.rs @@ -0,0 +1,10 @@ +//! The underlying implementations of the `iced_futures` contract! +pub mod null; + +#[cfg(not(target_arch = "wasm32"))] +pub mod native; + +#[cfg(target_arch = "wasm32")] +pub mod wasm; + +pub mod default; diff --git a/futures/src/backend/default.rs b/futures/src/backend/default.rs new file mode 100644 index 00000000..76264f55 --- /dev/null +++ b/futures/src/backend/default.rs @@ -0,0 +1,38 @@ +//! A default, cross-platform backend. +//! +//! - On native platforms, it will use: +//! - `backend::native::tokio` when the `tokio` feature is enabled. +//! - `backend::native::async-std` when the `async-std` feature is +//! enabled. +//! - `backend::native::smol` when the `smol` feature is enabled. +//! - `backend::native::thread_pool` otherwise. +//! +//! - On Wasm, it will use `backend::wasm::wasm_bindgen`. +#[cfg(not(target_arch = "wasm32"))] +mod platform { + #[cfg(feature = "tokio")] + pub use crate::backend::native::tokio::*; + + #[cfg(all(feature = "async-std", not(feature = "tokio"),))] + pub use crate::backend::native::async_std::*; + + #[cfg(all( + feature = "smol", + not(any(feature = "tokio", feature = "async-std")), + ))] + pub use crate::backend::native::smol::*; + + #[cfg(not(any( + feature = "tokio", + feature = "async-std", + feature = "smol", + )))] + pub use crate::backend::native::thread_pool::*; +} + +#[cfg(target_arch = "wasm32")] +mod platform { + pub use crate::backend::wasm::wasm_bindgen::*; +} + +pub use platform::*; diff --git a/futures/src/backend/native.rs b/futures/src/backend/native.rs new file mode 100644 index 00000000..4199ad16 --- /dev/null +++ b/futures/src/backend/native.rs @@ -0,0 +1,16 @@ +//! Backends that are only available in native platforms: Windows, macOS, or Linux. +#[cfg_attr(docsrs, doc(cfg(feature = "tokio",)))] +#[cfg(feature = "tokio")] +pub mod tokio; + +#[cfg_attr(docsrs, doc(cfg(feature = "async-std",)))] +#[cfg(feature = "async-std")] +pub mod async_std; + +#[cfg_attr(docsrs, doc(cfg(feature = "smol",)))] +#[cfg(feature = "smol")] +pub mod smol; + +#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool",)))] +#[cfg(feature = "thread-pool")] +pub mod thread_pool; diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs new file mode 100644 index 00000000..e8641626 --- /dev/null +++ b/futures/src/backend/native/async_std.rs @@ -0,0 +1,59 @@ +//! An `async-std` backend. +use futures::Future; + +/// An `async-std` executor. +#[derive(Debug)] +pub struct Executor; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + Ok(Self) + } + + fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { + let _ = async_std::task::spawn(future); + } +} + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, std::time::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } + } +} diff --git a/futures/src/backend/native/smol.rs b/futures/src/backend/native/smol.rs new file mode 100644 index 00000000..d5201cde --- /dev/null +++ b/futures/src/backend/native/smol.rs @@ -0,0 +1,59 @@ +//! A `smol` backend. + +use futures::Future; + +/// A `smol` executor. +#[cfg_attr(docsrs, doc(cfg(feature = "smol")))] +#[derive(Debug)] +pub struct Executor; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + Ok(Self) + } + + fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { + smol::spawn(future).detach(); + } +} + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, std::time::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + smol::Timer::interval(self.0).boxed() + } + } +} diff --git a/futures/src/backend/native/thread_pool.rs b/futures/src/backend/native/thread_pool.rs new file mode 100644 index 00000000..6e791533 --- /dev/null +++ b/futures/src/backend/native/thread_pool.rs @@ -0,0 +1,16 @@ +//! A `ThreadPool` backend. +use futures::Future; + +/// A thread pool executor for futures. +#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] +pub type ThreadPool = futures::executor::ThreadPool; + +impl crate::Executor for futures::executor::ThreadPool { + fn new() -> Result<Self, futures::io::Error> { + futures::executor::ThreadPool::new() + } + + fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { + self.spawn_ok(future); + } +} diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs new file mode 100644 index 00000000..f86b0ea3 --- /dev/null +++ b/futures/src/backend/native/tokio.rs @@ -0,0 +1,72 @@ +//! A `tokio` backend. +use futures::Future; + +/// A `tokio` executor. +pub type Executor = tokio::runtime::Runtime; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + tokio::runtime::Runtime::new() + } + + fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { + let _ = tokio::runtime::Runtime::spawn(self, future); + } + + fn enter<R>(&self, f: impl FnOnce() -> R) -> R { + let _guard = tokio::runtime::Runtime::enter(self); + f() + } +} + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, std::time::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + let start = tokio::time::Instant::now() + self.0; + + let stream = { + futures::stream::unfold( + tokio::time::interval_at(start, self.0), + |mut interval| async move { + Some((interval.tick().await, interval)) + }, + ) + }; + + stream.map(tokio::time::Instant::into_std).boxed() + } + } +} diff --git a/futures/src/backend/null.rs b/futures/src/backend/null.rs new file mode 100644 index 00000000..e22e7921 --- /dev/null +++ b/futures/src/backend/null.rs @@ -0,0 +1,18 @@ +//! A backend that does nothing! +use futures::Future; + +/// An executor that drops all the futures, instead of spawning them. +#[derive(Debug)] +pub struct Executor; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + Ok(Self) + } + + #[cfg(not(target_arch = "wasm32"))] + fn spawn(&self, _future: impl Future<Output = ()> + Send + 'static) {} + + #[cfg(target_arch = "wasm32")] + fn spawn(&self, _future: impl Future<Output = ()> + 'static) {} +} diff --git a/futures/src/backend/wasm.rs b/futures/src/backend/wasm.rs new file mode 100644 index 00000000..a49d9e55 --- /dev/null +++ b/futures/src/backend/wasm.rs @@ -0,0 +1,2 @@ +//! Backends that are only available on Wasm targets. +pub mod wasm_bindgen; diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs new file mode 100644 index 00000000..e914aeba --- /dev/null +++ b/futures/src/backend/wasm/wasm_bindgen.rs @@ -0,0 +1,15 @@ +//! A `wasm-bindgein-futures` backend. + +/// A `wasm-bindgen-futures` executor. +#[derive(Debug)] +pub struct Executor; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + Ok(Self) + } + + fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) { + wasm_bindgen_futures::spawn_local(future); + } +} diff --git a/futures/src/executor.rs b/futures/src/executor.rs index 34e329a0..5ac76081 100644 --- a/futures/src/executor.rs +++ b/futures/src/executor.rs @@ -1,38 +1,4 @@ //! Choose your preferred executor to power a runtime. -mod null; - -#[cfg(all(not(target_arch = "wasm32"), feature = "thread-pool"))] -mod thread_pool; - -#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -mod tokio; - -#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] -mod async_std; - -#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))] -mod smol; - -#[cfg(target_arch = "wasm32")] -mod wasm_bindgen; - -pub use null::Null; - -#[cfg(all(not(target_arch = "wasm32"), feature = "thread-pool"))] -pub use thread_pool::ThreadPool; - -#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -pub use self::tokio::Tokio; - -#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] -pub use self::async_std::AsyncStd; - -#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))] -pub use self::smol::Smol; - -#[cfg(target_arch = "wasm32")] -pub use wasm_bindgen::WasmBindgen; - use crate::MaybeSend; use futures::Future; diff --git a/futures/src/executor/async_std.rs b/futures/src/executor/async_std.rs deleted file mode 100644 index 471be369..00000000 --- a/futures/src/executor/async_std.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// An `async-std` runtime. -#[cfg_attr(docsrs, doc(cfg(feature = "async-std")))] -#[derive(Debug)] -pub struct AsyncStd; - -impl Executor for AsyncStd { - fn new() -> Result<Self, futures::io::Error> { - Ok(Self) - } - - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { - let _ = async_std::task::spawn(future); - } -} diff --git a/futures/src/executor/null.rs b/futures/src/executor/null.rs deleted file mode 100644 index 65e2e2df..00000000 --- a/futures/src/executor/null.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// An executor that drops all the futures, instead of spawning them. -#[derive(Debug)] -pub struct Null; - -impl Executor for Null { - fn new() -> Result<Self, futures::io::Error> { - Ok(Self) - } - - #[cfg(not(target_arch = "wasm32"))] - fn spawn(&self, _future: impl Future<Output = ()> + Send + 'static) {} - - #[cfg(target_arch = "wasm32")] - fn spawn(&self, _future: impl Future<Output = ()> + 'static) {} -} diff --git a/futures/src/executor/smol.rs b/futures/src/executor/smol.rs deleted file mode 100644 index deafd43a..00000000 --- a/futures/src/executor/smol.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// A `smol` runtime. -#[cfg_attr(docsrs, doc(cfg(feature = "smol")))] -#[derive(Debug)] -pub struct Smol; - -impl Executor for Smol { - fn new() -> Result<Self, futures::io::Error> { - Ok(Self) - } - - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { - smol::spawn(future).detach(); - } -} diff --git a/futures/src/executor/thread_pool.rs b/futures/src/executor/thread_pool.rs deleted file mode 100644 index a6c6168e..00000000 --- a/futures/src/executor/thread_pool.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// A thread pool runtime for futures. -#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] -pub type ThreadPool = futures::executor::ThreadPool; - -impl Executor for futures::executor::ThreadPool { - fn new() -> Result<Self, futures::io::Error> { - futures::executor::ThreadPool::new() - } - - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { - self.spawn_ok(future); - } -} diff --git a/futures/src/executor/tokio.rs b/futures/src/executor/tokio.rs deleted file mode 100644 index c6a21cec..00000000 --- a/futures/src/executor/tokio.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// A `tokio` runtime. -#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] -pub type Tokio = tokio::runtime::Runtime; - -impl Executor for Tokio { - fn new() -> Result<Self, futures::io::Error> { - tokio::runtime::Runtime::new() - } - - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { - let _ = tokio::runtime::Runtime::spawn(self, future); - } - - fn enter<R>(&self, f: impl FnOnce() -> R) -> R { - let _guard = tokio::runtime::Runtime::enter(self); - f() - } -} diff --git a/futures/src/executor/wasm_bindgen.rs b/futures/src/executor/wasm_bindgen.rs deleted file mode 100644 index 94d694c8..00000000 --- a/futures/src/executor/wasm_bindgen.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::Executor; - -/// A `wasm-bindgen-futures` runtime. -#[derive(Debug)] -pub struct WasmBindgen; - -impl Executor for WasmBindgen { - fn new() -> Result<Self, futures::io::Error> { - Ok(Self) - } - - fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) { - wasm_bindgen_futures::spawn_local(future); - } -} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 10ade9ed..b0b2f6ce 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -17,23 +17,10 @@ mod command; mod maybe_send; mod runtime; +pub mod backend; pub mod executor; pub mod subscription; -#[cfg(all( - any(feature = "tokio", feature = "async-std", feature = "smol"), - not(target_arch = "wasm32") -))] -#[cfg_attr( - docsrs, - doc(cfg(any( - feature = "tokio", - feature = "async-std", - feature = "smol" - ))) -)] -pub mod time; - pub use command::Command; pub use executor::Executor; pub use maybe_send::MaybeSend; diff --git a/futures/src/time.rs b/futures/src/time.rs deleted file mode 100644 index 0ece6f04..00000000 --- a/futures/src/time.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! Listen and react to time. -use crate::subscription::{self, Subscription}; - -/// Returns a [`Subscription`] that produces messages at a set interval. -/// -/// The first message is produced after a `duration`, and then continues to -/// produce more messages every `duration` after that. -pub fn every<H: std::hash::Hasher, E>( - duration: std::time::Duration, -) -> Subscription<H, E, std::time::Instant> { - Subscription::from_recipe(Every(duration)) -} - -struct Every(std::time::Duration); - -#[cfg(all( - not(any(feature = "tokio", feature = "async-std")), - feature = "smol" -))] -impl<H, E> subscription::Recipe<H, E> for Every -where - H: std::hash::Hasher, -{ - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - smol::Timer::interval(self.0).boxed() - } -} - -#[cfg(feature = "async-std")] -impl<H, E> subscription::Recipe<H, E> for Every -where - H: std::hash::Hasher, -{ - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } -} - -#[cfg(all( - feature = "tokio", - not(any(feature = "async-std", feature = "smol")) -))] -impl<H, E> subscription::Recipe<H, E> for Every -where - H: std::hash::Hasher, -{ - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - let start = tokio::time::Instant::now() + self.0; - - let stream = { - futures::stream::unfold( - tokio::time::interval_at(start, self.0), - |mut interval| async move { - Some((interval.tick().await, interval)) - }, - ) - }; - - stream.map(tokio::time::Instant::into_std).boxed() - } -} diff --git a/src/executor.rs b/src/executor.rs index c7166c68..36ae274e 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,86 +1,14 @@ //! Choose your preferred executor to power your application. pub use crate::runtime::Executor; -pub use platform::Default; - -#[cfg(not(target_arch = "wasm32"))] -mod platform { - use iced_futures::{executor, futures}; - - #[cfg(feature = "tokio")] - type Executor = executor::Tokio; - - #[cfg(all(feature = "async-std", not(feature = "tokio"),))] - type Executor = executor::AsyncStd; - - #[cfg(all( - feature = "smol", - not(any(feature = "tokio", feature = "async-std")), - ))] - type Executor = executor::Smol; - - #[cfg(not(any( - feature = "tokio", - feature = "async-std", - feature = "smol", - )))] - type Executor = executor::ThreadPool; - - /// A default cross-platform executor. - /// - /// - On native platforms, it will use: - /// - `iced_futures::executor::Tokio` when the `tokio` feature is enabled. - /// - `iced_futures::executor::AsyncStd` when the `async-std` feature is - /// enabled. - /// - `iced_futures::executor::ThreadPool` otherwise. - /// - On the Web, it will use `iced_futures::executor::WasmBindgen`. - #[derive(Debug)] - pub struct Default(Executor); - - impl super::Executor for Default { - fn new() -> Result<Self, futures::io::Error> { - Ok(Default(Executor::new()?)) - } - - fn spawn( - &self, - future: impl futures::Future<Output = ()> + Send + 'static, - ) { - let _ = self.0.spawn(future); - } - - fn enter<R>(&self, f: impl FnOnce() -> R) -> R { - super::Executor::enter(&self.0, f) - } - } -} - -#[cfg(target_arch = "wasm32")] -mod platform { - use iced_futures::{executor::WasmBindgen, futures, Executor}; - - /// A default cross-platform executor. - /// - /// - On native platforms, it will use: - /// - `iced_futures::executor::Tokio` when the `tokio` feature is enabled. - /// - `iced_futures::executor::AsyncStd` when the `async-std` feature is - /// enabled. - /// - `iced_futures::executor::ThreadPool` otherwise. - /// - On the Web, it will use `iced_futures::executor::WasmBindgen`. - #[derive(Debug)] - pub struct Default(WasmBindgen); - - impl Executor for Default { - fn new() -> Result<Self, futures::io::Error> { - Ok(Default(WasmBindgen::new()?)) - } - - fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) { - self.0.spawn(future); - } - - fn enter<R>(&self, f: impl FnOnce() -> R) -> R { - self.0.enter(f) - } - } -} +/// A default cross-platform executor. +/// +/// - On native platforms, it will use: +/// - `iced_futures::backend::native::tokio` when the `tokio` feature is enabled. +/// - `iced_futures::backend::native::async-std` when the `async-std` feature is +/// enabled. +/// - `iced_futures::backend::native::smol` when the `smol` feature is enabled. +/// - `iced_futures::backend::native::thread_pool` otherwise. +/// +/// - On Wasm, it will use `iced_futures::backend::wasm::wasm_bindgen`. +pub type Default = iced_futures::backend::default::Executor; diff --git a/src/sandbox.rs b/src/sandbox.rs index aabfb9c7..2306c650 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -156,7 +156,7 @@ impl<T> Application for T where T: Sandbox, { - type Executor = crate::runtime::executor::Null; + type Executor = iced_futures::backend::null::Executor; type Flags = (); type Message = T::Message; diff --git a/src/time.rs b/src/time.rs index 943aa42f..4f831171 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,12 +1,2 @@ //! Listen and react to time. -pub use crate::runtime::time::{Duration, Instant}; - -use crate::Subscription; - -/// Returns a [`Subscription`] that produces messages at a set interval. -/// -/// The first message is produced after a `duration`, and then continues to -/// produce more messages every `duration` after that. -pub fn every(duration: Duration) -> Subscription<Instant> { - iced_futures::time::every(duration) -} +pub use iced_futures::backend::default::time::*; -- cgit From e730d97f61bc2edc77d2f061b6a763c4d0a948df Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 18:43:20 +0700 Subject: Implement `time` module for `wasm-bindgen` backend in `iced_futures` --- core/Cargo.toml | 5 ++-- core/src/time.rs | 6 +++-- futures/Cargo.toml | 1 + futures/src/backend/wasm/wasm_bindgen.rs | 44 ++++++++++++++++++++++++++++++++ native/src/mouse/click.rs | 9 +++++-- src/lib.rs | 15 +---------- src/time.rs | 2 ++ 7 files changed, 61 insertions(+), 21 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 83458ac0..dde34326 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,6 +14,5 @@ bitflags = "1.2" version = "0.5" optional = true -[target.'cfg(target_arch = "wasm32")'.dependencies.instant] -version = "0.1" -features = ["wasm-bindgen"] +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-timer = { version = "0.2" } diff --git a/core/src/time.rs b/core/src/time.rs index 5f95ee86..f496d1a4 100644 --- a/core/src/time.rs +++ b/core/src/time.rs @@ -1,7 +1,9 @@ //! Keep track of time, both in native and web platforms! #[cfg(target_arch = "wasm32")] -pub use instant::{Duration, Instant}; +pub use wasm_timer::Instant; #[cfg(not(target_arch = "wasm32"))] -pub use std::time::{Duration, Instant}; +pub use std::time::Instant; + +pub use std::time::Duration; diff --git a/futures/Cargo.toml b/futures/Cargo.toml index aa55df1e..93c7693d 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -36,6 +36,7 @@ optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" +wasm-timer = "0.2" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs index e914aeba..b726501a 100644 --- a/futures/src/backend/wasm/wasm_bindgen.rs +++ b/futures/src/backend/wasm/wasm_bindgen.rs @@ -13,3 +13,47 @@ impl crate::Executor for Executor { wasm_bindgen_futures::spawn_local(future); } } + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + use crate::BoxStream; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, wasm_timer::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = wasm_timer::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: BoxStream<E>, + ) -> BoxStream<Self::Output> { + use futures::stream::StreamExt; + + wasm_timer::Interval::new(self.0) + .map(|_| wasm_timer::Instant::now()) + .boxed_local() + } + } +} diff --git a/native/src/mouse/click.rs b/native/src/mouse/click.rs index ec321387..4a7d796c 100644 --- a/native/src/mouse/click.rs +++ b/native/src/mouse/click.rs @@ -62,9 +62,14 @@ impl Click { } fn is_consecutive(&self, new_position: Point, time: Instant) -> bool { + let duration = if time > self.time { + Some(time - self.time) + } else { + None + }; + self.position == new_position - && time - .checked_duration_since(self.time) + && duration .map(|duration| duration.as_millis() <= 300) .unwrap_or(false) } diff --git a/src/lib.rs b/src/lib.rs index 41fb3a8b..c8047d7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,23 +191,10 @@ pub mod executor; pub mod keyboard; pub mod mouse; pub mod settings; +pub mod time; pub mod widget; pub mod window; -#[cfg(all( - any(feature = "tokio", feature = "async-std", feature = "smol"), - not(target_arch = "wasm32") -))] -#[cfg_attr( - docsrs, - doc(cfg(any( - feature = "tokio", - feature = "async-std" - feature = "smol" - ))) -)] -pub mod time; - #[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_winit as runtime; diff --git a/src/time.rs b/src/time.rs index 4f831171..37d454ed 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,2 +1,4 @@ //! Listen and react to time. +pub use iced_core::time::{Duration, Instant}; + pub use iced_futures::backend::default::time::*; -- cgit From 90afd1db8df17a7afa06b521ccd52455eced9277 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 28 Jan 2022 21:45:30 +0700 Subject: Use `MaybeSend` in `perform` and `map` for `Command` --- native/src/clipboard.rs | 7 ++++++- native/src/command.rs | 8 +++++--- native/src/command/action.rs | 7 ++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index 60703c31..c9105bc0 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -1,4 +1,6 @@ //! Access the clipboard. +use iced_futures::MaybeSend; + use std::fmt; /// A buffer for short-term storage and transfer within and between @@ -36,7 +38,10 @@ pub enum Action<T> { impl<T> Action<T> { /// Maps the output of a clipboard [`Action`] using the provided closure. - pub fn map<A>(self, f: impl Fn(T) -> A + 'static + Send + Sync) -> Action<A> + pub fn map<A>( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action<A> where T: 'static, { diff --git a/native/src/command.rs b/native/src/command.rs index 6fe518d7..89d0f045 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -3,6 +3,8 @@ mod action; pub use action::Action; +use iced_futures::MaybeSend; + use std::fmt; use std::future::Future; @@ -24,8 +26,8 @@ impl<T> Command<T> { /// Creates a [`Command`] that performs the action of the given future. pub fn perform<A>( - future: impl Future<Output = T> + 'static + Send, - f: impl Fn(T) -> A + 'static + Send, + future: impl Future<Output = T> + 'static + MaybeSend, + f: impl Fn(T) -> A + 'static + MaybeSend, ) -> Command<A> { use iced_futures::futures::FutureExt; @@ -45,7 +47,7 @@ impl<T> Command<T> { /// Applies a transformation to the result of a [`Command`]. pub fn map<A>( self, - f: impl Fn(T) -> A + 'static + Send + Sync + Clone, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync + Clone, ) -> Command<A> where T: 'static, diff --git a/native/src/command/action.rs b/native/src/command/action.rs index 77be1b59..5c7509c8 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,6 +1,8 @@ use crate::clipboard; use crate::window; +use iced_futures::MaybeSend; + use std::fmt; /// An action that a [`Command`] can perform. @@ -19,7 +21,10 @@ pub enum Action<T> { impl<T> Action<T> { /// Applies a transformation to the result of a [`Command`]. - pub fn map<A>(self, f: impl Fn(T) -> A + 'static + Send + Sync) -> Action<A> + pub fn map<A>( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action<A> where T: 'static, { -- cgit From 2f57051283107e83a392486e8d9075f91f1b3332 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sat, 29 Jan 2022 16:08:16 +0700 Subject: Add `index.html` for `todos` and `tour` example You can use `trunk serve` to easily compile them! --- .gitignore | 1 + examples/todos/index.html | 12 ++++++++++++ examples/tour/index.html | 12 ++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 examples/todos/index.html create mode 100644 examples/tour/index.html diff --git a/.gitignore b/.gitignore index 3872ed2f..56faba09 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ pkg/ **/*.rs.bk Cargo.lock .cargo/ +dist/ diff --git a/examples/todos/index.html b/examples/todos/index.html new file mode 100644 index 00000000..ee5570fb --- /dev/null +++ b/examples/todos/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Todos - Iced + + + + + + diff --git a/examples/tour/index.html b/examples/tour/index.html new file mode 100644 index 00000000..c64af912 --- /dev/null +++ b/examples/tour/index.html @@ -0,0 +1,12 @@ + + + + + + Tour - Iced + + + + + + -- cgit From 6f604ab3995cb345aacf183a569589988aa3ad1f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 31 Jan 2022 16:39:46 +0700 Subject: Allow `Application::run` to return on native platforms --- winit/src/application.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index 7ddb9947..ed077507 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -193,7 +193,7 @@ where let mut context = task::Context::from_waker(task::noop_waker_ref()); - event_loop.run(move |event, _, control_flow| { + platform::run(event_loop, move |event, _, control_flow| { use winit::event_loop::ControlFlow; if let ControlFlow::Exit = control_flow { @@ -225,7 +225,7 @@ where task::Poll::Ready(_) => ControlFlow::Exit, }; } - }); + }) } async fn run_instance( @@ -574,3 +574,43 @@ pub fn run_command( } } } + +#[cfg(not(target_arch = "wasm32"))] +mod platform { + pub fn run( + mut event_loop: winit::event_loop::EventLoop, + event_handler: F, + ) -> Result<(), super::Error> + where + F: 'static + + FnMut( + winit::event::Event<'_, T>, + &winit::event_loop::EventLoopWindowTarget, + &mut winit::event_loop::ControlFlow, + ), + { + use winit::platform::run_return::EventLoopExtRunReturn; + + let _ = event_loop.run_return(event_handler); + + Ok(()) + } +} + +#[cfg(target_arch = "wasm32")] +mod platform { + pub fn run( + event_loop: winit::event_loop::EventLoop, + event_handler: F, + ) -> ! + where + F: 'static + + FnMut( + winit::event::Event<'_, T>, + &winit::event_loop::EventLoopWindowTarget, + &mut winit::event_loop::ControlFlow, + ), + { + event_loop.run(event_handler) + } +} -- cgit