From 5100b5d0a1f654ec1254b7765ceadfb9091d6939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 24 Feb 2023 23:24:48 +0100 Subject: [PATCH 01/57] Introduce `iced_renderer` subcrate featuring runtime renderer fallback --- Cargo.toml | 14 +-- graphics/src/lib.rs | 2 +- graphics/src/renderer.rs | 7 +- graphics/src/window/gl_compositor.rs | 2 +- renderer/Cargo.toml | 26 +++++ renderer/src/backend.rs | 94 ++++++++++++++++++ renderer/src/lib.rs | 17 ++++ renderer/src/settings.rs | 30 ++++++ renderer/src/window.rs | 3 + renderer/src/window/compositor.rs | 96 ++++++++++++++++++ src/application.rs | 4 +- src/lib.rs | 2 +- wgpu/src/window.rs | 3 +- wgpu/src/window/compositor.rs | 139 +++++++++++++++++---------- winit/src/application.rs | 2 +- winit/src/system.rs | 2 +- 16 files changed, 371 insertions(+), 72 deletions(-) create mode 100644 renderer/Cargo.toml create mode 100644 renderer/src/backend.rs create mode 100644 renderer/src/lib.rs create mode 100644 renderer/src/settings.rs create mode 100644 renderer/src/window.rs create mode 100644 renderer/src/window/compositor.rs diff --git a/Cargo.toml b/Cargo.toml index 551e12ac9c..d999febe9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,9 @@ categories = ["gui"] [features] # Enables the `Image` widget -image = ["iced_wgpu/image", "image_rs"] +image = ["iced_renderer/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu/svg"] +svg = ["iced_renderer/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget @@ -35,7 +35,7 @@ system = ["iced_winit/system"] # Enables chrome traces chrome-trace = [ "iced_winit/chrome-trace", - "iced_wgpu/tracing", + "iced_renderer/tracing", ] [badges] @@ -48,6 +48,7 @@ members = [ "graphics", "lazy", "native", + "renderer", "style", "wgpu", "winit", @@ -59,6 +60,7 @@ iced_core = { version = "0.8", path = "core" } iced_futures = { version = "0.6", path = "futures" } iced_native = { version = "0.9", path = "native" } iced_graphics = { version = "0.7", path = "graphics" } +iced_renderer = { version = "0.1", path = "renderer" } iced_winit = { version = "0.8", path = "winit", features = ["application"] } thiserror = "1.0" @@ -67,12 +69,6 @@ version = "0.24" package = "image" optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.9", path = "wgpu" } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"] } - [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas", "qr_code"] diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 41bef2c368..bbbdfa0e14 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -46,9 +46,9 @@ pub use primitive::Primitive; pub use renderer::Renderer; pub use transformation::Transformation; pub use viewport::Viewport; -pub use window::compositor; pub use iced_native::alignment; +pub use iced_native::text; pub use iced_native::{ Alignment, Background, Color, Font, Point, Rectangle, Size, Vector, }; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index b052c09421..859ebc0418 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -43,8 +43,11 @@ impl Renderer { /// Runs the given closure with the [`Backend`] and the recorded primitives /// of the [`Renderer`]. - pub fn with_primitives(&mut self, f: impl FnOnce(&mut B, &[Primitive])) { - f(&mut self.backend, &self.primitives); + pub fn with_primitives( + &mut self, + f: impl FnOnce(&mut B, &[Primitive]) -> O, + ) -> O { + f(&mut self.backend, &self.primitives) } } diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs index a45a7ca114..3e6dfd9edc 100644 --- a/graphics/src/window/gl_compositor.rs +++ b/graphics/src/window/gl_compositor.rs @@ -1,6 +1,6 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::compositor::Information; +use crate::window::compositor::Information; use crate::{Color, Error, Size, Viewport}; use core::ffi::c_void; diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml new file mode 100644 index 0000000000..2a179f3a47 --- /dev/null +++ b/renderer/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "iced_renderer" +version = "0.1.0" +edition = "2021" + +[features] +image = ["iced_wgpu/image"] +svg = ["iced_wgpu/svg"] +tracing = ["iced_wgpu/tracing"] + +[dependencies] +raw-window-handle = "0.5" + +[dependencies.iced_native] +version = "0.9" +path = "../native" + +[dependencies.iced_graphics] +version = "0.7" +path = "../graphics" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +iced_wgpu = { version = "0.9", path = "../wgpu" } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs new file mode 100644 index 0000000000..a9a09593f5 --- /dev/null +++ b/renderer/src/backend.rs @@ -0,0 +1,94 @@ +use crate::{Font, Point, Size}; + +use iced_graphics::backend; +use iced_graphics::text; + +use std::borrow::Cow; + +pub enum Backend { + Wgpu(iced_wgpu::Backend), +} + +impl iced_graphics::Backend for Backend {} + +impl backend::Text for Backend { + const ICON_FONT: Font = Font::Name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + match self { + Self::Wgpu(backend) => backend.default_font(), + } + } + + fn default_size(&self) -> f32 { + match self { + Self::Wgpu(backend) => backend.default_size(), + } + } + + fn measure( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + match self { + Self::Wgpu(backend) => { + backend.measure(contents, size, font, bounds) + } + } + } + + fn hit_test( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + position: Point, + nearest_only: bool, + ) -> Option { + match self { + Self::Wgpu(backend) => backend.hit_test( + contents, + size, + font, + bounds, + position, + nearest_only, + ), + } + } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + match self { + Self::Wgpu(backend) => { + backend.load_font(font); + } + } + } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { + match self { + Self::Wgpu(backend) => backend.dimensions(handle), + } + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + handle: &iced_native::svg::Handle, + ) -> Size { + match self { + Self::Wgpu(backend) => backend.viewport_dimensions(handle), + } + } +} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs new file mode 100644 index 0000000000..d0ba67930b --- /dev/null +++ b/renderer/src/lib.rs @@ -0,0 +1,17 @@ +pub mod window; + +mod backend; +mod settings; + +pub use backend::Backend; +pub use settings::Settings; + +pub use iced_graphics::{ + Antialiasing, Color, Error, Font, Point, Size, Viewport, +}; + +/// The default graphics renderer for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer = + iced_graphics::Renderer; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs new file mode 100644 index 0000000000..c4dc248b10 --- /dev/null +++ b/renderer/src/settings.rs @@ -0,0 +1,30 @@ +use crate::{Antialiasing, Font}; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { + /// The default [`Font`] to use. + pub default_font: Font, + + /// The default size of text. + /// + /// By default, it will be set to `16.0`. + pub default_text_size: f32, + + /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. + pub antialiasing: Option, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::SansSerif, + default_text_size: 16.0, + antialiasing: None, + } + } +} diff --git a/renderer/src/window.rs b/renderer/src/window.rs new file mode 100644 index 0000000000..a7c8911b15 --- /dev/null +++ b/renderer/src/window.rs @@ -0,0 +1,3 @@ +mod compositor; + +pub use compositor::Compositor; diff --git a/renderer/src/window/compositor.rs b/renderer/src/window/compositor.rs new file mode 100644 index 0000000000..ad78d8bf25 --- /dev/null +++ b/renderer/src/window/compositor.rs @@ -0,0 +1,96 @@ +use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + +pub use iced_graphics::window::compositor::{Information, SurfaceError}; + +pub enum Compositor { + Wgpu(iced_wgpu::window::Compositor), +} + +pub enum Surface { + Wgpu(iced_wgpu::window::Surface), +} + +impl iced_graphics::window::Compositor for Compositor { + type Settings = Settings; + type Renderer = Renderer; + type Surface = Surface; + + fn new( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let (compositor, backend) = iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + compatible_window, + )?; + + Ok(( + Self::Wgpu(compositor), + Renderer::new(Backend::Wgpu(backend)), + )) + } + + fn create_surface( + &mut self, + window: &W, + ) -> Surface { + match self { + Self::Wgpu(compositor) => { + Surface::Wgpu(compositor.create_surface(window)) + } + } + } + + fn configure_surface( + &mut self, + surface: &mut Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { + compositor.configure_surface(surface, width, height); + } + } + } + + fn fetch_information(&self) -> Information { + match self { + Self::Wgpu(compositor) => compositor.fetch_information(), + } + } + + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + renderer.with_primitives(|backend, primitives| { + match (self, backend, surface) { + ( + Self::Wgpu(compositor), + Backend::Wgpu(backend), + Surface::Wgpu(surface), + ) => iced_wgpu::window::compositor::present( + compositor, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + } + }) + } +} diff --git a/src/application.rs b/src/application.rs index 9a1c185597..b9871556e6 100644 --- a/src/application.rs +++ b/src/application.rs @@ -198,11 +198,11 @@ pub trait Application: Sized { default_font: settings.default_font, default_text_size: settings.default_text_size, antialiasing: if settings.antialiasing { - Some(crate::renderer::settings::Antialiasing::MSAAx4) + Some(crate::renderer::Antialiasing::MSAAx4) } else { None }, - ..crate::renderer::Settings::from_env() + ..crate::renderer::Settings::default() }; Ok(crate::runtime::application::run::< diff --git a/src/lib.rs b/src/lib.rs index 31ec4f4892..bb162f2d6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,7 +182,7 @@ pub mod touch; pub mod widget; pub mod window; -use iced_wgpu as renderer; +use iced_renderer as renderer; use iced_winit as runtime; pub use iced_native::theme; diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index aac5fb9ed8..9545a14e5b 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,4 +1,5 @@ //! Display rendering results on windows. -mod compositor; +pub mod compositor; pub use compositor::Compositor; +pub use wgpu::Surface; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 365cb60377..7406bfb8cc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,8 +1,9 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +//! Connect a window with a renderer. +use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; use futures::stream::{self, StreamExt}; -use iced_graphics::compositor; +use iced_graphics::window::compositor; use iced_native::futures; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; @@ -112,6 +113,77 @@ impl Compositor { } } +/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and +/// window. +pub fn new( + settings: Settings, + compatible_window: Option<&W>, +) -> Result<(Compositor, Backend), Error> { + let compositor = futures::executor::block_on(Compositor::request( + settings, + compatible_window, + )) + .ok_or(Error::GraphicsAdapterNotFound)?; + + let backend = compositor.create_backend(); + + Ok((compositor, backend)) +} + +/// Presents the given primitives with the given [`Compositor`] and [`Backend`]. +pub fn present>( + compositor: &mut Compositor, + backend: &mut Backend, + surface: &mut wgpu::Surface, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Result<(), compositor::SurfaceError> { + match surface.get_current_texture() { + Ok(frame) => { + let mut encoder = compositor.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("iced_wgpu encoder"), + }, + ); + + let view = &frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + backend.present( + &compositor.device, + &compositor.queue, + &mut encoder, + Some(background_color), + view, + primitives, + viewport, + overlay, + ); + + // Submit work + let _submission = compositor.queue.submit(Some(encoder.finish())); + frame.present(); + + Ok(()) + } + Err(error) => match error { + wgpu::SurfaceError::Timeout => { + Err(compositor::SurfaceError::Timeout) + } + wgpu::SurfaceError::Outdated => { + Err(compositor::SurfaceError::Outdated) + } + wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), + wgpu::SurfaceError::OutOfMemory => { + Err(compositor::SurfaceError::OutOfMemory) + } + }, + } +} + impl iced_graphics::window::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; @@ -121,13 +193,7 @@ impl iced_graphics::window::Compositor for Compositor { settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let compositor = futures::executor::block_on(Self::request( - settings, - compatible_window, - )) - .ok_or(Error::GraphicsAdapterNotFound)?; - - let backend = compositor.create_backend(); + let (compositor, backend) = new(settings, compatible_window)?; Ok((compositor, Renderer::new(backend))) } @@ -178,49 +244,16 @@ impl iced_graphics::window::Compositor for Compositor { background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { - match surface.get_current_texture() { - Ok(frame) => { - let mut encoder = self.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("iced_wgpu encoder"), - }, - ); - - let view = &frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - renderer.with_primitives(|backend, primitives| { - backend.present( - &self.device, - &self.queue, - &mut encoder, - Some(background_color), - view, - primitives, - viewport, - overlay, - ); - }); - - // Submit work - let _submission = self.queue.submit(Some(encoder.finish())); - frame.present(); - - Ok(()) - } - Err(error) => match error { - wgpu::SurfaceError::Timeout => { - Err(compositor::SurfaceError::Timeout) - } - wgpu::SurfaceError::Outdated => { - Err(compositor::SurfaceError::Outdated) - } - wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), - wgpu::SurfaceError::OutOfMemory => { - Err(compositor::SurfaceError::OutOfMemory) - } - }, - } + renderer.with_primitives(|backend, primitives| { + present( + self, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ) + }) } } diff --git a/winit/src/application.rs b/winit/src/application.rs index 889becad7b..1bfce3a10c 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -17,8 +17,8 @@ use crate::{ use iced_futures::futures; use iced_futures::futures::channel::mpsc; -use iced_graphics::compositor; use iced_graphics::window; +use iced_graphics::window::compositor; use iced_native::program::Program; use iced_native::time::Instant; use iced_native::user_interface::{self, UserInterface}; diff --git a/winit/src/system.rs b/winit/src/system.rs index 619086b80a..8a7b2a116d 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -2,7 +2,7 @@ use crate::command::{self, Command}; pub use iced_native::system::*; -use iced_graphics::compositor; +use iced_graphics::window::compositor; /// Query for available system information. pub fn fetch_information( From 1475f5fa58273e45e67ebd94642ae9e1251fe5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 25 Feb 2023 15:04:40 +0100 Subject: [PATCH 02/57] Update `resvg` in `iced_graphics` --- graphics/Cargo.toml | 12 ++---------- graphics/src/image/vector.rs | 17 +++++++---------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 13ab61d80b..a37c99a20e 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -svg = ["resvg", "usvg", "tiny-skia"] +svg = ["resvg"] image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] png = ["image_rs/png"] jpeg = ["image_rs/jpeg"] @@ -71,15 +71,7 @@ default-features = false optional = true [dependencies.resvg] -version = "0.18" -optional = true - -[dependencies.usvg] -version = "0.18" -optional = true - -[dependencies.tiny-skia] -version = "0.6" +version = "0.29" optional = true [dependencies.kamadak-exif] diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index 82d77aff64..c950ccd63f 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -5,6 +5,8 @@ use crate::Color; use iced_native::svg; use iced_native::Size; +use resvg::tiny_skia; +use resvg::usvg; use std::collections::{HashMap, HashSet}; use std::fs; @@ -21,7 +23,7 @@ impl Svg { pub fn viewport_dimensions(&self) -> Size { match self { Svg::Loaded(tree) => { - let size = tree.svg_node().size; + let size = tree.size; Size::new(size.width() as u32, size.height() as u32) } @@ -51,20 +53,14 @@ impl Cache { let svg = match handle.data() { svg::Data::Path(path) => { let tree = fs::read_to_string(path).ok().and_then(|contents| { - usvg::Tree::from_str( - &contents, - &usvg::Options::default().to_ref(), - ) - .ok() + usvg::Tree::from_str(&contents, &usvg::Options::default()) + .ok() }); tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) } svg::Data::Bytes(bytes) => { - match usvg::Tree::from_data( - bytes, - &usvg::Options::default().to_ref(), - ) { + match usvg::Tree::from_data(bytes, &usvg::Options::default()) { Ok(tree) => Svg::Loaded(tree), Err(_) => Svg::NotFound, } @@ -125,6 +121,7 @@ impl Cache { } else { usvg::FitTo::Height(height) }, + tiny_skia::Transform::default(), img.as_mut(), )?; From a01bc865a0561ff1daf255c4746746acf67524f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 25 Feb 2023 15:11:35 +0100 Subject: [PATCH 03/57] Trim measurements in `renderer::Backend` --- renderer/src/backend.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index a9a09593f5..7b09eea8dd 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -9,7 +9,13 @@ pub enum Backend { Wgpu(iced_wgpu::Backend), } -impl iced_graphics::Backend for Backend {} +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + match self { + Self::Wgpu(backend) => backend.trim_measurements(), + } + } +} impl backend::Text for Backend { const ICON_FONT: Font = Font::Name("Iced-Icons"); From 8c373cd497e370d356b480380482779397bdb510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 25 Feb 2023 15:38:25 +0100 Subject: [PATCH 04/57] Scaffold `iced_tiny_skia` and connect it to `iced_renderer` --- Cargo.toml | 1 + renderer/Cargo.toml | 8 ++- renderer/src/backend.rs | 20 +++++++ renderer/src/window/compositor.rs | 54 +++++++++++++---- tiny_skia/Cargo.toml | 21 +++++++ tiny_skia/src/backend.rs | 87 +++++++++++++++++++++++++++ tiny_skia/src/lib.rs | 16 +++++ tiny_skia/src/settings.rs | 24 ++++++++ tiny_skia/src/window.rs | 3 + tiny_skia/src/window/compositor.rs | 96 ++++++++++++++++++++++++++++++ 10 files changed, 318 insertions(+), 12 deletions(-) create mode 100644 tiny_skia/Cargo.toml create mode 100644 tiny_skia/src/backend.rs create mode 100644 tiny_skia/src/lib.rs create mode 100644 tiny_skia/src/settings.rs create mode 100644 tiny_skia/src/window.rs create mode 100644 tiny_skia/src/window/compositor.rs diff --git a/Cargo.toml b/Cargo.toml index d999febe9d..b1f5cf66bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "native", "renderer", "style", + "tiny_skia", "wgpu", "winit", "examples/*", diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 2a179f3a47..5ba5d426fe 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [features] -image = ["iced_wgpu/image"] -svg = ["iced_wgpu/svg"] +image = ["iced_wgpu/image", "iced_tiny_skia/image"] +svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] tracing = ["iced_wgpu/tracing"] [dependencies] @@ -19,6 +19,10 @@ path = "../native" version = "0.7" path = "../graphics" +[dependencies.iced_tiny_skia] +version = "0.1" +path = "../tiny_skia" + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] iced_wgpu = { version = "0.9", path = "../wgpu" } diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index 7b09eea8dd..a46d6f9b8f 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -7,12 +7,14 @@ use std::borrow::Cow; pub enum Backend { Wgpu(iced_wgpu::Backend), + TinySkia(iced_tiny_skia::Backend), } impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { match self { Self::Wgpu(backend) => backend.trim_measurements(), + Self::TinySkia(backend) => backend.trim_measurements(), } } } @@ -25,12 +27,14 @@ impl backend::Text for Backend { fn default_font(&self) -> Font { match self { Self::Wgpu(backend) => backend.default_font(), + Self::TinySkia(backend) => backend.default_font(), } } fn default_size(&self) -> f32 { match self { Self::Wgpu(backend) => backend.default_size(), + Self::TinySkia(backend) => backend.default_size(), } } @@ -45,6 +49,9 @@ impl backend::Text for Backend { Self::Wgpu(backend) => { backend.measure(contents, size, font, bounds) } + Self::TinySkia(backend) => { + backend.measure(contents, size, font, bounds) + } } } @@ -66,6 +73,14 @@ impl backend::Text for Backend { position, nearest_only, ), + Self::TinySkia(backend) => backend.hit_test( + contents, + size, + font, + bounds, + position, + nearest_only, + ), } } @@ -74,6 +89,9 @@ impl backend::Text for Backend { Self::Wgpu(backend) => { backend.load_font(font); } + Self::TinySkia(backend) => { + backend.load_font(font); + } } } } @@ -83,6 +101,7 @@ impl backend::Image for Backend { fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { match self { Self::Wgpu(backend) => backend.dimensions(handle), + Self::TinySkia(backend) => backend.dimensions(handle), } } } @@ -95,6 +114,7 @@ impl backend::Svg for Backend { ) -> Size { match self { Self::Wgpu(backend) => backend.viewport_dimensions(handle), + Self::TinySkia(backend) => backend.viewport_dimensions(handle), } } } diff --git a/renderer/src/window/compositor.rs b/renderer/src/window/compositor.rs index ad78d8bf25..42afddc4e9 100644 --- a/renderer/src/window/compositor.rs +++ b/renderer/src/window/compositor.rs @@ -6,10 +6,12 @@ pub use iced_graphics::window::compositor::{Information, SurfaceError}; pub enum Compositor { Wgpu(iced_wgpu::window::Compositor), + TinySkia(iced_tiny_skia::window::Compositor), } pub enum Surface { Wgpu(iced_wgpu::window::Surface), + TinySkia(iced_tiny_skia::window::Surface), } impl iced_graphics::window::Compositor for Compositor { @@ -19,21 +21,31 @@ impl iced_graphics::window::Compositor for Compositor { fn new( settings: Self::Settings, - compatible_window: Option<&W>, + _compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let (compositor, backend) = iced_wgpu::window::compositor::new( - iced_wgpu::Settings { + //let (compositor, backend) = iced_wgpu::window::compositor::new( + // iced_wgpu::Settings { + // default_font: settings.default_font, + // default_text_size: settings.default_text_size, + // antialiasing: settings.antialiasing, + // ..iced_wgpu::Settings::from_env() + // }, + // compatible_window, + //)?; + + //Ok(( + // Self::Wgpu(compositor), + // Renderer::new(Backend::Wgpu(backend)), + //)) + let (compositor, backend) = + iced_tiny_skia::window::compositor::new(iced_tiny_skia::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - ..iced_wgpu::Settings::from_env() - }, - compatible_window, - )?; + }); Ok(( - Self::Wgpu(compositor), - Renderer::new(Backend::Wgpu(backend)), + Self::TinySkia(compositor), + Renderer::new(Backend::TinySkia(backend)), )) } @@ -45,6 +57,9 @@ impl iced_graphics::window::Compositor for Compositor { Self::Wgpu(compositor) => { Surface::Wgpu(compositor.create_surface(window)) } + Self::TinySkia(compositor) => { + Surface::TinySkia(compositor.create_surface(window)) + } } } @@ -58,12 +73,17 @@ impl iced_graphics::window::Compositor for Compositor { (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { compositor.configure_surface(surface, width, height); } + (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { + compositor.configure_surface(surface, width, height); + } + _ => unreachable!(), } } fn fetch_information(&self) -> Information { match self { Self::Wgpu(compositor) => compositor.fetch_information(), + Self::TinySkia(compositor) => compositor.fetch_information(), } } @@ -90,6 +110,20 @@ impl iced_graphics::window::Compositor for Compositor { background_color, overlay, ), + ( + Self::TinySkia(compositor), + Backend::TinySkia(backend), + Surface::TinySkia(surface), + ) => iced_tiny_skia::window::compositor::present( + compositor, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + _ => unreachable!(), } }) } diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml new file mode 100644 index 0000000000..7f4b0f8c48 --- /dev/null +++ b/tiny_skia/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "iced_tiny_skia" +version = "0.1.0" +edition = "2021" + +[features] +image = [] +svg = [] + +[dependencies] +raw-window-handle = "0.5" +softbuffer = "0.2" +tiny-skia = "0.8" + +[dependencies.iced_native] +version = "0.9" +path = "../native" + +[dependencies.iced_graphics] +version = "0.7" +path = "../graphics" diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs new file mode 100644 index 0000000000..4282a745c3 --- /dev/null +++ b/tiny_skia/src/backend.rs @@ -0,0 +1,87 @@ +use crate::{Font, Settings, Size}; + +use iced_graphics::backend; +use iced_graphics::text; + +use std::borrow::Cow; + +pub struct Backend { + default_font: Font, + default_text_size: f32, +} + +impl Backend { + pub fn new(settings: Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + } + } +} + +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + // TODO + } +} + +impl backend::Text for Backend { + const ICON_FONT: Font = Font::Name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + self.default_font + } + + fn default_size(&self) -> f32 { + self.default_text_size + } + + fn measure( + &self, + _contents: &str, + _size: f32, + _font: Font, + _bounds: Size, + ) -> (f32, f32) { + // TODO + (0.0, 0.0) + } + + fn hit_test( + &self, + _contents: &str, + _size: f32, + _font: Font, + _bounds: Size, + _point: iced_native::Point, + _nearest_only: bool, + ) -> Option { + // TODO + None + } + + fn load_font(&mut self, _font: Cow<'static, [u8]>) { + // TODO + } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, _handle: &iced_native::image::Handle) -> Size { + // TODO + Size::new(0, 0) + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + _handle: &iced_native::svg::Handle, + ) -> Size { + // TODO + Size::new(0, 0) + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs new file mode 100644 index 0000000000..fce44e9e19 --- /dev/null +++ b/tiny_skia/src/lib.rs @@ -0,0 +1,16 @@ +pub mod window; + +mod backend; +mod settings; + +pub use backend::Backend; +pub use settings::Settings; + +pub use iced_graphics::{Color, Error, Font, Point, Size, Vector, Viewport}; + +/// A [`tiny-skia`] graphics renderer for [`iced`]. +/// +/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer = + iced_graphics::Renderer; diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs new file mode 100644 index 0000000000..88098345f2 --- /dev/null +++ b/tiny_skia/src/settings.rs @@ -0,0 +1,24 @@ +use crate::Font; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { + /// The default [`Font`] to use. + pub default_font: Font, + + /// The default size of text. + /// + /// By default, it will be set to `16.0`. + pub default_text_size: f32, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::SansSerif, + default_text_size: 16.0, + } + } +} diff --git a/tiny_skia/src/window.rs b/tiny_skia/src/window.rs new file mode 100644 index 0000000000..d8d9378e16 --- /dev/null +++ b/tiny_skia/src/window.rs @@ -0,0 +1,3 @@ +pub mod compositor; + +pub use compositor::{Compositor, Surface}; diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs new file mode 100644 index 0000000000..053bb29b28 --- /dev/null +++ b/tiny_skia/src/window/compositor.rs @@ -0,0 +1,96 @@ +use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; + +use iced_graphics::window::compositor::{self, Information, SurfaceError}; +use iced_graphics::Primitive; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::marker::PhantomData; + +pub struct Compositor { + _theme: PhantomData, +} + +pub struct Surface; + +impl iced_graphics::window::Compositor for Compositor { + type Settings = Settings; + type Renderer = Renderer; + type Surface = Surface; + + fn new( + settings: Self::Settings, + _compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let (compositor, backend) = new(settings); + + Ok((compositor, Renderer::new(backend))) + } + + fn create_surface( + &mut self, + _window: &W, + ) -> Surface { + // TODO + Surface + } + + fn configure_surface( + &mut self, + _surface: &mut Surface, + _width: u32, + _height: u32, + ) { + // TODO + } + + fn fetch_information(&self) -> Information { + Information { + adapter: String::from("CPU"), + backend: String::from("tiny-skia"), + } + } + + fn present>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), SurfaceError> { + renderer.with_primitives(|backend, primitives| { + present( + self, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ) + }) + } +} + +pub fn new(settings: Settings) -> (Compositor, Backend) { + // TODO + ( + Compositor { + _theme: PhantomData, + }, + Backend::new(settings), + ) +} + +pub fn present>( + _compositor: &mut Compositor, + _backend: &mut Backend, + _surface: &mut Surface, + _primitives: &[Primitive], + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], +) -> Result<(), compositor::SurfaceError> { + // TODO + Ok(()) +} From 535d7a4d57e131e661587b36e41820dd6ccccc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 25 Feb 2023 16:05:42 +0100 Subject: [PATCH 05/57] Implement basic presentation with `softbuffer` for `iced_tiny_skia` --- graphics/src/window/compositor.rs | 2 + renderer/src/window/compositor.rs | 10 +++-- tiny_skia/src/backend.rs | 19 ++++++++- tiny_skia/src/window/compositor.rs | 66 +++++++++++++++++++++++------- wgpu/src/window/compositor.rs | 10 +++-- winit/src/application.rs | 13 +++--- 6 files changed, 90 insertions(+), 30 deletions(-) diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index db4ba45dc3..15f8dab55f 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -28,6 +28,8 @@ pub trait Compositor: Sized { fn create_surface( &mut self, window: &W, + width: u32, + height: u32, ) -> Self::Surface; /// Configures a new [`Surface`] with the given dimensions. diff --git a/renderer/src/window/compositor.rs b/renderer/src/window/compositor.rs index 42afddc4e9..a11374eda6 100644 --- a/renderer/src/window/compositor.rs +++ b/renderer/src/window/compositor.rs @@ -52,14 +52,16 @@ impl iced_graphics::window::Compositor for Compositor { fn create_surface( &mut self, window: &W, + width: u32, + height: u32, ) -> Surface { match self { Self::Wgpu(compositor) => { - Surface::Wgpu(compositor.create_surface(window)) - } - Self::TinySkia(compositor) => { - Surface::TinySkia(compositor.create_surface(window)) + Surface::Wgpu(compositor.create_surface(window, width, height)) } + Self::TinySkia(compositor) => Surface::TinySkia( + compositor.create_surface(window, width, height), + ), } } diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 4282a745c3..62373ec706 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,7 +1,8 @@ -use crate::{Font, Settings, Size}; +use crate::{Color, Font, Settings, Size, Viewport}; use iced_graphics::backend; use iced_graphics::text; +use iced_graphics::Primitive; use std::borrow::Cow; @@ -17,6 +18,22 @@ impl Backend { default_text_size: settings.default_text_size, } } + + pub fn draw>( + &mut self, + pixels: &mut tiny_skia::Pixmap, + _primitives: &[Primitive], + _viewport: &Viewport, + background_color: Color, + _overlay: &[T], + ) { + pixels.fill(into_color(background_color)); + } +} + +fn into_color(color: Color) -> tiny_skia::Color { + tiny_skia::Color::from_rgba(color.r, color.g, color.b, color.a) + .expect("Convert color from iced to tiny_skia") } impl iced_graphics::Backend for Backend { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 053bb29b28..164d99c6cf 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -10,7 +10,11 @@ pub struct Compositor { _theme: PhantomData, } -pub struct Surface; +pub struct Surface { + window: softbuffer::GraphicsContext, + pixels: tiny_skia::Pixmap, + buffer: Vec, +} impl iced_graphics::window::Compositor for Compositor { type Settings = Settings; @@ -28,19 +32,33 @@ impl iced_graphics::window::Compositor for Compositor { fn create_surface( &mut self, - _window: &W, + window: &W, + width: u32, + height: u32, ) -> Surface { - // TODO - Surface + let window = + unsafe { softbuffer::GraphicsContext::new(window, window) } + .expect("Create softbuffer for window"); + + let pixels = tiny_skia::Pixmap::new(width, height) + .expect("Create pixmap for window"); + + Surface { + window, + pixels, + buffer: vec![0; (width * height) as usize], + } } fn configure_surface( &mut self, - _surface: &mut Surface, - _width: u32, - _height: u32, + surface: &mut Surface, + width: u32, + height: u32, ) { - // TODO + surface.pixels = tiny_skia::Pixmap::new(width, height) + .expect("Create pixmap for window"); + surface.buffer = vec![0; (width * height) as usize]; } fn fetch_information(&self) -> Information { @@ -84,13 +102,33 @@ pub fn new(settings: Settings) -> (Compositor, Backend) { pub fn present>( _compositor: &mut Compositor, - _backend: &mut Backend, - _surface: &mut Surface, - _primitives: &[Primitive], - _viewport: &Viewport, - _background_color: Color, - _overlay: &[T], + backend: &mut Backend, + surface: &mut Surface, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], ) -> Result<(), compositor::SurfaceError> { + backend.draw( + &mut surface.pixels, + primitives, + viewport, + background_color, + overlay, + ); + + for (i, pixel) in surface.pixels.pixels_mut().iter().enumerate() { + surface.buffer[i] = u32::from(pixel.red()) << 16 + | u32::from(pixel.green()) << 8 + | u32::from(pixel.blue()); + } + + surface.window.set_buffer( + &surface.buffer, + surface.pixels.width() as u16, + surface.pixels.height() as u16, + ); + // TODO Ok(()) } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 7406bfb8cc..3a4a71233a 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -201,11 +201,15 @@ impl iced_graphics::window::Compositor for Compositor { fn create_surface( &mut self, window: &W, + width: u32, + height: u32, ) -> wgpu::Surface { #[allow(unsafe_code)] - unsafe { - self.instance.create_surface(window) - } + let mut surface = unsafe { self.instance.create_surface(window) }; + + self.configure_surface(&mut surface, width, height); + + surface } fn configure_surface( diff --git a/winit/src/application.rs b/winit/src/application.rs index 1bfce3a10c..b52f01974a 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -285,21 +285,18 @@ async fn run_instance( use winit::event; use winit::event_loop::ControlFlow; - let mut clipboard = Clipboard::connect(&window); - let mut cache = user_interface::Cache::default(); - let mut surface = compositor.create_surface(&window); - let mut should_exit = false; - let mut state = State::new(&application, &window); let mut viewport_version = state.viewport_version(); - let physical_size = state.physical_size(); - compositor.configure_surface( - &mut surface, + let mut clipboard = Clipboard::connect(&window); + let mut cache = user_interface::Cache::default(); + let mut surface = compositor.create_surface( + &window, physical_size.width, physical_size.height, ); + let mut should_exit = false; if should_be_visible { window.set_visible(true); From 445b31c6c5f16ecc9f07bd072f246e827aa5b854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 25 Feb 2023 21:06:58 +0100 Subject: [PATCH 06/57] Resize `Surface::buffer` instead of reallocating in `iced_tiny_skia` --- tiny_skia/src/window/compositor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 164d99c6cf..8bb52a0338 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -58,7 +58,8 @@ impl iced_graphics::window::Compositor for Compositor { ) { surface.pixels = tiny_skia::Pixmap::new(width, height) .expect("Create pixmap for window"); - surface.buffer = vec![0; (width * height) as usize]; + + surface.buffer.resize((width * height) as usize, 0); } fn fetch_information(&self) -> Information { From 871b7e0311c73358213aae6326973300f0b56faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 25 Feb 2023 21:28:21 +0100 Subject: [PATCH 07/57] Fix `Padding::fit` on irregular values for an axis --- core/src/padding.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/padding.rs b/core/src/padding.rs index 752b2b8699..0b1bba13da 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -77,12 +77,14 @@ impl Padding { /// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`]. pub fn fit(self, inner: Size, outer: Size) -> Self { let available = (outer - inner).max(Size::ZERO); + let new_top = self.top.min(available.height); + let new_left = self.left.min(available.width); Padding { - top: self.top.min(available.height / 2.0), - right: self.right.min(available.width / 2.0), - bottom: self.bottom.min(available.height / 2.0), - left: self.left.min(available.width / 2.0), + top: new_top, + bottom: self.bottom.min(available.height - new_top), + left: new_left, + right: self.right.min(available.width - new_left), } } } From 5f93437285c4451a8b5ffac1b3c9e1b91e8a5918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 25 Feb 2023 21:43:53 +0100 Subject: [PATCH 08/57] Bump version of `iced_core` :tada: --- core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 43865e4d83..0d6310d3bd 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_core" -version = "0.8.0" +version = "0.8.1" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "The essential concepts of Iced" From df5d66423de141a009bbed993d99d491ed6373c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 26 Feb 2023 00:38:46 +0100 Subject: [PATCH 09/57] Draft support for `Quad` and `Clip` primitives in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 153 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 3 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 62373ec706..54752a215b 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -2,7 +2,7 @@ use crate::{Color, Font, Settings, Size, Viewport}; use iced_graphics::backend; use iced_graphics::text; -use iced_graphics::Primitive; +use iced_graphics::{Background, Primitive, Rectangle, Vector}; use std::borrow::Cow; @@ -22,12 +22,133 @@ impl Backend { pub fn draw>( &mut self, pixels: &mut tiny_skia::Pixmap, - _primitives: &[Primitive], - _viewport: &Viewport, + primitives: &[Primitive], + viewport: &Viewport, background_color: Color, _overlay: &[T], ) { pixels.fill(into_color(background_color)); + + let scale_factor = viewport.scale_factor() as f32; + + for primitive in primitives { + draw_primitive(primitive, pixels, None, scale_factor, Vector::ZERO); + } + } +} + +fn draw_primitive( + primitive: &Primitive, + pixels: &mut tiny_skia::Pixmap, + clip_mask: Option<&tiny_skia::ClipMask>, + scale_factor: f32, + translation: Vector, +) { + match primitive { + Primitive::None => {} + Primitive::Quad { + bounds, + background, + border_radius: _, // TODO + border_width, + border_color, + } => { + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor); + + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .expect("Create quad rectangle"), + ); + + pixels.fill_path( + &path, + &tiny_skia::Paint { + shader: match background { + Background::Color(color) => { + tiny_skia::Shader::SolidColor(into_color(*color)) + } + }, + anti_alias: true, + ..tiny_skia::Paint::default() + }, + tiny_skia::FillRule::EvenOdd, + transform, + clip_mask, + ); + + if *border_width > 0.0 { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + *border_color, + )), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: *border_width, + ..tiny_skia::Stroke::default() + }, + transform, + clip_mask, + ); + } + } + Primitive::Text { .. } => { + // TODO + } + Primitive::Image { .. } => { + // TODO + } + Primitive::Svg { .. } => { + // TODO + } + Primitive::Group { primitives } => { + for primitive in primitives { + draw_primitive( + primitive, + pixels, + clip_mask, + scale_factor, + translation, + ); + } + } + Primitive::Translate { + translation: offset, + content, + } => { + draw_primitive( + content, + pixels, + clip_mask, + scale_factor, + translation + *offset, + ); + } + Primitive::Clip { bounds, content } => { + draw_primitive( + content, + pixels, + Some(&rectangular_clip_mask(pixels, *bounds * scale_factor)), + scale_factor, + translation, + ); + } + Primitive::Cached { cache } => { + draw_primitive(cache, pixels, clip_mask, scale_factor, translation); + } + Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} } } @@ -36,6 +157,32 @@ fn into_color(color: Color) -> tiny_skia::Color { .expect("Convert color from iced to tiny_skia") } +fn rectangular_clip_mask( + pixels: &tiny_skia::Pixmap, + bounds: Rectangle, +) -> tiny_skia::ClipMask { + let mut clip_mask = tiny_skia::ClipMask::new(); + + let path = { + let mut builder = tiny_skia::PathBuilder::new(); + builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height); + + builder.finish().unwrap() + }; + + clip_mask + .set_path( + pixels.width(), + pixels.height(), + &path, + tiny_skia::FillRule::EvenOdd, + true, + ) + .expect("Set path of clipping area"); + + clip_mask +} + impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { // TODO From 744f3028f484c44899fed56d9190387569828a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 26 Feb 2023 00:49:27 +0100 Subject: [PATCH 10/57] Use `Surface::buffer` directly for drawing in `iced_tiny_skia` ... with a nice little color trick :^) --- tiny_skia/Cargo.toml | 1 + tiny_skia/src/backend.rs | 8 ++++---- tiny_skia/src/window/compositor.rs | 30 +++++++++++------------------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 7f4b0f8c48..70b1f5e7fc 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -11,6 +11,7 @@ svg = [] raw-window-handle = "0.5" softbuffer = "0.2" tiny-skia = "0.8" +bytemuck = "1" [dependencies.iced_native] version = "0.9" diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 54752a215b..9eea1a3290 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -21,7 +21,7 @@ impl Backend { pub fn draw>( &mut self, - pixels: &mut tiny_skia::Pixmap, + pixels: &mut tiny_skia::PixmapMut<'_>, primitives: &[Primitive], viewport: &Viewport, background_color: Color, @@ -39,7 +39,7 @@ impl Backend { fn draw_primitive( primitive: &Primitive, - pixels: &mut tiny_skia::Pixmap, + pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::ClipMask>, scale_factor: f32, translation: Vector, @@ -153,12 +153,12 @@ fn draw_primitive( } fn into_color(color: Color) -> tiny_skia::Color { - tiny_skia::Color::from_rgba(color.r, color.g, color.b, color.a) + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) .expect("Convert color from iced to tiny_skia") } fn rectangular_clip_mask( - pixels: &tiny_skia::Pixmap, + pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, ) -> tiny_skia::ClipMask { let mut clip_mask = tiny_skia::ClipMask::new(); diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 8bb52a0338..2bd5831e38 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -12,7 +12,6 @@ pub struct Compositor { pub struct Surface { window: softbuffer::GraphicsContext, - pixels: tiny_skia::Pixmap, buffer: Vec, } @@ -40,13 +39,9 @@ impl iced_graphics::window::Compositor for Compositor { unsafe { softbuffer::GraphicsContext::new(window, window) } .expect("Create softbuffer for window"); - let pixels = tiny_skia::Pixmap::new(width, height) - .expect("Create pixmap for window"); - Surface { window, - pixels, - buffer: vec![0; (width * height) as usize], + buffer: vec![0; width as usize * height as usize], } } @@ -56,9 +51,6 @@ impl iced_graphics::window::Compositor for Compositor { width: u32, height: u32, ) { - surface.pixels = tiny_skia::Pixmap::new(width, height) - .expect("Create pixmap for window"); - surface.buffer.resize((width * height) as usize, 0); } @@ -110,26 +102,26 @@ pub fn present>( background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { + let physical_size = viewport.physical_size(); + backend.draw( - &mut surface.pixels, + &mut tiny_skia::PixmapMut::from_bytes( + bytemuck::cast_slice_mut(&mut surface.buffer), + physical_size.width, + physical_size.height, + ) + .expect("Create pixel map"), primitives, viewport, background_color, overlay, ); - for (i, pixel) in surface.pixels.pixels_mut().iter().enumerate() { - surface.buffer[i] = u32::from(pixel.red()) << 16 - | u32::from(pixel.green()) << 8 - | u32::from(pixel.blue()); - } - surface.window.set_buffer( &surface.buffer, - surface.pixels.width() as u16, - surface.pixels.height() as u16, + physical_size.width as u16, + physical_size.height as u16, ); - // TODO Ok(()) } From 64fb722dfe8769d4a92edb0133f1863383ecfd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 26 Feb 2023 23:40:17 +0100 Subject: [PATCH 11/57] Draft text support in `iced_tiny_skia` --- tiny_skia/Cargo.toml | 15 ++ tiny_skia/src/backend.rs | 269 +++++++++++++++++------------- tiny_skia/src/lib.rs | 1 + tiny_skia/src/text.rs | 350 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 523 insertions(+), 112 deletions(-) create mode 100644 tiny_skia/src/text.rs diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 70b1f5e7fc..55f7f5b7d6 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -12,6 +12,8 @@ raw-window-handle = "0.5" softbuffer = "0.2" tiny-skia = "0.8" bytemuck = "1" +rustc-hash = "1.1" +ouroboros = "0.15" [dependencies.iced_native] version = "0.9" @@ -20,3 +22,16 @@ path = "../native" [dependencies.iced_graphics] version = "0.7" path = "../graphics" + +[dependencies.cosmic-text] +features = ["std", "swash"] +git = "https://github.com/hecrj/cosmic-text" +rev = "dc83efbf00a2efb4118403538e8a47bfd69c3e5e" + +[dependencies.twox-hash] +version = "1.6" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] +version = "1.6.1" +features = ["std"] diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 9eea1a3290..5e743479ee 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -9,6 +9,7 @@ use std::borrow::Cow; pub struct Backend { default_font: Font, default_text_size: f32, + text_pipeline: crate::text::Pipeline, } impl Backend { @@ -16,6 +17,7 @@ impl Backend { Self { default_font: settings.default_font, default_text_size: settings.default_text_size, + text_pipeline: crate::text::Pipeline::new(), } } @@ -32,123 +34,161 @@ impl Backend { let scale_factor = viewport.scale_factor() as f32; for primitive in primitives { - draw_primitive(primitive, pixels, None, scale_factor, Vector::ZERO); + self.draw_primitive( + primitive, + pixels, + None, + scale_factor, + Vector::ZERO, + ); } + + self.text_pipeline.end_frame(); } -} -fn draw_primitive( - primitive: &Primitive, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: Option<&tiny_skia::ClipMask>, - scale_factor: f32, - translation: Vector, -) { - match primitive { - Primitive::None => {} - Primitive::Quad { - bounds, - background, - border_radius: _, // TODO - border_width, - border_color, - } => { - let transform = tiny_skia::Transform::from_translate( - translation.x, - translation.y, - ) - .post_scale(scale_factor, scale_factor); - - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - bounds.x, - bounds.y, - bounds.width, - bounds.height, + fn draw_primitive( + &mut self, + primitive: &Primitive, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::ClipMask>, + scale_factor: f32, + translation: Vector, + ) { + match primitive { + Primitive::None => {} + Primitive::Quad { + bounds, + background, + border_radius: _, // TODO + border_width, + border_color, + } => { + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, ) - .expect("Create quad rectangle"), - ); + .post_scale(scale_factor, scale_factor); - pixels.fill_path( - &path, - &tiny_skia::Paint { - shader: match background { - Background::Color(color) => { - tiny_skia::Shader::SolidColor(into_color(*color)) - } - }, - anti_alias: true, - ..tiny_skia::Paint::default() - }, - tiny_skia::FillRule::EvenOdd, - transform, - clip_mask, - ); + let path = tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .expect("Create quad rectangle"), + ); - if *border_width > 0.0 { - pixels.stroke_path( + pixels.fill_path( &path, &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color( - *border_color, - )), + shader: match background { + Background::Color(color) => { + tiny_skia::Shader::SolidColor(into_color( + *color, + )) + } + }, anti_alias: true, ..tiny_skia::Paint::default() }, - &tiny_skia::Stroke { - width: *border_width, - ..tiny_skia::Stroke::default() - }, + tiny_skia::FillRule::EvenOdd, transform, clip_mask, ); + + if *border_width > 0.0 { + pixels.stroke_path( + &path, + &tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(into_color( + *border_color, + )), + anti_alias: true, + ..tiny_skia::Paint::default() + }, + &tiny_skia::Stroke { + width: *border_width, + ..tiny_skia::Stroke::default() + }, + transform, + clip_mask, + ); + } } - } - Primitive::Text { .. } => { - // TODO - } - Primitive::Image { .. } => { - // TODO - } - Primitive::Svg { .. } => { - // TODO - } - Primitive::Group { primitives } => { - for primitive in primitives { - draw_primitive( - primitive, + Primitive::Text { + content, + bounds, + color, + size, + font, + horizontal_alignment, + vertical_alignment, + } => { + self.text_pipeline.draw( + content, + (*bounds + translation) * scale_factor, + *color, + *size * scale_factor, + *font, + *horizontal_alignment, + *vertical_alignment, + pixels, + clip_mask, + ); + } + Primitive::Image { .. } => { + // TODO + } + Primitive::Svg { .. } => { + // TODO + } + Primitive::Group { primitives } => { + for primitive in primitives { + self.draw_primitive( + primitive, + pixels, + clip_mask, + scale_factor, + translation, + ); + } + } + Primitive::Translate { + translation: offset, + content, + } => { + self.draw_primitive( + content, pixels, clip_mask, scale_factor, + translation + *offset, + ); + } + Primitive::Clip { bounds, content } => { + self.draw_primitive( + content, + pixels, + Some(&rectangular_clip_mask( + pixels, + *bounds * scale_factor, + )), + scale_factor, translation, ); } + Primitive::Cached { cache } => { + self.draw_primitive( + cache, + pixels, + clip_mask, + scale_factor, + translation, + ); + } + Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} } - Primitive::Translate { - translation: offset, - content, - } => { - draw_primitive( - content, - pixels, - clip_mask, - scale_factor, - translation + *offset, - ); - } - Primitive::Clip { bounds, content } => { - draw_primitive( - content, - pixels, - Some(&rectangular_clip_mask(pixels, *bounds * scale_factor)), - scale_factor, - translation, - ); - } - Primitive::Cached { cache } => { - draw_primitive(cache, pixels, clip_mask, scale_factor, translation); - } - Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} } } @@ -185,7 +225,7 @@ fn rectangular_clip_mask( impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { - // TODO + self.text_pipeline.trim_measurement_cache(); } } @@ -204,30 +244,35 @@ impl backend::Text for Backend { fn measure( &self, - _contents: &str, - _size: f32, - _font: Font, - _bounds: Size, + contents: &str, + size: f32, + font: Font, + bounds: Size, ) -> (f32, f32) { - // TODO - (0.0, 0.0) + self.text_pipeline.measure(contents, size, font, bounds) } fn hit_test( &self, - _contents: &str, - _size: f32, - _font: Font, - _bounds: Size, - _point: iced_native::Point, - _nearest_only: bool, + contents: &str, + size: f32, + font: Font, + bounds: Size, + point: iced_native::Point, + nearest_only: bool, ) -> Option { - // TODO - None + self.text_pipeline.hit_test( + contents, + size, + font, + bounds, + point, + nearest_only, + ) } - fn load_font(&mut self, _font: Cow<'static, [u8]>) { - // TODO + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); } } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index fce44e9e19..420a1ffbeb 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -2,6 +2,7 @@ pub mod window; mod backend; mod settings; +mod text; pub use backend::Backend; pub use settings::Settings; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs new file mode 100644 index 0000000000..5bd6eff1ad --- /dev/null +++ b/tiny_skia/src/text.rs @@ -0,0 +1,350 @@ +pub use iced_native::text::Hit; + +use iced_native::alignment; +use iced_native::{Color, Font, Rectangle, Size}; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::hash_map; +use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; + +#[allow(missing_debug_implementations)] +pub struct Pipeline { + system: Option, +} + +#[ouroboros::self_referencing] +struct System { + fonts: cosmic_text::FontSystem, + + #[borrows(fonts)] + #[not_covariant] + measurement_cache: RefCell>, + + #[borrows(fonts)] + #[not_covariant] + render_cache: Cache<'this>, +} + +impl Pipeline { + pub fn new() -> Self { + Pipeline { + system: Some( + SystemBuilder { + fonts: cosmic_text::FontSystem::new_with_fonts( + [cosmic_text::fontdb::Source::Binary(Arc::new( + include_bytes!("../../wgpu/fonts/Iced-Icons.ttf") + .as_slice(), + ))] + .into_iter(), + ), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ), + } + } + + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + let heads = self.system.take().unwrap().into_heads(); + + let (locale, mut db) = heads.fonts.into_locale_and_db(); + + db.load_font_source(cosmic_text::fontdb::Source::Binary(Arc::new( + bytes.into_owned(), + ))); + + self.system = Some( + SystemBuilder { + fonts: cosmic_text::FontSystem::new_with_locale_and_db( + locale, db, + ), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ); + } + + pub fn draw( + &mut self, + content: &str, + bounds: Rectangle, + color: Color, + size: f32, + font: Font, + _horizontal_alignment: alignment::Horizontal, // TODO + _vertical_alignment: alignment::Vertical, // TODO + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::ClipMask>, + ) { + self.system.as_mut().unwrap().with_mut(|fields| { + let key = Key { + bounds: bounds.size(), + content, + font, + size, + }; + + let (_, buffer) = fields.render_cache.allocate(&fields.fonts, key); + + let mut swash = cosmic_text::SwashCache::new(&fields.fonts); + + for run in buffer.layout_runs() { + for glyph in run.glyphs { + // TODO: Outline support + if let Some(image) = swash.get_image(glyph.cache_key) { + let glyph_size = image.placement.width as usize + * image.placement.height as usize; + + if glyph_size == 0 { + continue; + } + + // TODO: Cache glyph rasterization + let mut buffer = vec![0u32; glyph_size]; + + match image.content { + cosmic_text::SwashContent::Mask => { + let mut i = 0; + + // TODO: Blend alpha + let [r, g, b, _a] = color.into_rgba8(); + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + buffer[i] = + tiny_skia::ColorU8::from_rgba( + b, + g, + r, + image.data[i], + ) + .premultiply() + .get(); + + i += 1; + } + } + } + cosmic_text::SwashContent::Color => { + let mut i = 0; + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + // TODO: Blend alpha + buffer[i] = (image.data[i + 3] as u32) + << 24 + | (image.data[i + 2] as u32) << 16 + | (image.data[i + 1] as u32) << 8 + | image.data[i] as u32; + + i += 1; + } + } + } + cosmic_text::SwashContent::SubpixelMask => { + // TODO + } + } + + let pixmap = tiny_skia::PixmapRef::from_bytes( + bytemuck::cast_slice(&buffer), + image.placement.width, + image.placement.height, + ) + .expect("Create glyph pixel map"); + + pixels.draw_pixmap( + bounds.x as i32 + + glyph.x_int + + image.placement.left, + bounds.y as i32 - glyph.y_int - image.placement.top + + run.line_y as i32, + pixmap, + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::identity(), + clip_mask, + ); + } + } + } + }); + } + + pub fn end_frame(&mut self) { + self.system + .as_mut() + .unwrap() + .with_render_cache_mut(|cache| cache.trim()); + } + + pub fn measure( + &self, + content: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size, + font, + bounds, + }, + ); + + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + (max_width, size * 1.2 * total_lines as f32) + }) + } + + pub fn hit_test( + &self, + content: &str, + size: f32, + font: iced_native::Font, + bounds: iced_native::Size, + point: iced_native::Point, + _nearest_only: bool, + ) -> Option { + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size, + font, + bounds, + }, + ); + + let cursor = paragraph.hit(point.x, point.y)?; + + Some(Hit::CharOffset(cursor.index)) + }) + } + + pub fn trim_measurement_cache(&mut self) { + self.system + .as_mut() + .unwrap() + .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); + } +} + +fn to_family(font: Font) -> cosmic_text::Family<'static> { + match font { + Font::Name(name) => cosmic_text::Family::Name(name), + Font::SansSerif => cosmic_text::Family::SansSerif, + Font::Serif => cosmic_text::Family::Serif, + Font::Cursive => cosmic_text::Family::Cursive, + Font::Fantasy => cosmic_text::Family::Fantasy, + Font::Monospace => cosmic_text::Family::Monospace, + } +} + +struct Cache<'a> { + entries: FxHashMap>, + recently_used: FxHashSet, + hasher: HashBuilder, + trim_count: usize, +} + +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault; + +impl<'a> Cache<'a> { + const TRIM_INTERVAL: usize = 300; + + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: HashBuilder::default(), + trim_count: 0, + } + } + + fn allocate( + &mut self, + fonts: &'a cosmic_text::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut cosmic_text::Buffer<'a>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + key.size.to_bits().hash(&mut hasher); + key.font.hash(&mut hasher); + key.bounds.width.to_bits().hash(&mut hasher); + key.bounds.height.to_bits().hash(&mut hasher); + + hasher.finish() + }; + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { + let metrics = cosmic_text::Metrics::new(key.size, key.size * 1.2); + let mut buffer = cosmic_text::Buffer::new(fonts, metrics); + + buffer.set_size( + key.bounds.width, + key.bounds.height.max(key.size * 1.2), + ); + buffer.set_text( + key.content, + cosmic_text::Attrs::new() + .family(to_family(key.font)) + .monospaced(matches!(key.font, Font::Monospace)), + ); + + let _ = entry.insert(buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + if self.trim_count >= Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + font: Font, + bounds: Size, +} + +type KeyHash = u64; From 3386402f5a3e75cdacd230f5e76cd54f4868d87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 26 Feb 2023 23:44:50 +0100 Subject: [PATCH 12/57] Implement text alignment support in `iced_tiny_skia` --- tiny_skia/src/text.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 5bd6eff1ad..64f31aae0a 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -76,8 +76,8 @@ impl Pipeline { color: Color, size: f32, font: Font, - _horizontal_alignment: alignment::Horizontal, // TODO - _vertical_alignment: alignment::Vertical, // TODO + horizontal_alignment: alignment::Horizontal, + vertical_alignment: alignment::Vertical, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::ClipMask>, ) { @@ -91,6 +91,27 @@ impl Pipeline { let (_, buffer) = fields.render_cache.allocate(&fields.fonts, key); + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = total_lines as f32 * size * 1.2; + + let x = match horizontal_alignment { + alignment::Horizontal::Left => bounds.x, + alignment::Horizontal::Center => bounds.x - max_width / 2.0, + alignment::Horizontal::Right => bounds.x - max_width, + }; + + let y = match vertical_alignment { + alignment::Vertical::Top => bounds.y, + alignment::Vertical::Center => bounds.y - total_height / 2.0, + alignment::Vertical::Bottom => bounds.y - total_height, + }; + let mut swash = cosmic_text::SwashCache::new(&fields.fonts); for run in buffer.layout_runs() { @@ -159,10 +180,8 @@ impl Pipeline { .expect("Create glyph pixel map"); pixels.draw_pixmap( - bounds.x as i32 - + glyph.x_int - + image.placement.left, - bounds.y as i32 - glyph.y_int - image.placement.top + x as i32 + glyph.x_int + image.placement.left, + y as i32 - glyph.y_int - image.placement.top + run.line_y as i32, pixmap, &tiny_skia::PixmapPaint::default(), From 4067c427db19eb59c4ec6c8c6d6658a9643df580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 26 Feb 2023 23:49:58 +0100 Subject: [PATCH 13/57] Fix glyphs with color mask in `iced_tiny_skia` --- tiny_skia/src/text.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 64f31aae0a..fc26703b97 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -157,13 +157,17 @@ impl Pipeline { for _y in 0..image.placement.height { for _x in 0..image.placement.width { // TODO: Blend alpha - buffer[i] = (image.data[i + 3] as u32) - << 24 - | (image.data[i + 2] as u32) << 16 - | (image.data[i + 1] as u32) << 8 - | image.data[i] as u32; + buffer[i >> 2] = + tiny_skia::ColorU8::from_rgba( + image.data[i + 2], + image.data[i + 1], + image.data[i], + image.data[i + 3], + ) + .premultiply() + .get(); - i += 1; + i += 4; } } } From 53573cf7cfd48f9bfd97d9e8b82308a0290d2b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 26 Feb 2023 23:59:00 +0100 Subject: [PATCH 14/57] Draw debug overlay in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 5e743479ee..cefed71fc8 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,5 +1,6 @@ use crate::{Color, Font, Settings, Size, Viewport}; +use iced_graphics::alignment; use iced_graphics::backend; use iced_graphics::text; use iced_graphics::{Background, Primitive, Rectangle, Vector}; @@ -27,7 +28,7 @@ impl Backend { primitives: &[Primitive], viewport: &Viewport, background_color: Color, - _overlay: &[T], + overlay: &[T], ) { pixels.fill(into_color(background_color)); @@ -43,6 +44,31 @@ impl Backend { ); } + for (i, text) in overlay.iter().enumerate() { + const OVERLAY_TEXT_SIZE: f32 = 20.0; + + self.draw_primitive( + &Primitive::Text { + content: text.as_ref().to_owned(), + size: OVERLAY_TEXT_SIZE, + bounds: Rectangle { + x: 10.0, + y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: Color::BLACK, + font: Font::Monospace, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }, + pixels, + None, + scale_factor, + Vector::ZERO, + ); + } + self.text_pipeline.end_frame(); } From fbb14bf9b879d3d154618fa8d6a81bac018fee69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 00:47:53 +0100 Subject: [PATCH 15/57] Implement `border_radius` support for quads in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 80 +++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index cefed71fc8..b1dd6a4672 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -85,7 +85,7 @@ impl Backend { Primitive::Quad { bounds, background, - border_radius: _, // TODO + border_radius, border_width, border_color, } => { @@ -95,15 +95,7 @@ impl Backend { ) .post_scale(scale_factor, scale_factor); - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ) - .expect("Create quad rectangle"), - ); + let path = rounded_rectangle(*bounds, *border_radius); pixels.fill_path( &path, @@ -223,6 +215,74 @@ fn into_color(color: Color) -> tiny_skia::Color { .expect("Convert color from iced to tiny_skia") } +fn rounded_rectangle( + bounds: Rectangle, + border_radius: [f32; 4], +) -> tiny_skia::Path { + let [top_left, top_right, bottom_right, bottom_left] = border_radius; + + if top_left == top_right + && top_left == bottom_right + && top_left == bottom_left + && top_left == bounds.width / 2.0 + && top_left == bounds.height / 2.0 + { + return tiny_skia::PathBuilder::from_circle( + bounds.x + bounds.width / 2.0, + bounds.y + bounds.height / 2.0, + top_left, + ) + .expect("Build circle path"); + } + + let mut builder = tiny_skia::PathBuilder::new(); + + builder.move_to(bounds.x + top_left, bounds.y); + builder.line_to(bounds.x + bounds.width - top_right, bounds.y); + + if top_right > 0.0 { + builder.quad_to( + bounds.x + bounds.width, + bounds.y, + bounds.x + bounds.width, + bounds.y + top_right, + ); + } + + builder.line_to( + bounds.x + bounds.width, + bounds.y + bounds.height - bottom_right, + ); + + if bottom_right > 0.0 { + builder.quad_to( + bounds.x + bounds.width, + bounds.y + bounds.height, + bounds.x + bounds.width - bottom_right, + bounds.y + bounds.height, + ); + } + + builder.line_to(bounds.x + bottom_left, bounds.y + bounds.height); + + if bottom_right > 0.0 { + builder.quad_to( + bounds.x, + bounds.y + bounds.height, + bounds.x, + bounds.y + bounds.height - bottom_left, + ); + } + + builder.line_to(bounds.x, bounds.y + top_left); + + if top_left > 0.0 { + builder.quad_to(bounds.x, bounds.y, bounds.x + top_left, bounds.y); + } + + builder.finish().expect("Build rounded rectangle path") +} + fn rectangular_clip_mask( pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, From 4e615a65cab9dfc5fa4a17a72580c573c1c040d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 01:12:06 +0100 Subject: [PATCH 16/57] Fix `clippy` lints --- renderer/src/backend.rs | 1 + tiny_skia/src/text.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index a46d6f9b8f..b0a409dc76 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -5,6 +5,7 @@ use iced_graphics::text; use std::borrow::Cow; +#[allow(clippy::large_enum_variant)] pub enum Backend { Wgpu(iced_wgpu::Backend), TinySkia(iced_tiny_skia::Backend), diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index fc26703b97..6d4cfe963a 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -89,7 +89,7 @@ impl Pipeline { size, }; - let (_, buffer) = fields.render_cache.allocate(&fields.fonts, key); + let (_, buffer) = fields.render_cache.allocate(fields.fonts, key); let (total_lines, max_width) = buffer .layout_runs() @@ -112,7 +112,7 @@ impl Pipeline { alignment::Vertical::Bottom => bounds.y - total_height, }; - let mut swash = cosmic_text::SwashCache::new(&fields.fonts); + let mut swash = cosmic_text::SwashCache::new(fields.fonts); for run in buffer.layout_runs() { for glyph in run.glyphs { From 37ce30f360ce7cba9ad05654e1faf26276a1dc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 02:58:02 +0100 Subject: [PATCH 17/57] Use `kurbo` to approximate arcs in `iced_tiny_skia` --- tiny_skia/Cargo.toml | 1 + tiny_skia/src/backend.rs | 80 +++++++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 55f7f5b7d6..7fee49cbae 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -14,6 +14,7 @@ tiny-skia = "0.8" bytemuck = "1" rustc-hash = "1.1" ouroboros = "0.15" +kurbo = "0.9" [dependencies.iced_native] version = "0.9" diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b1dd6a4672..38a6c51dcf 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -241,48 +241,108 @@ fn rounded_rectangle( builder.line_to(bounds.x + bounds.width - top_right, bounds.y); if top_right > 0.0 { - builder.quad_to( - bounds.x + bounds.width, + arc_to( + &mut builder, + bounds.x + bounds.width - top_right, bounds.y, bounds.x + bounds.width, bounds.y + top_right, + top_right, ); } - builder.line_to( + maybe_line_to( + &mut builder, bounds.x + bounds.width, bounds.y + bounds.height - bottom_right, ); if bottom_right > 0.0 { - builder.quad_to( + arc_to( + &mut builder, bounds.x + bounds.width, - bounds.y + bounds.height, + bounds.y + bounds.height - bottom_right, bounds.x + bounds.width - bottom_right, bounds.y + bounds.height, + bottom_right, ); } - builder.line_to(bounds.x + bottom_left, bounds.y + bounds.height); + maybe_line_to( + &mut builder, + bounds.x + bottom_left, + bounds.y + bounds.height, + ); if bottom_right > 0.0 { - builder.quad_to( - bounds.x, + arc_to( + &mut builder, + bounds.x + bottom_left, bounds.y + bounds.height, bounds.x, bounds.y + bounds.height - bottom_left, + bottom_left, ); } - builder.line_to(bounds.x, bounds.y + top_left); + maybe_line_to(&mut builder, bounds.x, bounds.y + top_left); if top_left > 0.0 { - builder.quad_to(bounds.x, bounds.y, bounds.x + top_left, bounds.y); + arc_to( + &mut builder, + bounds.x, + bounds.y + top_left, + bounds.x + top_left, + bounds.y, + top_left, + ); } builder.finish().expect("Build rounded rectangle path") } +fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) { + if path.last_point() != Some(tiny_skia::Point { x, y }) { + path.line_to(x, y); + } +} + +fn arc_to( + path: &mut tiny_skia::PathBuilder, + x_from: f32, + y_from: f32, + x_to: f32, + y_to: f32, + radius: f32, +) { + let svg_arc = kurbo::SvgArc { + from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)), + to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)), + radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)), + x_rotation: 0.0, + large_arc: false, + sweep: true, + }; + + match kurbo::Arc::from_svg_arc(&svg_arc) { + Some(arc) => { + arc.to_cubic_beziers(0.1, |p1, p2, p| { + path.cubic_to( + p1.x as f32, + p1.y as f32, + p2.x as f32, + p2.y as f32, + p.x as f32, + p.y as f32, + ); + }); + } + None => { + path.line_to(x_to as f32, y_to as f32); + } + } +} + fn rectangular_clip_mask( pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, From 8750d83337041f1d0fac8c2c0f4c40fd3a051a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 03:02:13 +0100 Subject: [PATCH 18/57] Short-circuit rectangle path building in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 38a6c51dcf..d0977462a6 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -221,6 +221,22 @@ fn rounded_rectangle( ) -> tiny_skia::Path { let [top_left, top_right, bottom_right, bottom_left] = border_radius; + if top_left == 0.0 + && top_right == 0.0 + && bottom_right == 0.0 + && bottom_left == 0.0 + { + return tiny_skia::PathBuilder::from_rect( + tiny_skia::Rect::from_xywh( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ) + .expect("Build quad rectangle"), + ); + } + if top_left == top_right && top_left == bottom_right && top_left == bottom_left From 3105ad2e0036e101e66b5e6ab437b570f2d923a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 03:04:05 +0100 Subject: [PATCH 19/57] Remove useless `f32` conversion in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index d0977462a6..66d832210b 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -354,7 +354,7 @@ fn arc_to( }); } None => { - path.line_to(x_to as f32, y_to as f32); + path.line_to(x_to, y_to); } } } From c1ff803b8f98beb2a73bb4252b34921110aa6cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 16:28:19 +0100 Subject: [PATCH 20/57] Implement basic glyph cache in `iced_tiny_skia` --- tiny_skia/Cargo.toml | 2 +- tiny_skia/src/text.rs | 183 +++++++++++++++++++++++++++--------------- 2 files changed, 120 insertions(+), 65 deletions(-) diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 7fee49cbae..781e7d3458 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -27,7 +27,7 @@ path = "../graphics" [dependencies.cosmic-text] features = ["std", "swash"] git = "https://github.com/hecrj/cosmic-text" -rev = "dc83efbf00a2efb4118403538e8a47bfd69c3e5e" +rev = "81080c1b9498933b43c1889601a7ea6a3d16e161" [dependencies.twox-hash] version = "1.6" diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 6d4cfe963a..cd7eb1ed13 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -13,6 +13,7 @@ use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { system: Option, + glyph_cache: GlyphCache, } #[ouroboros::self_referencing] @@ -45,6 +46,7 @@ impl Pipeline { } .build(), ), + glyph_cache: GlyphCache::new(), } } @@ -116,76 +118,20 @@ impl Pipeline { for run in buffer.layout_runs() { for glyph in run.glyphs { - // TODO: Outline support - if let Some(image) = swash.get_image(glyph.cache_key) { - let glyph_size = image.placement.width as usize - * image.placement.height as usize; - - if glyph_size == 0 { - continue; - } - - // TODO: Cache glyph rasterization - let mut buffer = vec![0u32; glyph_size]; - - match image.content { - cosmic_text::SwashContent::Mask => { - let mut i = 0; - - // TODO: Blend alpha - let [r, g, b, _a] = color.into_rgba8(); - - for _y in 0..image.placement.height { - for _x in 0..image.placement.width { - buffer[i] = - tiny_skia::ColorU8::from_rgba( - b, - g, - r, - image.data[i], - ) - .premultiply() - .get(); - - i += 1; - } - } - } - cosmic_text::SwashContent::Color => { - let mut i = 0; - - for _y in 0..image.placement.height { - for _x in 0..image.placement.width { - // TODO: Blend alpha - buffer[i >> 2] = - tiny_skia::ColorU8::from_rgba( - image.data[i + 2], - image.data[i + 1], - image.data[i], - image.data[i + 3], - ) - .premultiply() - .get(); - - i += 4; - } - } - } - cosmic_text::SwashContent::SubpixelMask => { - // TODO - } - } - + if let Some((buffer, placement)) = self + .glyph_cache + .allocate(glyph.cache_key, color, &mut swash) + { let pixmap = tiny_skia::PixmapRef::from_bytes( bytemuck::cast_slice(&buffer), - image.placement.width, - image.placement.height, + placement.width, + placement.height, ) .expect("Create glyph pixel map"); pixels.draw_pixmap( - x as i32 + glyph.x_int + image.placement.left, - y as i32 - glyph.y_int - image.placement.top + x as i32 + glyph.x_int + placement.left, + y as i32 - glyph.y_int - placement.top + run.line_y as i32, pixmap, &tiny_skia::PixmapPaint::default(), @@ -203,6 +149,8 @@ impl Pipeline { .as_mut() .unwrap() .with_render_cache_mut(|cache| cache.trim()); + + self.glyph_cache.trim(); } pub fn measure( @@ -283,6 +231,113 @@ fn to_family(font: Font) -> cosmic_text::Family<'static> { } } +#[derive(Debug, Clone, Default)] +struct GlyphCache { + entries: FxHashMap< + (cosmic_text::CacheKey, [u8; 3]), + (Vec, cosmic_text::Placement), + >, + recently_used: FxHashSet<(cosmic_text::CacheKey, [u8; 3])>, + trim_count: usize, +} + +impl GlyphCache { + fn new() -> Self { + GlyphCache::default() + } + + fn allocate( + &mut self, + cache_key: cosmic_text::CacheKey, + color: Color, + swash: &mut cosmic_text::SwashCache<'_>, + ) -> Option<(&[u8], cosmic_text::Placement)> { + let [r, g, b, _a] = color.into_rgba8(); + let key = (cache_key, [r, g, b]); + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) { + // TODO: Outline support + let image = swash.get_image(cache_key).as_ref()?; + + let glyph_size = image.placement.width as usize + * image.placement.height as usize; + + if glyph_size == 0 { + return None; + } + + // TODO: Cache glyph rasterization + let mut buffer = vec![0u32; glyph_size]; + + match image.content { + cosmic_text::SwashContent::Mask => { + let mut i = 0; + + // TODO: Blend alpha + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + buffer[i] = tiny_skia::ColorU8::from_rgba( + b, + g, + r, + image.data[i], + ) + .premultiply() + .get(); + + i += 1; + } + } + } + cosmic_text::SwashContent::Color => { + let mut i = 0; + + for _y in 0..image.placement.height { + for _x in 0..image.placement.width { + // TODO: Blend alpha + buffer[i >> 2] = tiny_skia::ColorU8::from_rgba( + image.data[i + 2], + image.data[i + 1], + image.data[i], + image.data[i + 3], + ) + .premultiply() + .get(); + + i += 4; + } + } + } + cosmic_text::SwashContent::SubpixelMask => { + // TODO + } + } + + entry.insert((buffer, image.placement)); + } + + self.recently_used.insert(key); + + self.entries.get(&key).map(|(buffer, placement)| { + (bytemuck::cast_slice(buffer.as_slice()), *placement) + }) + } + + pub fn trim(&mut self) { + if self.trim_count > 300 { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + + self.trim_count = 0; + } else { + self.trim_count += 1; + } + } +} + struct Cache<'a> { entries: FxHashMap>, recently_used: FxHashSet, From 151daf95b70d5a53007496e58f49fc618c1a22e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 16:30:54 +0100 Subject: [PATCH 21/57] Remove unnecessary `cast_slice` in `iced_tiny_skia` --- tiny_skia/src/text.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index cd7eb1ed13..37b0fe475f 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -123,7 +123,7 @@ impl Pipeline { .allocate(glyph.cache_key, color, &mut swash) { let pixmap = tiny_skia::PixmapRef::from_bytes( - bytemuck::cast_slice(&buffer), + buffer, placement.width, placement.height, ) @@ -266,7 +266,6 @@ impl GlyphCache { return None; } - // TODO: Cache glyph rasterization let mut buffer = vec![0u32; glyph_size]; match image.content { From 9e815cb749f1bf6bce0232e870be266aca0c0742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 27 Feb 2023 16:49:25 +0100 Subject: [PATCH 22/57] Remove `Fill` variant for `Alignment` Implementing this generically in our `flex` logic has an exponential cost. Let's explore other options! --- core/src/alignment.rs | 3 -- examples/component/src/main.rs | 5 ++- examples/scrollable/src/main.rs | 1 - examples/websocket/src/main.rs | 4 +- native/src/layout/flex.rs | 67 ++++----------------------------- native/src/layout/node.rs | 6 --- 6 files changed, 14 insertions(+), 72 deletions(-) diff --git a/core/src/alignment.rs b/core/src/alignment.rs index 73f41d3fab..51b7fca9da 100644 --- a/core/src/alignment.rs +++ b/core/src/alignment.rs @@ -11,9 +11,6 @@ pub enum Alignment { /// Align at the end of the axis. End, - - /// Fill the entire axis. - Fill, } impl From for Alignment { diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index c407bb06c6..bbf549e757 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -127,7 +127,8 @@ mod numeric_input { .horizontal_alignment(alignment::Horizontal::Center) .vertical_alignment(alignment::Vertical::Center), ) - .width(50) + .width(40) + .height(40) .on_press(on_press) }; @@ -145,7 +146,7 @@ mod numeric_input { .padding(10), button("+", Event::IncrementPressed), ] - .align_items(Alignment::Fill) + .align_items(Alignment::Center) .spacing(10) .into() } diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 7c85896147..a3ade54fb9 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -254,7 +254,6 @@ impl Application for ScrollableDemo { scroll_to_beginning_button(), vertical_space(40), ] - .align_items(Alignment::Fill) .spacing(40), horizontal_space(1200), text("Horizontal - End!"), diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index ccd9c81582..e617b8ce2b 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -146,7 +146,9 @@ impl Application for WebSocket { } } - row![input, button].spacing(10).align_items(Alignment::Fill) + row![input, button] + .spacing(10) + .align_items(Alignment::Center) }; column![message_log, new_message_input] diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs index 5d70c2fc13..8b9678497b 100644 --- a/native/src/layout/flex.rs +++ b/native/src/layout/flex.rs @@ -81,32 +81,6 @@ where let mut nodes: Vec = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); - if align_items == Alignment::Fill { - let mut fill_cross = axis.cross(limits.min()); - - items.iter().for_each(|child| { - let cross_fill_factor = match axis { - Axis::Horizontal => child.as_widget().height(), - Axis::Vertical => child.as_widget().width(), - } - .fill_factor(); - - if cross_fill_factor == 0 { - let (max_width, max_height) = axis.pack(available, max_cross); - - let child_limits = - Limits::new(Size::ZERO, Size::new(max_width, max_height)); - - let layout = child.as_widget().layout(renderer, &child_limits); - let size = layout.size(); - - fill_cross = fill_cross.max(axis.cross(size)); - } - }); - - cross = fill_cross; - } - for (i, child) in items.iter().enumerate() { let fill_factor = match axis { Axis::Horizontal => child.as_widget().width(), @@ -115,31 +89,16 @@ where .fill_factor(); if fill_factor == 0 { - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(0.0, cross) - } else { - axis.pack(0.0, 0.0) - }; + let (max_width, max_height) = axis.pack(available, max_cross); - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(available, cross) - } else { - axis.pack(available, max_cross) - }; - - let child_limits = Limits::new( - Size::new(min_width, min_height), - Size::new(max_width, max_height), - ); + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); let layout = child.as_widget().layout(renderer, &child_limits); let size = layout.size(); available -= axis.main(size); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(size)); - } + cross = cross.max(axis.cross(size)); nodes[i] = layout; } else { @@ -164,17 +123,10 @@ where max_main }; - let (min_width, min_height) = if align_items == Alignment::Fill { - axis.pack(min_main, cross) - } else { - axis.pack(min_main, axis.cross(limits.min())) - }; + let (min_width, min_height) = + axis.pack(min_main, axis.cross(limits.min())); - let (max_width, max_height) = if align_items == Alignment::Fill { - axis.pack(max_main, cross) - } else { - axis.pack(max_main, max_cross) - }; + let (max_width, max_height) = axis.pack(max_main, max_cross); let child_limits = Limits::new( Size::new(min_width, min_height), @@ -182,10 +134,7 @@ where ); let layout = child.as_widget().layout(renderer, &child_limits); - - if align_items != Alignment::Fill { - cross = cross.max(axis.cross(layout.size())); - } + cross = cross.max(axis.cross(layout.size())); nodes[i] = layout; } diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs index e0c7dcb2fd..2b44a7d549 100644 --- a/native/src/layout/node.rs +++ b/native/src/layout/node.rs @@ -56,9 +56,6 @@ impl Node { Alignment::End => { self.bounds.x += space.width - self.bounds.width; } - Alignment::Fill => { - self.bounds.width = space.width; - } } match vertical_alignment { @@ -69,9 +66,6 @@ impl Node { Alignment::End => { self.bounds.y += space.height - self.bounds.height; } - Alignment::Fill => { - self.bounds.height = space.height; - } } } From fd06de5d9c5afa05c5b11cda730b0769aef92caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Feb 2023 03:48:34 +0100 Subject: [PATCH 23/57] Use `get_image_uncached` in `iced_tiny_skia` ... since we are not reusing the `SwashCache` --- tiny_skia/src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 37b0fe475f..da3a06bfb3 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -257,7 +257,7 @@ impl GlyphCache { if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) { // TODO: Outline support - let image = swash.get_image(cache_key).as_ref()?; + let image = swash.get_image_uncached(cache_key)?; let glyph_size = image.placement.width as usize * image.placement.height as usize; From 3f6e28fa9b1b8d911f765c9efb5249a9e0c942d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Feb 2023 20:47:13 +0100 Subject: [PATCH 24/57] Use `iced_renderer` instead of `iced_graphics` in root crate --- Cargo.toml | 5 ++--- renderer/Cargo.toml | 2 ++ renderer/src/lib.rs | 1 + renderer/src/widget.rs | 5 +++++ src/error.rs | 2 +- src/widget.rs | 4 ++-- 6 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 renderer/src/widget.rs diff --git a/Cargo.toml b/Cargo.toml index b1f5cf66bb..942966e5c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,9 @@ image = ["iced_renderer/image", "image_rs"] # Enables the `Svg` widget svg = ["iced_renderer/svg"] # Enables the `Canvas` widget -canvas = ["iced_graphics/canvas"] +canvas = ["iced_renderer/canvas"] # Enables the `QRCode` widget -qr_code = ["iced_graphics/qr_code"] +qr_code = ["iced_renderer/qr_code"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -60,7 +60,6 @@ members = [ iced_core = { version = "0.8", path = "core" } iced_futures = { version = "0.6", path = "futures" } iced_native = { version = "0.9", path = "native" } -iced_graphics = { version = "0.7", path = "graphics" } iced_renderer = { version = "0.1", path = "renderer" } iced_winit = { version = "0.8", path = "winit", features = ["application"] } thiserror = "1.0" diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 5ba5d426fe..1f21f06bb1 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [features] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] +canvas = ["iced_graphics/canvas"] +qr_code = ["iced_graphics/qr_code"] tracing = ["iced_wgpu/tracing"] [dependencies] diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index d0ba67930b..f9bfc37349 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,3 +1,4 @@ +pub mod widget; pub mod window; mod backend; diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs new file mode 100644 index 0000000000..417cc06fad --- /dev/null +++ b/renderer/src/widget.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "qr_code")] +pub use iced_graphics::widget::qr_code; + +#[cfg(feature = "canvas")] +pub use iced_graphics::widget::canvas; diff --git a/src/error.rs b/src/error.rs index 0bfa3ff1c4..5326718f1b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,7 +13,7 @@ pub enum Error { /// The application graphics context could not be created. #[error("the application graphics context could not be created")] - GraphicsCreationFailed(iced_graphics::Error), + GraphicsCreationFailed(iced_renderer::Error), } impl From for Error { diff --git a/src/widget.rs b/src/widget.rs index e2b0537e53..04cb0e50cc 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -165,7 +165,7 @@ pub use vertical_slider::VerticalSlider; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub use iced_graphics::widget::canvas; +pub use iced_renderer::widget::canvas; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] @@ -192,7 +192,7 @@ pub mod image { #[cfg(feature = "qr_code")] #[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub use iced_graphics::widget::qr_code; +pub use iced_renderer::widget::qr_code; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] From 5fd5d1cdf8e5354788dc40729c4565ef377d3bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 1 Mar 2023 21:34:26 +0100 Subject: [PATCH 25/57] Implement `Canvas` support for `iced_tiny_skia` --- core/Cargo.toml | 1 + {graphics => core}/src/gradient.rs | 0 {graphics => core}/src/gradient/linear.rs | 0 core/src/lib.rs | 2 + examples/arc/src/main.rs | 9 +- examples/bezier_tool/src/main.rs | 25 +- examples/clock/src/main.rs | 9 +- examples/color_palette/src/main.rs | 9 +- examples/game_of_life/src/main.rs | 73 ++--- examples/geometry/src/main.rs | 6 +- examples/modern_art/src/main.rs | 5 +- examples/multitouch/src/main.rs | 7 +- examples/sierpinski_triangle/src/main.rs | 9 +- examples/solar_system/src/main.rs | 28 +- graphics/Cargo.toml | 13 +- graphics/src/backend.rs | 2 + graphics/src/lib.rs | 13 +- graphics/src/primitive.rs | 128 +++++--- graphics/src/renderer.rs | 43 +-- graphics/src/triangle.rs | 32 -- native/Cargo.toml | 6 + native/src/lib.rs | 4 +- native/src/widget.rs | 16 + {graphics => native}/src/widget/canvas.rs | 101 +++---- .../src/widget/canvas/cursor.rs | 2 +- .../src/widget/canvas/event.rs | 8 +- .../src/widget/canvas/fill.rs | 18 +- .../src/widget/canvas/path.rs | 52 +--- .../src/widget/canvas/path/arc.rs | 2 +- .../src/widget/canvas/path/builder.rs | 24 +- .../src/widget/canvas/program.rs | 24 +- .../src/widget/canvas/stroke.rs | 22 +- .../src/widget/canvas/style.rs | 3 +- .../src/widget/canvas/text.rs | 0 renderer/Cargo.toml | 10 +- renderer/src/backend.rs | 4 +- renderer/src/lib.rs | 14 +- renderer/src/widget.rs | 12 +- renderer/src/widget/canvas.rs | 177 +++++++++++ renderer/src/widget/canvas/cache.rs | 85 ++++++ {graphics => renderer}/src/widget/qr_code.rs | 90 +++--- src/widget.rs | 5 +- tiny_skia/Cargo.toml | 1 + tiny_skia/src/backend.rs | 48 ++- tiny_skia/src/canvas.rs | 276 ++++++++++++++++++ tiny_skia/src/lib.rs | 10 +- tiny_skia/src/primitive.rs | 82 ++++++ tiny_skia/src/window/compositor.rs | 3 +- wgpu/Cargo.toml | 15 +- wgpu/src/backend.rs | 7 +- wgpu/src/image.rs | 2 +- {graphics => wgpu}/src/layer.rs | 80 ++--- {graphics => wgpu}/src/layer/image.rs | 0 {graphics => wgpu}/src/layer/mesh.rs | 6 +- {graphics => wgpu}/src/layer/quad.rs | 0 {graphics => wgpu}/src/layer/text.rs | 0 wgpu/src/lib.rs | 15 +- wgpu/src/quad.rs | 3 +- wgpu/src/text.rs | 3 +- wgpu/src/triangle.rs | 71 ++--- {graphics => wgpu}/src/widget.rs | 9 +- wgpu/src/widget/canvas.rs | 16 + {graphics => wgpu}/src/widget/canvas/cache.rs | 19 +- {graphics => wgpu}/src/widget/canvas/frame.rs | 157 +++++++--- .../src/widget/canvas/geometry.rs | 0 65 files changed, 1350 insertions(+), 566 deletions(-) rename {graphics => core}/src/gradient.rs (100%) rename {graphics => core}/src/gradient/linear.rs (100%) rename {graphics => native}/src/widget/canvas.rs (73%) rename {graphics => native}/src/widget/canvas/cursor.rs (98%) rename {graphics => native}/src/widget/canvas/event.rs (73%) rename {graphics => native}/src/widget/canvas/fill.rs (77%) rename {graphics => native}/src/widget/canvas/path.rs (50%) rename {graphics => native}/src/widget/canvas/path/arc.rs (97%) rename {graphics => native}/src/widget/canvas/path/builder.rs (91%) rename {graphics => native}/src/widget/canvas/program.rs (83%) rename {graphics => native}/src/widget/canvas/stroke.rs (79%) rename {graphics => native}/src/widget/canvas/style.rs (88%) rename {graphics => native}/src/widget/canvas/text.rs (100%) create mode 100644 renderer/src/widget/canvas.rs create mode 100644 renderer/src/widget/canvas/cache.rs rename {graphics => renderer}/src/widget/qr_code.rs (81%) create mode 100644 tiny_skia/src/canvas.rs create mode 100644 tiny_skia/src/primitive.rs rename {graphics => wgpu}/src/layer.rs (95%) rename {graphics => wgpu}/src/layer/image.rs (100%) rename {graphics => wgpu}/src/layer/mesh.rs (94%) rename {graphics => wgpu}/src/layer/quad.rs (100%) rename {graphics => wgpu}/src/layer/text.rs (100%) rename {graphics => wgpu}/src/widget.rs (56%) create mode 100644 wgpu/src/widget/canvas.rs rename {graphics => wgpu}/src/widget/canvas/cache.rs (87%) rename {graphics => wgpu}/src/widget/canvas/frame.rs (77%) rename {graphics => wgpu}/src/widget/canvas/geometry.rs (100%) diff --git a/core/Cargo.toml b/core/Cargo.toml index 43865e4d83..7ccb7b7aca 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/iced-rs/iced" [dependencies] bitflags = "1.2" +thiserror = "1" [dependencies.palette] version = "0.6" diff --git a/graphics/src/gradient.rs b/core/src/gradient.rs similarity index 100% rename from graphics/src/gradient.rs rename to core/src/gradient.rs diff --git a/graphics/src/gradient/linear.rs b/core/src/gradient/linear.rs similarity index 100% rename from graphics/src/gradient/linear.rs rename to core/src/gradient/linear.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index d73148514c..1e4f0411d9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -26,6 +26,7 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub mod alignment; pub mod font; +pub mod gradient; pub mod keyboard; pub mod mouse; pub mod time; @@ -46,6 +47,7 @@ pub use background::Background; pub use color::Color; pub use content_fit::ContentFit; pub use font::Font; +pub use gradient::Gradient; pub use length::Length; pub use padding::Padding; pub use pixels::Pixels; diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 7b6ea0e186..d71ba6f6f2 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -5,8 +5,8 @@ use iced::widget::canvas::{ self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke, }; use iced::{ - Application, Command, Element, Length, Point, Rectangle, Settings, - Subscription, Theme, + Application, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Subscription, Theme, }; pub fn main() -> iced::Result { @@ -69,17 +69,18 @@ impl Application for Arc { } } -impl canvas::Program for Arc { +impl canvas::Program for Arc { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let geometry = self.cache.draw(bounds.size(), |frame| { + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); let center = frame.center(); diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 7c3916d4a2..5bb463c3ee 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -64,7 +64,7 @@ mod bezier { use iced::widget::canvas::{ self, Canvas, Cursor, Frame, Geometry, Path, Stroke, }; - use iced::{Element, Length, Point, Rectangle, Theme}; + use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] pub struct State { @@ -92,7 +92,7 @@ mod bezier { curves: &'a [Curve], } - impl<'a> canvas::Program for Bezier<'a> { + impl<'a> canvas::Program for Bezier<'a> { type State = Option; fn update( @@ -152,22 +152,26 @@ mod bezier { fn draw( &self, state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec { - let content = - self.state.cache.draw(bounds.size(), |frame: &mut Frame| { + let content = self.state.cache.draw( + renderer, + bounds.size(), + |frame: &mut Frame| { Curve::draw_all(self.curves, frame); frame.stroke( &Path::rectangle(Point::ORIGIN, frame.size()), Stroke::default().with_width(2.0), ); - }); + }, + ); if let Some(pending) = state { - let pending_curve = pending.draw(bounds, cursor); + let pending_curve = pending.draw(renderer, bounds, cursor); vec![content, pending_curve] } else { @@ -216,8 +220,13 @@ mod bezier { } impl Pending { - fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry { - let mut frame = Frame::new(bounds.size()); + fn draw( + &self, + renderer: &Renderer, + bounds: Rectangle, + cursor: Cursor, + ) -> Geometry { + let mut frame = Frame::new(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(&bounds) { match *self { diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index a389c54f6b..6425e2da77 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -4,8 +4,8 @@ use iced::widget::canvas::{ }; use iced::widget::{canvas, container}; use iced::{ - Application, Color, Command, Element, Length, Point, Rectangle, Settings, - Subscription, Theme, Vector, + Application, Color, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Subscription, Theme, Vector, }; pub fn main() -> iced::Result { @@ -83,17 +83,18 @@ impl Application for Clock { } } -impl canvas::Program for Clock { +impl canvas::Program for Clock { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let clock = self.clock.draw(bounds.size(), |frame| { + let clock = self.clock.draw(renderer, bounds.size(), |frame| { let center = frame.center(); let radius = frame.width().min(frame.height()) / 2.0; diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index a2df36c2ee..1109a88388 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,8 +1,8 @@ use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ - alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox, - Settings, Size, Vector, + alignment, Alignment, Color, Element, Length, Point, Rectangle, Renderer, + Sandbox, Settings, Size, Vector, }; use palette::{self, convert::FromColor, Hsl, Srgb}; use std::marker::PhantomData; @@ -237,17 +237,18 @@ impl Theme { } } -impl canvas::Program for Theme { +impl canvas::Program for Theme { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &iced::Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let theme = self.canvas_cache.draw(bounds.size(), |frame| { + let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| { self.draw(frame); }); diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index ed911160e3..494f71a667 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -211,8 +211,8 @@ mod grid { Cache, Canvas, Cursor, Frame, Geometry, Path, Text, }; use iced::{ - alignment, mouse, Color, Element, Length, Point, Rectangle, Size, - Theme, Vector, + alignment, mouse, Color, Element, Length, Point, Rectangle, Renderer, + Size, Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -393,7 +393,7 @@ mod grid { } } - impl canvas::Program for Grid { + impl canvas::Program for Grid { type State = Interaction; fn update( @@ -536,13 +536,14 @@ mod grid { fn draw( &self, _interaction: &Interaction, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec { let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); - let life = self.life_cache.draw(bounds.size(), |frame| { + let life = self.life_cache.draw(renderer, bounds.size(), |frame| { let background = Path::rectangle(Point::ORIGIN, frame.size()); frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B)); @@ -565,7 +566,7 @@ mod grid { }); let overlay = { - let mut frame = Frame::new(bounds.size()); + let mut frame = Frame::new(renderer, bounds.size()); let hovered_cell = cursor.position_in(&bounds).map(|position| { @@ -626,38 +627,40 @@ mod grid { if self.scaling < 0.2 || !self.show_lines { vec![life, overlay] } else { - let grid = self.grid_cache.draw(bounds.size(), |frame| { - frame.translate(center); - frame.scale(self.scaling); - frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); - - let region = self.visible_region(frame.size()); - let rows = region.rows(); - let columns = region.columns(); - let (total_rows, total_columns) = - (rows.clone().count(), columns.clone().count()); - let width = 2.0 / Cell::SIZE as f32; - let color = Color::from_rgb8(70, 74, 83); - - frame.translate(Vector::new(-width / 2.0, -width / 2.0)); + let grid = + self.grid_cache.draw(renderer, bounds.size(), |frame| { + frame.translate(center); + frame.scale(self.scaling); + frame.translate(self.translation); + frame.scale(Cell::SIZE as f32); - for row in region.rows() { - frame.fill_rectangle( - Point::new(*columns.start() as f32, row as f32), - Size::new(total_columns as f32, width), - color, - ); - } + let region = self.visible_region(frame.size()); + let rows = region.rows(); + let columns = region.columns(); + let (total_rows, total_columns) = + (rows.clone().count(), columns.clone().count()); + let width = 2.0 / Cell::SIZE as f32; + let color = Color::from_rgb8(70, 74, 83); + + frame + .translate(Vector::new(-width / 2.0, -width / 2.0)); + + for row in region.rows() { + frame.fill_rectangle( + Point::new(*columns.start() as f32, row as f32), + Size::new(total_columns as f32, width), + color, + ); + } - for column in region.columns() { - frame.fill_rectangle( - Point::new(column as f32, *rows.start() as f32), - Size::new(width, total_rows as f32), - color, - ); - } - }); + for column in region.columns() { + frame.fill_rectangle( + Point::new(column as f32, *rows.start() as f32), + Size::new(width, total_rows as f32), + color, + ); + } + }); vec![life, grid, overlay] } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 9bacce7f6b..a77772d5b3 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -10,9 +10,9 @@ mod rainbow { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. + use iced_graphics::primitive::{ColoredVertex2D, Primitive}; use iced_graphics::renderer::{self, Renderer}; - use iced_graphics::triangle::ColoredVertex2D; - use iced_graphics::{Backend, Primitive}; + use iced_graphics::Backend; use iced_native::layout; use iced_native::widget::{self, Widget}; @@ -59,7 +59,7 @@ mod rainbow { cursor_position: Point, _viewport: &Rectangle, ) { - use iced_graphics::triangle::Mesh2D; + use iced_graphics::primitive::Mesh2D; use iced_native::Renderer as _; let b = layout.bounds(); diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs index 28ed3e215c..a43a2b2b0e 100644 --- a/examples/modern_art/src/main.rs +++ b/examples/modern_art/src/main.rs @@ -55,17 +55,18 @@ impl Application for ModernArt { } } -impl canvas::Program for ModernArt { +impl canvas::Program for ModernArt { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let geometry = self.cache.draw(bounds.size(), |frame| { + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let num_squares = thread_rng().gen_range(0..1200); let mut i = 0; diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index f5faae0f4a..7df6c929af 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -6,7 +6,7 @@ use iced::widget::canvas::stroke::{self, Stroke}; use iced::widget::canvas::{self, Canvas, Cursor, Geometry}; use iced::{ executor, touch, window, Application, Color, Command, Element, Length, - Point, Rectangle, Settings, Subscription, Theme, + Point, Rectangle, Renderer, Settings, Subscription, Theme, }; use std::collections::HashMap; @@ -95,7 +95,7 @@ impl Application for Multitouch { } } -impl canvas::Program for State { +impl canvas::Program for State { type State = (); fn update( @@ -125,11 +125,12 @@ impl canvas::Program for State { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let fingerweb = self.cache.draw(bounds.size(), |frame| { + let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| { if self.fingers.len() < 2 { return; } diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 1d25d17166..e85f839195 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -5,8 +5,8 @@ use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{self, Canvas}; use iced::widget::{column, row, slider, text}; use iced::{ - Application, Color, Command, Length, Point, Rectangle, Settings, Size, - Theme, + Application, Color, Command, Length, Point, Rectangle, Renderer, Settings, + Size, Theme, }; use rand::Rng; @@ -97,7 +97,7 @@ struct SierpinskiGraph { cache: canvas::Cache, } -impl canvas::Program for SierpinskiGraph { +impl canvas::Program for SierpinskiGraph { type State = (); fn update( @@ -134,11 +134,12 @@ impl canvas::Program for SierpinskiGraph { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: canvas::Cursor, ) -> Vec { - let geom = self.cache.draw(bounds.size(), |frame| { + let geom = self.cache.draw(renderer, bounds.size(), |frame| { frame.stroke( &canvas::Path::rectangle(Point::ORIGIN, frame.size()), canvas::Stroke::default(), diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9a4ee7549a..0023a69b73 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -15,8 +15,8 @@ use iced::widget::canvas::stroke::{self, Stroke}; use iced::widget::canvas::{Cursor, Path}; use iced::window; use iced::{ - Application, Color, Command, Element, Length, Point, Rectangle, Settings, - Size, Subscription, Vector, + Application, Color, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Size, Subscription, Vector, }; use std::time::Instant; @@ -150,30 +150,32 @@ impl State { } } -impl canvas::Program for State { +impl canvas::Program for State { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { use std::f32::consts::PI; - let background = self.space_cache.draw(bounds.size(), |frame| { - let stars = Path::new(|path| { - for (p, size) in &self.stars { - path.rectangle(*p, Size::new(*size, *size)); - } - }); + let background = + self.space_cache.draw(renderer, bounds.size(), |frame| { + let stars = Path::new(|path| { + for (p, size) in &self.stars { + path.rectangle(*p, Size::new(*size, *size)); + } + }); - frame.translate(frame.center() - Point::ORIGIN); - frame.fill(&stars, Color::WHITE); - }); + frame.translate(frame.center() - Point::ORIGIN); + frame.fill(&stars, Color::WHITE); + }); - let system = self.system_cache.draw(bounds.size(), |frame| { + let system = self.system_cache.draw(renderer, bounds.size(), |frame| { let center = frame.center(); let sun = Path::circle(center, Self::SUN_RADIUS); diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 9795d31f8b..36d8a516c7 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -24,8 +24,7 @@ bmp = ["image_rs/bmp"] hdr = ["image_rs/hdr"] dds = ["image_rs/dds"] farbfeld = ["image_rs/farbfeld"] -canvas = ["lyon"] -qr_code = ["qrcode", "canvas"] +canvas = ["iced_native/canvas"] opengl = [] image_rs = ["kamadak-exif"] @@ -35,6 +34,7 @@ log = "0.4" raw-window-handle = "0.5" thiserror = "1.0" bitflags = "1.2" +tiny-skia = "0.8" [dependencies.bytemuck] version = "1.4" @@ -48,15 +48,6 @@ path = "../native" version = "0.7" path = "../style" -[dependencies.lyon] -version = "1.0" -optional = true - -[dependencies.qrcode] -version = "0.12" -optional = true -default-features = false - [dependencies.image_rs] version = "0.24" package = "image" diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 8658cffe82..c44372e8e7 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -10,6 +10,8 @@ use std::borrow::Cow; /// /// [`Renderer`]: crate::Renderer pub trait Backend { + type Geometry: Into; + /// Trims the measurements cache. /// /// This method is currently necessary to properly trim the text cache in diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index bbbdfa0e14..576b2d7812 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,7 +9,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -23,25 +23,19 @@ #![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; mod error; -mod primitive; mod transformation; mod viewport; pub mod backend; -pub mod gradient; pub mod image; -pub mod layer; pub mod overlay; +pub mod primitive; pub mod renderer; -pub mod triangle; -pub mod widget; pub mod window; pub use antialiasing::Antialiasing; pub use backend::Backend; pub use error::Error; -pub use gradient::Gradient; -pub use layer::Layer; pub use primitive::Primitive; pub use renderer::Renderer; pub use transformation::Transformation; @@ -50,5 +44,6 @@ pub use viewport::Viewport; pub use iced_native::alignment; pub use iced_native::text; pub use iced_native::{ - Alignment, Background, Color, Font, Point, Rectangle, Size, Vector, + Alignment, Background, Color, Font, Gradient, Point, Rectangle, Size, + Vector, }; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 5a163a2f5f..e48265911e 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -1,23 +1,15 @@ +use crate::alignment; + use iced_native::image; use iced_native::svg; -use iced_native::{Background, Color, Font, Rectangle, Size, Vector}; - -use crate::alignment; -use crate::gradient::Gradient; -use crate::triangle; +use iced_native::{Background, Color, Font, Gradient, Rectangle, Size, Vector}; +use bytemuck::{Pod, Zeroable}; use std::sync::Arc; /// A rendering primitive. #[derive(Debug, Clone)] pub enum Primitive { - /// An empty primitive - None, - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec, - }, /// A text primitive Text { /// The contents of the text @@ -66,27 +58,12 @@ pub enum Primitive { /// The bounds of the viewport bounds: Rectangle, }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The content of the clip - content: Box, - }, - /// A primitive that applies a translation - Translate { - /// The translation vector - translation: Vector, - - /// The primitive to translate - content: Box, - }, /// A low-level primitive to render a mesh of triangles with a solid color. /// /// It can be used to render many kinds of geometry freely. SolidMesh { /// The vertices and indices of the mesh. - buffers: triangle::Mesh2D, + buffers: Mesh2D, /// The size of the drawable region of the mesh. /// @@ -98,7 +75,7 @@ pub enum Primitive { /// It can be used to render many kinds of geometry freely. GradientMesh { /// The vertices and indices of the mesh. - buffers: triangle::Mesh2D, + buffers: Mesh2D, /// The size of the drawable region of the mesh. /// @@ -108,18 +85,101 @@ pub enum Primitive { /// The [`Gradient`] to apply to the mesh. gradient: Gradient, }, + Fill { + path: tiny_skia::Path, + paint: tiny_skia::Paint<'static>, + rule: tiny_skia::FillRule, + transform: tiny_skia::Transform, + }, + Stroke { + path: tiny_skia::Path, + paint: tiny_skia::Paint<'static>, + stroke: tiny_skia::Stroke, + transform: tiny_skia::Transform, + }, + /// A group of primitives + Group { + /// The primitives of the group + primitives: Vec, + }, + /// A clip primitive + Clip { + /// The bounds of the clip + bounds: Rectangle, + /// The content of the clip + content: Box, + }, + /// A primitive that applies a translation + Translate { + /// The translation vector + translation: Vector, + + /// The primitive to translate + content: Box, + }, /// A cached primitive. /// /// This can be useful if you are implementing a widget where primitive /// generation is expensive. - Cached { + Cache { /// The cached primitive - cache: Arc, + content: Arc, }, } -impl Default for Primitive { - fn default() -> Primitive { - Primitive::None +impl Primitive { + pub fn group(primitives: Vec) -> Self { + Self::Group { primitives } + } + + pub fn clip(self, bounds: Rectangle) -> Self { + Self::Clip { + bounds, + content: Box::new(self), + } + } + + pub fn translate(self, translation: Vector) -> Self { + Self::Translate { + translation, + content: Box::new(self), + } + } +} + +/// A set of [`Vertex2D`] and indices representing a list of triangles. +#[derive(Clone, Debug)] +pub struct Mesh2D { + /// The vertices of the mesh + pub vertices: Vec, + + /// The list of vertex indices that defines the triangles of the mesh. + /// + /// Therefore, this list should always have a length that is a multiple of 3. + pub indices: Vec, +} + +/// A two-dimensional vertex. +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +#[repr(C)] +pub struct Vertex2D { + /// The vertex position in 2D space. + pub position: [f32; 2], +} + +/// A two-dimensional vertex with a color. +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +#[repr(C)] +pub struct ColoredVertex2D { + /// The vertex position in 2D space. + pub position: [f32; 2], + + /// The color of the vertex in __linear__ RGBA. + pub color: [f32; 4], +} + +impl From<()> for Primitive { + fn from(_: ()) -> Self { + Self::Group { primitives: vec![] } } } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 859ebc0418..793ee7d7ec 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,6 +1,7 @@ //! Create a renderer from a [`Backend`]. use crate::backend::{self, Backend}; use crate::{Primitive, Vector}; + use iced_native::image; use iced_native::layout; use iced_native::renderer; @@ -70,19 +71,13 @@ where } fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - let current_primitives = std::mem::take(&mut self.primitives); + let current = std::mem::take(&mut self.primitives); f(self); - let layer_primitives = - std::mem::replace(&mut self.primitives, current_primitives); + let layer = std::mem::replace(&mut self.primitives, current); - self.primitives.push(Primitive::Clip { - bounds, - content: Box::new(Primitive::Group { - primitives: layer_primitives, - }), - }); + self.primitives.push(Primitive::group(layer).clip(bounds)); } fn with_translation( @@ -90,19 +85,14 @@ where translation: Vector, f: impl FnOnce(&mut Self), ) { - let current_primitives = std::mem::take(&mut self.primitives); + let current = std::mem::take(&mut self.primitives); f(self); - let layer_primitives = - std::mem::replace(&mut self.primitives, current_primitives); + let layer = std::mem::replace(&mut self.primitives, current); - self.primitives.push(Primitive::Translate { - translation, - content: Box::new(Primitive::Group { - primitives: layer_primitives, - }), - }); + self.primitives + .push(Primitive::group(layer).translate(translation)); } fn fill_quad( @@ -199,7 +189,7 @@ where } fn draw(&mut self, handle: image::Handle, bounds: Rectangle) { - self.draw_primitive(Primitive::Image { handle, bounds }) + self.primitives.push(Primitive::Image { handle, bounds }) } } @@ -217,10 +207,23 @@ where color: Option, bounds: Rectangle, ) { - self.draw_primitive(Primitive::Svg { + self.primitives.push(Primitive::Svg { handle, color, bounds, }) } } + +#[cfg(feature = "canvas")] +impl iced_native::widget::canvas::Renderer for Renderer +where + B: Backend, +{ + type Geometry = B::Geometry; + + fn draw(&mut self, layers: Vec) { + self.primitives + .extend(layers.into_iter().map(B::Geometry::into)); + } +} diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index f52b2339b1..09b6176753 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,33 +1 @@ //! Draw geometry using meshes of triangles. -use bytemuck::{Pod, Zeroable}; - -/// A set of [`Vertex2D`] and indices representing a list of triangles. -#[derive(Clone, Debug)] -pub struct Mesh2D { - /// The vertices of the mesh - pub vertices: Vec, - - /// The list of vertex indices that defines the triangles of the mesh. - /// - /// Therefore, this list should always have a length that is a multiple of 3. - pub indices: Vec, -} - -/// A two-dimensional vertex. -#[derive(Copy, Clone, Debug, Zeroable, Pod)] -#[repr(C)] -pub struct Vertex2D { - /// The vertex position in 2D space. - pub position: [f32; 2], -} - -/// A two-dimensional vertex with a color. -#[derive(Copy, Clone, Debug, Zeroable, Pod)] -#[repr(C)] -pub struct ColoredVertex2D { - /// The vertex position in 2D space. - pub position: [f32; 2], - - /// The color of the vertex in __linear__ RGBA. - pub color: [f32; 4], -} diff --git a/native/Cargo.toml b/native/Cargo.toml index 3f92783eb9..23533e33c1 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -8,12 +8,14 @@ license = "MIT" repository = "https://github.com/iced-rs/iced" [features] +canvas = ["lyon_path"] debug = [] [dependencies] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" +thiserror = "1" [dependencies.iced_core] version = "0.8" @@ -27,3 +29,7 @@ features = ["thread-pool"] [dependencies.iced_style] version = "0.7" path = "../style" + +[dependencies.lyon_path] +version = "1" +optional = true diff --git a/native/src/lib.rs b/native/src/lib.rs index 27b6fc0dc4..c98827e76f 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -33,7 +33,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, @@ -79,6 +79,7 @@ mod debug; mod debug; pub use iced_core::alignment; +pub use iced_core::gradient; pub use iced_core::time; pub use iced_core::{ color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels, @@ -97,6 +98,7 @@ pub use debug::Debug; pub use element::Element; pub use event::Event; pub use font::Font; +pub use gradient::Gradient; pub use hasher::Hasher; pub use layout::Layout; pub use overlay::Overlay; diff --git a/native/src/widget.rs b/native/src/widget.rs index 2b3ca7be07..27330894a6 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -83,6 +83,22 @@ pub use tree::Tree; #[doc(no_inline)] pub use vertical_slider::VerticalSlider; +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; + pub use action::Action; pub use id::Id; pub use operation::Operation; diff --git a/graphics/src/widget/canvas.rs b/native/src/widget/canvas.rs similarity index 73% rename from graphics/src/widget/canvas.rs rename to native/src/widget/canvas.rs index a8d050f543..8a9addd285 100644 --- a/graphics/src/widget/canvas.rs +++ b/native/src/widget/canvas.rs @@ -8,34 +8,26 @@ pub mod fill; pub mod path; pub mod stroke; -mod cache; mod cursor; -mod frame; -mod geometry; mod program; mod style; mod text; pub use crate::gradient::{self, Gradient}; -pub use cache::Cache; pub use cursor::Cursor; pub use event::Event; -pub use fill::{Fill, FillRule}; -pub use frame::Frame; -pub use geometry::Geometry; +pub use fill::Fill; pub use path::Path; pub use program::Program; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; pub use text::Text; -use crate::{Backend, Primitive, Renderer}; - -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::{ +use crate::layout::{self, Layout}; +use crate::mouse; +use crate::renderer; +use crate::widget::tree::{self, Tree}; +use crate::{ Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget, }; @@ -85,20 +77,22 @@ use std::marker::PhantomData; /// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas +pub struct Canvas where - P: Program, + Renderer: self::Renderer, + P: Program, { width: Length, height: Length, program: P, message_: PhantomData, - theme_: PhantomData, + theme_: PhantomData, } -impl Canvas +impl Canvas where - P: Program, + Renderer: self::Renderer, + P: Program, { const DEFAULT_SIZE: f32 = 100.0; @@ -126,10 +120,11 @@ where } } -impl Widget> for Canvas +impl Widget + for Canvas where - P: Program, - B: Backend, + Renderer: self::Renderer, + P: Program, { fn tag(&self) -> tree::Tag { struct Tag(T); @@ -150,7 +145,7 @@ where fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let limits = limits.width(self.width).height(self.height); @@ -162,23 +157,19 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: crate::Event, layout: Layout<'_>, cursor_position: Point, - _renderer: &Renderer, + _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); let canvas_event = match event { - iced_native::Event::Mouse(mouse_event) => { - Some(Event::Mouse(mouse_event)) - } - iced_native::Event::Touch(touch_event) => { - Some(Event::Touch(touch_event)) - } - iced_native::Event::Keyboard(keyboard_event) => { + crate::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), + crate::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + crate::Event::Keyboard(keyboard_event) => { Some(Event::Keyboard(keyboard_event)) } _ => None, @@ -208,7 +199,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, - _renderer: &Renderer, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let cursor = Cursor::from_window_position(cursor_position); @@ -220,49 +211,49 @@ where fn draw( &self, tree: &Tree, - renderer: &mut Renderer, - theme: &T, + renderer: &mut Renderer, + theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, ) { - use iced_native::Renderer as _; - let bounds = layout.bounds(); if bounds.width < 1.0 || bounds.height < 1.0 { return; } - let translation = Vector::new(bounds.x, bounds.y); let cursor = Cursor::from_window_position(cursor_position); let state = tree.state.downcast_ref::(); - renderer.with_translation(translation, |renderer| { - renderer.draw_primitive(Primitive::Group { - primitives: self - .program - .draw(state, theme, bounds, cursor) - .into_iter() - .map(Geometry::into_primitive) - .collect(), - }); - }); + renderer.with_translation( + Vector::new(bounds.x, bounds.y), + |renderer| { + renderer.draw( + self.program.draw(state, renderer, theme, bounds, cursor), + ); + }, + ); } } -impl<'a, Message, P, B, T> From> - for Element<'a, Message, Renderer> +impl<'a, Message, Renderer, P> From> + for Element<'a, Message, Renderer> where Message: 'a, - P: Program + 'a, - B: Backend, - T: 'a, + Renderer: 'a + self::Renderer, + P: Program + 'a, { fn from( - canvas: Canvas, - ) -> Element<'a, Message, Renderer> { + canvas: Canvas, + ) -> Element<'a, Message, Renderer> { Element::new(canvas) } } + +pub trait Renderer: crate::Renderer { + type Geometry; + + fn draw(&mut self, geometry: Vec); +} diff --git a/graphics/src/widget/canvas/cursor.rs b/native/src/widget/canvas/cursor.rs similarity index 98% rename from graphics/src/widget/canvas/cursor.rs rename to native/src/widget/canvas/cursor.rs index 9588d12990..ef6a7771f0 100644 --- a/graphics/src/widget/canvas/cursor.rs +++ b/native/src/widget/canvas/cursor.rs @@ -1,4 +1,4 @@ -use iced_native::{Point, Rectangle}; +use crate::{Point, Rectangle}; /// The mouse cursor state. #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/graphics/src/widget/canvas/event.rs b/native/src/widget/canvas/event.rs similarity index 73% rename from graphics/src/widget/canvas/event.rs rename to native/src/widget/canvas/event.rs index 7c733a4d8a..1d726577aa 100644 --- a/graphics/src/widget/canvas/event.rs +++ b/native/src/widget/canvas/event.rs @@ -1,9 +1,9 @@ //! Handle events of a canvas. -use iced_native::keyboard; -use iced_native::mouse; -use iced_native::touch; +use crate::keyboard; +use crate::mouse; +use crate::touch; -pub use iced_native::event::Status; +pub use crate::event::Status; /// A [`Canvas`] event. /// diff --git a/graphics/src/widget/canvas/fill.rs b/native/src/widget/canvas/fill.rs similarity index 77% rename from graphics/src/widget/canvas/fill.rs rename to native/src/widget/canvas/fill.rs index e954ebb52b..92b1e47ef2 100644 --- a/graphics/src/widget/canvas/fill.rs +++ b/native/src/widget/canvas/fill.rs @@ -1,5 +1,6 @@ //! Fill [crate::widget::canvas::Geometry] with a certain style. -use crate::{Color, Gradient}; +use crate::widget::canvas::Gradient; +use crate::Color; pub use crate::widget::canvas::Style; @@ -19,14 +20,14 @@ pub struct Fill { /// By default, it is set to `NonZero`. /// /// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty - pub rule: FillRule, + pub rule: Rule, } impl Default for Fill { fn default() -> Self { Self { style: Style::Solid(Color::BLACK), - rule: FillRule::NonZero, + rule: Rule::NonZero, } } } @@ -57,16 +58,7 @@ impl From for Fill { /// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(missing_docs)] -pub enum FillRule { +pub enum Rule { NonZero, EvenOdd, } - -impl From for lyon::tessellation::FillRule { - fn from(rule: FillRule) -> lyon::tessellation::FillRule { - match rule { - FillRule::NonZero => lyon::tessellation::FillRule::NonZero, - FillRule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, - } - } -} diff --git a/graphics/src/widget/canvas/path.rs b/native/src/widget/canvas/path.rs similarity index 50% rename from graphics/src/widget/canvas/path.rs rename to native/src/widget/canvas/path.rs index aeb2589e02..30c387c50d 100644 --- a/graphics/src/widget/canvas/path.rs +++ b/native/src/widget/canvas/path.rs @@ -7,18 +7,16 @@ mod builder; pub use arc::Arc; pub use builder::Builder; -use crate::widget::canvas::LineDash; +pub use lyon_path; -use iced_native::{Point, Size}; -use lyon::algorithms::walk::{walk_along_path, RepeatedPattern, WalkerEvent}; -use lyon::path::iterator::PathIterator; +use crate::{Point, Size}; /// An immutable set of points that may or may not be connected. /// /// A single [`Path`] can represent different kinds of 2D shapes! #[derive(Debug, Clone)] pub struct Path { - raw: lyon::path::Path, + raw: lyon_path::Path, } impl Path { @@ -56,54 +54,14 @@ impl Path { } #[inline] - pub(crate) fn raw(&self) -> &lyon::path::Path { + pub fn raw(&self) -> &lyon_path::Path { &self.raw } #[inline] - pub(crate) fn transformed( - &self, - transform: &lyon::math::Transform, - ) -> Path { + pub fn transform(&self, transform: &lyon_path::math::Transform) -> Path { Path { raw: self.raw.clone().transformed(transform), } } } - -pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { - Path::new(|builder| { - let segments_odd = (line_dash.segments.len() % 2 == 1) - .then(|| [line_dash.segments, line_dash.segments].concat()); - - let mut draw_line = false; - - walk_along_path( - path.raw().iter().flattened(0.01), - 0.0, - lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, - &mut RepeatedPattern { - callback: |event: WalkerEvent<'_>| { - let point = Point { - x: event.position.x, - y: event.position.y, - }; - - if draw_line { - builder.line_to(point); - } else { - builder.move_to(point); - } - - draw_line = !draw_line; - - true - }, - index: line_dash.offset, - intervals: segments_odd - .as_deref() - .unwrap_or(line_dash.segments), - }, - ); - }) -} diff --git a/graphics/src/widget/canvas/path/arc.rs b/native/src/widget/canvas/path/arc.rs similarity index 97% rename from graphics/src/widget/canvas/path/arc.rs rename to native/src/widget/canvas/path/arc.rs index b8e72dafa1..e0747d3e07 100644 --- a/graphics/src/widget/canvas/path/arc.rs +++ b/native/src/widget/canvas/path/arc.rs @@ -1,5 +1,5 @@ //! Build and draw curves. -use iced_native::{Point, Vector}; +use crate::{Point, Vector}; /// A segment of a differentiable curve. #[derive(Debug, Clone, Copy)] diff --git a/graphics/src/widget/canvas/path/builder.rs b/native/src/widget/canvas/path/builder.rs similarity index 91% rename from graphics/src/widget/canvas/path/builder.rs rename to native/src/widget/canvas/path/builder.rs index 5121aa6861..84fda05227 100644 --- a/graphics/src/widget/canvas/path/builder.rs +++ b/native/src/widget/canvas/path/builder.rs @@ -1,35 +1,37 @@ use crate::widget::canvas::path::{arc, Arc, Path}; +use crate::{Point, Size}; -use iced_native::{Point, Size}; -use lyon::path::builder::SvgPathBuilder; +use lyon_path::builder::{self, SvgPathBuilder}; +use lyon_path::geom; +use lyon_path::math; /// A [`Path`] builder. /// /// Once a [`Path`] is built, it can no longer be mutated. #[allow(missing_debug_implementations)] pub struct Builder { - raw: lyon::path::builder::WithSvg, + raw: builder::WithSvg, } impl Builder { /// Creates a new [`Builder`]. pub fn new() -> Builder { Builder { - raw: lyon::path::Path::builder().with_svg(), + raw: lyon_path::Path::builder().with_svg(), } } /// Moves the starting point of a new sub-path to the given `Point`. #[inline] pub fn move_to(&mut self, point: Point) { - let _ = self.raw.move_to(lyon::math::Point::new(point.x, point.y)); + let _ = self.raw.move_to(math::Point::new(point.x, point.y)); } /// Connects the last point in the [`Path`] to the given `Point` with a /// straight line. #[inline] pub fn line_to(&mut self, point: Point) { - let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y)); + let _ = self.raw.line_to(math::Point::new(point.x, point.y)); } /// Adds an [`Arc`] to the [`Path`] from `start_angle` to `end_angle` in @@ -53,8 +55,6 @@ impl Builder { /// See [the HTML5 specification of `arcTo`](https://html.spec.whatwg.org/multipage/canvas.html#building-paths:dom-context-2d-arcto) /// for more details and examples. pub fn arc_to(&mut self, a: Point, b: Point, radius: f32) { - use lyon::{math, path}; - let start = self.raw.current_position(); let mid = math::Point::new(a.x, a.y); let end = math::Point::new(b.x, b.y); @@ -92,7 +92,7 @@ impl Builder { self.raw.arc_to( math::Vector::new(radius, radius), math::Angle::radians(0.0), - path::ArcFlags { + lyon_path::ArcFlags { large_arc: false, sweep, }, @@ -102,8 +102,6 @@ impl Builder { /// Adds an ellipse to the [`Path`] using a clockwise direction. pub fn ellipse(&mut self, arc: arc::Elliptical) { - use lyon::{geom, math}; - let arc = geom::Arc { center: math::Point::new(arc.center.x, arc.center.y), radii: math::Vector::new(arc.radii.x, arc.radii.y), @@ -128,8 +126,6 @@ impl Builder { control_b: Point, to: Point, ) { - use lyon::math; - let _ = self.raw.cubic_bezier_to( math::Point::new(control_a.x, control_a.y), math::Point::new(control_b.x, control_b.y), @@ -141,8 +137,6 @@ impl Builder { /// and its end point. #[inline] pub fn quadratic_curve_to(&mut self, control: Point, to: Point) { - use lyon::math; - let _ = self.raw.quadratic_bezier_to( math::Point::new(control.x, control.y), math::Point::new(to.x, to.y), diff --git a/graphics/src/widget/canvas/program.rs b/native/src/widget/canvas/program.rs similarity index 83% rename from graphics/src/widget/canvas/program.rs rename to native/src/widget/canvas/program.rs index 656dbfa627..17a5a13731 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/native/src/widget/canvas/program.rs @@ -1,6 +1,6 @@ use crate::widget::canvas::event::{self, Event}; use crate::widget::canvas::mouse; -use crate::widget::canvas::{Cursor, Geometry}; +use crate::widget::canvas::{Cursor, Renderer}; use crate::Rectangle; /// The state and logic of a [`Canvas`]. @@ -9,7 +9,10 @@ use crate::Rectangle; /// application. /// /// [`Canvas`]: crate::widget::Canvas -pub trait Program { +pub trait Program +where + Renderer: self::Renderer, +{ /// The internal state mutated by the [`Program`]. type State: Default + 'static; @@ -44,10 +47,11 @@ pub trait Program { fn draw( &self, state: &Self::State, - theme: &Theme, + renderer: &Renderer, + theme: &Renderer::Theme, bounds: Rectangle, cursor: Cursor, - ) -> Vec; + ) -> Vec; /// Returns the current mouse interaction of the [`Program`]. /// @@ -65,9 +69,10 @@ pub trait Program { } } -impl Program for &T +impl Program for &T where - T: Program, + Renderer: self::Renderer, + T: Program, { type State = T::State; @@ -84,11 +89,12 @@ where fn draw( &self, state: &Self::State, - theme: &Theme, + renderer: &Renderer, + theme: &Renderer::Theme, bounds: Rectangle, cursor: Cursor, - ) -> Vec { - T::draw(self, state, theme, bounds, cursor) + ) -> Vec { + T::draw(self, state, renderer, theme, bounds, cursor) } fn mouse_interaction( diff --git a/graphics/src/widget/canvas/stroke.rs b/native/src/widget/canvas/stroke.rs similarity index 79% rename from graphics/src/widget/canvas/stroke.rs rename to native/src/widget/canvas/stroke.rs index 4c19251d79..ab4727b269 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/native/src/widget/canvas/stroke.rs @@ -1,7 +1,7 @@ //! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. pub use crate::widget::canvas::Style; -use iced_native::Color; +use crate::Color; /// The style of a stroke. #[derive(Debug, Clone)] @@ -77,16 +77,6 @@ impl Default for LineCap { } } -impl From for lyon::tessellation::LineCap { - fn from(line_cap: LineCap) -> lyon::tessellation::LineCap { - match line_cap { - LineCap::Butt => lyon::tessellation::LineCap::Butt, - LineCap::Square => lyon::tessellation::LineCap::Square, - LineCap::Round => lyon::tessellation::LineCap::Round, - } - } -} - /// The shape used at the corners of paths or basic shapes when they are /// stroked. #[derive(Debug, Clone, Copy)] @@ -105,16 +95,6 @@ impl Default for LineJoin { } } -impl From for lyon::tessellation::LineJoin { - fn from(line_join: LineJoin) -> lyon::tessellation::LineJoin { - match line_join { - LineJoin::Miter => lyon::tessellation::LineJoin::Miter, - LineJoin::Round => lyon::tessellation::LineJoin::Round, - LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, - } - } -} - /// The dash pattern used when stroking the line. #[derive(Debug, Clone, Copy, Default)] pub struct LineDash<'a> { diff --git a/graphics/src/widget/canvas/style.rs b/native/src/widget/canvas/style.rs similarity index 88% rename from graphics/src/widget/canvas/style.rs rename to native/src/widget/canvas/style.rs index 6794f2e753..2642fdb858 100644 --- a/graphics/src/widget/canvas/style.rs +++ b/native/src/widget/canvas/style.rs @@ -1,4 +1,5 @@ -use crate::{Color, Gradient}; +use crate::widget::canvas::Gradient; +use crate::Color; /// The coloring style of some drawing. #[derive(Debug, Clone, PartialEq)] diff --git a/graphics/src/widget/canvas/text.rs b/native/src/widget/canvas/text.rs similarity index 100% rename from graphics/src/widget/canvas/text.rs rename to native/src/widget/canvas/text.rs diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 1f21f06bb1..429b55c261 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -6,12 +6,13 @@ edition = "2021" [features] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] +canvas = ["iced_wgpu/canvas", "iced_tiny_skia/canvas"] +qr_code = ["canvas", "qrcode"] tracing = ["iced_wgpu/tracing"] [dependencies] raw-window-handle = "0.5" +thiserror = "1" [dependencies.iced_native] version = "0.9" @@ -30,3 +31,8 @@ iced_wgpu = { version = "0.9", path = "../wgpu" } [target.'cfg(target_arch = "wasm32")'.dependencies] iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } + +[dependencies.qrcode] +version = "0.12" +optional = true +default-features = false diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index b0a409dc76..6c0b4e5c62 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -1,4 +1,4 @@ -use crate::{Font, Point, Size}; +use crate::{Font, Geometry, Point, Size}; use iced_graphics::backend; use iced_graphics::text; @@ -12,6 +12,8 @@ pub enum Backend { } impl iced_graphics::Backend for Backend { + type Geometry = Geometry; + fn trim_measurements(&mut self) { match self { Self::Wgpu(backend) => backend.trim_measurements(), diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index f9bfc37349..d9c85e82c4 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -4,11 +4,14 @@ pub mod window; mod backend; mod settings; +pub use iced_graphics::primitive; + pub use backend::Backend; +pub use primitive::Primitive; pub use settings::Settings; pub use iced_graphics::{ - Antialiasing, Color, Error, Font, Point, Size, Viewport, + Antialiasing, Color, Error, Font, Point, Rectangle, Size, Vector, Viewport, }; /// The default graphics renderer for [`iced`]. @@ -16,3 +19,12 @@ pub use iced_graphics::{ /// [`iced`]: https://github.com/iced-rs/iced pub type Renderer = iced_graphics::Renderer; + +#[derive(Debug, Clone)] +pub struct Geometry(pub(crate) Primitive); + +impl From for Primitive { + fn from(geometry: Geometry) -> Self { + geometry.0 + } +} diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs index 417cc06fad..6c0c2a833f 100644 --- a/renderer/src/widget.rs +++ b/renderer/src/widget.rs @@ -1,5 +1,11 @@ -#[cfg(feature = "qr_code")] -pub use iced_graphics::widget::qr_code; +#[cfg(feature = "canvas")] +pub mod canvas; #[cfg(feature = "canvas")] -pub use iced_graphics::widget::canvas; +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +pub use qr_code::QRCode; diff --git a/renderer/src/widget/canvas.rs b/renderer/src/widget/canvas.rs new file mode 100644 index 0000000000..f40a109707 --- /dev/null +++ b/renderer/src/widget/canvas.rs @@ -0,0 +1,177 @@ +mod cache; + +pub use cache::Cache; + +pub use iced_native::widget::canvas::event::{self, Event}; +pub use iced_native::widget::canvas::fill::{self, Fill}; +pub use iced_native::widget::canvas::gradient::{self, Gradient}; +pub use iced_native::widget::canvas::path::{self, Path}; +pub use iced_native::widget::canvas::stroke::{self, Stroke}; +pub use iced_native::widget::canvas::{ + Canvas, Cursor, LineCap, LineDash, LineJoin, Program, Renderer, Style, Text, +}; + +use crate::{Backend, Point, Rectangle, Size, Vector}; + +pub use crate::Geometry; + +pub enum Frame { + Wgpu(iced_wgpu::widget::canvas::Frame), + TinySkia(iced_tiny_skia::canvas::Frame), +} + +macro_rules! delegate { + ($frame:expr, $name:ident => $body:expr) => { + match $frame { + Self::Wgpu($name) => $body, + Self::TinySkia($name) => $body, + } + }; +} + +impl Frame { + pub fn new(renderer: &crate::Renderer, size: Size) -> Self { + match renderer.backend() { + Backend::Wgpu(_) => { + Frame::Wgpu(iced_wgpu::widget::canvas::Frame::new(size)) + } + Backend::TinySkia(_) => { + Frame::TinySkia(iced_tiny_skia::canvas::Frame::new(size)) + } + } + } + + /// Returns the width of the [`Frame`]. + #[inline] + pub fn width(&self) -> f32 { + delegate!(self, frame => frame.width()) + } + + /// Returns the height of the [`Frame`]. + #[inline] + pub fn height(&self) -> f32 { + delegate!(self, frame => frame.height()) + } + + /// Returns the dimensions of the [`Frame`]. + #[inline] + pub fn size(&self) -> Size { + delegate!(self, frame => frame.size()) + } + + /// Returns the coordinate of the center of the [`Frame`]. + #[inline] + pub fn center(&self) -> Point { + delegate!(self, frame => frame.center()) + } + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + pub fn fill(&mut self, path: &Path, fill: impl Into) { + delegate!(self, frame => frame.fill(path, fill)); + } + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + delegate!(self, frame => frame.fill_rectangle(top_left, size, fill)); + } + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + delegate!(self, frame => frame.stroke(path, stroke)); + } + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + /// + /// [`Canvas`]: crate::widget::Canvas + pub fn fill_text(&mut self, text: impl Into) { + delegate!(self, frame => frame.fill_text(text)); + } + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { + delegate!(self, frame => frame.push_transform()); + + f(self); + + delegate!(self, frame => frame.pop_transform()); + } + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { + let mut frame = match self { + Self::Wgpu(_) => { + Self::Wgpu(iced_wgpu::widget::canvas::Frame::new(region.size())) + } + Self::TinySkia(_) => Self::TinySkia( + iced_tiny_skia::canvas::Frame::new(region.size()), + ), + }; + + f(&mut frame); + + let translation = Vector::new(region.x, region.y); + + match (self, frame) { + (Self::Wgpu(target), Self::Wgpu(frame)) => { + target.clip(frame, translation); + } + (Self::TinySkia(target), Self::TinySkia(frame)) => { + target.clip(frame, translation); + } + _ => unreachable!(), + }; + } + + /// Applies a translation to the current transform of the [`Frame`]. + #[inline] + pub fn translate(&mut self, translation: Vector) { + delegate!(self, frame => frame.translate(translation)); + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + #[inline] + pub fn rotate(&mut self, angle: f32) { + delegate!(self, frame => frame.rotate(angle)); + } + + /// Applies a scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale(&mut self, scale: f32) { + delegate!(self, frame => frame.scale(scale)); + } + + pub fn into_geometry(self) -> Geometry { + Geometry(delegate!(self, frame => frame.into_primitive())) + } +} diff --git a/renderer/src/widget/canvas/cache.rs b/renderer/src/widget/canvas/cache.rs new file mode 100644 index 0000000000..7d6b4811e7 --- /dev/null +++ b/renderer/src/widget/canvas/cache.rs @@ -0,0 +1,85 @@ +use crate::widget::canvas::{Frame, Geometry}; +use crate::{Primitive, Renderer, Size}; + +use std::cell::RefCell; +use std::sync::Arc; + +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +#[derive(Debug, Default)] +pub struct Cache { + state: RefCell, +} + +#[derive(Debug, Default)] +enum State { + #[default] + Empty, + Filled { + bounds: Size, + primitive: Arc, + }, +} + +impl Cache { + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: Default::default(), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw( + &self, + renderer: &Renderer, + bounds: Size, + draw_fn: impl FnOnce(&mut Frame), + ) -> Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + primitive, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry(Primitive::Cache { + content: primitive.clone(), + }); + } + } + + let mut frame = Frame::new(renderer, bounds); + draw_fn(&mut frame); + + let primitive = { + let geometry = frame.into_geometry(); + + Arc::new(geometry.0) + }; + + *self.state.borrow_mut() = State::Filled { + bounds, + primitive: primitive.clone(), + }; + + Geometry(Primitive::Cache { content: primitive }) + } +} diff --git a/graphics/src/widget/qr_code.rs b/renderer/src/widget/qr_code.rs similarity index 81% rename from graphics/src/widget/qr_code.rs rename to renderer/src/widget/qr_code.rs index 12ce5b1f90..aae4ec8863 100644 --- a/graphics/src/widget/qr_code.rs +++ b/renderer/src/widget/qr_code.rs @@ -1,7 +1,8 @@ //! Encode and display information in a QR code. -use crate::renderer::{self, Renderer}; use crate::widget::canvas; -use crate::Backend; +use crate::Renderer; + +use iced_graphics::renderer; use iced_native::layout; use iced_native::widget::Tree; @@ -48,10 +49,7 @@ impl<'a> QRCode<'a> { } } -impl<'a, Message, B, T> Widget> for QRCode<'a> -where - B: Backend, -{ +impl<'a, Message, Theme> Widget> for QRCode<'a> { fn width(&self) -> Length { Length::Shrink } @@ -62,7 +60,7 @@ where fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 @@ -74,8 +72,8 @@ where fn draw( &self, _state: &Tree, - renderer: &mut Renderer, - _theme: &T, + renderer: &mut Renderer, + _theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, @@ -87,50 +85,52 @@ where let side_length = self.state.width + 2 * QUIET_ZONE; // Reuse cache if possible - let geometry = self.state.cache.draw(bounds.size(), |frame| { - // Scale units to cell size - frame.scale(f32::from(self.cell_size)); - - // Draw background - frame.fill_rectangle( - Point::ORIGIN, - Size::new(side_length as f32, side_length as f32), - self.light, - ); - - // Avoid drawing on the quiet zone - frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32)); - - // Draw contents - self.state - .contents - .iter() - .enumerate() - .filter(|(_, value)| **value == qrcode::Color::Dark) - .for_each(|(index, _)| { - let row = index / self.state.width; - let column = index % self.state.width; - - frame.fill_rectangle( - Point::new(column as f32, row as f32), - Size::UNIT, - self.dark, - ); - }); - }); + let geometry = + self.state.cache.draw(renderer, bounds.size(), |frame| { + // Scale units to cell size + frame.scale(f32::from(self.cell_size)); + + // Draw background + frame.fill_rectangle( + Point::ORIGIN, + Size::new(side_length as f32, side_length as f32), + self.light, + ); + + // Avoid drawing on the quiet zone + frame.translate(Vector::new( + QUIET_ZONE as f32, + QUIET_ZONE as f32, + )); + + // Draw contents + self.state + .contents + .iter() + .enumerate() + .filter(|(_, value)| **value == qrcode::Color::Dark) + .for_each(|(index, _)| { + let row = index / self.state.width; + let column = index % self.state.width; + + frame.fill_rectangle( + Point::new(column as f32, row as f32), + Size::UNIT, + self.dark, + ); + }); + }); let translation = Vector::new(bounds.x, bounds.y); renderer.with_translation(translation, |renderer| { - renderer.draw_primitive(geometry.into_primitive()); + renderer.draw_primitive(geometry.0); }); } } -impl<'a, Message, B, T> From> - for Element<'a, Message, Renderer> -where - B: Backend, +impl<'a, Message, Theme> From> + for Element<'a, Message, Renderer> { fn from(qr_code: QRCode<'a>) -> Self { Self::new(qr_code) diff --git a/src/widget.rs b/src/widget.rs index 04cb0e50cc..f3a66101f7 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -170,9 +170,10 @@ pub use iced_renderer::widget::canvas; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] /// Creates a new [`Canvas`]. -pub fn canvas(program: P) -> Canvas +pub fn canvas(program: P) -> Canvas where - P: canvas::Program, + Renderer: canvas::Renderer, + P: canvas::Program, { Canvas::new(program) } diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 781e7d3458..5f39fce28f 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [features] image = [] svg = [] +canvas = ["iced_native/canvas"] [dependencies] raw-window-handle = "0.5" diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 66d832210b..e08cede703 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,9 +1,9 @@ -use crate::{Color, Font, Settings, Size, Viewport}; +use crate::{Color, Font, Primitive, Settings, Size, Viewport}; use iced_graphics::alignment; use iced_graphics::backend; use iced_graphics::text; -use iced_graphics::{Background, Primitive, Rectangle, Vector}; +use iced_graphics::{Background, Rectangle, Vector}; use std::borrow::Cow; @@ -81,7 +81,6 @@ impl Backend { translation: Vector, ) { match primitive { - Primitive::None => {} Primitive::Quad { bounds, background, @@ -161,6 +160,38 @@ impl Backend { Primitive::Svg { .. } => { // TODO } + Primitive::Fill { + path, + paint, + rule, + transform, + } => { + pixels.fill_path( + path, + paint, + *rule, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_mask, + ); + } + Primitive::Stroke { + path, + paint, + stroke, + transform, + } => { + pixels.stroke_path( + path, + paint, + stroke, + transform + .post_translate(translation.x, translation.y) + .post_scale(scale_factor, scale_factor), + clip_mask, + ); + } Primitive::Group { primitives } => { for primitive in primitives { self.draw_primitive( @@ -196,16 +227,19 @@ impl Backend { translation, ); } - Primitive::Cached { cache } => { + Primitive::Cache { content } => { self.draw_primitive( - cache, + content, pixels, clip_mask, scale_factor, translation, ); } - Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {} + Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => { + // Not supported! + // TODO: Draw a placeholder (?) / Log it (?) + } } } } @@ -386,6 +420,8 @@ fn rectangular_clip_mask( } impl iced_graphics::Backend for Backend { + type Geometry = (); + fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); } diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs new file mode 100644 index 0000000000..c3b8b31626 --- /dev/null +++ b/tiny_skia/src/canvas.rs @@ -0,0 +1,276 @@ +use crate::{Point, Primitive, Rectangle, Size, Vector}; + +use iced_native::widget::canvas::fill::{self, Fill}; +use iced_native::widget::canvas::stroke::{self, Stroke}; +use iced_native::widget::canvas::{Path, Style, Text}; +use iced_native::Gradient; + +pub struct Frame { + size: Size, + transform: tiny_skia::Transform, + stack: Vec, + primitives: Vec, +} + +impl Frame { + pub fn new(size: Size) -> Self { + Self { + size, + transform: tiny_skia::Transform::identity(), + stack: Vec::new(), + primitives: Vec::new(), + } + } + + pub fn width(&self) -> f32 { + self.size.width + } + + pub fn height(&self) -> f32 { + self.size.height + } + + pub fn size(&self) -> Size { + self.size + } + + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + pub fn fill(&mut self, path: &Path, fill: impl Into) { + let path = convert_path(path); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: into_paint(fill.style), + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); + } + + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + self.fill(&Path::rectangle(top_left, size), fill); + } + + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + let path = convert_path(path); + let stroke = stroke.into(); + let skia_stroke = into_stroke(&stroke); + + self.primitives.push(Primitive::Stroke { + path, + paint: into_paint(stroke.style), + stroke: skia_stroke, + transform: self.transform, + }); + } + + pub fn fill_text(&mut self, text: impl Into) { + let text = text.into(); + + let position = if self.transform.is_identity() { + text.position + } else { + let mut transformed = [tiny_skia::Point { + x: text.position.x, + y: text.position.y, + }]; + + self.transform.map_points(&mut transformed); + + Point::new(transformed[0].x, transformed[0].y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + pub fn push_transform(&mut self) { + self.stack.push(self.transform); + } + + pub fn pop_transform(&mut self) { + self.transform = self.stack.pop().expect("Pop transform"); + } + + pub fn clip(&mut self, _frame: Self, _translation: Vector) {} + + pub fn translate(&mut self, translation: Vector) { + self.transform = + self.transform.pre_translate(translation.x, translation.y); + } + + pub fn rotate(&mut self, angle: f32) { + self.transform = self + .transform + .pre_concat(tiny_skia::Transform::from_rotate(angle.to_degrees())); + } + + pub fn scale(&mut self, scale: f32) { + self.transform = self.transform.pre_scale(scale, scale); + } + + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +fn convert_path(path: &Path) -> tiny_skia::Path { + use iced_native::widget::canvas::path::lyon_path; + + let mut builder = tiny_skia::PathBuilder::new(); + let mut last_point = Default::default(); + + for event in path.raw().iter() { + match event { + lyon_path::Event::Begin { at } => { + builder.move_to(at.x, at.y); + + last_point = at; + } + lyon_path::Event::Line { from, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.line_to(to.x, to.y); + + last_point = to; + } + lyon_path::Event::Quadratic { from, ctrl, to } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder.quad_to(ctrl.x, ctrl.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::Cubic { + from, + ctrl1, + ctrl2, + to, + } => { + if last_point != from { + builder.move_to(from.x, from.y); + } + + builder + .cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y); + + last_point = to; + } + lyon_path::Event::End { close, .. } => { + if close { + builder.close(); + } + } + } + } + + builder + .finish() + .expect("Convert lyon path to tiny_skia path") +} + +pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { + tiny_skia::Paint { + shader: match style { + Style::Solid(color) => tiny_skia::Shader::SolidColor( + tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) + .expect("Create color"), + ), + Style::Gradient(gradient) => match gradient { + Gradient::Linear(linear) => tiny_skia::LinearGradient::new( + tiny_skia::Point { + x: linear.start.x, + y: linear.start.y, + }, + tiny_skia::Point { + x: linear.end.x, + y: linear.end.y, + }, + linear + .color_stops + .into_iter() + .map(|stop| { + tiny_skia::GradientStop::new( + stop.offset, + tiny_skia::Color::from_rgba( + stop.color.b, + stop.color.g, + stop.color.r, + stop.color.a, + ) + .expect("Create color"), + ) + }) + .collect(), + tiny_skia::SpreadMode::Pad, + tiny_skia::Transform::identity(), + ) + .expect("Create linear gradient"), + }, + }, + anti_alias: true, + ..Default::default() + } +} + +pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { + match rule { + fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd, + fill::Rule::NonZero => tiny_skia::FillRule::Winding, + } +} + +pub fn into_stroke(stroke: &Stroke) -> tiny_skia::Stroke { + tiny_skia::Stroke { + width: stroke.width, + line_cap: match stroke.line_cap { + stroke::LineCap::Butt => tiny_skia::LineCap::Butt, + stroke::LineCap::Square => tiny_skia::LineCap::Square, + stroke::LineCap::Round => tiny_skia::LineCap::Round, + }, + line_join: match stroke.line_join { + stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter, + stroke::LineJoin::Round => tiny_skia::LineJoin::Round, + stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel, + }, + dash: if stroke.line_dash.segments.is_empty() { + None + } else { + tiny_skia::StrokeDash::new( + stroke.line_dash.segments.into(), + stroke.line_dash.offset as f32, + ) + }, + ..Default::default() + } +} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 420a1ffbeb..e66e6412fa 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -4,10 +4,18 @@ mod backend; mod settings; mod text; +#[cfg(feature = "canvas")] +pub mod canvas; + +pub use iced_graphics::primitive; + pub use backend::Backend; +pub use primitive::Primitive; pub use settings::Settings; -pub use iced_graphics::{Color, Error, Font, Point, Size, Vector, Viewport}; +pub use iced_graphics::{ + Color, Error, Font, Point, Rectangle, Size, Vector, Viewport, +}; /// A [`tiny-skia`] graphics renderer for [`iced`]. /// diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs new file mode 100644 index 0000000000..22daaedc57 --- /dev/null +++ b/tiny_skia/src/primitive.rs @@ -0,0 +1,82 @@ +use crate::{Rectangle, Vector}; + +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub enum Primitive { + /// A group of primitives + Group { + /// The primitives of the group + primitives: Vec, + }, + /// A clip primitive + Clip { + /// The bounds of the clip + bounds: Rectangle, + /// The content of the clip + content: Box, + }, + /// A primitive that applies a translation + Translate { + /// The translation vector + translation: Vector, + + /// The primitive to translate + content: Box, + }, + /// A cached primitive. + /// + /// This can be useful if you are implementing a widget where primitive + /// generation is expensive. + Cached { + /// The cached primitive + cache: Arc, + }, + /// A basic primitive. + Basic(iced_graphics::Primitive), +} + +impl iced_graphics::backend::Primitive for Primitive { + fn translate(self, translation: Vector) -> Self { + Self::Translate { + translation, + content: Box::new(self), + } + } + + fn clip(self, bounds: Rectangle) -> Self { + Self::Clip { + bounds, + content: Box::new(self), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct Recording(pub(crate) Vec); + +impl iced_graphics::backend::Recording for Recording { + type Primitive = Primitive; + + fn push(&mut self, primitive: Primitive) { + self.0.push(primitive); + } + + fn push_basic(&mut self, basic: iced_graphics::Primitive) { + self.0.push(Primitive::Basic(basic)); + } + + fn group(self) -> Self::Primitive { + Primitive::Group { primitives: self.0 } + } + + fn clear(&mut self) { + self.0.clear(); + } +} + +impl Recording { + pub fn primitives(&self) -> &[Primitive] { + &self.0 + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 2bd5831e38..08159cd801 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,7 +1,6 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; use iced_graphics::window::compositor::{self, Information, SurfaceError}; -use iced_graphics::Primitive; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 632873a370..0bcef71c29 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -21,8 +21,7 @@ bmp = ["iced_graphics/bmp"] hdr = ["iced_graphics/hdr"] dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] +canvas = ["iced_graphics/canvas", "lyon"] spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] @@ -62,10 +61,6 @@ version = "0.2" git = "https://github.com/hecrj/glyphon.git" rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955" -[dependencies.tracing] -version = "0.1.6" -optional = true - [dependencies.encase] version = "0.3.0" features = ["glam"] @@ -73,6 +68,14 @@ features = ["glam"] [dependencies.glam] version = "0.21.3" +[dependencies.lyon] +version = "1.0" +optional = true + +[dependencies.tracing] +version = "0.1.6" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index e650d9a5eb..10dc5b4ff3 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,11 +1,10 @@ use crate::quad; use crate::text; use crate::triangle; -use crate::{Settings, Transformation}; +use crate::{Layer, Primitive, Settings, Transformation}; use iced_graphics::backend; -use iced_graphics::layer::Layer; -use iced_graphics::{Color, Font, Primitive, Size, Viewport}; +use iced_graphics::{Color, Font, Size, Viewport}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -330,6 +329,8 @@ impl Backend { } impl iced_graphics::Backend for Backend { + type Geometry = (); + fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache() } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index db05d2ffc4..2159a3ec65 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -6,10 +6,10 @@ use iced_graphics::image::raster; #[cfg(feature = "svg")] use iced_graphics::image::vector; +use crate::layer; use crate::{Buffer, Transformation}; use atlas::Atlas; -use iced_graphics::layer; use iced_native::{Rectangle, Size}; use std::cell::RefCell; diff --git a/graphics/src/layer.rs b/wgpu/src/layer.rs similarity index 95% rename from graphics/src/layer.rs rename to wgpu/src/layer.rs index f6eb2fdd00..0840555a94 100644 --- a/graphics/src/layer.rs +++ b/wgpu/src/layer.rs @@ -10,10 +10,11 @@ pub use mesh::Mesh; pub use quad::Quad; pub use text::Text; -use crate::alignment; -use crate::{ - Background, Color, Font, Point, Primitive, Rectangle, Size, Vector, - Viewport, +use crate::Primitive; + +use iced_graphics::alignment; +use iced_graphics::{ + Background, Color, Font, Point, Rectangle, Size, Vector, Viewport, }; /// A group of primitives that should be clipped together. @@ -110,18 +111,6 @@ impl<'a> Layer<'a> { current_layer: usize, ) { match primitive { - Primitive::None => {} - Primitive::Group { primitives } => { - // TODO: Inspect a bit and regroup (?) - for primitive in primitives { - Self::process_primitive( - layers, - translation, - primitive, - current_layer, - ) - } - } Primitive::Text { content, bounds, @@ -167,6 +156,27 @@ impl<'a> Layer<'a> { border_color: border_color.into_linear(), }); } + Primitive::Image { handle, bounds } => { + let layer = &mut layers[current_layer]; + + layer.images.push(Image::Raster { + handle: handle.clone(), + bounds: *bounds + translation, + }); + } + Primitive::Svg { + handle, + color, + bounds, + } => { + let layer = &mut layers[current_layer]; + + layer.images.push(Image::Vector { + handle: handle.clone(), + color: *color, + bounds: *bounds + translation, + }); + } Primitive::SolidMesh { buffers, size } => { let layer = &mut layers[current_layer]; @@ -206,6 +216,17 @@ impl<'a> Layer<'a> { }); } } + Primitive::Group { primitives } => { + // TODO: Inspect a bit and regroup (?) + for primitive in primitives { + Self::process_primitive( + layers, + translation, + primitive, + current_layer, + ) + } + } Primitive::Clip { bounds, content } => { let layer = &mut layers[current_layer]; let translated_bounds = *bounds + translation; @@ -236,34 +257,17 @@ impl<'a> Layer<'a> { current_layer, ); } - Primitive::Cached { cache } => { + Primitive::Cache { content } => { Self::process_primitive( layers, translation, - cache, + content, current_layer, ); } - Primitive::Image { handle, bounds } => { - let layer = &mut layers[current_layer]; - - layer.images.push(Image::Raster { - handle: handle.clone(), - bounds: *bounds + translation, - }); - } - Primitive::Svg { - handle, - color, - bounds, - } => { - let layer = &mut layers[current_layer]; - - layer.images.push(Image::Vector { - handle: handle.clone(), - color: *color, - bounds: *bounds + translation, - }); + Primitive::Fill { .. } | Primitive::Stroke { .. } => { + // Unsupported! + // TODO: Draw a placeholder (?) } } } diff --git a/graphics/src/layer/image.rs b/wgpu/src/layer/image.rs similarity index 100% rename from graphics/src/layer/image.rs rename to wgpu/src/layer/image.rs diff --git a/graphics/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs similarity index 94% rename from graphics/src/layer/mesh.rs rename to wgpu/src/layer/mesh.rs index 7661c5c9c3..5c1e41ad26 100644 --- a/graphics/src/layer/mesh.rs +++ b/wgpu/src/layer/mesh.rs @@ -1,5 +1,5 @@ //! A collection of triangle primitives. -use crate::triangle; +use crate::primitive; use crate::{Gradient, Point, Rectangle}; /// A mesh of triangles. @@ -11,7 +11,7 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a triangle::Mesh2D, + buffers: &'a primitive::Mesh2D, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle, @@ -22,7 +22,7 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a triangle::Mesh2D, + buffers: &'a primitive::Mesh2D, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle, diff --git a/graphics/src/layer/quad.rs b/wgpu/src/layer/quad.rs similarity index 100% rename from graphics/src/layer/quad.rs rename to wgpu/src/layer/quad.rs diff --git a/graphics/src/layer/text.rs b/wgpu/src/layer/text.rs similarity index 100% rename from graphics/src/layer/text.rs rename to wgpu/src/layer/text.rs diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9da4057261..8be12602d0 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -25,7 +25,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -38,7 +38,9 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] +pub mod layer; pub mod settings; +pub mod widget; pub mod window; mod backend; @@ -47,16 +49,23 @@ mod quad; mod text; mod triangle; +pub use iced_graphics::primitive; pub use iced_graphics::{ - Antialiasing, Color, Error, Font, Primitive, Viewport, + Antialiasing, Color, Error, Font, Gradient, Point, Rectangle, Size, Vector, + Viewport, }; +pub use iced_native::alignment; + pub use iced_native::Theme; pub use wgpu; pub use backend::Backend; +pub use layer::Layer; +pub use primitive::Primitive; pub use settings::Settings; -use crate::buffer::Buffer; +use buffer::Buffer; + use iced_graphics::Transformation; #[cfg(any(feature = "image", feature = "svg"))] diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 246cc5e1dd..8a568968b1 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,5 +1,6 @@ +use crate::layer; use crate::{Buffer, Transformation}; -use iced_graphics::layer; + use iced_native::Rectangle; use bytemuck::{Pod, Zeroable}; diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index dea6ab18c6..0dc8a64cae 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,6 +1,7 @@ +use crate::layer::Text; + pub use iced_native::text::Hit; -use iced_graphics::layer::Text; use iced_native::alignment; use iced_native::{Color, Font, Rectangle, Size}; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 4b4fa16da4..706e428258 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,12 +2,12 @@ mod msaa; use crate::buffer::r#static::Buffer; +use crate::layer::mesh::{self, Mesh}; use crate::settings; use crate::Transformation; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::ColoredVertex2D; use iced_graphics::Size; + #[cfg(feature = "tracing")] use tracing::info_span; @@ -468,6 +468,7 @@ mod solid { use crate::settings; use crate::triangle; use encase::ShaderType; + use iced_graphics::primitive; use iced_graphics::Transformation; #[derive(Debug)] @@ -478,7 +479,7 @@ mod solid { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer, + pub vertices: Buffer, pub uniforms: dynamic::Buffer, pub constants: wgpu::BindGroup, } @@ -596,7 +597,7 @@ mod solid { entry_point: "vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< - triangle::ColoredVertex2D, + primitive::ColoredVertex2D, >() as u64, step_mode: wgpu::VertexStepMode::Vertex, @@ -637,7 +638,7 @@ mod gradient { use encase::ShaderType; use glam::{IVec4, Vec4}; - use iced_graphics::triangle::Vertex2D; + use iced_graphics::primitive; #[derive(Debug)] pub struct Pipeline { @@ -647,7 +648,7 @@ mod gradient { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer, + pub vertices: Buffer, pub uniforms: dynamic::Buffer, pub storage: dynamic::Buffer, pub constants: wgpu::BindGroup, @@ -810,34 +811,38 @@ mod gradient { ), }); - let pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::gradient pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() - as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - ), - }], + let pipeline = + device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::triangle::gradient pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::< + primitive::Vertex2D, + >( + ) + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + ), + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[triangle::fragment_target(format)], + }), + primitive: triangle::primitive_state(), + depth_stencil: None, + multisample: triangle::multisample_state(antialiasing), + multiview: None, }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[triangle::fragment_target(format)], - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - }, - ); + ); Self { pipeline, diff --git a/graphics/src/widget.rs b/wgpu/src/widget.rs similarity index 56% rename from graphics/src/widget.rs rename to wgpu/src/widget.rs index e7fab97c17..8d05041e6d 100644 --- a/graphics/src/widget.rs +++ b/wgpu/src/widget.rs @@ -1,4 +1,5 @@ //! Use the graphical widgets supported out-of-the-box. + #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] pub mod canvas; @@ -6,11 +7,3 @@ pub mod canvas; #[cfg(feature = "canvas")] #[doc(no_inline)] pub use canvas::Canvas; - -#[cfg(feature = "qr_code")] -#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub mod qr_code; - -#[cfg(feature = "qr_code")] -#[doc(no_inline)] -pub use qr_code::QRCode; diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs new file mode 100644 index 0000000000..41444fcf45 --- /dev/null +++ b/wgpu/src/widget/canvas.rs @@ -0,0 +1,16 @@ +mod cache; +mod frame; +mod geometry; + +pub use cache::Cache; +pub use frame::Frame; +pub use geometry::Geometry; + +pub use iced_native::widget::canvas::event::{self, Event}; +pub use iced_native::widget::canvas::fill::{self, Fill}; +pub use iced_native::widget::canvas::gradient::{self, Gradient}; +pub use iced_native::widget::canvas::path::{self, Path}; +pub use iced_native::widget::canvas::stroke::{self, Stroke}; +pub use iced_native::widget::canvas::{ + Canvas, Cursor, LineCap, LineDash, LineJoin, Program, Renderer, Style, Text, +}; diff --git a/graphics/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs similarity index 87% rename from graphics/src/widget/canvas/cache.rs rename to wgpu/src/widget/canvas/cache.rs index 52217bbbe0..09b26b900e 100644 --- a/graphics/src/widget/canvas/cache.rs +++ b/wgpu/src/widget/canvas/cache.rs @@ -4,7 +4,9 @@ use crate::Primitive; use iced_native::Size; use std::{cell::RefCell, sync::Arc}; +#[derive(Default)] enum State { + #[default] Empty, Filled { bounds: Size, @@ -12,11 +14,6 @@ enum State { }, } -impl Default for State { - fn default() -> Self { - State::Empty - } -} /// A simple cache that stores generated [`Geometry`] to avoid recomputation. /// /// A [`Cache`] will not redraw its geometry unless the dimensions of its layer @@ -62,8 +59,8 @@ impl Cache { } = self.state.borrow().deref() { if *cached_bounds == bounds { - return Geometry::from_primitive(Primitive::Cached { - cache: primitive.clone(), + return Geometry::from_primitive(Primitive::Cache { + content: primitive.clone(), }); } } @@ -71,18 +68,14 @@ impl Cache { let mut frame = Frame::new(bounds); draw_fn(&mut frame); - let primitive = { - let geometry = frame.into_geometry(); - - Arc::new(geometry.into_primitive()) - }; + let primitive = Arc::new(frame.into_primitive()); *self.state.borrow_mut() = State::Filled { bounds, primitive: primitive.clone(), }; - Geometry::from_primitive(Primitive::Cached { cache: primitive }) + Geometry::from_primitive(Primitive::Cache { content: primitive }) } } diff --git a/graphics/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs similarity index 77% rename from graphics/src/widget/canvas/frame.rs rename to wgpu/src/widget/canvas/frame.rs index d68548ae8d..987570ec00 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,9 +1,10 @@ -use crate::gradient::Gradient; -use crate::triangle; -use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text}; -use crate::Primitive; +use crate::primitive::{self, Primitive}; +use crate::widget::canvas::fill::{self, Fill}; +use crate::widget::canvas::{ + LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +}; -use iced_native::{Point, Rectangle, Size, Vector}; +use iced_native::{Gradient, Point, Rectangle, Size, Vector}; use lyon::geom::euclid; use lyon::tessellation; @@ -23,9 +24,9 @@ pub struct Frame { } enum Buffer { - Solid(tessellation::VertexBuffers), + Solid(tessellation::VertexBuffers), Gradient( - tessellation::VertexBuffers, + tessellation::VertexBuffers, Gradient, ), } @@ -196,8 +197,8 @@ impl Frame { .buffers .get_fill(&self.transforms.current.transform_style(style)); - let options = - tessellation::FillOptions::default().with_fill_rule(rule.into()); + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); if self.transforms.current.is_identity { self.fill_tessellator.tessellate_path( @@ -206,7 +207,7 @@ impl Frame { buffer.as_mut(), ) } else { - let path = path.transformed(&self.transforms.current.raw); + let path = path.transform(&self.transforms.current.raw); self.fill_tessellator.tessellate_path( path.raw(), @@ -241,8 +242,8 @@ impl Frame { lyon::math::Vector::new(size.width, size.height), ); - let options = - tessellation::FillOptions::default().with_fill_rule(rule.into()); + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); self.fill_tessellator .tessellate_rectangle( @@ -264,14 +265,14 @@ impl Frame { let mut options = tessellation::StrokeOptions::default(); options.line_width = stroke.width; - options.start_cap = stroke.line_cap.into(); - options.end_cap = stroke.line_cap.into(); - options.line_join = stroke.line_join.into(); + options.start_cap = into_line_cap(stroke.line_cap); + options.end_cap = into_line_cap(stroke.line_cap); + options.line_join = into_line_join(stroke.line_join); let path = if stroke.line_dash.segments.is_empty() { Cow::Borrowed(path) } else { - Cow::Owned(path::dashed(path, stroke.line_dash)) + Cow::Owned(dashed(path, stroke.line_dash)) }; if self.transforms.current.is_identity { @@ -281,7 +282,7 @@ impl Frame { buffer.as_mut(), ) } else { - let path = path.transformed(&self.transforms.current.raw); + let path = path.transform(&self.transforms.current.raw); self.stroke_tessellator.tessellate_path( path.raw(), @@ -344,10 +345,18 @@ impl Frame { /// operations in different coordinate systems. #[inline] pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { - self.transforms.previous.push(self.transforms.current); + self.push_transform(); f(self); + self.pop_transform(); + } + + pub fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } + + pub fn pop_transform(&mut self) { self.transforms.current = self.transforms.previous.pop().unwrap(); } @@ -363,14 +372,19 @@ impl Frame { f(&mut frame); + let translation = Vector::new(region.x, region.y); + + self.clip(frame, translation); + } + + pub fn clip(&mut self, frame: Frame, translation: Vector) { + let size = frame.size(); let primitives = frame.into_primitives(); let (text, meshes) = primitives .into_iter() .partition(|primitive| matches!(primitive, Primitive::Text { .. })); - let translation = Vector::new(region.x, region.y); - self.primitives.push(Primitive::Group { primitives: vec![ Primitive::Translate { @@ -380,7 +394,7 @@ impl Frame { Primitive::Translate { translation, content: Box::new(Primitive::Clip { - bounds: Rectangle::with_size(region.size()), + bounds: Rectangle::with_size(size), content: Box::new(Primitive::Group { primitives: text, }), @@ -423,11 +437,11 @@ impl Frame { self.transforms.current.is_identity = false; } - /// Produces the [`Geometry`] representing everything drawn on the [`Frame`]. - pub fn into_geometry(self) -> Geometry { - Geometry::from_primitive(Primitive::Group { + /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. + pub fn into_primitive(self) -> Primitive { + Primitive::Group { primitives: self.into_primitives(), - }) + } } fn into_primitives(mut self) -> Vec { @@ -436,7 +450,7 @@ impl Frame { Buffer::Solid(buffer) => { if !buffer.indices.is_empty() { self.primitives.push(Primitive::SolidMesh { - buffers: triangle::Mesh2D { + buffers: primitive::Mesh2D { vertices: buffer.vertices, indices: buffer.indices, }, @@ -447,7 +461,7 @@ impl Frame { Buffer::Gradient(buffer, gradient) => { if !buffer.indices.is_empty() { self.primitives.push(Primitive::GradientMesh { - buffers: triangle::Mesh2D { + buffers: primitive::Mesh2D { vertices: buffer.vertices, indices: buffer.indices, }, @@ -465,31 +479,31 @@ impl Frame { struct Vertex2DBuilder; -impl tessellation::FillVertexConstructor +impl tessellation::FillVertexConstructor for Vertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> triangle::Vertex2D { + ) -> primitive::Vertex2D { let position = vertex.position(); - triangle::Vertex2D { + primitive::Vertex2D { position: [position.x, position.y], } } } -impl tessellation::StrokeVertexConstructor +impl tessellation::StrokeVertexConstructor for Vertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> triangle::Vertex2D { + ) -> primitive::Vertex2D { let position = vertex.position(); - triangle::Vertex2D { + primitive::Vertex2D { position: [position.x, position.y], } } @@ -497,34 +511,99 @@ impl tessellation::StrokeVertexConstructor struct TriangleVertex2DBuilder([f32; 4]); -impl tessellation::FillVertexConstructor +impl tessellation::FillVertexConstructor for TriangleVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> triangle::ColoredVertex2D { + ) -> primitive::ColoredVertex2D { let position = vertex.position(); - triangle::ColoredVertex2D { + primitive::ColoredVertex2D { position: [position.x, position.y], color: self.0, } } } -impl tessellation::StrokeVertexConstructor +impl tessellation::StrokeVertexConstructor for TriangleVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> triangle::ColoredVertex2D { + ) -> primitive::ColoredVertex2D { let position = vertex.position(); - triangle::ColoredVertex2D { + primitive::ColoredVertex2D { position: [position.x, position.y], color: self.0, } } } + +fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { + match line_join { + LineJoin::Miter => lyon::tessellation::LineJoin::Miter, + LineJoin::Round => lyon::tessellation::LineJoin::Round, + LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, + } +} + +fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { + match line_cap { + LineCap::Butt => lyon::tessellation::LineCap::Butt, + LineCap::Square => lyon::tessellation::LineCap::Square, + LineCap::Round => lyon::tessellation::LineCap::Round, + } +} + +fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { + match rule { + fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, + fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, + } +} + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { + use lyon::algorithms::walk::{ + walk_along_path, RepeatedPattern, WalkerEvent, + }; + use lyon::path::iterator::PathIterator; + + Path::new(|builder| { + let segments_odd = (line_dash.segments.len() % 2 == 1) + .then(|| [line_dash.segments, line_dash.segments].concat()); + + let mut draw_line = false; + + walk_along_path( + path.raw().iter().flattened(0.01), + 0.0, + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + &mut RepeatedPattern { + callback: |event: WalkerEvent<'_>| { + let point = Point { + x: event.position.x, + y: event.position.y, + }; + + if draw_line { + builder.line_to(point); + } else { + builder.move_to(point); + } + + draw_line = !draw_line; + + true + }, + index: line_dash.offset, + intervals: segments_odd + .as_deref() + .unwrap_or(line_dash.segments), + }, + ); + }) +} diff --git a/graphics/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs similarity index 100% rename from graphics/src/widget/canvas/geometry.rs rename to wgpu/src/widget/canvas/geometry.rs From 5c0427edbb4358896412c736af2f441c12601d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 1 Mar 2023 21:41:32 +0100 Subject: [PATCH 26/57] Fix `Clip` primitive translation in `iced_tiny_skia` --- examples/game_of_life/src/main.rs | 2 +- tiny_skia/src/backend.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 494f71a667..cdb33aca38 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -145,7 +145,7 @@ impl Application for GameOfLife { self.grid .view() .map(move |message| Message::Grid(message, version)), - controls + controls, ]; container(content) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index e08cede703..838426f552 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -221,7 +221,7 @@ impl Backend { pixels, Some(&rectangular_clip_mask( pixels, - *bounds * scale_factor, + (*bounds + translation) * scale_factor, )), scale_factor, translation, From 838fd96212b14f20fe2224c4844904a8995f2db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 1 Mar 2023 21:47:15 +0100 Subject: [PATCH 27/57] Disable `anti_alias` for `Frame::fill_rectangle` in `iced_tiny_skia` --- tiny_skia/src/canvas.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs index c3b8b31626..59765f5956 100644 --- a/tiny_skia/src/canvas.rs +++ b/tiny_skia/src/canvas.rs @@ -44,7 +44,7 @@ impl Frame { self.primitives.push(Primitive::Fill { path, - paint: into_paint(fill.style), + paint: into_paint(fill.style, true), rule: into_fill_rule(fill.rule), transform: self.transform, }); @@ -56,7 +56,15 @@ impl Frame { size: Size, fill: impl Into, ) { - self.fill(&Path::rectangle(top_left, size), fill); + let path = convert_path(&Path::rectangle(top_left, size)); + let fill = fill.into(); + + self.primitives.push(Primitive::Fill { + path, + paint: into_paint(fill.style, false), + rule: into_fill_rule(fill.rule), + transform: self.transform, + }); } pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { @@ -66,7 +74,7 @@ impl Frame { self.primitives.push(Primitive::Stroke { path, - paint: into_paint(stroke.style), + paint: into_paint(stroke.style, true), stroke: skia_stroke, transform: self.transform, }); @@ -199,7 +207,7 @@ fn convert_path(path: &Path) -> tiny_skia::Path { .expect("Convert lyon path to tiny_skia path") } -pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { +pub fn into_paint(style: Style, anti_alias: bool) -> tiny_skia::Paint<'static> { tiny_skia::Paint { shader: match style { Style::Solid(color) => tiny_skia::Shader::SolidColor( @@ -238,7 +246,7 @@ pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { .expect("Create linear gradient"), }, }, - anti_alias: true, + anti_alias, ..Default::default() } } From 119cf2ecd10f70471199439acb1c4f9d96a57ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 1 Mar 2023 21:48:27 +0100 Subject: [PATCH 28/57] Remove magic boolean in `into_paint` --- tiny_skia/src/canvas.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/canvas.rs index 59765f5956..958063d253 100644 --- a/tiny_skia/src/canvas.rs +++ b/tiny_skia/src/canvas.rs @@ -44,7 +44,7 @@ impl Frame { self.primitives.push(Primitive::Fill { path, - paint: into_paint(fill.style, true), + paint: into_paint(fill.style), rule: into_fill_rule(fill.rule), transform: self.transform, }); @@ -61,7 +61,10 @@ impl Frame { self.primitives.push(Primitive::Fill { path, - paint: into_paint(fill.style, false), + paint: tiny_skia::Paint { + anti_alias: false, + ..into_paint(fill.style) + }, rule: into_fill_rule(fill.rule), transform: self.transform, }); @@ -74,7 +77,7 @@ impl Frame { self.primitives.push(Primitive::Stroke { path, - paint: into_paint(stroke.style, true), + paint: into_paint(stroke.style), stroke: skia_stroke, transform: self.transform, }); @@ -207,7 +210,7 @@ fn convert_path(path: &Path) -> tiny_skia::Path { .expect("Convert lyon path to tiny_skia path") } -pub fn into_paint(style: Style, anti_alias: bool) -> tiny_skia::Paint<'static> { +pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { tiny_skia::Paint { shader: match style { Style::Solid(color) => tiny_skia::Shader::SolidColor( @@ -246,7 +249,7 @@ pub fn into_paint(style: Style, anti_alias: bool) -> tiny_skia::Paint<'static> { .expect("Create linear gradient"), }, }, - anti_alias, + anti_alias: true, ..Default::default() } } From 350427e82c3a49367da65086c20a307e9b864a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 1 Mar 2023 21:52:12 +0100 Subject: [PATCH 29/57] Fix missing `qr_code` module in `iced_native` --- native/src/widget.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/native/src/widget.rs b/native/src/widget.rs index 27330894a6..f107cd69d5 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -91,14 +91,6 @@ pub mod canvas; #[doc(no_inline)] pub use canvas::Canvas; -#[cfg(feature = "qr_code")] -#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub mod qr_code; - -#[cfg(feature = "qr_code")] -#[doc(no_inline)] -pub use qr_code::QRCode; - pub use action::Action; pub use id::Id; pub use operation::Operation; From 868f79d22e2be82e98b06d66da3b4cbc6139d7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 2 Mar 2023 00:40:36 +0100 Subject: [PATCH 30/57] Reuse `ClipMask` in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 41 ++++++++++++++++++++---------- tiny_skia/src/window/compositor.rs | 7 +++-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 838426f552..2e4663ea69 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -25,6 +25,7 @@ impl Backend { pub fn draw>( &mut self, pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: &mut tiny_skia::ClipMask, primitives: &[Primitive], viewport: &Viewport, background_color: Color, @@ -38,6 +39,7 @@ impl Backend { self.draw_primitive( primitive, pixels, + clip_mask, None, scale_factor, Vector::ZERO, @@ -63,6 +65,7 @@ impl Backend { vertical_alignment: alignment::Vertical::Top, }, pixels, + clip_mask, None, scale_factor, Vector::ZERO, @@ -76,7 +79,8 @@ impl Backend { &mut self, primitive: &Primitive, pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: Option<&tiny_skia::ClipMask>, + clip_mask: &mut tiny_skia::ClipMask, + clip_bounds: Option, scale_factor: f32, translation: Vector, ) { @@ -95,6 +99,7 @@ impl Backend { .post_scale(scale_factor, scale_factor); let path = rounded_rectangle(*bounds, *border_radius); + let clip_mask = clip_bounds.map(|_| clip_mask as &_); pixels.fill_path( &path, @@ -151,7 +156,7 @@ impl Backend { *horizontal_alignment, *vertical_alignment, pixels, - clip_mask, + clip_bounds.map(|_| clip_mask as &_), ); } Primitive::Image { .. } => { @@ -173,7 +178,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_mask, + clip_bounds.map(|_| clip_mask as &_), ); } Primitive::Stroke { @@ -189,7 +194,7 @@ impl Backend { transform .post_translate(translation.x, translation.y) .post_scale(scale_factor, scale_factor), - clip_mask, + clip_bounds.map(|_| clip_mask as &_), ); } Primitive::Group { primitives } => { @@ -198,6 +203,7 @@ impl Backend { primitive, pixels, clip_mask, + clip_bounds, scale_factor, translation, ); @@ -211,27 +217,37 @@ impl Backend { content, pixels, clip_mask, + clip_bounds, scale_factor, translation + *offset, ); } Primitive::Clip { bounds, content } => { + let bounds = (*bounds + translation) * scale_factor; + + adjust_clip_mask(clip_mask, pixels, bounds); + self.draw_primitive( content, pixels, - Some(&rectangular_clip_mask( - pixels, - (*bounds + translation) * scale_factor, - )), + clip_mask, + Some(bounds), scale_factor, translation, ); + + if let Some(bounds) = clip_bounds { + adjust_clip_mask(clip_mask, pixels, bounds); + } else { + clip_mask.clear(); + } } Primitive::Cache { content } => { self.draw_primitive( content, pixels, clip_mask, + clip_bounds, scale_factor, translation, ); @@ -393,12 +409,11 @@ fn arc_to( } } -fn rectangular_clip_mask( +fn adjust_clip_mask( + clip_mask: &mut tiny_skia::ClipMask, pixels: &tiny_skia::PixmapMut<'_>, bounds: Rectangle, -) -> tiny_skia::ClipMask { - let mut clip_mask = tiny_skia::ClipMask::new(); - +) { let path = { let mut builder = tiny_skia::PathBuilder::new(); builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height); @@ -415,8 +430,6 @@ fn rectangular_clip_mask( true, ) .expect("Set path of clipping area"); - - clip_mask } impl iced_graphics::Backend for Backend { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 08159cd801..76f371e17e 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -6,6 +6,7 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; pub struct Compositor { + clip_mask: tiny_skia::ClipMask, _theme: PhantomData, } @@ -83,9 +84,10 @@ impl iced_graphics::window::Compositor for Compositor { } pub fn new(settings: Settings) -> (Compositor, Backend) { - // TODO + // TOD ( Compositor { + clip_mask: tiny_skia::ClipMask::new(), _theme: PhantomData, }, Backend::new(settings), @@ -93,7 +95,7 @@ pub fn new(settings: Settings) -> (Compositor, Backend) { } pub fn present>( - _compositor: &mut Compositor, + compositor: &mut Compositor, backend: &mut Backend, surface: &mut Surface, primitives: &[Primitive], @@ -110,6 +112,7 @@ pub fn present>( physical_size.height, ) .expect("Create pixel map"), + &mut compositor.clip_mask, primitives, viewport, background_color, From b2a9a1e73cb7b2026b2eeaa2be2c04a61c5efb21 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 2 Mar 2023 08:31:39 -0800 Subject: [PATCH 31/57] Fixed fullscreen only being possible on primary monitor. --- winit/src/application.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index 9781a453eb..b13b7214ea 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -762,7 +762,7 @@ pub fn run_command( window::Action::ChangeMode(mode) => { window.set_visible(conversion::visible(mode)); window.set_fullscreen(conversion::fullscreen( - window.primary_monitor(), + window.current_monitor(), mode, )); } From a9ca89ca55157d7e94dc6422b4842826139ca2db Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 2 Mar 2023 08:43:58 -0800 Subject: [PATCH 32/57] Added example of toggling fullscreen to TODOs. --- examples/todos/src/main.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 6408f09c0b..0f5bfe3003 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,6 +1,6 @@ use iced::alignment::{self, Alignment}; use iced::event::{self, Event}; -use iced::keyboard; +use iced::keyboard::{self, KeyCode, Modifiers}; use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::{ @@ -8,6 +8,8 @@ use iced::widget::{ text_input, Text, }; use iced::window; +#[cfg(not(target_arch = "wasm32"))] +use iced::window::Mode; use iced::{Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; @@ -49,7 +51,11 @@ enum Message { CreateTask, FilterChanged(Filter), TaskMessage(usize, TaskMessage), - TabPressed { shift: bool }, + TabPressed { + shift: bool, + }, + #[cfg(not(target_arch = "wasm32"))] + ToggleFullscreen(Mode), } impl Application for Todos { @@ -156,6 +162,10 @@ impl Application for Todos { widget::focus_next() } } + #[cfg(not(target_arch = "wasm32"))] + Message::ToggleFullscreen(mode) => { + window::change_mode(mode) + } _ => Command::none(), }; @@ -266,6 +276,22 @@ impl Application for Todos { ) => Some(Message::TabPressed { shift: modifiers.shift(), }), + #[cfg(not(target_arch = "wasm32"))] + ( + Event::Keyboard(keyboard::Event::KeyPressed { + key_code, + modifiers: Modifiers::SHIFT, + }), + event::Status::Ignored, + ) => match key_code { + KeyCode::Up => { + Some(Message::ToggleFullscreen(Mode::Fullscreen)) + } + KeyCode::Down => { + Some(Message::ToggleFullscreen(Mode::Windowed)) + } + _ => None, + }, _ => None, }) } From bbeaf10c04a922af5c1c3b898f0c4301d23feab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 3 Mar 2023 03:55:07 +0100 Subject: [PATCH 33/57] Mark `Primitive` as `non-exhaustive` in `iced_graphics` --- graphics/Cargo.toml | 5 ++++- graphics/src/primitive.rs | 3 +++ tiny_skia/Cargo.toml | 1 + wgpu/src/layer.rs | 3 +-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 36d8a516c7..62e67cf8da 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -34,7 +34,6 @@ log = "0.4" raw-window-handle = "0.5" thiserror = "1.0" bitflags = "1.2" -tiny-skia = "0.8" [dependencies.bytemuck] version = "1.4" @@ -48,6 +47,10 @@ path = "../native" version = "0.7" path = "../style" +[dependencies.tiny-skia] +version = "0.8" +optional = true + [dependencies.image_rs] version = "0.24" package = "image" diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index e48265911e..5a48639d00 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -9,6 +9,7 @@ use std::sync::Arc; /// A rendering primitive. #[derive(Debug, Clone)] +#[non_exhaustive] pub enum Primitive { /// A text primitive Text { @@ -85,12 +86,14 @@ pub enum Primitive { /// The [`Gradient`] to apply to the mesh. gradient: Gradient, }, + #[cfg(feature = "tiny_skia")] Fill { path: tiny_skia::Path, paint: tiny_skia::Paint<'static>, rule: tiny_skia::FillRule, transform: tiny_skia::Transform, }, + #[cfg(feature = "tiny_skia")] Stroke { path: tiny_skia::Path, paint: tiny_skia::Paint<'static>, diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 5f39fce28f..72181735f6 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -24,6 +24,7 @@ path = "../native" [dependencies.iced_graphics] version = "0.7" path = "../graphics" +features = ["tiny-skia"] [dependencies.cosmic-text] features = ["std", "swash"] diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 0840555a94..69fcf8995e 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -265,9 +265,8 @@ impl<'a> Layer<'a> { current_layer, ); } - Primitive::Fill { .. } | Primitive::Stroke { .. } => { + _ => { // Unsupported! - // TODO: Draw a placeholder (?) } } } From d13d19ba3569560edd67f20b48f37548d10ceee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 3 Mar 2023 04:00:44 +0100 Subject: [PATCH 34/57] Rename `canvas::frame` to `canvas` in `iced_wgpu` --- graphics/src/primitive.rs | 4 +- renderer/src/widget/canvas.rs | 6 +- tiny_skia/src/backend.rs | 3 + .../src/{widget/canvas/frame.rs => canvas.rs} | 7 +- wgpu/src/lib.rs | 5 +- wgpu/src/widget.rs | 9 -- wgpu/src/widget/canvas.rs | 16 ---- wgpu/src/widget/canvas/cache.rs | 93 ------------------- wgpu/src/widget/canvas/geometry.rs | 24 ----- 9 files changed, 14 insertions(+), 153 deletions(-) rename wgpu/src/{widget/canvas/frame.rs => canvas.rs} (99%) delete mode 100644 wgpu/src/widget.rs delete mode 100644 wgpu/src/widget/canvas.rs delete mode 100644 wgpu/src/widget/canvas/cache.rs delete mode 100644 wgpu/src/widget/canvas/geometry.rs diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 5a48639d00..f900b3fdae 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -86,14 +86,14 @@ pub enum Primitive { /// The [`Gradient`] to apply to the mesh. gradient: Gradient, }, - #[cfg(feature = "tiny_skia")] + #[cfg(feature = "tiny-skia")] Fill { path: tiny_skia::Path, paint: tiny_skia::Paint<'static>, rule: tiny_skia::FillRule, transform: tiny_skia::Transform, }, - #[cfg(feature = "tiny_skia")] + #[cfg(feature = "tiny-skia")] Stroke { path: tiny_skia::Path, paint: tiny_skia::Paint<'static>, diff --git a/renderer/src/widget/canvas.rs b/renderer/src/widget/canvas.rs index f40a109707..35c8fff960 100644 --- a/renderer/src/widget/canvas.rs +++ b/renderer/src/widget/canvas.rs @@ -16,7 +16,7 @@ use crate::{Backend, Point, Rectangle, Size, Vector}; pub use crate::Geometry; pub enum Frame { - Wgpu(iced_wgpu::widget::canvas::Frame), + Wgpu(iced_wgpu::canvas::Frame), TinySkia(iced_tiny_skia::canvas::Frame), } @@ -33,7 +33,7 @@ impl Frame { pub fn new(renderer: &crate::Renderer, size: Size) -> Self { match renderer.backend() { Backend::Wgpu(_) => { - Frame::Wgpu(iced_wgpu::widget::canvas::Frame::new(size)) + Frame::Wgpu(iced_wgpu::canvas::Frame::new(size)) } Backend::TinySkia(_) => { Frame::TinySkia(iced_tiny_skia::canvas::Frame::new(size)) @@ -131,7 +131,7 @@ impl Frame { pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { let mut frame = match self { Self::Wgpu(_) => { - Self::Wgpu(iced_wgpu::widget::canvas::Frame::new(region.size())) + Self::Wgpu(iced_wgpu::canvas::Frame::new(region.size())) } Self::TinySkia(_) => Self::TinySkia( iced_tiny_skia::canvas::Frame::new(region.size()), diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 2e4663ea69..6883a953ed 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -256,6 +256,9 @@ impl Backend { // Not supported! // TODO: Draw a placeholder (?) / Log it (?) } + _ => { + // Not supported! + } } } } diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/canvas.rs similarity index 99% rename from wgpu/src/widget/canvas/frame.rs rename to wgpu/src/canvas.rs index 987570ec00..e8d540c3ea 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/canvas.rs @@ -1,9 +1,8 @@ -use crate::primitive::{self, Primitive}; -use crate::widget::canvas::fill::{self, Fill}; -use crate::widget::canvas::{ +use iced_graphics::primitive::{self, Primitive}; +use iced_native::widget::canvas::fill::{self, Fill}; +use iced_native::widget::canvas::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; - use iced_native::{Gradient, Point, Rectangle, Size, Vector}; use lyon::geom::euclid; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 8be12602d0..31db16a8de 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -37,12 +37,13 @@ #![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] - pub mod layer; pub mod settings; -pub mod widget; pub mod window; +#[cfg(feature = "canvas")] +pub mod canvas; + mod backend; mod buffer; mod quad; diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs deleted file mode 100644 index 8d05041e6d..0000000000 --- a/wgpu/src/widget.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Use the graphical widgets supported out-of-the-box. - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub mod canvas; - -#[cfg(feature = "canvas")] -#[doc(no_inline)] -pub use canvas::Canvas; diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs deleted file mode 100644 index 41444fcf45..0000000000 --- a/wgpu/src/widget/canvas.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod cache; -mod frame; -mod geometry; - -pub use cache::Cache; -pub use frame::Frame; -pub use geometry::Geometry; - -pub use iced_native::widget::canvas::event::{self, Event}; -pub use iced_native::widget::canvas::fill::{self, Fill}; -pub use iced_native::widget::canvas::gradient::{self, Gradient}; -pub use iced_native::widget::canvas::path::{self, Path}; -pub use iced_native::widget::canvas::stroke::{self, Stroke}; -pub use iced_native::widget::canvas::{ - Canvas, Cursor, LineCap, LineDash, LineJoin, Program, Renderer, Style, Text, -}; diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs deleted file mode 100644 index 09b26b900e..0000000000 --- a/wgpu/src/widget/canvas/cache.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::widget::canvas::{Frame, Geometry}; -use crate::Primitive; - -use iced_native::Size; -use std::{cell::RefCell, sync::Arc}; - -#[derive(Default)] -enum State { - #[default] - Empty, - Filled { - bounds: Size, - primitive: Arc, - }, -} - -/// A simple cache that stores generated [`Geometry`] to avoid recomputation. -/// -/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer -/// change or it is explicitly cleared. -#[derive(Debug, Default)] -pub struct Cache { - state: RefCell, -} - -impl Cache { - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: Default::default(), - } - } - - /// Clears the [`Cache`], forcing a redraw the next time it is used. - pub fn clear(&self) { - *self.state.borrow_mut() = State::Empty; - } - - /// Draws [`Geometry`] using the provided closure and stores it in the - /// [`Cache`]. - /// - /// The closure will only be called when - /// - the bounds have changed since the previous draw call. - /// - the [`Cache`] is empty or has been explicitly cleared. - /// - /// Otherwise, the previously stored [`Geometry`] will be returned. The - /// [`Cache`] is not cleared in this case. In other words, it will keep - /// returning the stored [`Geometry`] if needed. - pub fn draw( - &self, - bounds: Size, - draw_fn: impl FnOnce(&mut Frame), - ) -> Geometry { - use std::ops::Deref; - - if let State::Filled { - bounds: cached_bounds, - primitive, - } = self.state.borrow().deref() - { - if *cached_bounds == bounds { - return Geometry::from_primitive(Primitive::Cache { - content: primitive.clone(), - }); - } - } - - let mut frame = Frame::new(bounds); - draw_fn(&mut frame); - - let primitive = Arc::new(frame.into_primitive()); - - *self.state.borrow_mut() = State::Filled { - bounds, - primitive: primitive.clone(), - }; - - Geometry::from_primitive(Primitive::Cache { content: primitive }) - } -} - -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - State::Empty => write!(f, "Empty"), - State::Filled { primitive, bounds } => f - .debug_struct("Filled") - .field("primitive", primitive) - .field("bounds", bounds) - .finish(), - } - } -} diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs deleted file mode 100644 index e8ac621d22..0000000000 --- a/wgpu/src/widget/canvas/geometry.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::Primitive; - -/// A bunch of shapes that can be drawn. -/// -/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a -/// [`Cache`]. -/// -/// [`Frame`]: crate::widget::canvas::Frame -/// [`Cache`]: crate::widget::canvas::Cache -#[derive(Debug, Clone)] -pub struct Geometry(Primitive); - -impl Geometry { - pub(crate) fn from_primitive(primitive: Primitive) -> Self { - Self(primitive) - } - - /// Turns the [`Geometry`] into a [`Primitive`]. - /// - /// This can be useful if you are building a custom widget. - pub fn into_primitive(self) -> Primitive { - self.0 - } -} From 6cc48b5c62bac287b8f9f1c79c1fb7486c51b18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 3 Mar 2023 04:57:55 +0100 Subject: [PATCH 35/57] Move `Canvas` and `QRCode` to `iced` crate Rename `canvas` modules to `geometry` in graphics subcrates --- Cargo.toml | 9 +- examples/arc/src/main.rs | 2 +- examples/bezier_tool/src/main.rs | 2 +- examples/color_palette/src/main.rs | 2 +- examples/game_of_life/src/main.rs | 2 +- examples/sierpinski_triangle/src/main.rs | 2 +- examples/solar_system/src/main.rs | 2 +- graphics/Cargo.toml | 6 +- graphics/src/backend.rs | 2 - graphics/src/geometry.rs | 36 ++++++++ .../canvas => graphics/src/geometry}/fill.rs | 5 +- .../canvas => graphics/src/geometry}/path.rs | 0 .../src/geometry}/path/arc.rs | 0 .../src/geometry}/path/builder.rs | 2 +- .../src/geometry}/stroke.rs | 2 +- .../canvas => graphics/src/geometry}/style.rs | 3 +- .../canvas => graphics/src/geometry}/text.rs | 0 graphics/src/lib.rs | 6 ++ graphics/src/renderer.rs | 8 +- native/Cargo.toml | 5 -- renderer/Cargo.toml | 8 +- renderer/src/backend.rs | 4 +- .../src/{widget/canvas.rs => geometry.rs} | 23 ++--- .../src/{widget/canvas => geometry}/cache.rs | 2 +- renderer/src/lib.rs | 13 +-- src/widget.rs | 8 +- {native/src => src}/widget/canvas.rs | 83 +++++++------------ {native/src => src}/widget/canvas/cursor.rs | 0 {native/src => src}/widget/canvas/event.rs | 8 +- {native/src => src}/widget/canvas/program.rs | 8 +- {renderer/src => src}/widget/qr_code.rs | 3 +- tiny_skia/Cargo.toml | 2 +- tiny_skia/src/backend.rs | 2 - tiny_skia/src/{canvas.rs => geometry.rs} | 10 +-- tiny_skia/src/lib.rs | 4 +- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 2 - wgpu/src/{canvas.rs => geometry.rs} | 8 +- wgpu/src/lib.rs | 4 +- 39 files changed, 141 insertions(+), 149 deletions(-) create mode 100644 graphics/src/geometry.rs rename {native/src/widget/canvas => graphics/src/geometry}/fill.rs (93%) rename {native/src/widget/canvas => graphics/src/geometry}/path.rs (100%) rename {native/src/widget/canvas => graphics/src/geometry}/path/arc.rs (100%) rename {native/src/widget/canvas => graphics/src/geometry}/path/builder.rs (99%) rename {native/src/widget/canvas => graphics/src/geometry}/stroke.rs (98%) rename {native/src/widget/canvas => graphics/src/geometry}/style.rs (88%) rename {native/src/widget/canvas => graphics/src/geometry}/text.rs (100%) rename renderer/src/{widget/canvas.rs => geometry.rs} (86%) rename renderer/src/{widget/canvas => geometry}/cache.rs (97%) rename {native/src => src}/widget/canvas.rs (73%) rename {native/src => src}/widget/canvas/cursor.rs (100%) rename {native/src => src}/widget/canvas/event.rs (73%) rename {native/src => src}/widget/canvas/program.rs (93%) rename {renderer/src => src}/widget/qr_code.rs (99%) rename tiny_skia/src/{canvas.rs => geometry.rs} (97%) rename wgpu/src/{canvas.rs => geometry.rs} (99%) diff --git a/Cargo.toml b/Cargo.toml index 942966e5c6..28938df911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,9 @@ image = ["iced_renderer/image", "image_rs"] # Enables the `Svg` widget svg = ["iced_renderer/svg"] # Enables the `Canvas` widget -canvas = ["iced_renderer/canvas"] +canvas = ["iced_renderer/geometry"] # Enables the `QRCode` widget -qr_code = ["iced_renderer/qr_code"] +qr_code = ["canvas", "qrcode"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -69,6 +69,11 @@ version = "0.24" package = "image" optional = true +[dependencies.qrcode] +version = "0.12" +optional = true +default-features = false + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas", "qr_code"] diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index d71ba6f6f2..80ad0b5b9e 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -69,7 +69,7 @@ impl Application for Arc { } } -impl canvas::Program for Arc { +impl canvas::Program for Arc { type State = (); fn draw( diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 5bb463c3ee..f1c83a16b1 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -92,7 +92,7 @@ mod bezier { curves: &'a [Curve], } - impl<'a> canvas::Program for Bezier<'a> { + impl<'a> canvas::Program for Bezier<'a> { type State = Option; fn update( diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 1109a88388..5c4304ee39 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -237,7 +237,7 @@ impl Theme { } } -impl canvas::Program for Theme { +impl canvas::Program for Theme { type State = (); fn draw( diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index cdb33aca38..eab8908bf7 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -393,7 +393,7 @@ mod grid { } } - impl canvas::Program for Grid { + impl canvas::Program for Grid { type State = Interaction; fn update( diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index e85f839195..4faac6d62b 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -97,7 +97,7 @@ struct SierpinskiGraph { cache: canvas::Cache, } -impl canvas::Program for SierpinskiGraph { +impl canvas::Program for SierpinskiGraph { type State = (); fn update( diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 0023a69b73..f2606feb82 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -150,7 +150,7 @@ impl State { } } -impl canvas::Program for State { +impl canvas::Program for State { type State = (); fn draw( diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 62e67cf8da..98e6f474ce 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -24,7 +24,7 @@ bmp = ["image_rs/bmp"] hdr = ["image_rs/hdr"] dds = ["image_rs/dds"] farbfeld = ["image_rs/farbfeld"] -canvas = ["iced_native/canvas"] +geometry = ["lyon_path"] opengl = [] image_rs = ["kamadak-exif"] @@ -65,6 +65,10 @@ optional = true version = "0.5" optional = true +[dependencies.lyon_path] +version = "1" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index c44372e8e7..8658cffe82 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -10,8 +10,6 @@ use std::borrow::Cow; /// /// [`Renderer`]: crate::Renderer pub trait Backend { - type Geometry: Into; - /// Trims the measurements cache. /// /// This method is currently necessary to properly trim the text cache in diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs new file mode 100644 index 0000000000..29ac84d64d --- /dev/null +++ b/graphics/src/geometry.rs @@ -0,0 +1,36 @@ +//! Draw 2D graphics for your users. +//! +//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a +//! [`Frame`]. It can be used for animation, data visualization, game graphics, +//! and more! +pub mod fill; +pub mod path; +pub mod stroke; + +mod style; +mod text; + +pub use fill::Fill; +pub use path::Path; +pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; +pub use style::Style; +pub use text::Text; + +pub use iced_native::gradient::{self, Gradient}; + +use crate::Primitive; + +#[derive(Debug, Clone)] +pub struct Geometry(pub Primitive); + +impl From for Primitive { + fn from(geometry: Geometry) -> Self { + geometry.0 + } +} + +pub trait Renderer: iced_native::Renderer { + type Geometry; + + fn draw(&mut self, geometry: Vec); +} diff --git a/native/src/widget/canvas/fill.rs b/graphics/src/geometry/fill.rs similarity index 93% rename from native/src/widget/canvas/fill.rs rename to graphics/src/geometry/fill.rs index 92b1e47ef2..109d5e990f 100644 --- a/native/src/widget/canvas/fill.rs +++ b/graphics/src/geometry/fill.rs @@ -1,8 +1,7 @@ //! Fill [crate::widget::canvas::Geometry] with a certain style. -use crate::widget::canvas::Gradient; -use crate::Color; +use crate::{Color, Gradient}; -pub use crate::widget::canvas::Style; +pub use crate::geometry::Style; /// The style used to fill geometry. #[derive(Debug, Clone)] diff --git a/native/src/widget/canvas/path.rs b/graphics/src/geometry/path.rs similarity index 100% rename from native/src/widget/canvas/path.rs rename to graphics/src/geometry/path.rs diff --git a/native/src/widget/canvas/path/arc.rs b/graphics/src/geometry/path/arc.rs similarity index 100% rename from native/src/widget/canvas/path/arc.rs rename to graphics/src/geometry/path/arc.rs diff --git a/native/src/widget/canvas/path/builder.rs b/graphics/src/geometry/path/builder.rs similarity index 99% rename from native/src/widget/canvas/path/builder.rs rename to graphics/src/geometry/path/builder.rs index 84fda05227..4a9c5e36cb 100644 --- a/native/src/widget/canvas/path/builder.rs +++ b/graphics/src/geometry/path/builder.rs @@ -1,4 +1,4 @@ -use crate::widget::canvas::path::{arc, Arc, Path}; +use crate::geometry::path::{arc, Arc, Path}; use crate::{Point, Size}; use lyon_path::builder::{self, SvgPathBuilder}; diff --git a/native/src/widget/canvas/stroke.rs b/graphics/src/geometry/stroke.rs similarity index 98% rename from native/src/widget/canvas/stroke.rs rename to graphics/src/geometry/stroke.rs index ab4727b269..b551a9c996 100644 --- a/native/src/widget/canvas/stroke.rs +++ b/graphics/src/geometry/stroke.rs @@ -1,5 +1,5 @@ //! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. -pub use crate::widget::canvas::Style; +pub use crate::geometry::Style; use crate::Color; diff --git a/native/src/widget/canvas/style.rs b/graphics/src/geometry/style.rs similarity index 88% rename from native/src/widget/canvas/style.rs rename to graphics/src/geometry/style.rs index 2642fdb858..6794f2e753 100644 --- a/native/src/widget/canvas/style.rs +++ b/graphics/src/geometry/style.rs @@ -1,5 +1,4 @@ -use crate::widget::canvas::Gradient; -use crate::Color; +use crate::{Color, Gradient}; /// The coloring style of some drawing. #[derive(Debug, Clone, PartialEq)] diff --git a/native/src/widget/canvas/text.rs b/graphics/src/geometry/text.rs similarity index 100% rename from native/src/widget/canvas/text.rs rename to graphics/src/geometry/text.rs diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 576b2d7812..e56f8ad844 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -33,6 +33,9 @@ pub mod primitive; pub mod renderer; pub mod window; +#[cfg(feature = "geometry")] +pub mod geometry; + pub use antialiasing::Antialiasing; pub use backend::Backend; pub use error::Error; @@ -41,6 +44,9 @@ pub use renderer::Renderer; pub use transformation::Transformation; pub use viewport::Viewport; +#[cfg(feature = "geometry")] +pub use geometry::Geometry; + pub use iced_native::alignment; pub use iced_native::text; pub use iced_native::{ diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 793ee7d7ec..cb57f429f2 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -215,15 +215,15 @@ where } } -#[cfg(feature = "canvas")] -impl iced_native::widget::canvas::Renderer for Renderer +#[cfg(feature = "geometry")] +impl crate::geometry::Renderer for Renderer where B: Backend, { - type Geometry = B::Geometry; + type Geometry = crate::Geometry; fn draw(&mut self, layers: Vec) { self.primitives - .extend(layers.into_iter().map(B::Geometry::into)); + .extend(layers.into_iter().map(crate::Geometry::into)); } } diff --git a/native/Cargo.toml b/native/Cargo.toml index 23533e33c1..1eedf0dac5 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -8,7 +8,6 @@ license = "MIT" repository = "https://github.com/iced-rs/iced" [features] -canvas = ["lyon_path"] debug = [] [dependencies] @@ -29,7 +28,3 @@ features = ["thread-pool"] [dependencies.iced_style] version = "0.7" path = "../style" - -[dependencies.lyon_path] -version = "1" -optional = true diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 429b55c261..189f530976 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -6,8 +6,7 @@ edition = "2021" [features] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] -canvas = ["iced_wgpu/canvas", "iced_tiny_skia/canvas"] -qr_code = ["canvas", "qrcode"] +geometry = ["iced_wgpu/geometry", "iced_tiny_skia/geometry"] tracing = ["iced_wgpu/tracing"] [dependencies] @@ -31,8 +30,3 @@ iced_wgpu = { version = "0.9", path = "../wgpu" } [target.'cfg(target_arch = "wasm32")'.dependencies] iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } - -[dependencies.qrcode] -version = "0.12" -optional = true -default-features = false diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index 6c0b4e5c62..b0a409dc76 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -1,4 +1,4 @@ -use crate::{Font, Geometry, Point, Size}; +use crate::{Font, Point, Size}; use iced_graphics::backend; use iced_graphics::text; @@ -12,8 +12,6 @@ pub enum Backend { } impl iced_graphics::Backend for Backend { - type Geometry = Geometry; - fn trim_measurements(&mut self) { match self { Self::Wgpu(backend) => backend.trim_measurements(), diff --git a/renderer/src/widget/canvas.rs b/renderer/src/geometry.rs similarity index 86% rename from renderer/src/widget/canvas.rs rename to renderer/src/geometry.rs index 35c8fff960..e491ea734e 100644 --- a/renderer/src/widget/canvas.rs +++ b/renderer/src/geometry.rs @@ -2,22 +2,13 @@ mod cache; pub use cache::Cache; -pub use iced_native::widget::canvas::event::{self, Event}; -pub use iced_native::widget::canvas::fill::{self, Fill}; -pub use iced_native::widget::canvas::gradient::{self, Gradient}; -pub use iced_native::widget::canvas::path::{self, Path}; -pub use iced_native::widget::canvas::stroke::{self, Stroke}; -pub use iced_native::widget::canvas::{ - Canvas, Cursor, LineCap, LineDash, LineJoin, Program, Renderer, Style, Text, -}; +pub use iced_graphics::geometry::*; use crate::{Backend, Point, Rectangle, Size, Vector}; -pub use crate::Geometry; - pub enum Frame { - Wgpu(iced_wgpu::canvas::Frame), - TinySkia(iced_tiny_skia::canvas::Frame), + Wgpu(iced_wgpu::geometry::Frame), + TinySkia(iced_tiny_skia::geometry::Frame), } macro_rules! delegate { @@ -33,10 +24,10 @@ impl Frame { pub fn new(renderer: &crate::Renderer, size: Size) -> Self { match renderer.backend() { Backend::Wgpu(_) => { - Frame::Wgpu(iced_wgpu::canvas::Frame::new(size)) + Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) } Backend::TinySkia(_) => { - Frame::TinySkia(iced_tiny_skia::canvas::Frame::new(size)) + Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) } } } @@ -131,10 +122,10 @@ impl Frame { pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { let mut frame = match self { Self::Wgpu(_) => { - Self::Wgpu(iced_wgpu::canvas::Frame::new(region.size())) + Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) } Self::TinySkia(_) => Self::TinySkia( - iced_tiny_skia::canvas::Frame::new(region.size()), + iced_tiny_skia::geometry::Frame::new(region.size()), ), }; diff --git a/renderer/src/widget/canvas/cache.rs b/renderer/src/geometry/cache.rs similarity index 97% rename from renderer/src/widget/canvas/cache.rs rename to renderer/src/geometry/cache.rs index 7d6b4811e7..1f1febddbb 100644 --- a/renderer/src/widget/canvas/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -1,4 +1,4 @@ -use crate::widget::canvas::{Frame, Geometry}; +use crate::geometry::{Frame, Geometry}; use crate::{Primitive, Renderer, Size}; use std::cell::RefCell; diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index d9c85e82c4..aae3322d41 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,6 +1,8 @@ -pub mod widget; pub mod window; +#[cfg(feature = "geometry")] +pub mod geometry; + mod backend; mod settings; @@ -19,12 +21,3 @@ pub use iced_graphics::{ /// [`iced`]: https://github.com/iced-rs/iced pub type Renderer = iced_graphics::Renderer; - -#[derive(Debug, Clone)] -pub struct Geometry(pub(crate) Primitive); - -impl From for Primitive { - fn from(geometry: Geometry) -> Self { - geometry.0 - } -} diff --git a/src/widget.rs b/src/widget.rs index f3a66101f7..19434e84b9 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -165,14 +165,14 @@ pub use vertical_slider::VerticalSlider; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub use iced_renderer::widget::canvas; +pub mod canvas; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] /// Creates a new [`Canvas`]. -pub fn canvas(program: P) -> Canvas +pub fn canvas(program: P) -> Canvas where - Renderer: canvas::Renderer, + Renderer: iced_renderer::geometry::Renderer, P: canvas::Program, { Canvas::new(program) @@ -193,7 +193,7 @@ pub mod image { #[cfg(feature = "qr_code")] #[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub use iced_renderer::widget::qr_code; +pub mod qr_code; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] diff --git a/native/src/widget/canvas.rs b/src/widget/canvas.rs similarity index 73% rename from native/src/widget/canvas.rs rename to src/widget/canvas.rs index 8a9addd285..bc5995c65e 100644 --- a/native/src/widget/canvas.rs +++ b/src/widget/canvas.rs @@ -1,35 +1,22 @@ //! Draw 2D graphics for your users. -//! -//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a -//! [`Frame`]. It can be used for animation, data visualization, game graphics, -//! and more! pub mod event; -pub mod fill; -pub mod path; -pub mod stroke; mod cursor; mod program; -mod style; -mod text; -pub use crate::gradient::{self, Gradient}; pub use cursor::Cursor; pub use event::Event; -pub use fill::Fill; -pub use path::Path; pub use program::Program; -pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; -pub use style::Style; -pub use text::Text; - -use crate::layout::{self, Layout}; -use crate::mouse; -use crate::renderer; -use crate::widget::tree::{self, Tree}; -use crate::{ - Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget, -}; + +pub use iced_renderer::geometry::*; + +use crate::{Length, Point, Rectangle, Size, Vector}; + +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::tree::{self, Tree}; +use iced_native::{Clipboard, Element, Shell, Widget}; use std::marker::PhantomData; @@ -39,14 +26,8 @@ use std::marker::PhantomData; /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # mod iced { -/// # pub mod widget { -/// # pub use iced_graphics::widget::canvas; -/// # } -/// # pub use iced_native::{Color, Rectangle, Theme}; -/// # } /// use iced::widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle, Theme}; +/// use iced::{Color, Rectangle, Theme, Renderer}; /// /// // First, we define the data we need for drawing /// #[derive(Debug)] @@ -58,9 +39,9 @@ use std::marker::PhantomData; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec{ +/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec{ /// // We prepare a new `Frame` -/// let mut frame = Frame::new(bounds.size()); +/// let mut frame = Frame::new(renderer, bounds.size()); /// /// // We create a `Path` representing a simple circle /// let circle = Path::circle(frame.center(), self.radius); @@ -77,9 +58,9 @@ use std::marker::PhantomData; /// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas +pub struct Canvas where - Renderer: self::Renderer, + Renderer: iced_renderer::geometry::Renderer, P: Program, { width: Length, @@ -89,9 +70,9 @@ where theme_: PhantomData, } -impl Canvas +impl Canvas where - Renderer: self::Renderer, + Renderer: iced_renderer::geometry::Renderer, P: Program, { const DEFAULT_SIZE: f32 = 100.0; @@ -120,10 +101,10 @@ where } } -impl Widget - for Canvas +impl Widget + for Canvas where - Renderer: self::Renderer, + Renderer: iced_renderer::geometry::Renderer, P: Program, { fn tag(&self) -> tree::Tag { @@ -157,7 +138,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: crate::Event, + event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, _renderer: &Renderer, @@ -167,9 +148,13 @@ where let bounds = layout.bounds(); let canvas_event = match event { - crate::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), - crate::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), - crate::Event::Keyboard(keyboard_event) => { + iced_native::Event::Mouse(mouse_event) => { + Some(Event::Mouse(mouse_event)) + } + iced_native::Event::Touch(touch_event) => { + Some(Event::Touch(touch_event)) + } + iced_native::Event::Keyboard(keyboard_event) => { Some(Event::Keyboard(keyboard_event)) } _ => None, @@ -238,22 +223,16 @@ where } } -impl<'a, Message, Renderer, P> From> +impl<'a, P, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + self::Renderer, + Renderer: 'a + iced_renderer::geometry::Renderer, P: Program + 'a, { fn from( - canvas: Canvas, + canvas: Canvas, ) -> Element<'a, Message, Renderer> { Element::new(canvas) } } - -pub trait Renderer: crate::Renderer { - type Geometry; - - fn draw(&mut self, geometry: Vec); -} diff --git a/native/src/widget/canvas/cursor.rs b/src/widget/canvas/cursor.rs similarity index 100% rename from native/src/widget/canvas/cursor.rs rename to src/widget/canvas/cursor.rs diff --git a/native/src/widget/canvas/event.rs b/src/widget/canvas/event.rs similarity index 73% rename from native/src/widget/canvas/event.rs rename to src/widget/canvas/event.rs index 1d726577aa..7c733a4d8a 100644 --- a/native/src/widget/canvas/event.rs +++ b/src/widget/canvas/event.rs @@ -1,9 +1,9 @@ //! Handle events of a canvas. -use crate::keyboard; -use crate::mouse; -use crate::touch; +use iced_native::keyboard; +use iced_native::mouse; +use iced_native::touch; -pub use crate::event::Status; +pub use iced_native::event::Status; /// A [`Canvas`] event. /// diff --git a/native/src/widget/canvas/program.rs b/src/widget/canvas/program.rs similarity index 93% rename from native/src/widget/canvas/program.rs rename to src/widget/canvas/program.rs index 17a5a13731..fd15663a6e 100644 --- a/native/src/widget/canvas/program.rs +++ b/src/widget/canvas/program.rs @@ -1,6 +1,6 @@ use crate::widget::canvas::event::{self, Event}; use crate::widget::canvas::mouse; -use crate::widget::canvas::{Cursor, Renderer}; +use crate::widget::canvas::Cursor; use crate::Rectangle; /// The state and logic of a [`Canvas`]. @@ -9,9 +9,9 @@ use crate::Rectangle; /// application. /// /// [`Canvas`]: crate::widget::Canvas -pub trait Program +pub trait Program where - Renderer: self::Renderer, + Renderer: iced_renderer::geometry::Renderer, { /// The internal state mutated by the [`Program`]. type State: Default + 'static; @@ -71,7 +71,7 @@ where impl Program for &T where - Renderer: self::Renderer, + Renderer: iced_renderer::geometry::Renderer, T: Program, { type State = T::State; diff --git a/renderer/src/widget/qr_code.rs b/src/widget/qr_code.rs similarity index 99% rename from renderer/src/widget/qr_code.rs rename to src/widget/qr_code.rs index aae4ec8863..66442d5d8c 100644 --- a/renderer/src/widget/qr_code.rs +++ b/src/widget/qr_code.rs @@ -2,9 +2,8 @@ use crate::widget::canvas; use crate::Renderer; -use iced_graphics::renderer; - use iced_native::layout; +use iced_native::renderer; use iced_native::widget::Tree; use iced_native::{ Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 72181735f6..c4c36abad5 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [features] image = [] svg = [] -canvas = ["iced_native/canvas"] +geometry = ["iced_graphics/geometry"] [dependencies] raw-window-handle = "0.5" diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 6883a953ed..050c6c75da 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -436,8 +436,6 @@ fn adjust_clip_mask( } impl iced_graphics::Backend for Backend { - type Geometry = (); - fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); } diff --git a/tiny_skia/src/canvas.rs b/tiny_skia/src/geometry.rs similarity index 97% rename from tiny_skia/src/canvas.rs rename to tiny_skia/src/geometry.rs index 958063d253..73fc1ebdef 100644 --- a/tiny_skia/src/canvas.rs +++ b/tiny_skia/src/geometry.rs @@ -1,9 +1,9 @@ use crate::{Point, Primitive, Rectangle, Size, Vector}; -use iced_native::widget::canvas::fill::{self, Fill}; -use iced_native::widget::canvas::stroke::{self, Stroke}; -use iced_native::widget::canvas::{Path, Style, Text}; -use iced_native::Gradient; +use iced_graphics::geometry::fill::{self, Fill}; +use iced_graphics::geometry::stroke::{self, Stroke}; +use iced_graphics::geometry::{Path, Style, Text}; +use iced_graphics::Gradient; pub struct Frame { size: Size, @@ -152,7 +152,7 @@ impl Frame { } fn convert_path(path: &Path) -> tiny_skia::Path { - use iced_native::widget::canvas::path::lyon_path; + use iced_graphics::geometry::path::lyon_path; let mut builder = tiny_skia::PathBuilder::new(); let mut last_point = Default::default(); diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index e66e6412fa..ef5c6b1db4 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -4,8 +4,8 @@ mod backend; mod settings; mod text; -#[cfg(feature = "canvas")] -pub mod canvas; +#[cfg(feature = "geometry")] +pub mod geometry; pub use iced_graphics::primitive; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 0bcef71c29..2e39a9e710 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -21,7 +21,7 @@ bmp = ["iced_graphics/bmp"] hdr = ["iced_graphics/hdr"] dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas", "lyon"] +geometry = ["iced_graphics/geometry", "lyon"] spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 10dc5b4ff3..6f39a5febb 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -329,8 +329,6 @@ impl Backend { } impl iced_graphics::Backend for Backend { - type Geometry = (); - fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache() } diff --git a/wgpu/src/canvas.rs b/wgpu/src/geometry.rs similarity index 99% rename from wgpu/src/canvas.rs rename to wgpu/src/geometry.rs index e8d540c3ea..11e8126fdd 100644 --- a/wgpu/src/canvas.rs +++ b/wgpu/src/geometry.rs @@ -1,9 +1,9 @@ -use iced_graphics::primitive::{self, Primitive}; -use iced_native::widget::canvas::fill::{self, Fill}; -use iced_native::widget::canvas::{ +use iced_graphics::geometry::fill::{self, Fill}; +use iced_graphics::geometry::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; -use iced_native::{Gradient, Point, Rectangle, Size, Vector}; +use iced_graphics::primitive::{self, Primitive}; +use iced_graphics::{Gradient, Point, Rectangle, Size, Vector}; use lyon::geom::euclid; use lyon::tessellation; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 31db16a8de..4439b18567 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -41,8 +41,8 @@ pub mod layer; pub mod settings; pub mod window; -#[cfg(feature = "canvas")] -pub mod canvas; +#[cfg(feature = "geometry")] +pub mod geometry; mod backend; mod buffer; From c54409d1711e1f615c7ea4b02c082954e340632a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 3 Mar 2023 17:31:44 +0100 Subject: [PATCH 36/57] Remove `canvas` leftovers in `iced_native` --- native/src/widget.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/native/src/widget.rs b/native/src/widget.rs index f107cd69d5..2b3ca7be07 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -83,14 +83,6 @@ pub use tree::Tree; #[doc(no_inline)] pub use vertical_slider::VerticalSlider; -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub mod canvas; - -#[cfg(feature = "canvas")] -#[doc(no_inline)] -pub use canvas::Canvas; - pub use action::Action; pub use id::Id; pub use operation::Operation; From 12781c717a08bf0e7bfb2594e568f89af3676d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 3 Mar 2023 20:45:49 +0100 Subject: [PATCH 37/57] Expose `window` commands for Wasm builds --- examples/todos/src/main.rs | 15 ++++----------- src/window.rs | 1 - 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 0f5bfe3003..6a87f58ce4 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -8,8 +8,6 @@ use iced::widget::{ text_input, Text, }; use iced::window; -#[cfg(not(target_arch = "wasm32"))] -use iced::window::Mode; use iced::{Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; @@ -51,11 +49,8 @@ enum Message { CreateTask, FilterChanged(Filter), TaskMessage(usize, TaskMessage), - TabPressed { - shift: bool, - }, - #[cfg(not(target_arch = "wasm32"))] - ToggleFullscreen(Mode), + TabPressed { shift: bool }, + ToggleFullscreen(window::Mode), } impl Application for Todos { @@ -162,7 +157,6 @@ impl Application for Todos { widget::focus_next() } } - #[cfg(not(target_arch = "wasm32"))] Message::ToggleFullscreen(mode) => { window::change_mode(mode) } @@ -276,7 +270,6 @@ impl Application for Todos { ) => Some(Message::TabPressed { shift: modifiers.shift(), }), - #[cfg(not(target_arch = "wasm32"))] ( Event::Keyboard(keyboard::Event::KeyPressed { key_code, @@ -285,10 +278,10 @@ impl Application for Todos { event::Status::Ignored, ) => match key_code { KeyCode::Up => { - Some(Message::ToggleFullscreen(Mode::Fullscreen)) + Some(Message::ToggleFullscreen(window::Mode::Fullscreen)) } KeyCode::Down => { - Some(Message::ToggleFullscreen(Mode::Windowed)) + Some(Message::ToggleFullscreen(window::Mode::Windowed)) } _ => None, }, diff --git a/src/window.rs b/src/window.rs index 2018053fbb..5a199580bf 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,5 +8,4 @@ pub use icon::Icon; pub use position::Position; pub use settings::Settings; -#[cfg(not(target_arch = "wasm32"))] pub use crate::runtime::window::*; From 3a0d34c0240f4421737a6a08761f99d6f8140d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 4 Mar 2023 05:37:11 +0100 Subject: [PATCH 38/57] Create `iced_widget` subcrate and re-organize the whole codebase --- Cargo.toml | 29 +- core/Cargo.toml | 1 + core/src/clipboard.rs | 23 ++ {native => core}/src/element.rs | 67 +++- {native => core}/src/event.rs | 2 +- {native => core}/src/hasher.rs | 0 {native => core}/src/image.rs | 0 {native => core}/src/layout.rs | 0 {native => core}/src/layout/DRUID_LICENSE | 0 {native => core}/src/layout/flex.rs | 0 {native => core}/src/layout/limits.rs | 0 {native => core}/src/layout/node.rs | 0 core/src/lib.rs | 24 ++ core/src/mouse.rs | 3 + {native => core}/src/mouse/click.rs | 0 {native => core}/src/overlay.rs | 3 - {native => core}/src/overlay/element.rs | 0 {native => core}/src/overlay/group.rs | 4 +- {native => core}/src/renderer.rs | 1 + {native => core}/src/renderer/null.rs | 4 +- {native => core}/src/shell.rs | 0 {native => core}/src/svg.rs | 0 {native => core}/src/text.rs | 0 {native => core}/src/touch.rs | 0 core/src/widget.rs | 145 +++++++ {native => core}/src/widget/id.rs | 2 +- {native => core}/src/widget/operation.rs | 0 .../src/widget/operation/focusable.rs | 0 .../src/widget/operation/scrollable.rs | 0 .../src/widget/operation/text_input.rs | 0 {native => core}/src/widget/text.rs | 54 ++- {native => core}/src/widget/tree.rs | 0 core/src/window.rs | 10 + {native => core}/src/window/event.rs | 0 {native => core}/src/window/mode.rs | 0 {native => core}/src/window/redraw_request.rs | 0 {native => core}/src/window/user_attention.rs | 0 examples/component/Cargo.toml | 4 +- examples/component/src/main.rs | 22 +- examples/custom_quad/Cargo.toml | 3 +- examples/custom_quad/src/main.rs | 8 +- examples/custom_widget/Cargo.toml | 3 +- examples/custom_widget/src/main.rs | 8 +- examples/events/Cargo.toml | 1 - examples/events/src/main.rs | 9 +- examples/geometry/Cargo.toml | 3 +- examples/geometry/src/main.rs | 38 +- examples/integration/Cargo.toml | 1 + examples/integration/src/controls.rs | 10 +- examples/integration/src/main.rs | 91 ++--- examples/integration/src/scene.rs | 2 +- examples/lazy/Cargo.toml | 3 +- examples/lazy/src/main.rs | 3 +- examples/modal/Cargo.toml | 3 +- examples/modal/src/main.rs | 44 ++- examples/pane_grid/Cargo.toml | 4 +- examples/pane_grid/src/main.rs | 8 +- examples/toast/Cargo.toml | 3 +- examples/toast/src/main.rs | 24 +- examples/url_handler/Cargo.toml | 1 - examples/url_handler/src/main.rs | 10 +- graphics/Cargo.toml | 10 +- graphics/src/backend.rs | 8 +- graphics/src/{window => }/compositor.rs | 6 +- graphics/src/geometry.rs | 4 +- graphics/src/geometry/fill.rs | 2 +- graphics/src/geometry/path.rs | 2 +- graphics/src/geometry/path/arc.rs | 2 +- graphics/src/geometry/path/builder.rs | 3 +- graphics/src/geometry/stroke.rs | 2 +- graphics/src/geometry/style.rs | 2 +- graphics/src/geometry/text.rs | 4 +- graphics/src/image/raster.rs | 4 +- graphics/src/image/storage.rs | 2 +- graphics/src/image/vector.rs | 5 +- graphics/src/lib.rs | 11 +- graphics/src/overlay.rs | 2 - graphics/src/overlay/menu.rs | 3 - graphics/src/primitive.rs | 9 +- graphics/src/renderer.rs | 20 +- graphics/src/viewport.rs | 4 +- graphics/src/window.rs | 10 - graphics/src/window/gl_compositor.rs | 71 ---- lazy/Cargo.toml | 18 - native/Cargo.toml | 7 - native/src/clipboard.rs | 22 -- native/src/command.rs | 4 +- native/src/debug/basic.rs | 2 +- native/src/lib.rs | 39 +- native/src/mouse.rs | 6 - native/src/program.rs | 6 +- native/src/program/state.rs | 18 +- native/src/runtime.rs | 4 +- native/src/subscription.rs | 17 +- native/src/user_interface.rs | 82 ++-- native/src/widget.rs | 206 ---------- native/src/widget/action.rs | 5 +- native/src/widget/helpers.rs | 317 --------------- native/src/window.rs | 13 +- native/src/window/action.rs | 4 +- renderer/Cargo.toml | 4 - renderer/src/backend.rs | 11 +- renderer/src/{window => }/compositor.rs | 9 +- renderer/src/geometry.rs | 6 +- renderer/src/geometry/cache.rs | 4 +- renderer/src/lib.rs | 14 +- renderer/src/settings.rs | 3 +- renderer/src/window.rs | 3 - src/advanced.rs | 9 + src/application.rs | 12 +- src/clipboard.rs | 3 - src/element.rs | 5 - src/error.rs | 16 +- src/executor.rs | 14 - src/keyboard.rs | 2 - src/lib.rs | 134 +++++-- src/mouse.rs | 2 - src/overlay.rs | 18 - src/result.rs | 6 - src/touch.rs | 2 +- src/widget.rs | 239 ------------ src/window.rs | 5 +- style/src/lib.rs | 3 +- style/src/text.rs | 2 +- style/src/theme.rs | 2 +- tiny_skia/src/backend.rs | 18 +- tiny_skia/src/geometry.rs | 12 +- tiny_skia/src/lib.rs | 11 +- tiny_skia/src/settings.rs | 2 +- tiny_skia/src/text.rs | 11 +- tiny_skia/src/window/compositor.rs | 9 +- wgpu/Cargo.toml | 4 - wgpu/src/backend.rs | 20 +- wgpu/src/geometry.rs | 8 +- wgpu/src/image.rs | 21 +- wgpu/src/image/atlas.rs | 4 +- wgpu/src/image/atlas/allocation.rs | 3 +- wgpu/src/image/atlas/allocator.rs | 4 +- wgpu/src/image/atlas/entry.rs | 5 +- wgpu/src/layer.rs | 9 +- wgpu/src/layer/image.rs | 6 +- wgpu/src/layer/mesh.rs | 4 +- wgpu/src/layer/text.rs | 3 +- wgpu/src/lib.rs | 15 +- wgpu/src/quad.rs | 6 +- wgpu/src/settings.rs | 5 +- wgpu/src/text.rs | 14 +- wgpu/src/triangle.rs | 24 +- wgpu/src/triangle/msaa.rs | 4 +- wgpu/src/window/compositor.rs | 10 +- widget/Cargo.toml | 37 ++ {native/src/widget => widget/src}/button.rs | 34 +- {src/widget => widget/src}/canvas.rs | 48 +-- {src/widget => widget/src}/canvas/cursor.rs | 2 +- {src/widget => widget/src}/canvas/event.rs | 8 +- {src/widget => widget/src}/canvas/program.rs | 13 +- {native/src/widget => widget/src}/checkbox.rs | 36 +- {native/src/widget => widget/src}/column.rs | 20 +- .../src/widget => widget/src}/container.rs | 28 +- widget/src/helpers.rs | 362 ++++++++++++++++++ {native/src/widget => widget/src}/image.rs | 15 +- .../src/widget => widget/src}/image/viewer.rs | 14 +- {lazy => widget}/src/lazy.rs | 47 ++- {lazy/src => widget/src/lazy}/cache.rs | 4 +- {lazy/src => widget/src/lazy}/component.rs | 32 +- lazy/src/lib.rs => widget/src/lazy/helpers.rs | 37 +- {lazy/src => widget/src/lazy}/responsive.rs | 40 +- widget/src/lib.rs | 122 ++++++ widget/src/overlay.rs | 1 + {native => widget}/src/overlay/menu.rs | 34 +- .../src/widget => widget/src}/pane_grid.rs | 40 +- .../widget => widget/src}/pane_grid/axis.rs | 2 +- .../src}/pane_grid/configuration.rs | 2 +- .../src}/pane_grid/content.rs | 30 +- .../src}/pane_grid/direction.rs | 0 .../src}/pane_grid/draggable.rs | 2 +- .../widget => widget/src}/pane_grid/node.rs | 4 +- .../widget => widget/src}/pane_grid/pane.rs | 0 .../widget => widget/src}/pane_grid/split.rs | 0 .../widget => widget/src}/pane_grid/state.rs | 6 +- .../src}/pane_grid/title_bar.rs | 24 +- .../src/widget => widget/src}/pick_list.rs | 33 +- .../src/widget => widget/src}/progress_bar.rs | 24 +- {src/widget => widget/src}/qr_code.rs | 15 +- {native/src/widget => widget/src}/radio.rs | 31 +- {native/src/widget => widget/src}/row.rs | 20 +- {native/src/widget => widget/src}/rule.rs | 20 +- .../src/widget => widget/src}/scrollable.rs | 168 ++++---- {native/src/widget => widget/src}/slider.rs | 32 +- {native/src/widget => widget/src}/space.rs | 13 +- {native/src/widget => widget/src}/svg.rs | 14 +- widget/src/text.rs | 4 + .../src/widget => widget/src}/text_input.rs | 39 +- .../src}/text_input/cursor.rs | 2 +- .../src}/text_input/editor.rs | 2 +- .../widget => widget/src}/text_input/value.rs | 0 {native/src/widget => widget/src}/toggler.rs | 30 +- {native/src/widget => widget/src}/tooltip.rs | 37 +- .../widget => widget/src}/vertical_slider.rs | 37 +- winit/Cargo.toml | 6 +- winit/src/application.rs | 77 ++-- winit/src/application/state.rs | 14 +- winit/src/clipboard.rs | 7 +- winit/src/conversion.rs | 11 +- winit/src/error.rs | 7 +- winit/src/lib.rs | 8 +- winit/src/proxy.rs | 2 +- winit/src/system.rs | 7 +- winit/src/window.rs | 52 +-- 209 files changed, 1963 insertions(+), 2187 deletions(-) create mode 100644 core/src/clipboard.rs rename {native => core}/src/element.rs (90%) rename {native => core}/src/event.rs (98%) rename {native => core}/src/hasher.rs (100%) rename {native => core}/src/image.rs (100%) rename {native => core}/src/layout.rs (100%) rename {native => core}/src/layout/DRUID_LICENSE (100%) rename {native => core}/src/layout/flex.rs (100%) rename {native => core}/src/layout/limits.rs (100%) rename {native => core}/src/layout/node.rs (100%) rename {native => core}/src/mouse/click.rs (100%) rename {native => core}/src/overlay.rs (99%) rename {native => core}/src/overlay/element.rs (100%) rename {native => core}/src/overlay/group.rs (97%) rename {native => core}/src/renderer.rs (99%) rename {native => core}/src/renderer/null.rs (94%) rename {native => core}/src/shell.rs (100%) rename {native => core}/src/svg.rs (100%) rename {native => core}/src/text.rs (100%) rename {native => core}/src/touch.rs (100%) create mode 100644 core/src/widget.rs rename {native => core}/src/widget/id.rs (97%) rename {native => core}/src/widget/operation.rs (100%) rename {native => core}/src/widget/operation/focusable.rs (100%) rename {native => core}/src/widget/operation/scrollable.rs (100%) rename {native => core}/src/widget/operation/text_input.rs (100%) rename {native => core}/src/widget/text.rs (87%) rename {native => core}/src/widget/tree.rs (100%) create mode 100644 core/src/window.rs rename {native => core}/src/window/event.rs (100%) rename {native => core}/src/window/mode.rs (100%) rename {native => core}/src/window/redraw_request.rs (100%) rename {native => core}/src/window/user_attention.rs (100%) rename graphics/src/{window => }/compositor.rs (96%) delete mode 100644 graphics/src/overlay.rs delete mode 100644 graphics/src/overlay/menu.rs delete mode 100644 graphics/src/window.rs delete mode 100644 graphics/src/window/gl_compositor.rs delete mode 100644 lazy/Cargo.toml delete mode 100644 native/src/mouse.rs delete mode 100644 native/src/widget/helpers.rs rename renderer/src/{window => }/compositor.rs (94%) delete mode 100644 renderer/src/window.rs create mode 100644 src/advanced.rs delete mode 100644 src/clipboard.rs delete mode 100644 src/element.rs delete mode 100644 src/executor.rs delete mode 100644 src/keyboard.rs delete mode 100644 src/mouse.rs delete mode 100644 src/overlay.rs delete mode 100644 src/result.rs delete mode 100644 src/widget.rs create mode 100644 widget/Cargo.toml rename {native/src/widget => widget/src}/button.rs (94%) rename {src/widget => widget/src}/canvas.rs (84%) rename {src/widget => widget/src}/canvas/cursor.rs (98%) rename {src/widget => widget/src}/canvas/event.rs (73%) rename {src/widget => widget/src}/canvas/program.rs (92%) rename {native/src/widget => widget/src}/checkbox.rs (91%) rename {native/src/widget => widget/src}/column.rs (95%) rename {native/src/widget => widget/src}/container.rs (94%) create mode 100644 widget/src/helpers.rs rename {native/src/widget => widget/src}/image.rs (96%) rename {native/src/widget => widget/src}/image/viewer.rs (98%) rename {lazy => widget}/src/lazy.rs (91%) rename {lazy/src => widget/src/lazy}/cache.rs (84%) rename {lazy/src => widget/src/lazy}/component.rs (96%) rename lazy/src/lib.rs => widget/src/lazy/helpers.rs (51%) rename {lazy/src => widget/src/lazy}/responsive.rs (93%) create mode 100644 widget/src/lib.rs create mode 100644 widget/src/overlay.rs rename {native => widget}/src/overlay/menu.rs (95%) rename {native/src/widget => widget/src}/pane_grid.rs (97%) rename {native/src/widget => widget/src}/pane_grid/axis.rs (99%) rename {native/src/widget => widget/src}/pane_grid/configuration.rs (94%) rename {native/src/widget => widget/src}/pane_grid/content.rs (94%) rename {native/src/widget => widget/src}/pane_grid/direction.rs (100%) rename {native/src/widget => widget/src}/pane_grid/draggable.rs (89%) rename {native/src/widget => widget/src}/pane_grid/node.rs (98%) rename {native/src/widget => widget/src}/pane_grid/pane.rs (100%) rename {native/src/widget => widget/src}/pane_grid/split.rs (100%) rename {native/src/widget => widget/src}/pane_grid/state.rs (98%) rename {native/src/widget => widget/src}/pane_grid/title_bar.rs (96%) rename {native/src/widget => widget/src}/pick_list.rs (97%) rename {native/src/widget => widget/src}/progress_bar.rs (89%) rename {src/widget => widget/src}/qr_code.rs (98%) rename {native/src/widget => widget/src}/radio.rs (92%) rename {native/src/widget => widget/src}/row.rs (94%) rename {native/src/widget => widget/src}/rule.rs (90%) rename {native/src/widget => widget/src}/scrollable.rs (91%) rename {native/src/widget => widget/src}/slider.rs (95%) rename {native/src/widget => widget/src}/space.rs (88%) rename {native/src/widget => widget/src}/svg.rs (95%) create mode 100644 widget/src/text.rs rename {native/src/widget => widget/src}/text_input.rs (97%) rename {native/src/widget => widget/src}/text_input/cursor.rs (99%) rename {native/src/widget => widget/src}/text_input/editor.rs (97%) rename {native/src/widget => widget/src}/text_input/value.rs (100%) rename {native/src/widget => widget/src}/toggler.rs (93%) rename {native/src/widget => widget/src}/tooltip.rs (93%) rename {native/src/widget => widget/src}/vertical_slider.rs (93%) diff --git a/Cargo.toml b/Cargo.toml index 28938df911..49a52311a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,15 @@ categories = ["gui"] [features] # Enables the `Image` widget -image = ["iced_renderer/image", "image_rs"] +image = ["iced_widget/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_renderer/svg"] +svg = ["iced_widget/svg"] # Enables the `Canvas` widget -canvas = ["iced_renderer/geometry"] +canvas = ["iced_widget/canvas"] # Enables the `QRCode` widget -qr_code = ["canvas", "qrcode"] +qr_code = ["iced_widget/qr_code"] +# Enables lazy widgets +lazy = ["iced_widget/lazy"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -32,11 +34,8 @@ smol = ["iced_futures/smol"] palette = ["iced_core/palette"] # Enables querying system information system = ["iced_winit/system"] -# Enables chrome traces -chrome-trace = [ - "iced_winit/chrome-trace", - "iced_renderer/tracing", -] +# Enables the advanced module +advanced = [] [badges] maintenance = { status = "actively-developed" } @@ -46,12 +45,12 @@ members = [ "core", "futures", "graphics", - "lazy", "native", "renderer", "style", "tiny_skia", "wgpu", + "widget", "winit", "examples/*", ] @@ -59,21 +58,15 @@ members = [ [dependencies] iced_core = { version = "0.8", path = "core" } iced_futures = { version = "0.6", path = "futures" } -iced_native = { version = "0.9", path = "native" } -iced_renderer = { version = "0.1", path = "renderer" } +iced_widget = { version = "0.1", path = "widget" } iced_winit = { version = "0.8", path = "winit", features = ["application"] } -thiserror = "1.0" +thiserror = "1" [dependencies.image_rs] version = "0.24" package = "image" optional = true -[dependencies.qrcode] -version = "0.12" -optional = true -default-features = false - [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas", "qr_code"] diff --git a/core/Cargo.toml b/core/Cargo.toml index 7ccb7b7aca..9edc20f6a1 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/iced-rs/iced" [dependencies] bitflags = "1.2" thiserror = "1" +twox-hash = { version = "1.5", default-features = false } [dependencies.palette] version = "0.6" diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs new file mode 100644 index 0000000000..081b40046c --- /dev/null +++ b/core/src/clipboard.rs @@ -0,0 +1,23 @@ +//! Access the clipboard. + +/// A buffer for short-term storage and transfer within and between +/// applications. +pub trait Clipboard { + /// Reads the current content of the [`Clipboard`] as text. + fn read(&self) -> Option; + + /// Writes the given text contents to the [`Clipboard`]. + fn write(&mut self, contents: String); +} + +/// A null implementation of the [`Clipboard`] trait. +#[derive(Debug, Clone, Copy)] +pub struct Null; + +impl Clipboard for Null { + fn read(&self) -> Option { + None + } + + fn write(&mut self, _contents: String) {} +} diff --git a/native/src/element.rs b/core/src/element.rs similarity index 90% rename from native/src/element.rs rename to core/src/element.rs index 0a677d20bb..98c5373786 100644 --- a/native/src/element.rs +++ b/core/src/element.rs @@ -90,41 +90,65 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// We compose the previous __messages__ with the index of the counter /// producing them. Let's implement our __view logic__ now: /// - /// ``` + /// ```no_run /// # mod counter { - /// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; - /// # /// # #[derive(Debug, Clone, Copy)] /// # pub enum Message {} /// # pub struct Counter; /// # /// # impl Counter { - /// # pub fn view(&mut self) -> Text { - /// # Text::new("") + /// # pub fn view( + /// # &self, + /// # ) -> iced_core::Element { + /// # unimplemented!() /// # } /// # } /// # } /// # - /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; - /// # } + /// # mod iced { + /// # pub use iced_core::renderer::Null as Renderer; + /// # pub use iced_core::Element; /// # - /// # use counter::Counter; + /// # pub mod widget { + /// # pub struct Row { + /// # _t: std::marker::PhantomData, + /// # } /// # - /// # struct ManyCounters { - /// # counters: Vec, - /// # } + /// # impl Row { + /// # pub fn new() -> Self { + /// # unimplemented!() + /// # } /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { - /// # Counter(usize, counter::Message) + /// # pub fn spacing(mut self, _: u32) -> Self { + /// # unimplemented!() + /// # } + /// # + /// # pub fn push( + /// # mut self, + /// # _: iced_core::Element, + /// # ) -> Self { + /// # unimplemented!() + /// # } + /// # } + /// # } /// # } - /// use iced_native::Element; - /// use iced_native::widget::Row; - /// use iced_wgpu::Renderer; + /// # + /// use counter::Counter; + /// + /// use iced::widget::Row; + /// use iced::{Element, Renderer}; + /// + /// struct ManyCounters { + /// counters: Vec, + /// } + /// + /// #[derive(Debug, Clone, Copy)] + /// pub enum Message { + /// Counter(usize, counter::Message), + /// } /// /// impl ManyCounters { - /// pub fn view(&mut self) -> Row { + /// pub fn view(&mut self) -> Row { /// // We can quickly populate a `Row` by folding over our counters /// self.counters.iter_mut().enumerate().fold( /// Row::new().spacing(20), @@ -137,9 +161,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// // Here we turn our `Element` into /// // an `Element` by combining the `index` and the /// // message of the `element`. - /// element.map(move |message| Message::Counter(index, message)) + /// element + /// .map(move |message| Message::Counter(index, message)), /// ) - /// } + /// }, /// ) /// } /// } diff --git a/native/src/event.rs b/core/src/event.rs similarity index 98% rename from native/src/event.rs rename to core/src/event.rs index bcfaf891bc..953cd73f94 100644 --- a/native/src/event.rs +++ b/core/src/event.rs @@ -62,7 +62,7 @@ impl Status { /// `Captured` takes precedence over `Ignored`: /// /// ``` - /// use iced_native::event::Status; + /// use iced_core::event::Status; /// /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored); /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); diff --git a/native/src/hasher.rs b/core/src/hasher.rs similarity index 100% rename from native/src/hasher.rs rename to core/src/hasher.rs diff --git a/native/src/image.rs b/core/src/image.rs similarity index 100% rename from native/src/image.rs rename to core/src/image.rs diff --git a/native/src/layout.rs b/core/src/layout.rs similarity index 100% rename from native/src/layout.rs rename to core/src/layout.rs diff --git a/native/src/layout/DRUID_LICENSE b/core/src/layout/DRUID_LICENSE similarity index 100% rename from native/src/layout/DRUID_LICENSE rename to core/src/layout/DRUID_LICENSE diff --git a/native/src/layout/flex.rs b/core/src/layout/flex.rs similarity index 100% rename from native/src/layout/flex.rs rename to core/src/layout/flex.rs diff --git a/native/src/layout/limits.rs b/core/src/layout/limits.rs similarity index 100% rename from native/src/layout/limits.rs rename to core/src/layout/limits.rs diff --git a/native/src/layout/node.rs b/core/src/layout/node.rs similarity index 100% rename from native/src/layout/node.rs rename to core/src/layout/node.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 1e4f0411d9..5bdcee6a4a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,33 +25,57 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub mod alignment; +pub mod clipboard; +pub mod event; pub mod font; pub mod gradient; +pub mod image; pub mod keyboard; +pub mod layout; pub mod mouse; +pub mod overlay; +pub mod renderer; +pub mod svg; +pub mod text; pub mod time; +pub mod touch; +pub mod widget; +pub mod window; mod background; mod color; mod content_fit; +mod element; +mod hasher; mod length; mod padding; mod pixels; mod point; mod rectangle; +mod shell; mod size; mod vector; pub use alignment::Alignment; pub use background::Background; +pub use clipboard::Clipboard; pub use color::Color; pub use content_fit::ContentFit; +pub use element::Element; +pub use event::Event; pub use font::Font; pub use gradient::Gradient; +pub use hasher::Hasher; +pub use layout::Layout; pub use length::Length; +pub use overlay::Overlay; pub use padding::Padding; pub use pixels::Pixels; pub use point::Point; pub use rectangle::Rectangle; +pub use renderer::Renderer; +pub use shell::Shell; pub use size::Size; +pub use text::Text; pub use vector::Vector; +pub use widget::Widget; diff --git a/core/src/mouse.rs b/core/src/mouse.rs index 48214f6558..0c405ce617 100644 --- a/core/src/mouse.rs +++ b/core/src/mouse.rs @@ -1,8 +1,11 @@ //! Handle mouse events. +pub mod click; + mod button; mod event; mod interaction; pub use button::Button; +pub use click::Click; pub use event::{Event, ScrollDelta}; pub use interaction::Interaction; diff --git a/native/src/mouse/click.rs b/core/src/mouse/click.rs similarity index 100% rename from native/src/mouse/click.rs rename to core/src/mouse/click.rs diff --git a/native/src/overlay.rs b/core/src/overlay.rs similarity index 99% rename from native/src/overlay.rs rename to core/src/overlay.rs index 6cada41655..b9f3c735a8 100644 --- a/native/src/overlay.rs +++ b/core/src/overlay.rs @@ -2,11 +2,8 @@ mod element; mod group; -pub mod menu; - pub use element::Element; pub use group::Group; -pub use menu::Menu; use crate::event::{self, Event}; use crate::layout; diff --git a/native/src/overlay/element.rs b/core/src/overlay/element.rs similarity index 100% rename from native/src/overlay/element.rs rename to core/src/overlay/element.rs diff --git a/native/src/overlay/group.rs b/core/src/overlay/group.rs similarity index 97% rename from native/src/overlay/group.rs rename to core/src/overlay/group.rs index 1126f0cf11..0c48df34a7 100644 --- a/native/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -1,12 +1,10 @@ -use iced_core::{Point, Rectangle, Size}; - use crate::event; use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; -use crate::{Clipboard, Event, Layout, Overlay, Shell}; +use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] /// children. diff --git a/native/src/renderer.rs b/core/src/renderer.rs similarity index 99% rename from native/src/renderer.rs rename to core/src/renderer.rs index 2ac7898298..d6247e39d2 100644 --- a/native/src/renderer.rs +++ b/core/src/renderer.rs @@ -1,6 +1,7 @@ //! Write your own renderer. #[cfg(debug_assertions)] mod null; + #[cfg(debug_assertions)] pub use null::Null; diff --git a/native/src/renderer/null.rs b/core/src/renderer/null.rs similarity index 94% rename from native/src/renderer/null.rs rename to core/src/renderer/null.rs index 150ee7864c..d93338aee8 100644 --- a/native/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,6 +1,6 @@ use crate::renderer::{self, Renderer}; use crate::text::{self, Text}; -use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; +use crate::{Background, Font, Point, Rectangle, Size, Vector}; use std::borrow::Cow; @@ -18,7 +18,7 @@ impl Null { } impl Renderer for Null { - type Theme = Theme; + type Theme = (); fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {} diff --git a/native/src/shell.rs b/core/src/shell.rs similarity index 100% rename from native/src/shell.rs rename to core/src/shell.rs diff --git a/native/src/svg.rs b/core/src/svg.rs similarity index 100% rename from native/src/svg.rs rename to core/src/svg.rs diff --git a/native/src/text.rs b/core/src/text.rs similarity index 100% rename from native/src/text.rs rename to core/src/text.rs diff --git a/native/src/touch.rs b/core/src/touch.rs similarity index 100% rename from native/src/touch.rs rename to core/src/touch.rs diff --git a/core/src/widget.rs b/core/src/widget.rs new file mode 100644 index 0000000000..70e2c2d9e6 --- /dev/null +++ b/core/src/widget.rs @@ -0,0 +1,145 @@ +//! Create custom widgets and operate on them. +pub mod operation; +pub mod text; +pub mod tree; + +mod id; + +pub use id::Id; +pub use operation::Operation; +pub use text::Text; +pub use tree::Tree; + +use crate::event::{self, Event}; +use crate::layout::{self, Layout}; +use crate::mouse; +use crate::overlay; +use crate::renderer; +use crate::{Clipboard, Length, Point, Rectangle, Shell}; + +/// A component that displays information and allows interaction. +/// +/// If you want to build your own widgets, you will need to implement this +/// trait. +/// +/// # Examples +/// The repository has some [examples] showcasing how to implement a custom +/// widget: +/// +/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using +/// [`lyon`]. +/// - [`custom_widget`], a demonstration of how to build a custom widget that +/// draws a circle. +/// - [`geometry`], a custom widget showcasing how to draw geometry with the +/// `Mesh2D` primitive in [`iced_wgpu`]. +/// +/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry +/// [`lyon`]: https://github.com/nical/lyon +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu +pub trait Widget +where + Renderer: crate::Renderer, +{ + /// Returns the width of the [`Widget`]. + fn width(&self) -> Length; + + /// Returns the height of the [`Widget`]. + fn height(&self) -> Length; + + /// Returns the [`layout::Node`] of the [`Widget`]. + /// + /// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the + /// user interface. + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node; + + /// Draws the [`Widget`] using the associated `Renderer`. + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ); + + /// Returns the [`Tag`] of the [`Widget`]. + /// + /// [`Tag`]: tree::Tag + fn tag(&self) -> tree::Tag { + tree::Tag::stateless() + } + + /// Returns the [`State`] of the [`Widget`]. + /// + /// [`State`]: tree::State + fn state(&self) -> tree::State { + tree::State::None + } + + /// Returns the state [`Tree`] of the children of the [`Widget`]. + fn children(&self) -> Vec { + Vec::new() + } + + /// Reconciliates the [`Widget`] with the provided [`Tree`]. + fn diff(&self, _tree: &mut Tree) {} + + /// Applies an [`Operation`] to the [`Widget`]. + fn operate( + &self, + _state: &mut Tree, + _layout: Layout<'_>, + _renderer: &Renderer, + _operation: &mut dyn Operation, + ) { + } + + /// Processes a runtime [`Event`]. + /// + /// By default, it does nothing. + fn on_event( + &mut self, + _state: &mut Tree, + _event: Event, + _layout: Layout<'_>, + _cursor_position: Point, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + _shell: &mut Shell<'_, Message>, + ) -> event::Status { + event::Status::Ignored + } + + /// Returns the current [`mouse::Interaction`] of the [`Widget`]. + /// + /// By default, it returns [`mouse::Interaction::Idle`]. + fn mouse_interaction( + &self, + _state: &Tree, + _layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + mouse::Interaction::Idle + } + + /// Returns the overlay of the [`Widget`], if there is any. + fn overlay<'a>( + &'a mut self, + _state: &'a mut Tree, + _layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + None + } +} diff --git a/native/src/widget/id.rs b/core/src/widget/id.rs similarity index 97% rename from native/src/widget/id.rs rename to core/src/widget/id.rs index 4b8fedf1e6..ae739bb73d 100644 --- a/native/src/widget/id.rs +++ b/core/src/widget/id.rs @@ -24,7 +24,7 @@ impl Id { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Internal { +enum Internal { Unique(usize), Custom(borrow::Cow<'static, str>), } diff --git a/native/src/widget/operation.rs b/core/src/widget/operation.rs similarity index 100% rename from native/src/widget/operation.rs rename to core/src/widget/operation.rs diff --git a/native/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs similarity index 100% rename from native/src/widget/operation/focusable.rs rename to core/src/widget/operation/focusable.rs diff --git a/native/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs similarity index 100% rename from native/src/widget/operation/scrollable.rs rename to core/src/widget/operation/scrollable.rs diff --git a/native/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs similarity index 100% rename from native/src/widget/operation/text_input.rs rename to core/src/widget/operation/text_input.rs diff --git a/native/src/widget/text.rs b/core/src/widget/text.rs similarity index 87% rename from native/src/widget/text.rs rename to core/src/widget/text.rs index aede754a87..485bb54209 100644 --- a/native/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -4,27 +4,13 @@ use crate::layout; use crate::renderer; use crate::text; use crate::widget::Tree; -use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget}; +use crate::{ + Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget, +}; use std::borrow::Cow; -pub use iced_style::text::{Appearance, StyleSheet}; - /// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_native::Color; -/// # -/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; -/// # -/// Text::new("I <3 iced!") -/// .size(40) -/// .style(Color::from([0.0, 0.0, 1.0])); -/// ``` -/// -/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true) #[allow(missing_debug_implementations)] pub struct Text<'a, Renderer> where @@ -211,7 +197,7 @@ pub fn draw( alignment::Vertical::Bottom => bounds.y + bounds.height, }; - renderer.fill_text(crate::text::Text { + renderer.fill_text(crate::Text { content, size: size.unwrap_or_else(|| renderer.default_size()), bounds: Rectangle { x, y, ..bounds }, @@ -252,12 +238,40 @@ where } } +impl<'a, Renderer> From<&'a str> for Text<'a, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn from(content: &'a str) -> Self { + Self::new(content) + } +} + impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer> where Renderer: text::Renderer + 'a, Renderer::Theme: StyleSheet, { - fn from(contents: &'a str) -> Self { - Text::new(contents).into() + fn from(content: &'a str) -> Self { + Text::from(content).into() } } + +/// The style sheet of some text. +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default + Copy; + + /// Produces the [`Appearance`] of some text. + fn appearance(&self, style: Self::Style) -> Appearance; +} + +/// The apperance of some text. +#[derive(Debug, Clone, Copy, Default)] +pub struct Appearance { + /// The [`Color`] of the text. + /// + /// The default, `None`, means using the inherited color. + pub color: Option, +} diff --git a/native/src/widget/tree.rs b/core/src/widget/tree.rs similarity index 100% rename from native/src/widget/tree.rs rename to core/src/widget/tree.rs diff --git a/core/src/window.rs b/core/src/window.rs new file mode 100644 index 0000000000..d829a4b4e0 --- /dev/null +++ b/core/src/window.rs @@ -0,0 +1,10 @@ +//! Build window-based GUI applications. +mod event; +mod mode; +mod redraw_request; +mod user_attention; + +pub use event::Event; +pub use mode::Mode; +pub use redraw_request::RedrawRequest; +pub use user_attention::UserAttention; diff --git a/native/src/window/event.rs b/core/src/window/event.rs similarity index 100% rename from native/src/window/event.rs rename to core/src/window/event.rs diff --git a/native/src/window/mode.rs b/core/src/window/mode.rs similarity index 100% rename from native/src/window/mode.rs rename to core/src/window/mode.rs diff --git a/native/src/window/redraw_request.rs b/core/src/window/redraw_request.rs similarity index 100% rename from native/src/window/redraw_request.rs rename to core/src/window/redraw_request.rs diff --git a/native/src/window/user_attention.rs b/core/src/window/user_attention.rs similarity index 100% rename from native/src/window/user_attention.rs rename to core/src/window/user_attention.rs diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml index dd435201f3..9db1e6b47f 100644 --- a/examples/component/Cargo.toml +++ b/examples/component/Cargo.toml @@ -6,6 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "lazy"] } diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index c407bb06c6..e59588b104 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -47,9 +47,8 @@ impl Sandbox for Component { mod numeric_input { use iced::alignment::{self, Alignment}; - use iced::widget::{self, button, row, text, text_input}; - use iced::{Element, Length}; - use iced_lazy::{self, Component}; + use iced::widget::{button, component, row, text, text_input, Component}; + use iced::{Element, Length, Renderer}; pub struct NumericInput { value: Option, @@ -82,13 +81,7 @@ mod numeric_input { } } - impl Component for NumericInput - where - Renderer: iced_native::text::Renderer + 'static, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, - { + impl Component for NumericInput { type State = (); type Event = Event; @@ -151,17 +144,12 @@ mod numeric_input { } } - impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> + impl<'a, Message> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'static + iced_native::text::Renderer, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, { fn from(numeric_input: NumericInput) -> Self { - iced_lazy::component(numeric_input) + component(numeric_input) } } } diff --git a/examples/custom_quad/Cargo.toml b/examples/custom_quad/Cargo.toml index 39154786e1..f097c2ddf4 100644 --- a/examples/custom_quad/Cargo.toml +++ b/examples/custom_quad/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index 6509887c52..b07f42ce87 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -1,9 +1,9 @@ //! This example showcases a drawing a quad. mod quad { - use iced_native::layout::{self, Layout}; - use iced_native::renderer; - use iced_native::widget::{self, Widget}; - use iced_native::{Color, Element, Length, Point, Rectangle, Size}; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{Color, Element, Length, Point, Rectangle, Size}; pub struct CustomQuad { size: f32, diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml index 067aab4a2d..dda0efe8e8 100644 --- a/examples/custom_widget/Cargo.toml +++ b/examples/custom_widget/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index f6bb3b1e6f..7854548c02 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -9,10 +9,10 @@ mod circle { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. - use iced_native::layout::{self, Layout}; - use iced_native::renderer; - use iced_native::widget::{self, Widget}; - use iced_native::{Color, Element, Length, Point, Rectangle, Size}; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{Color, Element, Length, Point, Rectangle, Size}; pub struct Circle { radius: f32, diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml index 8c56e4711f..15ffc0af12 100644 --- a/examples/events/Cargo.toml +++ b/examples/events/Cargo.toml @@ -7,4 +7,3 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 1b97018e1b..7f3a5e1d5f 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,12 +1,13 @@ use iced::alignment; use iced::executor; +use iced::subscription; use iced::widget::{button, checkbox, container, text, Column}; use iced::window; +use iced::Event; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_native::Event; pub fn main() -> iced::Result { Events::run(Settings { @@ -17,13 +18,13 @@ pub fn main() -> iced::Result { #[derive(Debug, Default)] struct Events { - last: Vec, + last: Vec, enabled: bool, } #[derive(Debug, Clone)] enum Message { - EventOccurred(iced_native::Event), + EventOccurred(Event), Toggled(bool), Exit, } @@ -70,7 +71,7 @@ impl Application for Events { } fn subscription(&self) -> Subscription { - iced_native::subscription::events().map(Message::EventOccurred) + subscription::events().map(Message::EventOccurred) } fn view(&self) -> Element { diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 22ede0e00d..79fe52d5a7 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -6,6 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } iced_graphics = { path = "../../graphics" } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index a77772d5b3..5cb41184d9 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -1,23 +1,13 @@ //! This example showcases a simple native custom widget that renders using //! arbitrary low-level geometry. mod rainbow { - // For now, to implement a custom native widget you will need to add - // `iced_native` and `iced_wgpu` to your dependencies. - // - // Then, you simply need to define your widget type and implement the - // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. - // - // Of course, you can choose to make the implementation renderer-agnostic, - // if you wish to, by creating your own `Renderer` trait, which could be - // implemented by `iced_wgpu` and other renderers. use iced_graphics::primitive::{ColoredVertex2D, Primitive}; - use iced_graphics::renderer::{self, Renderer}; - use iced_graphics::Backend; - use iced_native::layout; - use iced_native::widget::{self, Widget}; - use iced_native::{ - Element, Layout, Length, Point, Rectangle, Size, Vector, + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{ + Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; #[derive(Debug, Clone, Copy, Default)] @@ -27,10 +17,7 @@ mod rainbow { Rainbow } - impl Widget> for Rainbow - where - B: Backend, - { + impl Widget for Rainbow { fn width(&self) -> Length { Length::Fill } @@ -41,7 +28,7 @@ mod rainbow { fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let size = limits.width(Length::Fill).resolve(Size::ZERO); @@ -52,15 +39,15 @@ mod rainbow { fn draw( &self, _tree: &widget::Tree, - renderer: &mut Renderer, - _theme: &T, + renderer: &mut Renderer, + _theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, ) { + use iced::advanced::Renderer as _; use iced_graphics::primitive::Mesh2D; - use iced_native::Renderer as _; let b = layout.bounds(); @@ -151,10 +138,7 @@ mod rainbow { } } - impl<'a, Message, B, T> From for Element<'a, Message, Renderer> - where - B: Backend, - { + impl<'a, Message> From for Element<'a, Message, Renderer> { fn from(rainbow: Rainbow) -> Self { Self::new(rainbow) } diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 200306aaa9..6039024228 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } iced_wgpu = { path = "../../wgpu", features = ["webgl"] } +iced_widget = { path = "../../widget" } env_logger = "0.8" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 533cb6e2a4..16e2170966 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,6 +1,8 @@ use iced_wgpu::Renderer; -use iced_winit::widget::{slider, text_input, Column, Row, Text}; -use iced_winit::{Alignment, Color, Command, Element, Length, Program}; +use iced_widget::{slider, text_input, Column, Row, Text}; +use iced_winit::core::{Alignment, Color, Element, Length}; +use iced_winit::native::{Command, Program}; +use iced_winit::style::Theme; pub struct Controls { background_color: Color, @@ -27,7 +29,7 @@ impl Controls { } impl Program for Controls { - type Renderer = Renderer; + type Renderer = Renderer; type Message = Message; fn update(&mut self, message: Message) -> Command { @@ -43,7 +45,7 @@ impl Program for Controls { Command::none() } - fn view(&self) -> Element { + fn view(&self) -> Element> { let background_color = self.background_color; let text = &self.text; diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index a7a90ced11..c1f1f076d3 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -4,11 +4,14 @@ mod scene; use controls::Controls; use scene::Scene; -use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; -use iced_winit::{ - conversion, futures, program, renderer, winit, Clipboard, Color, Debug, - Size, -}; +use iced_wgpu::graphics::Viewport; +use iced_wgpu::{wgpu, Backend, Renderer, Settings}; +use iced_winit::core::renderer; +use iced_winit::core::{Color, Size}; +use iced_winit::native::program; +use iced_winit::native::Debug; +use iced_winit::style::Theme; +use iced_winit::{conversion, futures, winit, Clipboard}; use winit::{ dpi::PhysicalPosition, @@ -73,43 +76,45 @@ pub fn main() { let instance = wgpu::Instance::new(backend); let surface = unsafe { instance.create_surface(&window) }; - let (format, (device, queue)) = futures::executor::block_on(async { - 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 - .get_supported_formats(&adapter) - .first() - .copied() - .expect("Get preferred format"), - adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: adapter_features & wgpu::Features::default(), - limits: needed_limits, - }, - None, - ) - .await - .expect("Request device"), - ) - }); + let (format, (device, queue)) = + futures::futures::executor::block_on(async { + 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 + .get_supported_formats(&adapter) + .first() + .copied() + .expect("Get preferred format"), + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: adapter_features + & wgpu::Features::default(), + limits: needed_limits, + }, + None, + ) + .await + .expect("Request device"), + ) + }); surface.configure( &device, @@ -188,7 +193,7 @@ pub fn main() { viewport.scale_factor(), ), &mut renderer, - &iced_wgpu::Theme::Dark, + &Theme::Dark, &renderer::Style { text_color: Color::WHITE }, &mut clipboard, &mut debug, diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 3e41fbda5a..90c7efbfa7 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -1,5 +1,5 @@ use iced_wgpu::wgpu; -use iced_winit::Color; +use iced_winit::core::Color; pub struct Scene { pipeline: wgpu::RenderPipeline, diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml index 79255c2563..e03e89a960 100644 --- a/examples/lazy/Cargo.toml +++ b/examples/lazy/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "lazy"] } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 6512106f23..e1cdaefeb4 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -1,10 +1,9 @@ use iced::theme; use iced::widget::{ - button, column, horizontal_space, pick_list, row, scrollable, text, + button, column, horizontal_space, lazy, pick_list, row, scrollable, text, text_input, }; use iced::{Element, Length, Sandbox, Settings}; -use iced_lazy::lazy; use std::collections::HashSet; use std::hash::Hash; diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml index 8770acac6c..3ac61e6a04 100644 --- a/examples/modal/Cargo.toml +++ b/examples/modal/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = [] } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 54555684a0..214ec97e05 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -178,12 +178,15 @@ impl App { } mod modal { - use iced_native::alignment::Alignment; - use iced_native::widget::{self, Tree}; - use iced_native::{ - event, layout, mouse, overlay, renderer, Clipboard, Color, Element, - Event, Layout, Length, Point, Rectangle, Shell, Size, Widget, - }; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::overlay; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::advanced::{self, Clipboard, Shell}; + use iced::alignment::Alignment; + use iced::event; + use iced::mouse; + use iced::{Color, Element, Event, Length, Point, Rectangle, Size}; /// A widget that centers a modal element over some base element pub struct Modal<'a, Message, Renderer> { @@ -218,14 +221,17 @@ mod modal { impl<'a, Message, Renderer> Widget for Modal<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: advanced::Renderer, Message: Clone, { - fn children(&self) -> Vec { - vec![Tree::new(&self.base), Tree::new(&self.modal)] + fn children(&self) -> Vec { + vec![ + widget::Tree::new(&self.base), + widget::Tree::new(&self.modal), + ] } - fn diff(&self, tree: &mut Tree) { + fn diff(&self, tree: &mut widget::Tree) { tree.diff_children(&[&self.base, &self.modal]); } @@ -247,7 +253,7 @@ mod modal { fn on_event( &mut self, - state: &mut Tree, + state: &mut widget::Tree, event: Event, layout: Layout<'_>, cursor_position: Point, @@ -268,9 +274,9 @@ mod modal { fn draw( &self, - state: &Tree, + state: &widget::Tree, renderer: &mut Renderer, - theme: &::Theme, + theme: &::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -289,7 +295,7 @@ mod modal { fn overlay<'b>( &'b mut self, - state: &'b mut Tree, + state: &'b mut widget::Tree, layout: Layout<'_>, _renderer: &Renderer, ) -> Option> { @@ -306,7 +312,7 @@ mod modal { fn mouse_interaction( &self, - state: &Tree, + state: &widget::Tree, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, @@ -323,7 +329,7 @@ mod modal { fn operate( &self, - state: &mut Tree, + state: &mut widget::Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, @@ -339,7 +345,7 @@ mod modal { struct Overlay<'a, 'b, Message, Renderer> { content: &'b mut Element<'a, Message, Renderer>, - tree: &'b mut Tree, + tree: &'b mut widget::Tree, size: Size, on_blur: Option, } @@ -347,7 +353,7 @@ mod modal { impl<'a, 'b, Message, Renderer> overlay::Overlay for Overlay<'a, 'b, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: advanced::Renderer, Message: Clone, { fn layout( @@ -469,7 +475,7 @@ mod modal { impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a + iced_native::Renderer, + Renderer: 'a + advanced::Renderer, Message: 'a + Clone, { fn from(modal: Modal<'a, Message, Renderer>) -> Self { diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index dfd6dfa991..4c0bf072c1 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -6,6 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "lazy"] } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c9f1376cc0..dfb80853f4 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,14 +1,16 @@ use iced::alignment::{self, Alignment}; +use iced::event::{self, Event}; use iced::executor; use iced::keyboard; +use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::pane_grid::{self, PaneGrid}; -use iced::widget::{button, column, container, row, scrollable, text}; +use iced::widget::{ + button, column, container, responsive, row, scrollable, text, +}; use iced::{ Application, Color, Command, Element, Length, Settings, Size, Subscription, }; -use iced_lazy::responsive; -use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { Example::run(Settings::default()) diff --git a/examples/toast/Cargo.toml b/examples/toast/Cargo.toml index f1f986aabc..f703572c53 100644 --- a/examples/toast/Cargo.toml +++ b/examples/toast/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = [] } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index e74b3ee62c..78fb9de10b 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -176,17 +176,23 @@ mod toast { use std::fmt; use std::time::{Duration, Instant}; + use iced::advanced; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::overlay; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Operation, Tree}; + use iced::advanced::{Clipboard, Shell, Widget}; + use iced::event::{self, Event}; + use iced::mouse; use iced::theme; use iced::widget::{ button, column, container, horizontal_rule, horizontal_space, row, text, }; + use iced::window; use iced::{ Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; - use iced_native::widget::{tree, Operation, Tree}; - use iced_native::{event, layout, mouse, overlay, renderer, window}; - use iced_native::{Clipboard, Event, Layout, Shell, Widget}; pub const DEFAULT_TIMEOUT: u64 = 5; @@ -324,13 +330,13 @@ mod toast { self.content.as_widget().layout(renderer, limits) } - fn tag(&self) -> tree::Tag { + fn tag(&self) -> widget::tree::Tag { struct Marker(Vec); - iced_native::widget::tree::Tag::of::() + widget::tree::Tag::of::() } - fn state(&self) -> tree::State { - iced_native::widget::tree::State::new(Vec::>::new()) + fn state(&self) -> widget::tree::State { + widget::tree::State::new(Vec::>::new()) } fn children(&self) -> Vec { @@ -584,7 +590,7 @@ mod toast { fn draw( &self, renderer: &mut Renderer, - theme: &::Theme, + theme: &::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, @@ -613,7 +619,7 @@ mod toast { &mut self, layout: Layout<'_>, renderer: &Renderer, - operation: &mut dyn iced_native::widget::Operation, + operation: &mut dyn widget::Operation, ) { operation.container(None, &mut |operation| { self.toasts diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml index 63c7ec2783..4dcff92d7c 100644 --- a/examples/url_handler/Cargo.toml +++ b/examples/url_handler/Cargo.toml @@ -7,4 +7,3 @@ publish = false [dependencies] iced = { path = "../.." } -iced_native = { path = "../../native" } diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index 3257b5190a..f63fa06a24 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,12 +1,10 @@ +use iced::event::{Event, MacOS, PlatformSpecific}; use iced::executor; +use iced::subscription; use iced::widget::{container, text}; use iced::{ Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_native::{ - event::{MacOS, PlatformSpecific}, - Event, -}; pub fn main() -> iced::Result { App::run(Settings::default()) @@ -19,7 +17,7 @@ struct App { #[derive(Debug, Clone)] enum Message { - EventOccurred(iced_native::Event), + EventOccurred(Event), } impl Application for App { @@ -52,7 +50,7 @@ impl Application for App { } fn subscription(&self) -> Subscription { - iced_native::subscription::events().map(Message::EventOccurred) + subscription::events().map(Message::EventOccurred) } fn view(&self) -> Element { diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 98e6f474ce..4bb22b6ce8 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -39,13 +39,9 @@ bitflags = "1.2" version = "1.4" features = ["derive"] -[dependencies.iced_native] -version = "0.9" -path = "../native" - -[dependencies.iced_style] -version = "0.7" -path = "../style" +[dependencies.iced_core] +version = "0.8" +path = "../core" [dependencies.tiny-skia] version = "0.8" diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 8658cffe82..dd2888ab8f 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -1,8 +1,8 @@ //! Write a graphics backend. -use iced_native::image; -use iced_native::svg; -use iced_native::text; -use iced_native::{Font, Point, Size}; +use iced_core::image; +use iced_core::svg; +use iced_core::text; +use iced_core::{Font, Point, Size}; use std::borrow::Cow; diff --git a/graphics/src/window/compositor.rs b/graphics/src/compositor.rs similarity index 96% rename from graphics/src/window/compositor.rs rename to graphics/src/compositor.rs index 15f8dab55f..d55e801a54 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,6 +1,8 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::{Color, Error, Viewport}; +use crate::{Error, Viewport}; + +use iced_core::Color; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use thiserror::Error; @@ -11,7 +13,7 @@ pub trait Compositor: Sized { type Settings: Default; /// The iced renderer of the backend. - type Renderer: iced_native::Renderer; + type Renderer: iced_core::Renderer; /// The surface of the backend. type Surface; diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index 29ac84d64d..8db1594a72 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -16,7 +16,7 @@ pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; pub use text::Text; -pub use iced_native::gradient::{self, Gradient}; +pub use iced_core::gradient::{self, Gradient}; use crate::Primitive; @@ -29,7 +29,7 @@ impl From for Primitive { } } -pub trait Renderer: iced_native::Renderer { +pub trait Renderer: iced_core::Renderer { type Geometry; fn draw(&mut self, geometry: Vec); diff --git a/graphics/src/geometry/fill.rs b/graphics/src/geometry/fill.rs index 109d5e990f..2e8c1669be 100644 --- a/graphics/src/geometry/fill.rs +++ b/graphics/src/geometry/fill.rs @@ -1,5 +1,5 @@ //! Fill [crate::widget::canvas::Geometry] with a certain style. -use crate::{Color, Gradient}; +use iced_core::{Color, Gradient}; pub use crate::geometry::Style; diff --git a/graphics/src/geometry/path.rs b/graphics/src/geometry/path.rs index 30c387c50d..c3127bdfd0 100644 --- a/graphics/src/geometry/path.rs +++ b/graphics/src/geometry/path.rs @@ -9,7 +9,7 @@ pub use builder::Builder; pub use lyon_path; -use crate::{Point, Size}; +use iced_core::{Point, Size}; /// An immutable set of points that may or may not be connected. /// diff --git a/graphics/src/geometry/path/arc.rs b/graphics/src/geometry/path/arc.rs index e0747d3e07..2cdebb6624 100644 --- a/graphics/src/geometry/path/arc.rs +++ b/graphics/src/geometry/path/arc.rs @@ -1,5 +1,5 @@ //! Build and draw curves. -use crate::{Point, Vector}; +use iced_core::{Point, Vector}; /// A segment of a differentiable curve. #[derive(Debug, Clone, Copy)] diff --git a/graphics/src/geometry/path/builder.rs b/graphics/src/geometry/path/builder.rs index 4a9c5e36cb..794dd3bcfa 100644 --- a/graphics/src/geometry/path/builder.rs +++ b/graphics/src/geometry/path/builder.rs @@ -1,5 +1,6 @@ use crate::geometry::path::{arc, Arc, Path}; -use crate::{Point, Size}; + +use iced_core::{Point, Size}; use lyon_path::builder::{self, SvgPathBuilder}; use lyon_path::geom; diff --git a/graphics/src/geometry/stroke.rs b/graphics/src/geometry/stroke.rs index b551a9c996..2d760a6ccc 100644 --- a/graphics/src/geometry/stroke.rs +++ b/graphics/src/geometry/stroke.rs @@ -1,7 +1,7 @@ //! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. pub use crate::geometry::Style; -use crate::Color; +use iced_core::Color; /// The style of a stroke. #[derive(Debug, Clone)] diff --git a/graphics/src/geometry/style.rs b/graphics/src/geometry/style.rs index 6794f2e753..be9ee376de 100644 --- a/graphics/src/geometry/style.rs +++ b/graphics/src/geometry/style.rs @@ -1,4 +1,4 @@ -use crate::{Color, Gradient}; +use iced_core::{Color, Gradient}; /// The coloring style of some drawing. #[derive(Debug, Clone, PartialEq)] diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs index 8c0b2dfb31..06e0b4d0e2 100644 --- a/graphics/src/geometry/text.rs +++ b/graphics/src/geometry/text.rs @@ -1,5 +1,5 @@ -use crate::alignment; -use crate::{Color, Font, Point}; +use iced_core::alignment; +use iced_core::{Color, Font, Point}; /// A bunch of text that can be drawn to a canvas #[derive(Debug, Clone)] diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs index da46c30f6d..03211160e8 100644 --- a/graphics/src/image/raster.rs +++ b/graphics/src/image/raster.rs @@ -1,8 +1,8 @@ //! Raster image loading and caching. use crate::image::Storage; -use crate::Size; -use iced_native::image; +use iced_core::image; +use iced_core::Size; use bitflags::bitflags; use std::collections::{HashMap, HashSet}; diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs index 1b5b5c3556..4caa61410f 100644 --- a/graphics/src/image/storage.rs +++ b/graphics/src/image/storage.rs @@ -1,5 +1,5 @@ //! Store images. -use crate::Size; +use iced_core::Size; use std::fmt::Debug; diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index c950ccd63f..32729acdaa 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -1,9 +1,8 @@ //! Vector image loading and caching use crate::image::Storage; -use crate::Color; -use iced_native::svg; -use iced_native::Size; +use iced_core::svg; +use iced_core::{Color, Size}; use resvg::tiny_skia; use resvg::usvg; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index e56f8ad844..c6f9cf5717 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -27,17 +27,17 @@ mod transformation; mod viewport; pub mod backend; +pub mod compositor; pub mod image; -pub mod overlay; pub mod primitive; pub mod renderer; -pub mod window; #[cfg(feature = "geometry")] pub mod geometry; pub use antialiasing::Antialiasing; pub use backend::Backend; +pub use compositor::Compositor; pub use error::Error; pub use primitive::Primitive; pub use renderer::Renderer; @@ -47,9 +47,4 @@ pub use viewport::Viewport; #[cfg(feature = "geometry")] pub use geometry::Geometry; -pub use iced_native::alignment; -pub use iced_native::text; -pub use iced_native::{ - Alignment, Background, Color, Font, Gradient, Point, Rectangle, Size, - Vector, -}; +pub use iced_core as core; diff --git a/graphics/src/overlay.rs b/graphics/src/overlay.rs deleted file mode 100644 index bc0ed744f3..0000000000 --- a/graphics/src/overlay.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Display interactive elements on top of other widgets. -pub mod menu; diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs deleted file mode 100644 index 8b489e5efd..0000000000 --- a/graphics/src/overlay/menu.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Build and show dropdown menus. - -pub use iced_style::menu::{Appearance, StyleSheet}; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index f900b3fdae..195b62daa5 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -1,8 +1,7 @@ -use crate::alignment; - -use iced_native::image; -use iced_native::svg; -use iced_native::{Background, Color, Font, Gradient, Rectangle, Size, Vector}; +use iced_core::alignment; +use iced_core::image; +use iced_core::svg; +use iced_core::{Background, Color, Font, Gradient, Rectangle, Size, Vector}; use bytemuck::{Pod, Zeroable}; use std::sync::Arc; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index cb57f429f2..7bc462ef45 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,15 +1,15 @@ //! Create a renderer from a [`Backend`]. use crate::backend::{self, Backend}; -use crate::{Primitive, Vector}; +use crate::Primitive; -use iced_native::image; -use iced_native::layout; -use iced_native::renderer; -use iced_native::svg; -use iced_native::text::{self, Text}; -use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size}; - -pub use iced_native::renderer::Style; +use iced_core::image; +use iced_core::layout; +use iced_core::renderer; +use iced_core::svg; +use iced_core::text::{self, Text}; +use iced_core::{ + Background, Color, Element, Font, Point, Rectangle, Size, Vector, +}; use std::borrow::Cow; use std::marker::PhantomData; @@ -52,7 +52,7 @@ impl Renderer { } } -impl iced_native::Renderer for Renderer +impl iced_core::Renderer for Renderer where B: Backend, { diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 2c0b541adc..5792555df7 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -1,4 +1,6 @@ -use crate::{Size, Transformation}; +use crate::Transformation; + +use iced_core::Size; /// A viewing region for displaying computer graphics. #[derive(Debug, Clone)] diff --git a/graphics/src/window.rs b/graphics/src/window.rs deleted file mode 100644 index a38b81f36a..0000000000 --- a/graphics/src/window.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Draw graphics to window surfaces. -pub mod compositor; - -#[cfg(feature = "opengl")] -pub mod gl_compositor; - -pub use compositor::Compositor; - -#[cfg(feature = "opengl")] -pub use gl_compositor::GLCompositor; diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs deleted file mode 100644 index 3e6dfd9edc..0000000000 --- a/graphics/src/window/gl_compositor.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! A compositor is responsible for initializing a renderer and managing window -//! surfaces. -use crate::window::compositor::Information; -use crate::{Color, Error, Size, Viewport}; - -use core::ffi::c_void; - -/// A basic OpenGL compositor. -/// -/// A compositor is responsible for initializing a renderer and managing window -/// surfaces. -/// -/// For now, this compositor only deals with a single global surface -/// for drawing. However, the trait will most likely change in the near future -/// to handle multiple surfaces at once. -/// -/// If you implement an OpenGL renderer, you can implement this trait to ease -/// integration with existing windowing shells, like `iced_glutin`. -pub trait GLCompositor: Sized { - /// The renderer of the [`GLCompositor`]. - /// - /// This should point to your renderer type, which could be a type alias - /// of the [`Renderer`] provided in this crate with with a specific - /// [`Backend`]. - /// - /// [`Renderer`]: crate::Renderer - /// [`Backend`]: crate::Backend - type Renderer: iced_native::Renderer; - - /// The settings of the [`GLCompositor`]. - /// - /// It's up to you to decide the configuration supported by your renderer! - type Settings: Default; - - /// Creates a new [`GLCompositor`] and [`Renderer`] with the given - /// [`Settings`] and an OpenGL address loader function. - /// - /// # Safety - /// The `loader_function` should resolve to valid OpenGL bindings. - /// - /// [`Renderer`]: crate::Renderer - /// [`Backend`]: crate::Backend - /// [`Settings`]: Self::Settings - #[allow(unsafe_code)] - unsafe fn new( - settings: Self::Settings, - loader_function: impl FnMut(&str) -> *const c_void, - ) -> Result<(Self, Self::Renderer), Error>; - - /// Returns the amount of samples that should be used when configuring - /// an OpenGL context for this [`GLCompositor`]. - fn sample_count(settings: &Self::Settings) -> u32; - - /// Resizes the viewport of the [`GLCompositor`]. - fn resize_viewport(&mut self, physical_size: Size); - - /// Returns [`Information`] used by this [`GLCompositor`]. - fn fetch_information(&self) -> Information; - - /// Presents the primitives of the [`Renderer`] to the next frame of the - /// [`GLCompositor`]. - /// - /// [`Renderer`]: crate::Renderer - fn present>( - &mut self, - renderer: &mut Self::Renderer, - viewport: &Viewport, - background_color: Color, - overlay: &[T], - ); -} diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml deleted file mode 100644 index c739b312f8..0000000000 --- a/lazy/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "iced_lazy" -version = "0.5.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "Lazy widgets for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_lazy" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] - -[dependencies] -ouroboros = "0.13" - -[dependencies.iced_native] -version = "0.9" -path = "../native" diff --git a/native/Cargo.toml b/native/Cargo.toml index 1eedf0dac5..bc4e7ca19c 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -11,9 +11,6 @@ repository = "https://github.com/iced-rs/iced" debug = [] [dependencies] -twox-hash = { version = "1.5", default-features = false } -unicode-segmentation = "1.6" -num-traits = "0.2" thiserror = "1" [dependencies.iced_core] @@ -24,7 +21,3 @@ path = "../core" version = "0.6" path = "../futures" features = ["thread-pool"] - -[dependencies.iced_style] -version = "0.7" -path = "../style" diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index c9105bc094..e727c4a740 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -3,28 +3,6 @@ use iced_futures::MaybeSend; use std::fmt; -/// A buffer for short-term storage and transfer within and between -/// applications. -pub trait Clipboard { - /// Reads the current content of the [`Clipboard`] as text. - fn read(&self) -> Option; - - /// Writes the given text contents to the [`Clipboard`]. - fn write(&mut self, contents: String); -} - -/// A null implementation of the [`Clipboard`] trait. -#[derive(Debug, Clone, Copy)] -pub struct Null; - -impl Clipboard for Null { - fn read(&self) -> Option { - None - } - - fn write(&mut self, _contents: String) {} -} - /// A clipboard action to be performed by some [`Command`]. /// /// [`Command`]: crate::Command diff --git a/native/src/command.rs b/native/src/command.rs index ca9d0b64ad..39bee8f6d8 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -28,7 +28,9 @@ impl Command { } /// Creates a [`Command`] that performs a [`widget::Operation`]. - pub fn widget(operation: impl widget::Operation + 'static) -> Self { + pub fn widget( + operation: impl iced_core::widget::Operation + 'static, + ) -> Self { Self(iced_futures::Command::single(Action::Widget( widget::Action::new(operation), ))) diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs index 92f614da5f..32f725a1d6 100644 --- a/native/src/debug/basic.rs +++ b/native/src/debug/basic.rs @@ -1,5 +1,5 @@ #![allow(missing_docs)] -use crate::time; +use crate::core::time; use std::collections::VecDeque; diff --git a/native/src/lib.rs b/native/src/lib.rs index c98827e76f..0fc4f324c1 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -42,32 +42,19 @@ clippy::useless_conversion )] #![forbid(unsafe_code, rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] pub mod clipboard; pub mod command; -pub mod event; pub mod font; -pub mod image; pub mod keyboard; -pub mod layout; -pub mod mouse; -pub mod overlay; pub mod program; -pub mod renderer; pub mod subscription; -pub mod svg; pub mod system; -pub mod text; -pub mod touch; pub mod user_interface; pub mod widget; pub mod window; -mod element; -mod hasher; mod runtime; -mod shell; // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. @@ -78,35 +65,13 @@ mod debug; #[path = "debug/null.rs"] mod debug; -pub use iced_core::alignment; -pub use iced_core::gradient; -pub use iced_core::time; -pub use iced_core::{ - color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels, - Point, Rectangle, Size, Vector, -}; -pub use iced_futures::{executor, futures}; -pub use iced_style::application; -pub use iced_style::theme; +pub use iced_core as core; +pub use iced_futures as futures; -#[doc(no_inline)] -pub use executor::Executor; - -pub use clipboard::Clipboard; pub use command::Command; pub use debug::Debug; -pub use element::Element; -pub use event::Event; pub use font::Font; -pub use gradient::Gradient; -pub use hasher::Hasher; -pub use layout::Layout; -pub use overlay::Overlay; pub use program::Program; -pub use renderer::Renderer; pub use runtime::Runtime; -pub use shell::Shell; pub use subscription::Subscription; -pub use theme::Theme; pub use user_interface::UserInterface; -pub use widget::Widget; diff --git a/native/src/mouse.rs b/native/src/mouse.rs deleted file mode 100644 index 9ee406cfe2..0000000000 --- a/native/src/mouse.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Track mouse events. - -pub mod click; - -pub use click::Click; -pub use iced_core::mouse::*; diff --git a/native/src/program.rs b/native/src/program.rs index 25cab3329e..44585cc55d 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -1,6 +1,8 @@ //! Build interactive programs using The Elm Architecture. -use crate::text; -use crate::{Command, Element, Renderer}; +use crate::Command; + +use iced_core::text; +use iced_core::{Element, Renderer}; mod state; diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 8ae1cacbc8..2fa9934d8f 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -1,9 +1,9 @@ -use crate::application; -use crate::event::{self, Event}; -use crate::mouse; -use crate::renderer; +use crate::core::event::{self, Event}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::{Clipboard, Point, Size}; use crate::user_interface::{self, UserInterface}; -use crate::{Clipboard, Command, Debug, Point, Program, Size}; +use crate::{Command, Debug, Program}; /// The execution state of a [`Program`]. It leverages caching, event /// processing, and rendering primitive storage. @@ -22,7 +22,6 @@ where impl

State

where P: Program + 'static, - ::Theme: application::StyleSheet, { /// Creates a new [`State`] with the provided [`Program`], initializing its /// primitive with the given logical bounds and renderer. @@ -91,7 +90,7 @@ where bounds: Size, cursor_position: Point, renderer: &mut P::Renderer, - theme: &::Theme, + theme: &::Theme, style: &renderer::Style, clipboard: &mut dyn Clipboard, debug: &mut Debug, @@ -182,10 +181,7 @@ fn build_user_interface<'a, P: Program>( renderer: &mut P::Renderer, size: Size, debug: &mut Debug, -) -> UserInterface<'a, P::Message, P::Renderer> -where - ::Theme: application::StyleSheet, -{ +) -> UserInterface<'a, P::Message, P::Renderer> { debug.view_started(); let view = program.view(); debug.view_finished(); diff --git a/native/src/runtime.rs b/native/src/runtime.rs index 5b0a692550..1b81314f07 100644 --- a/native/src/runtime.rs +++ b/native/src/runtime.rs @@ -1,6 +1,6 @@ //! Run commands and subscriptions. -use crate::event::{self, Event}; -use crate::Hasher; +use iced_core::event::{self, Event}; +use iced_core::Hasher; /// A native runtime with a generic executor and receiver of results. /// diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 16e78e8296..b16bcb0370 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,10 +1,9 @@ //! Listen to external events in your application. -use crate::event::{self, Event}; -use crate::window; -use crate::Hasher; - -use iced_futures::futures::{self, Future, Stream}; -use iced_futures::{BoxStream, MaybeSend}; +use crate::core::event::{self, Event}; +use crate::core::window; +use crate::core::Hasher; +use crate::futures::futures::{self, Future, Stream}; +use crate::futures::{BoxStream, MaybeSend}; use std::hash::Hash; @@ -144,7 +143,9 @@ where /// /// ``` /// use iced_native::subscription::{self, Subscription}; -/// use iced_native::futures::channel::mpsc; +/// use iced_native::futures::futures; +/// +/// use futures::channel::mpsc; /// /// pub enum Event { /// Ready(mpsc::Sender), @@ -174,7 +175,7 @@ where /// (Some(Event::Ready(sender)), State::Ready(receiver)) /// } /// State::Ready(mut receiver) => { -/// use iced_native::futures::StreamExt; +/// use futures::StreamExt; /// /// // Read next input sent from `Application` /// let input = receiver.select_next_some().await; diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 68ccda55dd..315027fa08 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,14 +1,12 @@ //! Implement your own event loop to drive a user interface. -use crate::application; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget; -use crate::window; -use crate::{ - Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, -}; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget; +use crate::core::window; +use crate::core::{Clipboard, Point, Rectangle, Size, Vector}; +use crate::core::{Element, Layout, Shell}; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -34,8 +32,7 @@ pub struct UserInterface<'a, Message, Renderer> { impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> where - Renderer: crate::Renderer, - Renderer::Theme: application::StyleSheet, + Renderer: iced_core::Renderer, { /// Builds a user interface for an [`Element`]. /// @@ -48,24 +45,21 @@ where /// is naive way to set up our application loop: /// /// ```no_run - /// use iced_native::Size; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_native::core::renderer::Null as Renderer; /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_native::core::Size; + /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// /// // Initialization /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); @@ -124,25 +118,21 @@ where /// completing [the previous example](#example): /// /// ```no_run - /// use iced_native::{clipboard, Size, Point}; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_native::core::renderer::Null as Renderer; /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } - /// # pub fn update(&mut self, message: ()) {} + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_native::core::{clipboard, Size, Point}; + /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); @@ -357,27 +347,24 @@ where /// [completing the last example](#example-1): /// /// ```no_run - /// use iced_native::clipboard; - /// use iced_native::renderer; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_native::{Size, Point, Theme}; - /// use iced_wgpu::Renderer; - /// /// # mod iced_wgpu { - /// # pub use iced_native::renderer::Null as Renderer; + /// # pub use iced_native::core::renderer::Null as Renderer; + /// # pub type Theme = (); /// # } /// # - /// # use iced_native::widget::Column; - /// # /// # pub struct Counter; /// # /// # impl Counter { /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Column<(), Renderer> { - /// # Column::new() - /// # } - /// # pub fn update(&mut self, message: ()) {} + /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} /// # } + /// use iced_native::core::clipboard; + /// use iced_native::core::renderer; + /// use iced_native::core::{Element, Size, Point}; + /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_wgpu::{Renderer, Theme}; + /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); @@ -386,6 +373,7 @@ where /// let mut clipboard = clipboard::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); + /// let mut theme = Theme::default(); /// /// loop { /// // Obtain system events... @@ -407,7 +395,7 @@ where /// ); /// /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer, &Theme::default(), &renderer::Style::default(), cursor_position); + /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position); /// /// cache = user_interface.into_cache(); /// diff --git a/native/src/widget.rs b/native/src/widget.rs index 2b3ca7be07..0fdade5446 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -11,212 +11,6 @@ //! source of inspiration. //! //! [renderer]: crate::renderer -pub mod button; -pub mod checkbox; -pub mod column; -pub mod container; -pub mod helpers; -pub mod image; -pub mod operation; -pub mod pane_grid; -pub mod pick_list; -pub mod progress_bar; -pub mod radio; -pub mod row; -pub mod rule; -pub mod scrollable; -pub mod slider; -pub mod space; -pub mod svg; -pub mod text; -pub mod text_input; -pub mod toggler; -pub mod tooltip; -pub mod tree; -pub mod vertical_slider; - mod action; -mod id; - -#[doc(no_inline)] -pub use button::Button; -#[doc(no_inline)] -pub use checkbox::Checkbox; -#[doc(no_inline)] -pub use column::Column; -#[doc(no_inline)] -pub use container::Container; -#[doc(no_inline)] -pub use helpers::*; -#[doc(no_inline)] -pub use image::Image; -#[doc(no_inline)] -pub use pane_grid::PaneGrid; -#[doc(no_inline)] -pub use pick_list::PickList; -#[doc(no_inline)] -pub use progress_bar::ProgressBar; -#[doc(no_inline)] -pub use radio::Radio; -#[doc(no_inline)] -pub use row::Row; -#[doc(no_inline)] -pub use rule::Rule; -#[doc(no_inline)] -pub use scrollable::Scrollable; -#[doc(no_inline)] -pub use slider::Slider; -#[doc(no_inline)] -pub use space::Space; -#[doc(no_inline)] -pub use svg::Svg; -#[doc(no_inline)] -pub use text::Text; -#[doc(no_inline)] -pub use text_input::TextInput; -#[doc(no_inline)] -pub use toggler::Toggler; -#[doc(no_inline)] -pub use tooltip::Tooltip; -#[doc(no_inline)] -pub use tree::Tree; -#[doc(no_inline)] -pub use vertical_slider::VerticalSlider; pub use action::Action; -pub use id::Id; -pub use operation::Operation; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; - -/// A component that displays information and allows interaction. -/// -/// If you want to build your own widgets, you will need to implement this -/// trait. -/// -/// # Examples -/// The repository has some [examples] showcasing how to implement a custom -/// widget: -/// -/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using -/// [`lyon`]. -/// - [`custom_widget`], a demonstration of how to build a custom widget that -/// draws a circle. -/// - [`geometry`], a custom widget showcasing how to draw geometry with the -/// `Mesh2D` primitive in [`iced_wgpu`]. -/// -/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry -/// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu -pub trait Widget -where - Renderer: crate::Renderer, -{ - /// Returns the width of the [`Widget`]. - fn width(&self) -> Length; - - /// Returns the height of the [`Widget`]. - fn height(&self) -> Length; - - /// Returns the [`layout::Node`] of the [`Widget`]. - /// - /// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the - /// user interface. - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node; - - /// Draws the [`Widget`] using the associated `Renderer`. - fn draw( - &self, - state: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ); - - /// Returns the [`Tag`] of the [`Widget`]. - /// - /// [`Tag`]: tree::Tag - fn tag(&self) -> tree::Tag { - tree::Tag::stateless() - } - - /// Returns the [`State`] of the [`Widget`]. - /// - /// [`State`]: tree::State - fn state(&self) -> tree::State { - tree::State::None - } - - /// Returns the state [`Tree`] of the children of the [`Widget`]. - fn children(&self) -> Vec { - Vec::new() - } - - /// Reconciliates the [`Widget`] with the provided [`Tree`]. - fn diff(&self, _tree: &mut Tree) {} - - /// Applies an [`Operation`] to the [`Widget`]. - fn operate( - &self, - _state: &mut Tree, - _layout: Layout<'_>, - _renderer: &Renderer, - _operation: &mut dyn Operation, - ) { - } - - /// Processes a runtime [`Event`]. - /// - /// By default, it does nothing. - fn on_event( - &mut self, - _state: &mut Tree, - _event: Event, - _layout: Layout<'_>, - _cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> event::Status { - event::Status::Ignored - } - - /// Returns the current [`mouse::Interaction`] of the [`Widget`]. - /// - /// By default, it returns [`mouse::Interaction::Idle`]. - fn mouse_interaction( - &self, - _state: &Tree, - _layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - mouse::Interaction::Idle - } - - /// Returns the overlay of the [`Widget`], if there is any. - fn overlay<'a>( - &'a mut self, - _state: &'a mut Tree, - _layout: Layout<'_>, - _renderer: &Renderer, - ) -> Option> { - None - } -} diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 3f1b6b6cf6..f50d7aec25 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -1,8 +1,7 @@ -use crate::widget::operation::{ +use iced_core::widget::operation::{ self, Focusable, Operation, Scrollable, TextInput, }; -use crate::widget::Id; - +use iced_core::widget::Id; use iced_futures::MaybeSend; use std::any::Any; diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs deleted file mode 100644 index d13eca7598..0000000000 --- a/native/src/widget/helpers.rs +++ /dev/null @@ -1,317 +0,0 @@ -//! Helper functions to create pure widgets. -use crate::overlay; -use crate::widget; -use crate::{Element, Length, Pixels}; - -use std::borrow::Cow; -use std::ops::RangeInclusive; - -/// Creates a [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -#[macro_export] -macro_rules! column { - () => ( - $crate::widget::Column::new() - ); - ($($x:expr),+ $(,)?) => ( - $crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+]) - ); -} - -/// Creates a [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -#[macro_export] -macro_rules! row { - () => ( - $crate::widget::Row::new() - ); - ($($x:expr),+ $(,)?) => ( - $crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+]) - ); -} - -/// Creates a new [`Container`] with the provided content. -/// -/// [`Container`]: widget::Container -pub fn container<'a, Message, Renderer>( - content: impl Into>, -) -> widget::Container<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::container::StyleSheet, -{ - widget::Container::new(content) -} - -/// Creates a new [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -pub fn column( - children: Vec>, -) -> widget::Column<'_, Message, Renderer> { - widget::Column::with_children(children) -} - -/// Creates a new [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -pub fn row( - children: Vec>, -) -> widget::Row<'_, Message, Renderer> { - widget::Row::with_children(children) -} - -/// Creates a new [`Scrollable`] with the provided content. -/// -/// [`Scrollable`]: widget::Scrollable -pub fn scrollable<'a, Message, Renderer>( - content: impl Into>, -) -> widget::Scrollable<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::scrollable::StyleSheet, -{ - widget::Scrollable::new(content) -} - -/// Creates a new [`Button`] with the provided content. -/// -/// [`Button`]: widget::Button -pub fn button<'a, Message, Renderer>( - content: impl Into>, -) -> widget::Button<'a, Message, Renderer> -where - Renderer: crate::Renderer, - Renderer::Theme: widget::button::StyleSheet, - ::Style: Default, -{ - widget::Button::new(content) -} - -/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`]. -/// -/// [`Tooltip`]: widget::Tooltip -/// [`tooltip::Position`]: widget::tooltip::Position -pub fn tooltip<'a, Message, Renderer>( - content: impl Into>, - tooltip: impl ToString, - position: widget::tooltip::Position, -) -> widget::Tooltip<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet, -{ - widget::Tooltip::new(content, tooltip.to_string(), position) -} - -/// Creates a new [`Text`] widget with the provided content. -/// -/// [`Text`]: widget::Text -pub fn text<'a, Renderer>(text: impl ToString) -> widget::Text<'a, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::text::StyleSheet, -{ - widget::Text::new(text.to_string()) -} - -/// Creates a new [`Checkbox`]. -/// -/// [`Checkbox`]: widget::Checkbox -pub fn checkbox<'a, Message, Renderer>( - label: impl Into, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> widget::Checkbox<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet, -{ - widget::Checkbox::new(label, is_checked, f) -} - -/// Creates a new [`Radio`]. -/// -/// [`Radio`]: widget::Radio -pub fn radio( - label: impl Into, - value: V, - selected: Option, - on_click: impl FnOnce(V) -> Message, -) -> widget::Radio -where - Message: Clone, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::radio::StyleSheet, - V: Copy + Eq, -{ - widget::Radio::new(value, label, selected, on_click) -} - -/// Creates a new [`Toggler`]. -/// -/// [`Toggler`]: widget::Toggler -pub fn toggler<'a, Message, Renderer>( - label: impl Into>, - is_checked: bool, - f: impl Fn(bool) -> Message + 'a, -) -> widget::Toggler<'a, Message, Renderer> -where - Renderer: crate::text::Renderer, - Renderer::Theme: widget::toggler::StyleSheet, -{ - widget::Toggler::new(label, is_checked, f) -} - -/// Creates a new [`TextInput`]. -/// -/// [`TextInput`]: widget::TextInput -pub fn text_input<'a, Message, Renderer>( - placeholder: &str, - value: &str, - on_change: impl Fn(String) -> Message + 'a, -) -> widget::TextInput<'a, Message, Renderer> -where - Message: Clone, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::text_input::StyleSheet, -{ - widget::TextInput::new(placeholder, value, on_change) -} - -/// Creates a new [`Slider`]. -/// -/// [`Slider`]: widget::Slider -pub fn slider<'a, T, Message, Renderer>( - range: std::ops::RangeInclusive, - value: T, - on_change: impl Fn(T) -> Message + 'a, -) -> widget::Slider<'a, T, Message, Renderer> -where - T: Copy + From + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: widget::slider::StyleSheet, -{ - widget::Slider::new(range, value, on_change) -} - -/// Creates a new [`VerticalSlider`]. -/// -/// [`VerticalSlider`]: widget::VerticalSlider -pub fn vertical_slider<'a, T, Message, Renderer>( - range: std::ops::RangeInclusive, - value: T, - on_change: impl Fn(T) -> Message + 'a, -) -> widget::VerticalSlider<'a, T, Message, Renderer> -where - T: Copy + From + std::cmp::PartialOrd, - Message: Clone, - Renderer: crate::Renderer, - Renderer::Theme: widget::slider::StyleSheet, -{ - widget::VerticalSlider::new(range, value, on_change) -} - -/// Creates a new [`PickList`]. -/// -/// [`PickList`]: widget::PickList -pub fn pick_list<'a, Message, Renderer, T>( - options: impl Into>, - selected: Option, - on_selected: impl Fn(T) -> Message + 'a, -) -> widget::PickList<'a, T, Message, Renderer> -where - T: ToString + Eq + 'static, - [T]: ToOwned>, - Renderer: crate::text::Renderer, - Renderer::Theme: widget::pick_list::StyleSheet - + widget::scrollable::StyleSheet - + overlay::menu::StyleSheet - + widget::container::StyleSheet, - ::Style: - From<::Style>, -{ - widget::PickList::new(options, selected, on_selected) -} - -/// Creates a new [`Image`]. -/// -/// [`Image`]: widget::Image -pub fn image(handle: impl Into) -> widget::Image { - widget::Image::new(handle.into()) -} - -/// Creates a new horizontal [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn horizontal_space(width: impl Into) -> widget::Space { - widget::Space::with_width(width) -} - -/// Creates a new vertical [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn vertical_space(height: impl Into) -> widget::Space { - widget::Space::with_height(height) -} - -/// Creates a horizontal [`Rule`] with the given height. -/// -/// [`Rule`]: widget::Rule -pub fn horizontal_rule( - height: impl Into, -) -> widget::Rule -where - Renderer: crate::Renderer, - Renderer::Theme: widget::rule::StyleSheet, -{ - widget::Rule::horizontal(height) -} - -/// Creates a vertical [`Rule`] with the given width. -/// -/// [`Rule`]: widget::Rule -pub fn vertical_rule( - width: impl Into, -) -> widget::Rule -where - Renderer: crate::Renderer, - Renderer::Theme: widget::rule::StyleSheet, -{ - widget::Rule::vertical(width) -} - -/// Creates a new [`ProgressBar`]. -/// -/// It expects: -/// * an inclusive range of possible values, and -/// * the current value of the [`ProgressBar`]. -/// -/// [`ProgressBar`]: widget::ProgressBar -pub fn progress_bar( - range: RangeInclusive, - value: f32, -) -> widget::ProgressBar -where - Renderer: crate::Renderer, - Renderer::Theme: widget::progress_bar::StyleSheet, -{ - widget::ProgressBar::new(range, value) -} - -/// Creates a new [`Svg`] widget from the given [`Handle`]. -/// -/// [`Svg`]: widget::Svg -/// [`Handle`]: widget::svg::Handle -pub fn svg( - handle: impl Into, -) -> widget::Svg -where - Renderer: crate::svg::Renderer, - Renderer::Theme: widget::svg::StyleSheet, -{ - widget::Svg::new(handle) -} diff --git a/native/src/window.rs b/native/src/window.rs index a5cdc8ceda..97a96e5439 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,18 +1,11 @@ //! Build window-based GUI applications. mod action; -mod event; -mod mode; -mod redraw_request; -mod user_attention; pub use action::Action; -pub use event::Event; -pub use mode::Mode; -pub use redraw_request::RedrawRequest; -pub use user_attention::UserAttention; +use crate::core::time::Instant; +use crate::core::window::Event; use crate::subscription::{self, Subscription}; -use crate::time::Instant; /// Subscribes to the frames of the window of the running application. /// @@ -24,7 +17,7 @@ use crate::time::Instant; /// animations without missing any frames. pub fn frames() -> Subscription { subscription::raw_events(|event, _status| match event { - crate::Event::Window(Event::RedrawRequested(at)) => Some(at), + iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), _ => None, }) } diff --git a/native/src/window/action.rs b/native/src/window/action.rs index ce36d129d7..c1dbd84f2e 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -1,6 +1,6 @@ -use crate::window::{Mode, UserAttention}; +use crate::core::window::{Mode, UserAttention}; +use crate::futures::MaybeSend; -use iced_futures::MaybeSend; use std::fmt; /// An operation to be performed on some window. diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 189f530976..d0420ad04e 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -13,10 +13,6 @@ tracing = ["iced_wgpu/tracing"] raw-window-handle = "0.5" thiserror = "1" -[dependencies.iced_native] -version = "0.9" -path = "../native" - [dependencies.iced_graphics] version = "0.7" path = "../graphics" diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index b0a409dc76..bf5da322af 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -1,7 +1,6 @@ -use crate::{Font, Point, Size}; - -use iced_graphics::backend; -use iced_graphics::text; +use crate::core::text; +use crate::core::{Font, Point, Size}; +use crate::graphics::backend; use std::borrow::Cow; @@ -99,7 +98,7 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { match self { Self::Wgpu(backend) => backend.dimensions(handle), Self::TinySkia(backend) => backend.dimensions(handle), @@ -111,7 +110,7 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - handle: &iced_native::svg::Handle, + handle: &crate::core::svg::Handle, ) -> Size { match self { Self::Wgpu(backend) => backend.viewport_dimensions(handle), diff --git a/renderer/src/window/compositor.rs b/renderer/src/compositor.rs similarity index 94% rename from renderer/src/window/compositor.rs rename to renderer/src/compositor.rs index a11374eda6..0cdcb2938f 100644 --- a/renderer/src/window/compositor.rs +++ b/renderer/src/compositor.rs @@ -1,9 +1,10 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +use crate::core::Color; +use crate::graphics::compositor::{Information, SurfaceError}; +use crate::graphics::{Error, Viewport}; +use crate::{Backend, Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; -pub use iced_graphics::window::compositor::{Information, SurfaceError}; - pub enum Compositor { Wgpu(iced_wgpu::window::Compositor), TinySkia(iced_tiny_skia::window::Compositor), @@ -14,7 +15,7 @@ pub enum Surface { TinySkia(iced_tiny_skia::window::Surface), } -impl iced_graphics::window::Compositor for Compositor { +impl crate::graphics::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = Surface; diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index e491ea734e..361fc86bcb 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -2,9 +2,9 @@ mod cache; pub use cache::Cache; -pub use iced_graphics::geometry::*; - -use crate::{Backend, Point, Rectangle, Size, Vector}; +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; +use crate::Backend; pub enum Frame { Wgpu(iced_wgpu::geometry::Frame), diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs index 1f1febddbb..2a3534d02a 100644 --- a/renderer/src/geometry/cache.rs +++ b/renderer/src/geometry/cache.rs @@ -1,5 +1,7 @@ +use crate::core::Size; use crate::geometry::{Frame, Geometry}; -use crate::{Primitive, Renderer, Size}; +use crate::graphics::Primitive; +use crate::Renderer; use std::cell::RefCell; use std::sync::Arc; diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index aae3322d41..22ec7bd1d8 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,4 +1,4 @@ -pub mod window; +pub mod compositor; #[cfg(feature = "geometry")] pub mod geometry; @@ -6,18 +6,14 @@ pub mod geometry; mod backend; mod settings; -pub use iced_graphics::primitive; +pub use iced_graphics as graphics; +pub use iced_graphics::core; pub use backend::Backend; -pub use primitive::Primitive; +pub use compositor::Compositor; pub use settings::Settings; -pub use iced_graphics::{ - Antialiasing, Color, Error, Font, Point, Rectangle, Size, Vector, Viewport, -}; - /// The default graphics renderer for [`iced`]. /// /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = - iced_graphics::Renderer; +pub type Renderer = iced_graphics::Renderer; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs index c4dc248b10..d32c87d37a 100644 --- a/renderer/src/settings.rs +++ b/renderer/src/settings.rs @@ -1,4 +1,5 @@ -use crate::{Antialiasing, Font}; +use crate::core::Font; +use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. /// diff --git a/renderer/src/window.rs b/renderer/src/window.rs deleted file mode 100644 index a7c8911b15..0000000000 --- a/renderer/src/window.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod compositor; - -pub use compositor::Compositor; diff --git a/src/advanced.rs b/src/advanced.rs new file mode 100644 index 0000000000..714076e0c8 --- /dev/null +++ b/src/advanced.rs @@ -0,0 +1,9 @@ +//! Leverage advanced concepts like custom widgets. +pub use crate::core::image; +pub use crate::core::layout::{self, Layout}; +pub use crate::core::overlay::{self, Overlay}; +pub use crate::core::renderer::{self, Renderer}; +pub use crate::core::svg; +pub use crate::core::text::{self, Text}; +pub use crate::core::widget::{self, Widget}; +pub use crate::core::{Clipboard, Shell}; diff --git a/src/application.rs b/src/application.rs index b9871556e6..f5cf3317ea 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,7 +1,7 @@ //! Build interactive cross-platform applications. use crate::{Command, Element, Executor, Settings, Subscription}; -pub use iced_native::application::{Appearance, StyleSheet}; +pub use crate::style::application::{Appearance, StyleSheet}; /// An interactive cross-platform application. /// @@ -198,24 +198,24 @@ pub trait Application: Sized { default_font: settings.default_font, default_text_size: settings.default_text_size, antialiasing: if settings.antialiasing { - Some(crate::renderer::Antialiasing::MSAAx4) + Some(crate::graphics::Antialiasing::MSAAx4) } else { None }, ..crate::renderer::Settings::default() }; - Ok(crate::runtime::application::run::< + Ok(crate::shell::application::run::< Instance, Self::Executor, - crate::renderer::window::Compositor, + crate::renderer::Compositor, >(settings.into(), renderer_settings)?) } } struct Instance(A); -impl iced_winit::Program for Instance +impl crate::native::Program for Instance where A: Application, { @@ -231,7 +231,7 @@ where } } -impl crate::runtime::Application for Instance +impl crate::shell::Application for Instance where A: Application, { diff --git a/src/clipboard.rs b/src/clipboard.rs deleted file mode 100644 index dde170514b..0000000000 --- a/src/clipboard.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Access the clipboard. -#[cfg(not(target_arch = "wasm32"))] -pub use crate::runtime::clipboard::{read, write}; diff --git a/src/element.rs b/src/element.rs deleted file mode 100644 index 2eb1bb4db3..0000000000 --- a/src/element.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// A generic widget. -/// -/// This is an alias of an `iced_native` element with a default `Renderer`. -pub type Element<'a, Message, Renderer = crate::Renderer> = - crate::runtime::Element<'a, Message, Renderer>; diff --git a/src/error.rs b/src/error.rs index 5326718f1b..111bedf245 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,6 @@ -use iced_futures::futures; +use crate::futures; +use crate::graphics; +use crate::shell; /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] @@ -13,19 +15,19 @@ pub enum Error { /// The application graphics context could not be created. #[error("the application graphics context could not be created")] - GraphicsCreationFailed(iced_renderer::Error), + GraphicsCreationFailed(graphics::Error), } -impl From for Error { - fn from(error: iced_winit::Error) -> Error { +impl From for Error { + fn from(error: shell::Error) -> Error { match error { - iced_winit::Error::ExecutorCreationFailed(error) => { + shell::Error::ExecutorCreationFailed(error) => { Error::ExecutorCreationFailed(error) } - iced_winit::Error::WindowCreationFailed(error) => { + shell::Error::WindowCreationFailed(error) => { Error::WindowCreationFailed(Box::new(error)) } - iced_winit::Error::GraphicsCreationFailed(error) => { + shell::Error::GraphicsCreationFailed(error) => { Error::GraphicsCreationFailed(error) } } diff --git a/src/executor.rs b/src/executor.rs deleted file mode 100644 index 36ae274ec0..0000000000 --- a/src/executor.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Choose your preferred executor to power your application. -pub use crate::runtime::Executor; - -/// 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/keyboard.rs b/src/keyboard.rs deleted file mode 100644 index 2134a66bc3..0000000000 --- a/src/keyboard.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Listen and react to keyboard events. -pub use crate::runtime::keyboard::{Event, KeyCode, Modifiers}; diff --git a/src/lib.rs b/src/lib.rs index bb162f2d6f..b71b7781d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,51 +164,133 @@ #![forbid(rust_2018_idioms, unsafe_code)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] +use iced_widget::graphics; +use iced_widget::renderer; +use iced_widget::style; +use iced_winit as shell; +use iced_winit::core; +use iced_winit::native; + +pub use iced_futures::futures; -mod element; mod error; -mod result; mod sandbox; pub mod application; -pub mod clipboard; -pub mod executor; -pub mod keyboard; -pub mod mouse; -pub mod overlay; pub mod settings; pub mod time; -pub mod touch; -pub mod widget; pub mod window; -use iced_renderer as renderer; -use iced_winit as runtime; +#[cfg(feature = "advanced")] +pub mod advanced; + +pub use style::theme; + +pub use crate::core::alignment; +pub use crate::core::event; +pub use crate::core::{ + color, Alignment, Background, Color, ContentFit, Length, Padding, Point, + Rectangle, Size, Vector, +}; +pub use crate::native::Command; +pub use native::subscription; + +pub mod clipboard { + //! Access the clipboard. + pub use crate::shell::clipboard::{read, write}; +} + +pub mod executor { + //! Choose your preferred executor to power your application. + pub use iced_futures::Executor; + + /// 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; +} + +pub mod font { + //! Load and use fonts. + pub use crate::core::font::*; + pub use crate::native::font::*; +} + +pub mod keyboard { + //! Listen and react to keyboard events. + pub use crate::core::keyboard::{Event, KeyCode, Modifiers}; +} + +pub mod mouse { + //! Listen and react to mouse events. + pub use crate::core::mouse::{Button, Event, Interaction, ScrollDelta}; +} -pub use iced_native::theme; -pub use runtime::event; -pub use runtime::font; -pub use runtime::subscription; +#[cfg(feature = "system")] +pub mod system { + //! Retrieve system information. + pub use crate::native::system::Information; + pub use crate::shell::system::*; +} + +pub mod overlay { + //! Display interactive elements on top of other widgets. + + /// A generic [`Overlay`]. + /// + /// This is an alias of an `iced_native` element with a default `Renderer`. + /// + /// [`Overlay`]: iced_native::Overlay + pub type Element<'a, Message, Renderer = crate::Renderer> = + crate::core::overlay::Element<'a, Message, Renderer>; + + pub use iced_widget::overlay::*; +} + +pub mod touch { + //! Listen and react to touch events. + pub use crate::core::touch::{Event, Finger}; +} + +pub mod widget { + //! Use the built-in widgets or create your own. + pub use iced_widget::*; + + // We hide the re-exported modules by `iced_widget` + mod core {} + mod graphics {} + mod native {} + mod renderer {} + mod style {} +} pub use application::Application; -pub use element::Element; pub use error::Error; pub use event::Event; pub use executor::Executor; pub use font::Font; -pub use renderer::Renderer; -pub use result::Result; pub use sandbox::Sandbox; pub use settings::Settings; pub use subscription::Subscription; pub use theme::Theme; -pub use runtime::alignment; -pub use runtime::futures; -pub use runtime::{ - color, Alignment, Background, Color, Command, ContentFit, Length, Padding, - Point, Rectangle, Size, Vector, -}; +/// The default renderer. +pub type Renderer = renderer::Renderer; -#[cfg(feature = "system")] -pub use runtime::system; +/// A generic widget. +/// +/// This is an alias of an `iced_native` element with a default `Renderer`. +pub type Element<'a, Message, Renderer = crate::Renderer> = + crate::core::Element<'a, Message, Renderer>; + +/// The result of running an [`Application`]. +/// +/// [`Application`]: crate::Application +pub type Result = std::result::Result<(), Error>; diff --git a/src/mouse.rs b/src/mouse.rs deleted file mode 100644 index d61ed09a06..0000000000 --- a/src/mouse.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Listen and react to mouse events. -pub use crate::runtime::mouse::{Button, Event, Interaction, ScrollDelta}; diff --git a/src/overlay.rs b/src/overlay.rs deleted file mode 100644 index c0f4c49202..0000000000 --- a/src/overlay.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Display interactive elements on top of other widgets. - -/// A generic [`Overlay`]. -/// -/// This is an alias of an `iced_native` element with a default `Renderer`. -/// -/// [`Overlay`]: iced_native::Overlay -pub type Element<'a, Message, Renderer = crate::Renderer> = - iced_native::overlay::Element<'a, Message, Renderer>; - -pub mod menu { - //! Build and show dropdown menus. - pub use iced_native::overlay::menu::{Appearance, State, StyleSheet}; - - /// A widget that produces a message when clicked. - pub type Menu<'a, Message, Renderer = crate::Renderer> = - iced_native::overlay::Menu<'a, Message, Renderer>; -} diff --git a/src/result.rs b/src/result.rs deleted file mode 100644 index ef565bd6cb..0000000000 --- a/src/result.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::Error; - -/// The result of running an [`Application`]. -/// -/// [`Application`]: crate::Application -pub type Result = std::result::Result<(), Error>; diff --git a/src/touch.rs b/src/touch.rs index 0b77c386ee..f2bdfca827 100644 --- a/src/touch.rs +++ b/src/touch.rs @@ -1,2 +1,2 @@ //! Listen and react to touch events. -pub use crate::runtime::touch::{Event, Finger}; +pub use crate::core::touch::{Event, Finger}; diff --git a/src/widget.rs b/src/widget.rs deleted file mode 100644 index 19434e84b9..0000000000 --- a/src/widget.rs +++ /dev/null @@ -1,239 +0,0 @@ -//! Display information and interactive controls in your application. -pub use iced_native::widget::helpers::*; - -pub use iced_native::{column, row}; - -/// A container that distributes its contents vertically. -pub type Column<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Column<'a, Message, Renderer>; - -/// A container that distributes its contents horizontally. -pub type Row<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Row<'a, Message, Renderer>; - -pub mod text { - //! Write some text for your users to read. - pub use iced_native::widget::text::{Appearance, StyleSheet}; - - /// A paragraph of text. - pub type Text<'a, Renderer = crate::Renderer> = - iced_native::widget::Text<'a, Renderer>; -} - -pub mod button { - //! Allow your users to perform actions by pressing a button. - pub use iced_native::widget::button::{Appearance, StyleSheet}; - - /// A widget that produces a message when clicked. - pub type Button<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Button<'a, Message, Renderer>; -} - -pub mod checkbox { - //! Show toggle controls using checkboxes. - pub use iced_native::widget::checkbox::{Appearance, Icon, StyleSheet}; - - /// A box that can be checked. - pub type Checkbox<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Checkbox<'a, Message, Renderer>; -} - -pub mod container { - //! Decorate content and apply alignment. - pub use iced_native::widget::container::{Appearance, StyleSheet}; - - /// An element decorating some content. - pub type Container<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Container<'a, Message, Renderer>; -} - -pub mod pane_grid { - //! Let your users split regions of your application and organize layout dynamically. - //! - //! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) - //! - //! # Example - //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, - //! drag and drop, and hotkey support. - //! - //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid - pub use iced_native::widget::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Line, Node, Pane, - ResizeEvent, Split, State, StyleSheet, - }; - - /// A collection of panes distributed using either vertical or horizontal splits - /// to completely fill the space available. - /// - /// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) - pub type PaneGrid<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::PaneGrid<'a, Message, Renderer>; - - /// The content of a [`Pane`]. - pub type Content<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::pane_grid::Content<'a, Message, Renderer>; - - /// The title bar of a [`Pane`]. - pub type TitleBar<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::pane_grid::TitleBar<'a, Message, Renderer>; -} - -pub mod pick_list { - //! Display a dropdown list of selectable values. - pub use iced_native::widget::pick_list::{ - Appearance, Handle, Icon, StyleSheet, - }; - - /// A widget allowing the selection of a single value from a list of options. - pub type PickList<'a, T, Message, Renderer = crate::Renderer> = - iced_native::widget::PickList<'a, T, Message, Renderer>; -} - -pub mod radio { - //! Create choices using radio buttons. - pub use iced_native::widget::radio::{Appearance, StyleSheet}; - - /// A circular button representing a choice. - pub type Radio = - iced_native::widget::Radio; -} - -pub mod scrollable { - //! Navigate an endless amount of content with a scrollbar. - pub use iced_native::widget::scrollable::{ - snap_to, style::Scrollbar, style::Scroller, Id, Properties, - RelativeOffset, StyleSheet, - }; - - /// A widget that can vertically display an infinite amount of content - /// with a scrollbar. - pub type Scrollable<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Scrollable<'a, Message, Renderer>; -} - -pub mod toggler { - //! Show toggle controls using togglers. - pub use iced_native::widget::toggler::{Appearance, StyleSheet}; - - /// A toggler widget. - pub type Toggler<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Toggler<'a, Message, Renderer>; -} - -pub mod text_input { - //! Display fields that can be filled with text. - pub use iced_native::widget::text_input::{ - focus, move_cursor_to, move_cursor_to_end, move_cursor_to_front, - select_all, Appearance, Id, StyleSheet, - }; - - /// A field that can be filled with text. - pub type TextInput<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::TextInput<'a, Message, Renderer>; -} - -pub mod tooltip { - //! Display a widget over another. - pub use iced_native::widget::tooltip::Position; - - /// A widget allowing the selection of a single value from a list of options. - pub type Tooltip<'a, Message, Renderer = crate::Renderer> = - iced_native::widget::Tooltip<'a, Message, Renderer>; -} - -pub use iced_native::widget::progress_bar; -pub use iced_native::widget::rule; -pub use iced_native::widget::slider; -pub use iced_native::widget::vertical_slider; -pub use iced_native::widget::Space; - -pub use button::Button; -pub use checkbox::Checkbox; -pub use container::Container; -pub use pane_grid::PaneGrid; -pub use pick_list::PickList; -pub use progress_bar::ProgressBar; -pub use radio::Radio; -pub use rule::Rule; -pub use scrollable::Scrollable; -pub use slider::Slider; -pub use text::Text; -pub use text_input::TextInput; -pub use toggler::Toggler; -pub use tooltip::Tooltip; -pub use vertical_slider::VerticalSlider; - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub mod canvas; - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -/// Creates a new [`Canvas`]. -pub fn canvas(program: P) -> Canvas -where - Renderer: iced_renderer::geometry::Renderer, - P: canvas::Program, -{ - Canvas::new(program) -} - -#[cfg(feature = "image")] -#[cfg_attr(docsrs, doc(cfg(feature = "image")))] -pub mod image { - //! Display images in your user interface. - pub use iced_native::image::Handle; - - /// A frame that displays an image. - pub type Image = iced_native::widget::Image; - - pub use iced_native::widget::image::viewer; - pub use viewer::Viewer; -} - -#[cfg(feature = "qr_code")] -#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub mod qr_code; - -#[cfg(feature = "svg")] -#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] -pub mod svg { - //! Display vector graphics in your application. - pub use iced_native::svg::Handle; - pub use iced_native::widget::svg::{Appearance, StyleSheet, Svg}; -} - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub use canvas::Canvas; - -#[cfg(feature = "image")] -#[cfg_attr(docsrs, doc(cfg(feature = "image")))] -pub use image::Image; - -#[cfg(feature = "qr_code")] -#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] -pub use qr_code::QRCode; - -#[cfg(feature = "svg")] -#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] -pub use svg::Svg; - -use crate::Command; -use iced_native::widget::operation; - -/// Focuses the previous focusable widget. -pub fn focus_previous() -> Command -where - Message: 'static, -{ - Command::widget(operation::focusable::focus_previous()) -} - -/// Focuses the next focusable widget. -pub fn focus_next() -> Command -where - Message: 'static, -{ - Command::widget(operation::focusable::focus_next()) -} diff --git a/src/window.rs b/src/window.rs index 2018053fbb..2623906552 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,5 +8,6 @@ pub use icon::Icon; pub use position::Position; pub use settings::Settings; -#[cfg(not(target_arch = "wasm32"))] -pub use crate::runtime::window::*; +pub use crate::core::window::*; +pub use crate::native::window::*; +pub use crate::shell::window::*; diff --git a/style/src/lib.rs b/style/src/lib.rs index 59eb1eb8f9..286ff9db22 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -18,7 +18,7 @@ #![deny(missing_docs, unused_results)] #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] -pub use iced_core::{Background, Color}; +pub use iced_core as core; pub mod application; pub mod button; @@ -33,7 +33,6 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod svg; -pub mod text; pub mod text_input; pub mod theme; pub mod toggler; diff --git a/style/src/text.rs b/style/src/text.rs index f31c23068c..d5a16bef62 100644 --- a/style/src/text.rs +++ b/style/src/text.rs @@ -1,5 +1,5 @@ //! Change the appearance of text. -use iced_core::Color; +use crate::core::Color; /// The style sheet of some text. pub trait StyleSheet { diff --git a/style/src/theme.rs b/style/src/theme.rs index 4ba4facfe0..b507d096c8 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -8,6 +8,7 @@ use crate::application; use crate::button; use crate::checkbox; use crate::container; +use crate::core::widget::text; use crate::menu; use crate::pane_grid; use crate::pick_list; @@ -17,7 +18,6 @@ use crate::rule; use crate::scrollable; use crate::slider; use crate::svg; -use crate::text; use crate::text_input; use crate::toggler; diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 050c6c75da..d364e36a6b 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,9 +1,9 @@ -use crate::{Color, Font, Primitive, Settings, Size, Viewport}; - -use iced_graphics::alignment; -use iced_graphics::backend; -use iced_graphics::text; -use iced_graphics::{Background, Rectangle, Vector}; +use crate::core::alignment; +use crate::core::text; +use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::backend; +use crate::graphics::{Primitive, Viewport}; +use crate::Settings; use std::borrow::Cow; @@ -470,7 +470,7 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, - point: iced_native::Point, + point: Point, nearest_only: bool, ) -> Option { self.text_pipeline.hit_test( @@ -490,7 +490,7 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, _handle: &iced_native::image::Handle) -> Size { + fn dimensions(&self, _handle: &crate::core::image::Handle) -> Size { // TODO Size::new(0, 0) } @@ -500,7 +500,7 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - _handle: &iced_native::svg::Handle, + _handle: &crate::core::svg::Handle, ) -> Size { // TODO Size::new(0, 0) diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 73fc1ebdef..c66621dd8c 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,9 +1,9 @@ -use crate::{Point, Primitive, Rectangle, Size, Vector}; - -use iced_graphics::geometry::fill::{self, Fill}; -use iced_graphics::geometry::stroke::{self, Stroke}; -use iced_graphics::geometry::{Path, Style, Text}; -use iced_graphics::Gradient; +use crate::core::Gradient; +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::stroke::{self, Stroke}; +use crate::graphics::geometry::{Path, Style, Text}; +use crate::graphics::Primitive; pub struct Frame { size: Size, diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index ef5c6b1db4..bf83e4004d 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -7,19 +7,14 @@ mod text; #[cfg(feature = "geometry")] pub mod geometry; -pub use iced_graphics::primitive; +pub use iced_graphics as graphics; +pub use iced_graphics::core; pub use backend::Backend; -pub use primitive::Primitive; pub use settings::Settings; -pub use iced_graphics::{ - Color, Error, Font, Point, Rectangle, Size, Vector, Viewport, -}; - /// A [`tiny-skia`] graphics renderer for [`iced`]. /// /// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = - iced_graphics::Renderer; +pub type Renderer = iced_graphics::Renderer; diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index 88098345f2..9e4be0c495 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,4 +1,4 @@ -use crate::Font; +use crate::core::Font; /// The settings of a [`Backend`]. /// diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index da3a06bfb3..7a5034c2aa 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,7 +1,6 @@ -pub use iced_native::text::Hit; - -use iced_native::alignment; -use iced_native::{Color, Font, Rectangle, Size}; +use crate::core::alignment; +use crate::core::text::Hit; +use crate::core::{Color, Font, Point, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; @@ -189,8 +188,8 @@ impl Pipeline { content: &str, size: f32, font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, + bounds: Size, + point: Point, _nearest_only: bool, ) -> Option { self.system.as_ref().unwrap().with(|fields| { diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index 76f371e17e..cea1cabfb4 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,6 +1,7 @@ -use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; - -use iced_graphics::window::compositor::{self, Information, SurfaceError}; +use crate::core::Color; +use crate::graphics::compositor::{self, Information, SurfaceError}; +use crate::graphics::{Error, Primitive, Viewport}; +use crate::{Backend, Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -15,7 +16,7 @@ pub struct Surface { buffer: Vec, } -impl iced_graphics::window::Compositor for Compositor { +impl crate::graphics::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = Surface; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 2e39a9e710..f3ea66dd93 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -48,10 +48,6 @@ features = ["std"] version = "1.9" features = ["derive"] -[dependencies.iced_native] -version = "0.9" -path = "../native" - [dependencies.iced_graphics] version = "0.7" path = "../graphics" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6f39a5febb..9c9a1b7637 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,10 +1,11 @@ +use crate::core; +use crate::core::{Color, Font, Point, Size}; +use crate::graphics::backend; +use crate::graphics::{Primitive, Transformation, Viewport}; use crate::quad; use crate::text; use crate::triangle; -use crate::{Layer, Primitive, Settings, Transformation}; - -use iced_graphics::backend; -use iced_graphics::{Color, Font, Size, Viewport}; +use crate::{Layer, Settings}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -363,9 +364,9 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, - point: iced_native::Point, + point: Point, nearest_only: bool, - ) -> Option { + ) -> Option { self.text_pipeline.hit_test( contents, size, @@ -383,17 +384,14 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { + fn dimensions(&self, handle: &core::image::Handle) -> Size { self.image_pipeline.dimensions(handle) } } #[cfg(feature = "svg")] impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size { + fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size { self.image_pipeline.viewport_dimensions(handle) } } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 11e8126fdd..59ec31fef6 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,9 +1,9 @@ -use iced_graphics::geometry::fill::{self, Fill}; -use iced_graphics::geometry::{ +use crate::core::{Gradient, Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; -use iced_graphics::primitive::{self, Primitive}; -use iced_graphics::{Gradient, Point, Rectangle, Size, Vector}; +use crate::graphics::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 2159a3ec65..5eaa9a8651 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,16 +1,17 @@ mod atlas; -#[cfg(feature = "image")] -use iced_graphics::image::raster; - -#[cfg(feature = "svg")] -use iced_graphics::image::vector; +use atlas::Atlas; +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; use crate::layer; -use crate::{Buffer, Transformation}; -use atlas::Atlas; +use crate::Buffer; + +#[cfg(feature = "image")] +use crate::graphics::image::raster; -use iced_native::{Rectangle, Size}; +#[cfg(feature = "svg")] +use crate::graphics::image::vector; use std::cell::RefCell; use std::mem; @@ -18,10 +19,10 @@ use std::mem; use bytemuck::{Pod, Zeroable}; #[cfg(feature = "image")] -use iced_native::image; +use crate::core::image; #[cfg(feature = "svg")] -use iced_native::svg; +use crate::core::svg; #[cfg(feature = "tracing")] use tracing::info_span; diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 7df67abdc0..0a17ca33bf 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -12,8 +12,8 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; -use iced_graphics::image; -use iced_graphics::Size; +use crate::core::Size; +use crate::graphics::image; use std::num::NonZeroU32; diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 43aba87596..1128977117 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -1,7 +1,6 @@ +use crate::core::Size; use crate::image::atlas::{self, allocator}; -use iced_graphics::Size; - #[derive(Debug)] pub enum Allocation { Partial { diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 03effdcb2f..204a5c266a 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -46,10 +46,10 @@ impl Region { (rectangle.min.x as u32, rectangle.min.y as u32) } - pub fn size(&self) -> iced_graphics::Size { + pub fn size(&self) -> crate::core::Size { let size = self.allocation.rectangle.size(); - iced_graphics::Size::new(size.width as u32, size.height as u32) + crate::core::Size::new(size.width as u32, size.height as u32) } } diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 69c05a50ef..4b06bd951d 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,8 +1,7 @@ +use crate::core::Size; +use crate::graphics::image; use crate::image::atlas; -use iced_graphics::image; -use iced_graphics::Size; - #[derive(Debug)] pub enum Entry { Contiguous(atlas::Allocation), diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 69fcf8995e..cb9d5e2f69 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -10,12 +10,9 @@ pub use mesh::Mesh; pub use quad::Quad; pub use text::Text; -use crate::Primitive; - -use iced_graphics::alignment; -use iced_graphics::{ - Background, Color, Font, Point, Rectangle, Size, Vector, Viewport, -}; +use crate::core::alignment; +use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::{Primitive, Viewport}; /// A group of primitives that should be clipped together. #[derive(Debug)] diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs index 3eff239779..0de589f8e8 100644 --- a/wgpu/src/layer/image.rs +++ b/wgpu/src/layer/image.rs @@ -1,6 +1,6 @@ -use crate::{Color, Rectangle}; - -use iced_native::{image, svg}; +use crate::core::image; +use crate::core::svg; +use crate::core::{Color, Rectangle}; /// A raster or vector image. #[derive(Debug, Clone)] diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs index 5c1e41ad26..9dd143919b 100644 --- a/wgpu/src/layer/mesh.rs +++ b/wgpu/src/layer/mesh.rs @@ -1,6 +1,6 @@ //! A collection of triangle primitives. -use crate::primitive; -use crate::{Gradient, Point, Rectangle}; +use crate::core::{Gradient, Point, Rectangle}; +use crate::graphics::primitive; /// A mesh of triangles. #[derive(Debug, Clone, Copy)] diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index 38d626168a..fdbdaafb59 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,4 +1,5 @@ -use crate::{alignment, Color, Font, Rectangle}; +use crate::core::alignment; +use crate::core::{Color, Font, Rectangle}; /// A paragraph of text. #[derive(Debug, Clone, Copy)] diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4439b18567..473f362177 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -50,25 +50,17 @@ mod quad; mod text; mod triangle; -pub use iced_graphics::primitive; -pub use iced_graphics::{ - Antialiasing, Color, Error, Font, Gradient, Point, Rectangle, Size, Vector, - Viewport, -}; -pub use iced_native::alignment; +pub use iced_graphics as graphics; +pub use iced_graphics::core; -pub use iced_native::Theme; pub use wgpu; pub use backend::Backend; pub use layer::Layer; -pub use primitive::Primitive; pub use settings::Settings; use buffer::Buffer; -use iced_graphics::Transformation; - #[cfg(any(feature = "image", feature = "svg"))] mod image; @@ -76,5 +68,4 @@ mod image; /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = - iced_graphics::Renderer; +pub type Renderer = iced_graphics::Renderer; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 8a568968b1..b55216d739 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,7 +1,7 @@ +use crate::core::Rectangle; +use crate::graphics::Transformation; use crate::layer; -use crate::{Buffer, Transformation}; - -use iced_native::Rectangle; +use crate::Buffer; use bytemuck::{Pod, Zeroable}; use std::mem; diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index bd9cf473f5..7c0750efbf 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,7 +1,6 @@ //! Configure a renderer. -pub use crate::Antialiasing; - -use crate::Font; +use crate::core::Font; +use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. /// diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 0dc8a64cae..d7a27f3a8c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,10 +1,8 @@ +use crate::core::alignment; +use crate::core::text::Hit; +use crate::core::{Color, Font, Point, Rectangle, Size}; use crate::layer::Text; -pub use iced_native::text::Hit; - -use iced_native::alignment; -use iced_native::{Color, Font, Rectangle, Size}; - use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; @@ -269,9 +267,9 @@ impl Pipeline { &self, content: &str, size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, + font: Font, + bounds: Size, + point: Point, _nearest_only: bool, ) -> Option { self.system.as_ref().unwrap().with(|fields| { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 706e428258..9fa521d74d 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,11 +2,9 @@ mod msaa; use crate::buffer::r#static::Buffer; +use crate::core::{Gradient, Size}; +use crate::graphics::{Antialiasing, Transformation}; use crate::layer::mesh::{self, Mesh}; -use crate::settings; -use crate::Transformation; - -use iced_graphics::Size; #[cfg(feature = "tracing")] use tracing::info_span; @@ -137,7 +135,7 @@ impl Layer { gradient_vertex_offset += written_bytes; match gradient { - iced_graphics::Gradient::Linear(linear) => { + Gradient::Linear(linear) => { use glam::{IVec4, Vec4}; let start_offset = self.gradient.color_stop_offset; @@ -319,7 +317,7 @@ impl Pipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option, + antialiasing: Option, ) -> Pipeline { Pipeline { blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), @@ -453,7 +451,7 @@ fn primitive_state() -> wgpu::PrimitiveState { } fn multisample_state( - antialiasing: Option, + antialiasing: Option, ) -> wgpu::MultisampleState { wgpu::MultisampleState { count: antialiasing.map(|a| a.sample_count()).unwrap_or(1), @@ -465,11 +463,11 @@ fn multisample_state( mod solid { use crate::buffer::dynamic; use crate::buffer::r#static::Buffer; - use crate::settings; + use crate::graphics::primitive; + use crate::graphics::{Antialiasing, Transformation}; use crate::triangle; + use encase::ShaderType; - use iced_graphics::primitive; - use iced_graphics::Transformation; #[derive(Debug)] pub struct Pipeline { @@ -550,7 +548,7 @@ mod solid { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option, + antialiasing: Option, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { @@ -633,7 +631,7 @@ mod solid { mod gradient { use crate::buffer::dynamic; use crate::buffer::r#static::Buffer; - use crate::settings; + use crate::graphics::Antialiasing; use crate::triangle; use encase::ShaderType; @@ -755,7 +753,7 @@ mod gradient { pub(super) fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option, + antialiasing: Option, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index a3016ff8fb..7144125c69 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -1,4 +1,4 @@ -use crate::settings; +use crate::graphics; #[derive(Debug)] pub struct Blit { @@ -14,7 +14,7 @@ impl Blit { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: settings::Antialiasing, + antialiasing: graphics::Antialiasing, ) -> Blit { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 3a4a71233a..a67ac3c044 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,10 +1,12 @@ //! Connect a window with a renderer. -use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; +use crate::core::Color; +use crate::graphics; +use crate::graphics::compositor; +use crate::graphics::{Error, Primitive, Viewport}; +use crate::{Backend, Renderer, Settings}; use futures::stream::{self, StreamExt}; -use iced_graphics::window::compositor; -use iced_native::futures; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -184,7 +186,7 @@ pub fn present>( } } -impl iced_graphics::window::Compositor for Compositor { +impl graphics::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface; diff --git a/widget/Cargo.toml b/widget/Cargo.toml new file mode 100644 index 0000000000..fb617079c8 --- /dev/null +++ b/widget/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "iced_widget" +version = "0.1.0" +edition = "2021" + +[features] +lazy = ["ouroboros"] +image = ["iced_renderer/image"] +svg = ["iced_renderer/svg"] +canvas = ["iced_renderer/geometry"] +qr_code = ["canvas", "qrcode"] + +[dependencies] +unicode-segmentation = "1.6" +num-traits = "0.2" +thiserror = "1" + +[dependencies.iced_native] +version = "0.9" +path = "../native" + +[dependencies.iced_renderer] +version = "0.1" +path = "../renderer" + +[dependencies.iced_style] +version = "0.7" +path = "../style" + +[dependencies.ouroboros] +version = "0.13" +optional = true + +[dependencies.qrcode] +version = "0.12" +optional = true +default-features = false diff --git a/native/src/widget/button.rs b/widget/src/button.rs similarity index 94% rename from native/src/widget/button.rs rename to widget/src/button.rs index 3938717305..d6fd399753 100644 --- a/native/src/widget/button.rs +++ b/widget/src/button.rs @@ -1,15 +1,15 @@ //! Allow your users to perform actions by pressing a button. //! //! A [`Button`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::widget::Operation; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::Operation; +use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; @@ -20,7 +20,7 @@ pub use iced_style::button::{Appearance, StyleSheet}; /// /// ``` /// # type Button<'a, Message> = -/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; +/// # iced_widget::Button<'a, Message, iced_widget::renderer::Renderer>; /// # /// #[derive(Clone)] /// enum Message { @@ -35,7 +35,7 @@ pub use iced_style::button::{Appearance, StyleSheet}; /// /// ``` /// # type Button<'a, Message> = -/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; +/// # iced_widget::Button<'a, Message, iced_widget::renderer::Renderer>; /// # /// #[derive(Clone)] /// enum Message { @@ -51,9 +51,9 @@ pub use iced_style::button::{Appearance, StyleSheet}; /// } /// ``` #[allow(missing_debug_implementations)] -pub struct Button<'a, Message, Renderer> +pub struct Button<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { content: Element<'a, Message, Renderer>, @@ -66,7 +66,7 @@ where impl<'a, Message, Renderer> Button<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates a new [`Button`] with the given content. @@ -121,7 +121,7 @@ impl<'a, Message, Renderer> Widget for Button<'a, Message, Renderer> where Message: 'a + Clone, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -279,7 +279,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: Clone + 'a, - Renderer: crate::Renderer + 'a, + Renderer: crate::core::Renderer + 'a, Renderer::Theme: StyleSheet, { fn from(button: Button<'a, Message, Renderer>) -> Self { @@ -355,7 +355,7 @@ pub fn update<'a, Message: Clone>( } /// Draws a [`Button`]. -pub fn draw<'a, Renderer: crate::Renderer>( +pub fn draw<'a, Renderer: crate::core::Renderer>( renderer: &mut Renderer, bounds: Rectangle, cursor_position: Point, diff --git a/src/widget/canvas.rs b/widget/src/canvas.rs similarity index 84% rename from src/widget/canvas.rs rename to widget/src/canvas.rs index bc5995c65e..171c4534d8 100644 --- a/src/widget/canvas.rs +++ b/widget/src/canvas.rs @@ -8,15 +8,17 @@ pub use cursor::Cursor; pub use event::Event; pub use program::Program; -pub use iced_renderer::geometry::*; - -use crate::{Length, Point, Rectangle, Size, Vector}; - -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::{Clipboard, Element, Shell, Widget}; +pub use crate::graphics::geometry::*; +pub use crate::renderer::geometry::*; + +use crate::core; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{Clipboard, Element, Shell, Widget}; +use crate::core::{Length, Point, Rectangle, Size, Vector}; +use crate::graphics::geometry; use std::marker::PhantomData; @@ -26,9 +28,11 @@ use std::marker::PhantomData; /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// use iced::widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle, Theme, Renderer}; -/// +/// # use iced_widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; +/// # use iced_widget::core::{Color, Rectangle}; +/// # use iced_widget::style::Theme; +/// # +/// # pub type Renderer = iced_widget::renderer::Renderer; /// // First, we define the data we need for drawing /// #[derive(Debug)] /// struct Circle { @@ -60,7 +64,7 @@ use std::marker::PhantomData; #[derive(Debug)] pub struct Canvas where - Renderer: iced_renderer::geometry::Renderer, + Renderer: geometry::Renderer, P: Program, { width: Length, @@ -72,7 +76,7 @@ where impl Canvas where - Renderer: iced_renderer::geometry::Renderer, + Renderer: geometry::Renderer, P: Program, { const DEFAULT_SIZE: f32 = 100.0; @@ -104,7 +108,7 @@ where impl Widget for Canvas where - Renderer: iced_renderer::geometry::Renderer, + Renderer: geometry::Renderer, P: Program, { fn tag(&self) -> tree::Tag { @@ -138,7 +142,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: core::Event, layout: Layout<'_>, cursor_position: Point, _renderer: &Renderer, @@ -148,13 +152,9 @@ where let bounds = layout.bounds(); let canvas_event = match event { - iced_native::Event::Mouse(mouse_event) => { - Some(Event::Mouse(mouse_event)) - } - iced_native::Event::Touch(touch_event) => { - Some(Event::Touch(touch_event)) - } - iced_native::Event::Keyboard(keyboard_event) => { + core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), + core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), + core::Event::Keyboard(keyboard_event) => { Some(Event::Keyboard(keyboard_event)) } _ => None, @@ -227,7 +227,7 @@ impl<'a, P, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + iced_renderer::geometry::Renderer, + Renderer: 'a + geometry::Renderer, P: Program + 'a, { fn from( diff --git a/src/widget/canvas/cursor.rs b/widget/src/canvas/cursor.rs similarity index 98% rename from src/widget/canvas/cursor.rs rename to widget/src/canvas/cursor.rs index ef6a7771f0..5a65e9a79e 100644 --- a/src/widget/canvas/cursor.rs +++ b/widget/src/canvas/cursor.rs @@ -1,4 +1,4 @@ -use crate::{Point, Rectangle}; +use crate::core::{Point, Rectangle}; /// The mouse cursor state. #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/src/widget/canvas/event.rs b/widget/src/canvas/event.rs similarity index 73% rename from src/widget/canvas/event.rs rename to widget/src/canvas/event.rs index 7c733a4d8a..4508c18453 100644 --- a/src/widget/canvas/event.rs +++ b/widget/src/canvas/event.rs @@ -1,9 +1,9 @@ //! Handle events of a canvas. -use iced_native::keyboard; -use iced_native::mouse; -use iced_native::touch; +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; -pub use iced_native::event::Status; +pub use crate::core::event::Status; /// A [`Canvas`] event. /// diff --git a/src/widget/canvas/program.rs b/widget/src/canvas/program.rs similarity index 92% rename from src/widget/canvas/program.rs rename to widget/src/canvas/program.rs index fd15663a6e..efb33c5678 100644 --- a/src/widget/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,7 +1,8 @@ -use crate::widget::canvas::event::{self, Event}; -use crate::widget::canvas::mouse; -use crate::widget::canvas::Cursor; -use crate::Rectangle; +use crate::canvas::event::{self, Event}; +use crate::canvas::mouse; +use crate::canvas::Cursor; +use crate::core::Rectangle; +use crate::graphics::geometry; /// The state and logic of a [`Canvas`]. /// @@ -11,7 +12,7 @@ use crate::Rectangle; /// [`Canvas`]: crate::widget::Canvas pub trait Program where - Renderer: iced_renderer::geometry::Renderer, + Renderer: geometry::Renderer, { /// The internal state mutated by the [`Program`]. type State: Default + 'static; @@ -71,7 +72,7 @@ where impl Program for &T where - Renderer: iced_renderer::geometry::Renderer, + Renderer: geometry::Renderer, T: Program, { type State = T::State; diff --git a/native/src/widget/checkbox.rs b/widget/src/checkbox.rs similarity index 91% rename from native/src/widget/checkbox.rs rename to widget/src/checkbox.rs index cd8b9c6b16..6a062f94e6 100644 --- a/native/src/widget/checkbox.rs +++ b/widget/src/checkbox.rs @@ -1,16 +1,17 @@ //! Show toggle controls using checkboxes. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text; +use crate::core::touch; +use crate::core::widget::Tree; +use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Widget, }; +use crate::{Row, Text}; pub use iced_style::checkbox::{Appearance, StyleSheet}; @@ -30,7 +31,8 @@ pub struct Icon { /// # Example /// /// ``` -/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>; +/// # type Checkbox<'a, Message> = +/// # iced_widget::Checkbox<'a, Message, iced_widget::renderer::Renderer>; /// # /// pub enum Message { /// CheckboxToggled(bool), @@ -43,10 +45,10 @@ pub struct Icon { /// /// ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true) #[allow(missing_debug_implementations)] -pub struct Checkbox<'a, Message, Renderer> +pub struct Checkbox<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { is_checked: bool, on_toggle: Box Message + 'a>, @@ -63,7 +65,7 @@ where impl<'a, Message, Renderer> Checkbox<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { /// The default size of a [`Checkbox`]. const DEFAULT_SIZE: f32 = 20.0; @@ -153,7 +155,7 @@ impl<'a, Message, Renderer> Widget for Checkbox<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn width(&self) -> Length { self.width @@ -289,14 +291,14 @@ where { let label_layout = children.next().unwrap(); - widget::text::draw( + crate::text::draw( renderer, style, label_layout, &self.label, self.text_size, self.font, - widget::text::Appearance { + crate::text::Appearance { color: custom_style.text_color, }, alignment::Horizontal::Left, @@ -311,7 +313,7 @@ impl<'a, Message, Renderer> From> where Message: 'a, Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn from( checkbox: Checkbox<'a, Message, Renderer>, diff --git a/native/src/widget/column.rs b/widget/src/column.rs similarity index 95% rename from native/src/widget/column.rs rename to widget/src/column.rs index ebe579d5b7..8f363ec65f 100644 --- a/native/src/widget/column.rs +++ b/widget/src/column.rs @@ -1,18 +1,18 @@ //! Distribute content vertically. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{Operation, Tree}; +use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Widget, }; /// A container that distributes its contents vertically. #[allow(missing_debug_implementations)] -pub struct Column<'a, Message, Renderer> { +pub struct Column<'a, Message, Renderer = crate::Renderer> { spacing: f32, padding: Padding, width: Length, @@ -102,7 +102,7 @@ impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget for Column<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, { fn children(&self) -> Vec { self.children.iter().map(Tree::new).collect() @@ -256,7 +256,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: crate::Renderer + 'a, + Renderer: crate::core::Renderer + 'a, { fn from(column: Column<'a, Message, Renderer>) -> Self { Self::new(column) diff --git a/native/src/widget/container.rs b/widget/src/container.rs similarity index 94% rename from native/src/widget/container.rs rename to widget/src/container.rs index b77bf50d23..9d932772d1 100644 --- a/native/src/widget/container.rs +++ b/widget/src/container.rs @@ -1,12 +1,12 @@ //! Decorate content and apply alignment. -use crate::alignment::{self, Alignment}; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{self, Operation, Tree}; -use crate::{ +use crate::core::alignment::{self, Alignment}; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{self, Operation, Tree}; +use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Widget, }; @@ -17,9 +17,9 @@ pub use iced_style::container::{Appearance, StyleSheet}; /// /// It is normally used for alignment purposes. #[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer> +pub struct Container<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { id: Option, @@ -36,7 +36,7 @@ where impl<'a, Message, Renderer> Container<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates an empty [`Container`]. @@ -131,7 +131,7 @@ where impl<'a, Message, Renderer> Widget for Container<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn children(&self) -> Vec { @@ -276,7 +276,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -326,7 +326,7 @@ pub fn draw_background( appearance: &Appearance, bounds: Rectangle, ) where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, { if appearance.background.is_some() || appearance.border_width > 0.0 { renderer.fill_quad( diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs new file mode 100644 index 0000000000..1a73c16f13 --- /dev/null +++ b/widget/src/helpers.rs @@ -0,0 +1,362 @@ +//! Helper functions to create pure widgets. +use crate::button::{self, Button}; +use crate::checkbox::{self, Checkbox}; +use crate::container::{self, Container}; +use crate::core; +use crate::core::widget::operation; +use crate::core::{Element, Length, Pixels}; +use crate::native::Command; +use crate::overlay; +use crate::pick_list::{self, PickList}; +use crate::progress_bar::{self, ProgressBar}; +use crate::radio::{self, Radio}; +use crate::rule::{self, Rule}; +use crate::scrollable::{self, Scrollable}; +use crate::slider::{self, Slider}; +use crate::text::{self, Text}; +use crate::text_input::{self, TextInput}; +use crate::toggler::{self, Toggler}; +use crate::tooltip::{self, Tooltip}; +use crate::{Column, Row, Space, VerticalSlider}; + +use std::borrow::Cow; +use std::ops::RangeInclusive; + +/// Creates a [`Column`] with the given children. +/// +/// [`Column`]: widget::Column +#[macro_export] +macro_rules! column { + () => ( + $crate::Column::new() + ); + ($($x:expr),+ $(,)?) => ( + $crate::Column::with_children(vec![$($crate::core::Element::from($x)),+]) + ); +} + +/// Creates a [`Row`] with the given children. +/// +/// [`Row`]: widget::Row +#[macro_export] +macro_rules! row { + () => ( + $crate::Row::new() + ); + ($($x:expr),+ $(,)?) => ( + $crate::Row::with_children(vec![$($crate::core::Element::from($x)),+]) + ); +} + +/// Creates a new [`Container`] with the provided content. +/// +/// [`Container`]: widget::Container +pub fn container<'a, Message, Renderer>( + content: impl Into>, +) -> Container<'a, Message, Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: container::StyleSheet, +{ + Container::new(content) +} + +/// Creates a new [`Column`] with the given children. +/// +/// [`Column`]: widget::Column +pub fn column( + children: Vec>, +) -> Column<'_, Message, Renderer> { + Column::with_children(children) +} + +/// Creates a new [`Row`] with the given children. +/// +/// [`Row`]: widget::Row +pub fn row( + children: Vec>, +) -> Row<'_, Message, Renderer> { + Row::with_children(children) +} + +/// Creates a new [`Scrollable`] with the provided content. +/// +/// [`Scrollable`]: widget::Scrollable +pub fn scrollable<'a, Message, Renderer>( + content: impl Into>, +) -> Scrollable<'a, Message, Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: scrollable::StyleSheet, +{ + Scrollable::new(content) +} + +/// Creates a new [`Button`] with the provided content. +/// +/// [`Button`]: widget::Button +pub fn button<'a, Message, Renderer>( + content: impl Into>, +) -> Button<'a, Message, Renderer> +where + Renderer: core::Renderer, + Renderer::Theme: button::StyleSheet, + ::Style: Default, +{ + Button::new(content) +} + +/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`]. +/// +/// [`Tooltip`]: widget::Tooltip +/// [`tooltip::Position`]: widget::tooltip::Position +pub fn tooltip<'a, Message, Renderer>( + content: impl Into>, + tooltip: impl ToString, + position: tooltip::Position, +) -> crate::Tooltip<'a, Message, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: container::StyleSheet + text::StyleSheet, +{ + Tooltip::new(content, tooltip.to_string(), position) +} + +/// Creates a new [`Text`] widget with the provided content. +/// +/// [`Text`]: widget::Text +pub fn text<'a, Renderer>(text: impl ToString) -> Text<'a, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: text::StyleSheet, +{ + Text::new(text.to_string()) +} + +/// Creates a new [`Checkbox`]. +/// +/// [`Checkbox`]: widget::Checkbox +pub fn checkbox<'a, Message, Renderer>( + label: impl Into, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> Checkbox<'a, Message, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: checkbox::StyleSheet + text::StyleSheet, +{ + Checkbox::new(label, is_checked, f) +} + +/// Creates a new [`Radio`]. +/// +/// [`Radio`]: widget::Radio +pub fn radio( + label: impl Into, + value: V, + selected: Option, + on_click: impl FnOnce(V) -> Message, +) -> Radio +where + Message: Clone, + Renderer: core::text::Renderer, + Renderer::Theme: radio::StyleSheet, + V: Copy + Eq, +{ + Radio::new(value, label, selected, on_click) +} + +/// Creates a new [`Toggler`]. +/// +/// [`Toggler`]: widget::Toggler +pub fn toggler<'a, Message, Renderer>( + label: impl Into>, + is_checked: bool, + f: impl Fn(bool) -> Message + 'a, +) -> Toggler<'a, Message, Renderer> +where + Renderer: core::text::Renderer, + Renderer::Theme: toggler::StyleSheet, +{ + Toggler::new(label, is_checked, f) +} + +/// Creates a new [`TextInput`]. +/// +/// [`TextInput`]: widget::TextInput +pub fn text_input<'a, Message, Renderer>( + placeholder: &str, + value: &str, + on_change: impl Fn(String) -> Message + 'a, +) -> TextInput<'a, Message, Renderer> +where + Message: Clone, + Renderer: core::text::Renderer, + Renderer::Theme: text_input::StyleSheet, +{ + TextInput::new(placeholder, value, on_change) +} + +/// Creates a new [`Slider`]. +/// +/// [`Slider`]: widget::Slider +pub fn slider<'a, T, Message, Renderer>( + range: std::ops::RangeInclusive, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> Slider<'a, T, Message, Renderer> +where + T: Copy + From + std::cmp::PartialOrd, + Message: Clone, + Renderer: core::Renderer, + Renderer::Theme: slider::StyleSheet, +{ + Slider::new(range, value, on_change) +} + +/// Creates a new [`VerticalSlider`]. +/// +/// [`VerticalSlider`]: widget::VerticalSlider +pub fn vertical_slider<'a, T, Message, Renderer>( + range: std::ops::RangeInclusive, + value: T, + on_change: impl Fn(T) -> Message + 'a, +) -> VerticalSlider<'a, T, Message, Renderer> +where + T: Copy + From + std::cmp::PartialOrd, + Message: Clone, + Renderer: core::Renderer, + Renderer::Theme: slider::StyleSheet, +{ + VerticalSlider::new(range, value, on_change) +} + +/// Creates a new [`PickList`]. +/// +/// [`PickList`]: widget::PickList +pub fn pick_list<'a, Message, Renderer, T>( + options: impl Into>, + selected: Option, + on_selected: impl Fn(T) -> Message + 'a, +) -> PickList<'a, T, Message, Renderer> +where + T: ToString + Eq + 'static, + [T]: ToOwned>, + Renderer: core::text::Renderer, + Renderer::Theme: pick_list::StyleSheet + + scrollable::StyleSheet + + overlay::menu::StyleSheet + + container::StyleSheet, + ::Style: + From<::Style>, +{ + PickList::new(options, selected, on_selected) +} + +/// Creates a new horizontal [`Space`] with the given [`Length`]. +/// +/// [`Space`]: widget::Space +pub fn horizontal_space(width: impl Into) -> Space { + Space::with_width(width) +} + +/// Creates a new vertical [`Space`] with the given [`Length`]. +/// +/// [`Space`]: widget::Space +pub fn vertical_space(height: impl Into) -> Space { + Space::with_height(height) +} + +/// Creates a horizontal [`Rule`] with the given height. +/// +/// [`Rule`]: widget::Rule +pub fn horizontal_rule(height: impl Into) -> Rule +where + Renderer: core::Renderer, + Renderer::Theme: rule::StyleSheet, +{ + Rule::horizontal(height) +} + +/// Creates a vertical [`Rule`] with the given width. +/// +/// [`Rule`]: widget::Rule +pub fn vertical_rule(width: impl Into) -> Rule +where + Renderer: core::Renderer, + Renderer::Theme: rule::StyleSheet, +{ + Rule::vertical(width) +} + +/// Creates a new [`ProgressBar`]. +/// +/// It expects: +/// * an inclusive range of possible values, and +/// * the current value of the [`ProgressBar`]. +/// +/// [`ProgressBar`]: widget::ProgressBar +pub fn progress_bar( + range: RangeInclusive, + value: f32, +) -> ProgressBar +where + Renderer: core::Renderer, + Renderer::Theme: progress_bar::StyleSheet, +{ + ProgressBar::new(range, value) +} + +/// Creates a new [`Image`]. +/// +/// [`Image`]: widget::Image +#[cfg(feature = "image")] +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub fn image(handle: impl Into) -> crate::Image { + crate::Image::new(handle.into()) +} + +/// Creates a new [`Svg`] widget from the given [`Handle`]. +/// +/// [`Svg`]: widget::Svg +/// [`Handle`]: widget::svg::Handle +#[cfg(feature = "svg")] +#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] +pub fn svg( + handle: impl Into, +) -> crate::Svg +where + Renderer: core::svg::Renderer, + Renderer::Theme: crate::svg::StyleSheet, +{ + crate::Svg::new(handle) +} + +/// Creates a new [`Canvas`]. +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub fn canvas( + program: P, +) -> crate::Canvas +where + Renderer: crate::graphics::geometry::Renderer, + P: crate::canvas::Program, +{ + crate::Canvas::new(program) +} + +/// Focuses the previous focusable widget. +pub fn focus_previous() -> Command +where + Message: 'static, +{ + Command::widget(operation::focusable::focus_previous()) +} + +/// Focuses the next focusable widget. +pub fn focus_next() -> Command +where + Message: 'static, +{ + Command::widget(operation::focusable::focus_next()) +} diff --git a/native/src/widget/image.rs b/widget/src/image.rs similarity index 96% rename from native/src/widget/image.rs rename to widget/src/image.rs index 73257a747f..22a3a1a16e 100644 --- a/native/src/widget/image.rs +++ b/widget/src/image.rs @@ -2,16 +2,18 @@ pub mod viewer; pub use viewer::Viewer; -use crate::image; -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ +use crate::core::image; +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{ ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use std::hash::Hash; +pub use image::Handle; + /// Creates a new [`Viewer`] with the given image `Handle`. pub fn viewer(handle: Handle) -> Viewer { Viewer::new(handle) @@ -22,8 +24,7 @@ pub fn viewer(handle: Handle) -> Viewer { /// # Example /// /// ``` -/// # use iced_native::widget::Image; -/// # use iced_native::image; +/// # use iced_widget::image::{self, Image}; /// # /// let image = Image::::new("resources/ferris.png"); /// ``` diff --git a/native/src/widget/image/viewer.rs b/widget/src/image/viewer.rs similarity index 98% rename from native/src/widget/image/viewer.rs rename to widget/src/image/viewer.rs index 1f8d5d7a49..0d60d81829 100644 --- a/native/src/widget/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -1,11 +1,11 @@ //! Zoom and pan on an image. -use crate::event::{self, Event}; -use crate::image; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget::tree::{self, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::image; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; diff --git a/lazy/src/lazy.rs b/widget/src/lazy.rs similarity index 91% rename from lazy/src/lazy.rs rename to widget/src/lazy.rs index 5e909a498c..b08ed8cb9d 100644 --- a/lazy/src/lazy.rs +++ b/widget/src/lazy.rs @@ -1,12 +1,25 @@ -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::widget::{self, Widget}; -use iced_native::Element; -use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; +#![allow(clippy::await_holding_refcell_ref, clippy::type_complexity)] +pub(crate) mod helpers; + +pub mod component; +pub mod responsive; + +pub use component::Component; +pub use responsive::Responsive; + +mod cache; + +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::tree::{self, Tree}; +use crate::core::widget::{self, Widget}; +use crate::core::Element; +use crate::core::{ + self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, +}; use ouroboros::self_referencing; use std::cell::RefCell; @@ -41,7 +54,7 @@ where fn with_element( &self, - f: impl FnOnce(&Element) -> T, + f: impl FnOnce(&Element<'_, Message, Renderer>) -> T, ) -> T { f(self .element @@ -55,7 +68,7 @@ where fn with_element_mut( &self, - f: impl FnOnce(&mut Element) -> T, + f: impl FnOnce(&mut Element<'_, Message, Renderer>) -> T, ) -> T { f(self .element @@ -79,7 +92,7 @@ where View: Into> + 'static, Dependency: Hash + 'a, Message: 'static, - Renderer: iced_native::Renderer + 'static, + Renderer: core::Renderer + 'static, { fn tag(&self) -> tree::Tag { struct Tag(T); @@ -163,7 +176,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -304,7 +317,7 @@ impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> { impl<'a, Message, Renderer> overlay::Overlay for Overlay<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn layout( &self, @@ -353,7 +366,7 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -370,7 +383,7 @@ where shell, ) }) - .unwrap_or(iced_native::event::Status::Ignored) + .unwrap_or(event::Status::Ignored) } fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { @@ -386,7 +399,7 @@ impl<'a, Message, Renderer, Dependency, View> for Element<'a, Message, Renderer> where View: Into> + 'static, - Renderer: iced_native::Renderer + 'static, + Renderer: core::Renderer + 'static, Message: 'static, Dependency: Hash + 'a, { diff --git a/lazy/src/cache.rs b/widget/src/lazy/cache.rs similarity index 84% rename from lazy/src/cache.rs rename to widget/src/lazy/cache.rs index 5b4a39f696..e7b876142d 100644 --- a/lazy/src/cache.rs +++ b/widget/src/lazy/cache.rs @@ -1,5 +1,5 @@ -use iced_native::overlay; -use iced_native::Element; +use crate::core::overlay; +use crate::core::Element; use ouroboros::self_referencing; diff --git a/lazy/src/component.rs b/widget/src/lazy/component.rs similarity index 96% rename from lazy/src/component.rs rename to widget/src/lazy/component.rs index b23da9f7b4..0b8070af2c 100644 --- a/lazy/src/component.rs +++ b/widget/src/lazy/component.rs @@ -1,13 +1,13 @@ //! Build and reuse custom widgets using The Elm Architecture. -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget; -use iced_native::widget::tree::{self, Tree}; -use iced_native::{ - Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, +use crate::core::event; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use ouroboros::self_referencing; @@ -67,7 +67,7 @@ where C: Component + 'a, C::State: 'static, Message: 'a, - Renderer: iced_native::Renderer + 'a, + Renderer: core::Renderer + 'a, { Element::new(Instance { state: RefCell::new(Some( @@ -159,7 +159,7 @@ impl<'a, Message, Renderer, Event, S> Widget for Instance<'a, Message, Renderer, Event, S> where S: 'static + Default, - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { struct Tag(T); @@ -203,7 +203,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: core::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -447,7 +447,7 @@ impl<'a, 'b, Message, Renderer, Event, S> impl<'a, 'b, Message, Renderer, Event, S> overlay::Overlay for OverlayInstance<'a, 'b, Message, Renderer, Event, S> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, S: 'static + Default, { fn layout( @@ -497,13 +497,13 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: core::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> iced_native::event::Status { + ) -> event::Status { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); @@ -518,7 +518,7 @@ where &mut local_shell, ) }) - .unwrap_or(iced_native::event::Status::Ignored); + .unwrap_or(event::Status::Ignored); local_shell.revalidate_layout(|| shell.invalidate_layout()); diff --git a/lazy/src/lib.rs b/widget/src/lazy/helpers.rs similarity index 51% rename from lazy/src/lib.rs rename to widget/src/lazy/helpers.rs index 41a2877361..be60bb78b2 100644 --- a/lazy/src/lib.rs +++ b/widget/src/lazy/helpers.rs @@ -1,34 +1,7 @@ -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny( - missing_debug_implementations, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion -)] -#![forbid(unsafe_code)] -#![allow( - clippy::await_holding_refcell_ref, - clippy::inherent_to_string, - clippy::type_complexity -)] -#![cfg_attr(docsrs, feature(doc_cfg))] -mod lazy; +use crate::core::{self, Element, Size}; +use crate::lazy::component::{self, Component}; +use crate::lazy::{Lazy, Responsive}; -pub mod component; -pub mod responsive; - -pub use component::Component; -pub use lazy::Lazy; -pub use responsive::Responsive; - -mod cache; - -use iced_native::{Element, Size}; use std::hash::Hash; pub fn lazy<'a, Message, Renderer, Dependency, View>( @@ -51,7 +24,7 @@ where C: Component + 'a, C::State: 'static, Message: 'a, - Renderer: iced_native::Renderer + 'a, + Renderer: core::Renderer + 'a, { component::view(component) } @@ -60,7 +33,7 @@ pub fn responsive<'a, Message, Renderer>( f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a, ) -> Responsive<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { Responsive::new(f) } diff --git a/lazy/src/responsive.rs b/widget/src/lazy/responsive.rs similarity index 93% rename from lazy/src/responsive.rs rename to widget/src/lazy/responsive.rs index 57c07de119..7b2fc37c4e 100644 --- a/lazy/src/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -1,13 +1,14 @@ -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::widget::{self, horizontal_space}; -use iced_native::{ - Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; +use crate::horizontal_space; use ouroboros::self_referencing; use std::cell::{RefCell, RefMut}; @@ -19,14 +20,14 @@ use std::ops::Deref; /// A [`Responsive`] widget will always try to fill all the available space of /// its parent. #[allow(missing_debug_implementations)] -pub struct Responsive<'a, Message, Renderer> { +pub struct Responsive<'a, Message, Renderer = crate::Renderer> { view: Box Element<'a, Message, Renderer> + 'a>, content: RefCell>, } impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { /// Creates a new [`Responsive`] widget with a closure that produces its /// contents. @@ -56,7 +57,7 @@ struct Content<'a, Message, Renderer> { impl<'a, Message, Renderer> Content<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn update( &mut self, @@ -114,7 +115,7 @@ struct State { impl<'a, Message, Renderer> Widget for Responsive<'a, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -168,7 +169,7 @@ where fn on_event( &mut self, tree: &mut Tree, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -271,7 +272,8 @@ where content: self.content.borrow_mut(), tree: state.tree.borrow_mut(), types: PhantomData, - overlay_builder: |content: &mut RefMut>, tree| { + overlay_builder: |content: &mut RefMut<'_, Content<'_, _, _>>, + tree| { content.update( tree, renderer, @@ -309,7 +311,7 @@ where impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: iced_native::Renderer + 'a, + Renderer: core::Renderer + 'a, Message: 'a, { fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { @@ -347,7 +349,7 @@ impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { impl<'a, 'b, Message, Renderer> overlay::Overlay for Overlay<'a, 'b, Message, Renderer> where - Renderer: iced_native::Renderer, + Renderer: core::Renderer, { fn layout( &self, @@ -396,7 +398,7 @@ where fn on_event( &mut self, - event: iced_native::Event, + event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, @@ -413,7 +415,7 @@ where shell, ) }) - .unwrap_or(iced_native::event::Status::Ignored) + .unwrap_or(event::Status::Ignored) } fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { diff --git a/widget/src/lib.rs b/widget/src/lib.rs new file mode 100644 index 0000000000..4c1e7c1c0a --- /dev/null +++ b/widget/src/lib.rs @@ -0,0 +1,122 @@ +//! Use the built-in widgets or create your own. +#![doc( + html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" +)] +#![deny( + missing_debug_implementations, + //missing_docs, + unused_results, + clippy::extra_unused_lifetimes, + clippy::from_over_into, + clippy::needless_borrow, + clippy::new_without_default, + clippy::useless_conversion +)] +#![forbid(unsafe_code, rust_2018_idioms)] +#![allow(clippy::inherent_to_string, clippy::type_complexity)] +pub use iced_native as native; +pub use iced_native::core; +pub use iced_renderer as renderer; +pub use iced_renderer::graphics; +pub use iced_style as style; + +mod column; +mod row; + +pub mod button; +pub mod checkbox; +pub mod container; +pub mod overlay; +pub mod pane_grid; +pub mod pick_list; +pub mod progress_bar; +pub mod radio; +pub mod rule; +pub mod scrollable; +pub mod slider; +pub mod space; +pub mod text; +pub mod text_input; +pub mod toggler; +pub mod tooltip; +pub mod vertical_slider; + +mod helpers; + +pub use helpers::*; + +#[cfg(feature = "lazy")] +mod lazy; + +#[cfg(feature = "lazy")] +pub use crate::lazy::{Component, Lazy, Responsive}; + +#[cfg(feature = "lazy")] +pub use crate::lazy::helpers::*; + +#[doc(no_inline)] +pub use button::Button; +#[doc(no_inline)] +pub use checkbox::Checkbox; +#[doc(no_inline)] +pub use column::Column; +#[doc(no_inline)] +pub use container::Container; +#[doc(no_inline)] +pub use pane_grid::PaneGrid; +#[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)] +pub use radio::Radio; +#[doc(no_inline)] +pub use row::Row; +#[doc(no_inline)] +pub use rule::Rule; +#[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] +pub use slider::Slider; +#[doc(no_inline)] +pub use space::Space; +#[doc(no_inline)] +pub use text::Text; +#[doc(no_inline)] +pub use text_input::TextInput; +#[doc(no_inline)] +pub use toggler::Toggler; +#[doc(no_inline)] +pub use tooltip::Tooltip; +#[doc(no_inline)] +pub use vertical_slider::VerticalSlider; + +#[cfg(feature = "svg")] +pub mod svg; + +#[cfg(feature = "svg")] +#[doc(no_inline)] +pub use svg::Svg; + +#[cfg(feature = "image")] +pub mod image; + +#[cfg(feature = "image")] +#[doc(no_inline)] +pub use image::Image; + +#[cfg(feature = "canvas")] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; + +type Renderer = renderer::Renderer; diff --git a/widget/src/overlay.rs b/widget/src/overlay.rs new file mode 100644 index 0000000000..b9a0e3e05d --- /dev/null +++ b/widget/src/overlay.rs @@ -0,0 +1 @@ +pub mod menu; diff --git a/native/src/overlay/menu.rs b/widget/src/overlay/menu.rs similarity index 95% rename from native/src/overlay/menu.rs rename to widget/src/overlay/menu.rs index bd58a12263..c322c8bae4 100644 --- a/native/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -1,25 +1,25 @@ //! Build and show dropdown menus. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container::{self, Container}; -use crate::widget::scrollable::{self, Scrollable}; -use crate::widget::Tree; -use crate::{ - Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, +use crate::container::{self, Container}; +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text::{self, Text}; +use crate::core::touch; +use crate::core::widget::Tree; +use crate::core::{ + Clipboard, Color, Length, Padding, Pixels, Point, Rectangle, Size, Vector, }; +use crate::core::{Element, Shell, Widget}; +use crate::scrollable::{self, Scrollable}; pub use iced_style::menu::{Appearance, StyleSheet}; /// A list of selectable options. #[allow(missing_debug_implementations)] -pub struct Menu<'a, T, Renderer> +pub struct Menu<'a, T, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -137,7 +137,7 @@ impl Default for State { struct Overlay<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { state: &'a mut Tree, @@ -193,7 +193,7 @@ where } } -impl<'a, Message, Renderer> crate::Overlay +impl<'a, Message, Renderer> crate::core::Overlay for Overlay<'a, Message, Renderer> where Renderer: text::Renderer, diff --git a/native/src/widget/pane_grid.rs b/widget/src/pane_grid.rs similarity index 97% rename from native/src/widget/pane_grid.rs rename to widget/src/pane_grid.rs index bcb17ebddc..a97cef6096 100644 --- a/native/src/widget/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -30,18 +30,18 @@ pub use split::Split; pub use state::State; pub use title_bar::TitleBar; -pub use iced_style::pane_grid::{Line, StyleSheet}; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay::{self, Group}; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::container; -use crate::widget::tree::{self, Tree}; -use crate::{ +pub use crate::style::pane_grid::{Line, StyleSheet}; + +use crate::container; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay::{self, Group}; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; @@ -68,10 +68,10 @@ use crate::{ /// ## Example /// /// ``` -/// # use iced_native::widget::{pane_grid, text}; +/// # use iced_widget::{pane_grid, text}; /// # /// # type PaneGrid<'a, Message> = -/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>; +/// # iced_widget::PaneGrid<'a, Message, iced_widget::renderer::Renderer>; /// # /// enum PaneState { /// SomePane, @@ -96,9 +96,9 @@ use crate::{ /// .on_resize(10, Message::PaneResized); /// ``` #[allow(missing_debug_implementations)] -pub struct PaneGrid<'a, Message, Renderer> +pub struct PaneGrid<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { contents: Contents<'a, Content<'a, Message, Renderer>>, @@ -113,7 +113,7 @@ where impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { /// Creates a [`PaneGrid`] with the given [`State`] and view function. @@ -232,7 +232,7 @@ where impl<'a, Message, Renderer> Widget for PaneGrid<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { fn tag(&self) -> tree::Tag { @@ -468,7 +468,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { fn from( @@ -755,7 +755,7 @@ pub fn draw( &Rectangle, ), ) where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { let picked_pane = action.picked_pane(); diff --git a/native/src/widget/pane_grid/axis.rs b/widget/src/pane_grid/axis.rs similarity index 99% rename from native/src/widget/pane_grid/axis.rs rename to widget/src/pane_grid/axis.rs index 02bde06493..a304923004 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/widget/src/pane_grid/axis.rs @@ -1,4 +1,4 @@ -use crate::Rectangle; +use crate::core::Rectangle; /// A fixed reference line for the measurement of coordinates. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] diff --git a/native/src/widget/pane_grid/configuration.rs b/widget/src/pane_grid/configuration.rs similarity index 94% rename from native/src/widget/pane_grid/configuration.rs rename to widget/src/pane_grid/configuration.rs index 7d68fb4625..ddbc3bc2f2 100644 --- a/native/src/widget/pane_grid/configuration.rs +++ b/widget/src/pane_grid/configuration.rs @@ -1,4 +1,4 @@ -use crate::widget::pane_grid::Axis; +use crate::pane_grid::Axis; /// The arrangement of a [`PaneGrid`]. /// diff --git a/native/src/widget/pane_grid/content.rs b/widget/src/pane_grid/content.rs similarity index 94% rename from native/src/widget/pane_grid/content.rs rename to widget/src/pane_grid/content.rs index c9b0df078f..035ef05b4e 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -1,20 +1,20 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::pane_grid::{Draggable, TitleBar}; -use crate::widget::{self, Tree}; -use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::container; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{self, Tree}; +use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::pane_grid::{Draggable, TitleBar}; /// The content of a [`Pane`]. /// /// [`Pane`]: crate::widget::pane_grid::Pane #[allow(missing_debug_implementations)] -pub struct Content<'a, Message, Renderer> +pub struct Content<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { title_bar: Option>, @@ -24,7 +24,7 @@ where impl<'a, Message, Renderer> Content<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { /// Creates a new [`Content`] with the provided body. @@ -57,7 +57,7 @@ where impl<'a, Message, Renderer> Content<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { pub(super) fn state(&self) -> Tree { @@ -342,7 +342,7 @@ where impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { fn can_be_dragged_at( @@ -364,7 +364,7 @@ where impl<'a, T, Message, Renderer> From for Content<'a, Message, Renderer> where T: Into>, - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { fn from(element: T) -> Self { diff --git a/native/src/widget/pane_grid/direction.rs b/widget/src/pane_grid/direction.rs similarity index 100% rename from native/src/widget/pane_grid/direction.rs rename to widget/src/pane_grid/direction.rs diff --git a/native/src/widget/pane_grid/draggable.rs b/widget/src/pane_grid/draggable.rs similarity index 89% rename from native/src/widget/pane_grid/draggable.rs rename to widget/src/pane_grid/draggable.rs index 6044871d54..a9274dadda 100644 --- a/native/src/widget/pane_grid/draggable.rs +++ b/widget/src/pane_grid/draggable.rs @@ -1,4 +1,4 @@ -use crate::{Layout, Point}; +use crate::core::{Layout, Point}; /// A pane that can be dragged. pub trait Draggable { diff --git a/native/src/widget/pane_grid/node.rs b/widget/src/pane_grid/node.rs similarity index 98% rename from native/src/widget/pane_grid/node.rs rename to widget/src/pane_grid/node.rs index cc304b96a7..3976acd8b1 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/widget/src/pane_grid/node.rs @@ -1,5 +1,5 @@ -use crate::widget::pane_grid::{Axis, Pane, Split}; -use crate::{Rectangle, Size}; +use crate::core::{Rectangle, Size}; +use crate::pane_grid::{Axis, Pane, Split}; use std::collections::BTreeMap; diff --git a/native/src/widget/pane_grid/pane.rs b/widget/src/pane_grid/pane.rs similarity index 100% rename from native/src/widget/pane_grid/pane.rs rename to widget/src/pane_grid/pane.rs diff --git a/native/src/widget/pane_grid/split.rs b/widget/src/pane_grid/split.rs similarity index 100% rename from native/src/widget/pane_grid/split.rs rename to widget/src/pane_grid/split.rs diff --git a/native/src/widget/pane_grid/state.rs b/widget/src/pane_grid/state.rs similarity index 98% rename from native/src/widget/pane_grid/state.rs rename to widget/src/pane_grid/state.rs index c4ae0a0e22..a6e2ec7fa6 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -1,10 +1,8 @@ //! The state of a [`PaneGrid`]. //! //! [`PaneGrid`]: crate::widget::PaneGrid -use crate::widget::pane_grid::{ - Axis, Configuration, Direction, Node, Pane, Split, -}; -use crate::{Point, Size}; +use crate::core::{Point, Size}; +use crate::pane_grid::{Axis, Configuration, Direction, Node, Pane, Split}; use std::collections::HashMap; diff --git a/native/src/widget/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs similarity index 96% rename from native/src/widget/pane_grid/title_bar.rs rename to widget/src/pane_grid/title_bar.rs index 107078ef07..2129937be8 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -1,11 +1,11 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::{self, Tree}; -use crate::{ +use crate::container; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{self, Tree}; +use crate::core::{ Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, }; @@ -13,9 +13,9 @@ use crate::{ /// /// [`Pane`]: crate::widget::pane_grid::Pane #[allow(missing_debug_implementations)] -pub struct TitleBar<'a, Message, Renderer> +pub struct TitleBar<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { content: Element<'a, Message, Renderer>, @@ -27,7 +27,7 @@ where impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { /// Creates a new [`TitleBar`] with the given content. @@ -84,7 +84,7 @@ where impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: container::StyleSheet, { pub(super) fn state(&self) -> Tree { diff --git a/native/src/widget/pick_list.rs b/widget/src/pick_list.rs similarity index 97% rename from native/src/widget/pick_list.rs rename to widget/src/pick_list.rs index 8ff82f3b07..cd23cdd235 100644 --- a/native/src/widget/pick_list.rs +++ b/widget/src/pick_list.rs @@ -1,28 +1,29 @@ //! Display a dropdown list of selectable values. -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::overlay::menu::{self, Menu}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container; -use crate::widget::scrollable; -use crate::widget::tree::{self, Tree}; -use crate::{ +use crate::container; +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text::{self, Text}; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Widget, }; +use crate::overlay::menu::{self, Menu}; +use crate::scrollable; + use std::borrow::Cow; -pub use iced_style::pick_list::{Appearance, StyleSheet}; +pub use crate::style::pick_list::{Appearance, StyleSheet}; /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] -pub struct PickList<'a, T, Message, Renderer> +pub struct PickList<'a, T, Message, Renderer = crate::Renderer> where [T]: ToOwned>, Renderer: text::Renderer, diff --git a/native/src/widget/progress_bar.rs b/widget/src/progress_bar.rs similarity index 89% rename from native/src/widget/progress_bar.rs rename to widget/src/progress_bar.rs index dd46fa76ce..896d40feed 100644 --- a/native/src/widget/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -1,8 +1,10 @@ //! Provide progress feedback to your users. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget}; +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{ + Color, Element, Layout, Length, Point, Rectangle, Size, Widget, +}; use std::ops::RangeInclusive; @@ -12,7 +14,9 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet}; /// /// # Example /// ``` -/// # type ProgressBar = iced_native::widget::ProgressBar; +/// # type ProgressBar = +/// # iced_widget::ProgressBar>; +/// # /// let value = 50.0; /// /// ProgressBar::new(0.0..=100.0, value); @@ -20,9 +24,9 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet}; /// /// ![Progress bar drawn with `iced_wgpu`](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png) #[allow(missing_debug_implementations)] -pub struct ProgressBar +pub struct ProgressBar where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { range: RangeInclusive, @@ -34,7 +38,7 @@ where impl ProgressBar where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// The default height of a [`ProgressBar`]. @@ -79,7 +83,7 @@ where impl Widget for ProgressBar where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn width(&self) -> Length { @@ -157,7 +161,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( diff --git a/src/widget/qr_code.rs b/widget/src/qr_code.rs similarity index 98% rename from src/widget/qr_code.rs rename to widget/src/qr_code.rs index 66442d5d8c..7709125f90 100644 --- a/src/widget/qr_code.rs +++ b/widget/src/qr_code.rs @@ -1,13 +1,12 @@ //! Encode and display information in a QR code. -use crate::widget::canvas; -use crate::Renderer; - -use iced_native::layout; -use iced_native::renderer; -use iced_native::widget::Tree; -use iced_native::{ +use crate::canvas; +use crate::core::layout; +use crate::core::renderer::{self, Renderer as _}; +use crate::core::widget::Tree; +use crate::core::{ Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; +use crate::Renderer; use thiserror::Error; const DEFAULT_CELL_SIZE: u16 = 4; @@ -78,8 +77,6 @@ impl<'a, Message, Theme> Widget> for QRCode<'a> { _cursor_position: Point, _viewport: &Rectangle, ) { - use iced_native::Renderer as _; - let bounds = layout.bounds(); let side_length = self.state.width + 2 * QUIET_ZONE; diff --git a/native/src/widget/radio.rs b/widget/src/radio.rs similarity index 92% rename from native/src/widget/radio.rs rename to widget/src/radio.rs index 5f60eaefad..e5c67f3d31 100644 --- a/native/src/widget/radio.rs +++ b/widget/src/radio.rs @@ -1,16 +1,17 @@ //! Create choices using radio buttons. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text; +use crate::core::touch; +use crate::core::widget::Tree; +use crate::core::{ Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Widget, }; +use crate::{Row, Text}; pub use iced_style::radio::{Appearance, StyleSheet}; @@ -19,7 +20,7 @@ pub use iced_style::radio::{Appearance, StyleSheet}; /// # Example /// ``` /// # type Radio = -/// # iced_native::widget::Radio; +/// # iced_widget::Radio>; /// # /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// pub enum Choice { @@ -41,7 +42,7 @@ pub use iced_style::radio::{Appearance, StyleSheet}; /// /// ![Radio buttons drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true) #[allow(missing_debug_implementations)] -pub struct Radio +pub struct Radio where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -144,7 +145,7 @@ impl Widget for Radio where Message: Clone, Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn width(&self) -> Length { self.width @@ -269,14 +270,14 @@ where { let label_layout = children.next().unwrap(); - widget::text::draw( + crate::text::draw( renderer, style, label_layout, &self.label, self.text_size, self.font, - widget::text::Appearance { + crate::text::Appearance { color: custom_style.text_color, }, alignment::Horizontal::Left, @@ -291,7 +292,7 @@ impl<'a, Message, Renderer> From> where Message: 'a + Clone, Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn from(radio: Radio) -> Element<'a, Message, Renderer> { Element::new(radio) diff --git a/native/src/widget/row.rs b/widget/src/row.rs similarity index 94% rename from native/src/widget/row.rs rename to widget/src/row.rs index 286c1c2d8c..3ce363f8d6 100644 --- a/native/src/widget/row.rs +++ b/widget/src/row.rs @@ -1,18 +1,18 @@ //! Distribute content horizontally. -use crate::event::{self, Event}; -use crate::layout::{self, Layout}; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget::{Operation, Tree}; +use crate::core::{ Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Widget, }; /// A container that distributes its contents horizontally. #[allow(missing_debug_implementations)] -pub struct Row<'a, Message, Renderer> { +pub struct Row<'a, Message, Renderer = crate::Renderer> { spacing: f32, padding: Padding, width: Length, @@ -94,7 +94,7 @@ impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget for Row<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, { fn children(&self) -> Vec { self.children.iter().map(Tree::new).collect() @@ -245,7 +245,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: crate::Renderer + 'a, + Renderer: crate::core::Renderer + 'a, { fn from(row: Row<'a, Message, Renderer>) -> Self { Self::new(row) diff --git a/native/src/widget/rule.rs b/widget/src/rule.rs similarity index 90% rename from native/src/widget/rule.rs rename to widget/src/rule.rs index 1ab6a0d300..3749d7cea2 100644 --- a/native/src/widget/rule.rs +++ b/widget/src/rule.rs @@ -1,18 +1,18 @@ //! Display a horizontal or vertical rule for dividing content. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{ Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget, }; -pub use iced_style::rule::{Appearance, FillMode, StyleSheet}; +pub use crate::style::rule::{Appearance, FillMode, StyleSheet}; /// Display a horizontal or vertical rule for dividing content. #[allow(missing_debug_implementations)] -pub struct Rule +pub struct Rule where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { width: Length, @@ -23,7 +23,7 @@ where impl Rule where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates a horizontal [`Rule`] with the given height. @@ -58,7 +58,7 @@ where impl Widget for Rule where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn width(&self) -> Length { @@ -138,7 +138,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from(rule: Rule) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/scrollable.rs b/widget/src/scrollable.rs similarity index 91% rename from native/src/widget/scrollable.rs rename to widget/src/scrollable.rs index c1df8c39dc..49c780deac 100644 --- a/native/src/widget/scrollable.rs +++ b/widget/src/scrollable.rs @@ -1,35 +1,29 @@ //! Navigate an endless amount of content with a scrollbar. -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::{ - Background, Clipboard, Color, Command, Element, Layout, Length, Pixels, - Point, Rectangle, Shell, Size, Vector, Widget, +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget; +use crate::core::widget::operation::{self, Operation}; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, + Rectangle, Shell, Size, Vector, Widget, }; +use crate::native::Command; -pub use iced_style::scrollable::StyleSheet; +pub use crate::style::scrollable::{Scrollbar, Scroller, StyleSheet}; pub use operation::scrollable::RelativeOffset; -pub mod style { - //! The styles of a [`Scrollable`]. - //! - //! [`Scrollable`]: crate::widget::Scrollable - pub use iced_style::scrollable::{Scrollbar, Scroller}; -} - /// A widget that can vertically display an infinite amount of content with a /// scrollbar. #[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> +pub struct Scrollable<'a, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { id: Option, @@ -43,7 +37,7 @@ where impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// Creates a new [`Scrollable`]. @@ -153,7 +147,7 @@ impl Properties { impl<'a, Message, Renderer> Widget for Scrollable<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -345,7 +339,7 @@ impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -763,7 +757,7 @@ pub fn draw( style: &::Style, draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle), ) where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { let bounds = layout.bounds(); @@ -809,8 +803,8 @@ pub fn draw( let draw_scrollbar = |renderer: &mut Renderer, - style: style::Scrollbar, - scrollbar: &Scrollbar| { + style: Scrollbar, + scrollbar: &internals::Scrollbar| { //track if style.background.is_some() || (style.border_color != Color::TRANSPARENT @@ -1068,8 +1062,8 @@ impl State { #[derive(Debug)] /// State of both [`Scrollbar`]s. struct Scrollbars { - y: Option, - x: Option, + y: Option, + x: Option, } impl Scrollbars { @@ -1139,10 +1133,10 @@ impl Scrollbars { height: scroller_height, }; - Some(Scrollbar { + Some(internals::Scrollbar { total_bounds: total_scrollbar_bounds, bounds: scrollbar_bounds, - scroller: Scroller { + scroller: internals::Scroller { bounds: scroller_bounds, }, }) @@ -1199,10 +1193,10 @@ impl Scrollbars { height: scroller_width, }; - Some(Scrollbar { + Some(internals::Scrollbar { total_bounds: total_scrollbar_bounds, bounds: scrollbar_bounds, - scroller: Scroller { + scroller: internals::Scroller { bounds: scroller_bounds, }, }) @@ -1264,64 +1258,68 @@ impl Scrollbars { } } -/// The scrollbar of a [`Scrollable`]. -#[derive(Debug, Copy, Clone)] -struct Scrollbar { - /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller, - /// and the scrollbar margin. - total_bounds: Rectangle, +pub(super) mod internals { + use crate::core::{Point, Rectangle}; - /// The bounds of just the [`Scrollbar`]. - bounds: Rectangle, + /// The scrollbar of a [`Scrollable`]. + #[derive(Debug, Copy, Clone)] + pub struct Scrollbar { + /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller, + /// and the scrollbar margin. + pub total_bounds: Rectangle, - /// The state of this scrollbar's [`Scroller`]. - scroller: Scroller, -} + /// The bounds of just the [`Scrollbar`]. + pub bounds: Rectangle, -impl Scrollbar { - /// Returns whether the mouse is over the scrollbar or not. - fn is_mouse_over(&self, cursor_position: Point) -> bool { - self.total_bounds.contains(cursor_position) + /// The state of this scrollbar's [`Scroller`]. + pub scroller: Scroller, } - /// Returns the y-axis scrolled percentage from the cursor position. - fn scroll_percentage_y( - &self, - grabbed_at: f32, - cursor_position: Point, - ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - // cursor position is unavailable! Set to either end or beginning of scrollbar depending - // on where the thumb currently is in the track - (self.scroller.bounds.y / self.total_bounds.height).round() - } else { - (cursor_position.y - - self.bounds.y - - self.scroller.bounds.height * grabbed_at) - / (self.bounds.height - self.scroller.bounds.height) + impl Scrollbar { + /// Returns whether the mouse is over the scrollbar or not. + pub fn is_mouse_over(&self, cursor_position: Point) -> bool { + self.total_bounds.contains(cursor_position) } - } - /// Returns the x-axis scrolled percentage from the cursor position. - fn scroll_percentage_x( - &self, - grabbed_at: f32, - cursor_position: Point, - ) -> f32 { - if cursor_position.x < 0.0 && cursor_position.y < 0.0 { - (self.scroller.bounds.x / self.total_bounds.width).round() - } else { - (cursor_position.x - - self.bounds.x - - self.scroller.bounds.width * grabbed_at) - / (self.bounds.width - self.scroller.bounds.width) + /// Returns the y-axis scrolled percentage from the cursor position. + pub fn scroll_percentage_y( + &self, + grabbed_at: f32, + cursor_position: Point, + ) -> f32 { + if cursor_position.x < 0.0 && cursor_position.y < 0.0 { + // cursor position is unavailable! Set to either end or beginning of scrollbar depending + // on where the thumb currently is in the track + (self.scroller.bounds.y / self.total_bounds.height).round() + } else { + (cursor_position.y + - self.bounds.y + - self.scroller.bounds.height * grabbed_at) + / (self.bounds.height - self.scroller.bounds.height) + } + } + + /// Returns the x-axis scrolled percentage from the cursor position. + pub fn scroll_percentage_x( + &self, + grabbed_at: f32, + cursor_position: Point, + ) -> f32 { + if cursor_position.x < 0.0 && cursor_position.y < 0.0 { + (self.scroller.bounds.x / self.total_bounds.width).round() + } else { + (cursor_position.x + - self.bounds.x + - self.scroller.bounds.width * grabbed_at) + / (self.bounds.width - self.scroller.bounds.width) + } } } -} -/// The handle of a [`Scrollbar`]. -#[derive(Debug, Clone, Copy)] -struct Scroller { - /// The bounds of the [`Scroller`]. - bounds: Rectangle, + /// The handle of a [`Scrollbar`]. + #[derive(Debug, Clone, Copy)] + pub struct Scroller { + /// The bounds of the [`Scroller`]. + pub bounds: Rectangle, + } } diff --git a/native/src/widget/slider.rs b/widget/src/slider.rs similarity index 95% rename from native/src/widget/slider.rs rename to widget/src/slider.rs index d3715b1c0d..70a84fc61b 100644 --- a/native/src/widget/slider.rs +++ b/widget/src/slider.rs @@ -1,13 +1,13 @@ //! Display an interactive selector of a single value from a range of values. //! //! A [`Slider`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::{ +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget, }; @@ -26,10 +26,8 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; /// /// # Example /// ``` -/// # use iced_native::widget::slider; -/// # use iced_native::renderer::Null; -/// # -/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>; +/// # type Slider<'a, T, Message> = +/// # iced_widget::Slider<'a, Message, T, iced_widget::renderer::Renderer>; /// # /// #[derive(Clone)] /// pub enum Message { @@ -43,9 +41,9 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; /// /// ![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, Renderer> +pub struct Slider<'a, T, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { range: RangeInclusive, @@ -62,7 +60,7 @@ impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { /// The default height of a [`Slider`]. @@ -148,7 +146,7 @@ impl<'a, T, Message, Renderer> Widget where T: Copy + Into + num_traits::FromPrimitive, Message: Clone, - Renderer: crate::Renderer, + Renderer: crate::core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -245,7 +243,7 @@ impl<'a, T, Message, Renderer> From> where T: 'a + Copy + Into + num_traits::FromPrimitive, Message: 'a + Clone, - Renderer: 'a + crate::Renderer, + Renderer: 'a + crate::core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -354,7 +352,7 @@ pub fn draw( style: &::Style, ) where T: Into + Copy, - R: crate::Renderer, + R: crate::core::Renderer, R::Theme: StyleSheet, { let bounds = layout.bounds(); diff --git a/native/src/widget/space.rs b/widget/src/space.rs similarity index 88% rename from native/src/widget/space.rs rename to widget/src/space.rs index a6fc977e01..e1e09d5a92 100644 --- a/native/src/widget/space.rs +++ b/widget/src/space.rs @@ -1,8 +1,9 @@ //! Distribute content vertically. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget}; +use crate::core; +use crate::core::layout; +use crate::core::renderer; +use crate::core::widget::Tree; +use crate::core::{Element, Layout, Length, Point, Rectangle, Size, Widget}; /// An amount of empty space. /// @@ -41,7 +42,7 @@ impl Space { impl Widget for Space where - Renderer: crate::Renderer, + Renderer: core::Renderer, { fn width(&self) -> Length { self.width @@ -76,7 +77,7 @@ where impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> where - Renderer: crate::Renderer, + Renderer: core::Renderer, Message: 'a, { fn from(space: Space) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/svg.rs b/widget/src/svg.rs similarity index 95% rename from native/src/widget/svg.rs rename to widget/src/svg.rs index f5ed0a6c88..89017fcff3 100644 --- a/native/src/widget/svg.rs +++ b/widget/src/svg.rs @@ -1,15 +1,15 @@ //! Display vector graphics in your application. -use crate::layout; -use crate::renderer; -use crate::svg; -use crate::widget::Tree; -use crate::{ +use crate::core::layout; +use crate::core::renderer; +use crate::core::svg; +use crate::core::widget::Tree; +use crate::core::{ ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use std::path::PathBuf; -pub use iced_style::svg::{Appearance, StyleSheet}; +pub use crate::style::svg::{Appearance, StyleSheet}; pub use svg::Handle; /// A vector graphics image. @@ -19,7 +19,7 @@ pub use svg::Handle; /// [`Svg`] images can have a considerable rendering cost when resized, /// specially when they are complex. #[allow(missing_debug_implementations)] -pub struct Svg +pub struct Svg where Renderer: svg::Renderer, Renderer::Theme: StyleSheet, diff --git a/widget/src/text.rs b/widget/src/text.rs new file mode 100644 index 0000000000..04c31edcd3 --- /dev/null +++ b/widget/src/text.rs @@ -0,0 +1,4 @@ +pub use crate::core::widget::text::*; + +pub type Text<'a, Renderer = crate::Renderer> = + crate::core::widget::Text<'a, Renderer>; diff --git a/native/src/widget/text_input.rs b/widget/src/text_input.rs similarity index 97% rename from native/src/widget/text_input.rs rename to widget/src/text_input.rs index 65a9bd3b21..67d80e2b69 100644 --- a/native/src/widget/text_input.rs +++ b/widget/src/text_input.rs @@ -11,23 +11,24 @@ pub use value::Value; use editor::Editor; -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse::{self, click}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::time::{Duration, Instant}; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::window; -use crate::{ - Clipboard, Color, Command, Element, Layout, Length, Padding, Pixels, Point, +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::keyboard; +use crate::core::layout; +use crate::core::mouse::{self, click}; +use crate::core::renderer; +use crate::core::text::{self, Text}; +use crate::core::time::{Duration, Instant}; +use crate::core::touch; +use crate::core::widget; +use crate::core::widget::operation::{self, Operation}; +use crate::core::widget::tree::{self, Tree}; +use crate::core::window; +use crate::core::{ + Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; +use crate::native::Command; pub use iced_style::text_input::{Appearance, StyleSheet}; @@ -35,7 +36,9 @@ pub use iced_style::text_input::{Appearance, StyleSheet}; /// /// # Example /// ``` -/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>; +/// # pub type TextInput<'a, Message> = +/// # iced_widget::TextInput<'a, Message, iced_widget::renderer::Renderer>; +/// # /// #[derive(Debug, Clone)] /// enum Message { /// TextInputChanged(String), @@ -52,7 +55,7 @@ pub use iced_style::text_input::{Appearance, StyleSheet}; /// ``` /// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true) #[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message, Renderer> +pub struct TextInput<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -1109,7 +1112,7 @@ impl operation::TextInput for State { } mod platform { - use crate::keyboard; + use crate::core::keyboard; pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { if cfg!(target_os = "macos") { diff --git a/native/src/widget/text_input/cursor.rs b/widget/src/text_input/cursor.rs similarity index 99% rename from native/src/widget/text_input/cursor.rs rename to widget/src/text_input/cursor.rs index 4f3b159b9d..9680dfd746 100644 --- a/native/src/widget/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -1,5 +1,5 @@ //! Track the cursor of a text input. -use crate::widget::text_input::Value; +use crate::text_input::Value; /// The cursor of a text input. #[derive(Debug, Copy, Clone)] diff --git a/native/src/widget/text_input/editor.rs b/widget/src/text_input/editor.rs similarity index 97% rename from native/src/widget/text_input/editor.rs rename to widget/src/text_input/editor.rs index d53fa8d964..f1fd641f31 100644 --- a/native/src/widget/text_input/editor.rs +++ b/widget/src/text_input/editor.rs @@ -1,4 +1,4 @@ -use crate::widget::text_input::{Cursor, Value}; +use crate::text_input::{Cursor, Value}; pub struct Editor<'a> { value: &'a mut Value, diff --git a/native/src/widget/text_input/value.rs b/widget/src/text_input/value.rs similarity index 100% rename from native/src/widget/text_input/value.rs rename to widget/src/text_input/value.rs diff --git a/native/src/widget/toggler.rs b/widget/src/toggler.rs similarity index 93% rename from native/src/widget/toggler.rs rename to widget/src/toggler.rs index d9c80ebe31..f23f9b27a0 100644 --- a/native/src/widget/toggler.rs +++ b/widget/src/toggler.rs @@ -1,24 +1,26 @@ //! Show toggle controls using togglers. -use crate::alignment; -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ +use crate::core::alignment; +use crate::core::event; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::text; +use crate::core::widget::Tree; +use crate::core::{ Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Widget, }; +use crate::{Row, Text}; -pub use iced_style::toggler::{Appearance, StyleSheet}; +pub use crate::style::toggler::{Appearance, StyleSheet}; /// A toggler widget. /// /// # Example /// /// ``` -/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>; +/// # type Toggler<'a, Message> = +/// # iced_widget::Toggler<'a, Message, iced_widget::renderer::Renderer>; /// # /// pub enum Message { /// TogglerToggled(bool), @@ -29,7 +31,7 @@ pub use iced_style::toggler::{Appearance, StyleSheet}; /// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); /// ``` #[allow(missing_debug_implementations)] -pub struct Toggler<'a, Message, Renderer> +pub struct Toggler<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -136,7 +138,7 @@ impl<'a, Message, Renderer> Widget for Toggler<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn width(&self) -> Length { self.width @@ -237,7 +239,7 @@ where if let Some(label) = &self.label { let label_layout = children.next().unwrap(); - crate::widget::text::draw( + crate::text::draw( renderer, style, label_layout, @@ -314,7 +316,7 @@ impl<'a, Message, Renderer> From> where Message: 'a, Renderer: 'a + text::Renderer, - Renderer::Theme: StyleSheet + widget::text::StyleSheet, + Renderer::Theme: StyleSheet + crate::text::StyleSheet, { fn from( toggler: Toggler<'a, Message, Renderer>, diff --git a/native/src/widget/tooltip.rs b/widget/src/tooltip.rs similarity index 93% rename from native/src/widget/tooltip.rs rename to widget/src/tooltip.rs index 2a24c05566..084650d104 100644 --- a/native/src/widget/tooltip.rs +++ b/widget/src/tooltip.rs @@ -1,26 +1,27 @@ //! Display a widget over another. -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget; -use crate::widget::container; -use crate::widget::overlay; -use crate::widget::{Text, Tree}; -use crate::{ - Clipboard, Element, Event, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, +use crate::container; +use crate::core; +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text; +use crate::core::widget::Tree; +use crate::core::{ + Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size, + Vector, Widget, }; +use crate::Text; use std::borrow::Cow; /// An element to display a widget over another. #[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: text::Renderer> +pub struct Tooltip<'a, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { content: Element<'a, Message, Renderer>, tooltip: Text<'a, Renderer>, @@ -34,7 +35,7 @@ where impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { /// The default padding of a [`Tooltip`] drawn by this renderer. const DEFAULT_PADDING: f32 = 5.0; @@ -104,7 +105,7 @@ impl<'a, Message, Renderer> Widget for Tooltip<'a, Message, Renderer> where Renderer: text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { fn children(&self) -> Vec { vec![Tree::new(&self.content)] @@ -239,7 +240,7 @@ impl<'a, Message, Renderer> From> where Message: 'a, Renderer: 'a + text::Renderer, - Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, + Renderer::Theme: container::StyleSheet + crate::text::StyleSheet, { fn from( tooltip: Tooltip<'a, Message, Renderer>, @@ -285,7 +286,7 @@ pub fn draw( &Rectangle, ), ) where - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: container::StyleSheet, { use container::StyleSheet; diff --git a/native/src/widget/vertical_slider.rs b/widget/src/vertical_slider.rs similarity index 93% rename from native/src/widget/vertical_slider.rs rename to widget/src/vertical_slider.rs index f1687e38dd..64a9583c1b 100644 --- a/native/src/widget/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -3,13 +3,18 @@ //! A [`VerticalSlider`] has some local [`State`]. use std::ops::RangeInclusive; -pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; - -use crate::event::{self, Event}; -use crate::widget::tree::{self, Tree}; -use crate::{ - layout, mouse, renderer, touch, Background, Clipboard, Color, Element, - Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget, +pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet}; + +use crate::core; +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::touch; +use crate::core::widget::tree::{self, Tree}; +use crate::core::{ + Background, Clipboard, Color, Element, Length, Pixels, Point, Rectangle, + Shell, Size, Widget, }; /// An vertical bar and a handle that selects a single value from a range of @@ -22,10 +27,8 @@ use crate::{ /// /// # Example /// ``` -/// # use iced_native::widget::vertical_slider; -/// # use iced_native::renderer::Null; -/// # -/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>; +/// # type VerticalSlider<'a, T, Message> = +/// # iced_widget::VerticalSlider<'a, T, Message, iced_widget::renderer::Renderer>; /// # /// #[derive(Clone)] /// pub enum Message { @@ -37,9 +40,9 @@ use crate::{ /// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged); /// ``` #[allow(missing_debug_implementations)] -pub struct VerticalSlider<'a, T, Message, Renderer> +pub struct VerticalSlider<'a, T, Message, Renderer = crate::Renderer> where - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: StyleSheet, { range: RangeInclusive, @@ -56,7 +59,7 @@ impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer> where T: Copy + From + std::cmp::PartialOrd, Message: Clone, - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: StyleSheet, { /// The default width of a [`VerticalSlider`]. @@ -142,7 +145,7 @@ impl<'a, T, Message, Renderer> Widget where T: Copy + Into + num_traits::FromPrimitive, Message: Clone, - Renderer: crate::Renderer, + Renderer: core::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -239,7 +242,7 @@ impl<'a, T, Message, Renderer> From> where T: 'a + Copy + Into + num_traits::FromPrimitive, Message: 'a + Clone, - Renderer: 'a + crate::Renderer, + Renderer: 'a + core::Renderer, Renderer::Theme: StyleSheet, { fn from( @@ -349,7 +352,7 @@ pub fn draw( style: &::Style, ) where T: Into + Copy, - R: crate::Renderer, + R: core::Renderer, R::Theme: StyleSheet, { let bounds = layout.bounds(); diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 60e464c6ca..21c14f68ba 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -35,9 +35,9 @@ path = "../native" version = "0.7" path = "../graphics" -[dependencies.iced_futures] -version = "0.6" -path = "../futures" +[dependencies.iced_style] +version = "0.7" +path = "../style" [dependencies.tracing] version = "0.1.37" diff --git a/winit/src/application.rs b/winit/src/application.rs index b52f01974a..c8162c9b53 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -5,25 +5,25 @@ mod state; pub use state::State; -use crate::clipboard::{self, Clipboard}; use crate::conversion; -use crate::mouse; -use crate::renderer; -use crate::widget::operation; -use crate::{ - Command, Debug, Error, Event, Executor, Proxy, Runtime, Settings, Size, - Subscription, -}; - -use iced_futures::futures; -use iced_futures::futures::channel::mpsc; -use iced_graphics::window; -use iced_graphics::window::compositor; -use iced_native::program::Program; -use iced_native::time::Instant; -use iced_native::user_interface::{self, UserInterface}; - -pub use iced_native::application::{Appearance, StyleSheet}; +use crate::core; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::time::Instant; +use crate::core::widget::operation; +use crate::core::window; +use crate::core::{Event, Size}; +use crate::futures::futures; +use crate::futures::Executor; +use crate::graphics::compositor::{self, Compositor}; +use crate::native::clipboard; +use crate::native::program::Program; +use crate::native::user_interface::{self, UserInterface}; +use crate::native::{Command, Debug, Runtime, Subscription}; +use crate::style::application::{Appearance, StyleSheet}; +use crate::{Clipboard, Error, Proxy, Settings}; + +use futures::channel::mpsc; use std::mem::ManuallyDrop; @@ -45,7 +45,7 @@ use tracing::{info_span, instrument::Instrument}; /// can be toggled by pressing `F12`. pub trait Application: Program where - ::Theme: StyleSheet, + ::Theme: StyleSheet, { /// The data needed to initialize your [`Application`]. type Flags; @@ -67,12 +67,12 @@ where fn title(&self) -> String; /// Returns the current `Theme` of the [`Application`]. - fn theme(&self) -> ::Theme; + fn theme(&self) -> ::Theme; /// Returns the `Style` variation of the `Theme`. fn style( &self, - ) -> <::Theme as StyleSheet>::Style { + ) -> <::Theme as StyleSheet>::Style { Default::default() } @@ -112,8 +112,8 @@ pub fn run( where A: Application + 'static, E: Executor + 'static, - C: window::Compositor + 'static, - ::Theme: StyleSheet, + C: Compositor + 'static, + ::Theme: StyleSheet, { use futures::task; use futures::Future; @@ -278,10 +278,10 @@ async fn run_instance( ) where A: Application + 'static, E: Executor + 'static, - C: window::Compositor + 'static, - ::Theme: StyleSheet, + C: Compositor + 'static, + ::Theme: StyleSheet, { - use iced_futures::futures::stream::StreamExt; + use futures::stream::StreamExt; use winit::event; use winit::event_loop::ControlFlow; @@ -411,7 +411,7 @@ async fn run_instance( // Then, we can use the `interface_state` here to decide if a redraw // is needed right away, or simply wait until a specific time. let redraw_event = Event::Window( - crate::window::Event::RedrawRequested(Instant::now()), + window::Event::RedrawRequested(Instant::now()), ); let (interface_state, _) = user_interface.update( @@ -442,17 +442,14 @@ async fn run_instance( } window.request_redraw(); - runtime - .broadcast((redraw_event, crate::event::Status::Ignored)); + runtime.broadcast((redraw_event, core::event::Status::Ignored)); let _ = control_sender.start_send(match interface_state { user_interface::State::Updated { redraw_request: Some(redraw_request), } => match redraw_request { - crate::window::RedrawRequest::NextFrame => { - ControlFlow::Poll - } - crate::window::RedrawRequest::At(at) => { + window::RedrawRequest::NextFrame => ControlFlow::Poll, + window::RedrawRequest::At(at) => { ControlFlow::WaitUntil(at) } }, @@ -464,9 +461,9 @@ async fn run_instance( event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), )) => { - use iced_native::event; + use crate::core::event; - events.push(iced_native::Event::PlatformSpecific( + events.push(Event::PlatformSpecific( event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( url, )), @@ -615,7 +612,7 @@ pub fn build_user_interface<'a, A: Application>( debug: &mut Debug, ) -> UserInterface<'a, A::Message, A::Renderer> where - ::Theme: StyleSheet, + ::Theme: StyleSheet, { #[cfg(feature = "trace")] let view_span = info_span!("Application", "VIEW").entered(); @@ -656,7 +653,7 @@ pub fn update( window: &winit::window::Window, graphics_info: impl FnOnce() -> compositor::Information + Copy, ) where - ::Theme: StyleSheet, + ::Theme: StyleSheet, { for message in messages.drain(..) { #[cfg(feature = "trace")] @@ -708,7 +705,7 @@ pub fn run_command( ) where A: Application, E: Executor, - ::Theme: StyleSheet, + ::Theme: StyleSheet, { use iced_native::command; use iced_native::system; @@ -767,7 +764,7 @@ pub fn run_command( let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) } else { - window::Mode::Hidden + core::window::Mode::Hidden }; proxy @@ -849,7 +846,7 @@ pub fn run_command( *cache = current_cache; } command::Action::LoadFont { bytes, tagger } => { - use crate::text::Renderer; + use crate::core::text::Renderer; // TODO: Error handling (?) renderer.load_font(bytes); diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 8d6a1df11f..b727e03ca9 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -1,6 +1,10 @@ use crate::application::{self, StyleSheet as _}; use crate::conversion; -use crate::{Application, Color, Debug, Point, Size, Viewport}; +use crate::core; +use crate::core::{Color, Point, Size}; +use crate::graphics::Viewport; +use crate::native::Debug; +use crate::Application; use std::marker::PhantomData; use winit::event::{Touch, WindowEvent}; @@ -10,7 +14,7 @@ use winit::window::Window; #[allow(missing_debug_implementations)] pub struct State where - ::Theme: application::StyleSheet, + ::Theme: application::StyleSheet, { title: String, scale_factor: f64, @@ -18,14 +22,14 @@ where viewport_version: usize, cursor_position: winit::dpi::PhysicalPosition, modifiers: winit::event::ModifiersState, - theme: ::Theme, + theme: ::Theme, appearance: application::Appearance, application: PhantomData, } impl State where - ::Theme: application::StyleSheet, + ::Theme: application::StyleSheet, { /// Creates a new [`State`] for the provided [`Application`] and window. pub fn new(application: &A, window: &Window) -> Self { @@ -98,7 +102,7 @@ where } /// Returns the current theme of the [`State`]. - pub fn theme(&self) -> &::Theme { + pub fn theme(&self) -> &::Theme { &self.theme } diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index c1fd8813ee..2250913080 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,7 +1,6 @@ //! Access the clipboard. -pub use iced_native::clipboard::Action; - -use crate::command::{self, Command}; +use crate::native::clipboard::Action; +use crate::native::command::{self, Command}; /// A buffer for short-term storage and transfer within and between /// applications. @@ -56,7 +55,7 @@ impl Clipboard { } } -impl iced_native::Clipboard for Clipboard { +impl crate::core::Clipboard for Clipboard { fn read(&self) -> Option { self.read() } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 1b2ead36a4..0759ad9dd7 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -2,11 +2,12 @@ //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native -use crate::keyboard; -use crate::mouse; -use crate::touch; -use crate::window; -use crate::{Event, Point, Position}; +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::touch; +use crate::core::window; +use crate::core::{Event, Point}; +use crate::Position; /// Converts a winit window event into an iced event. pub fn window_event( diff --git a/winit/src/error.rs b/winit/src/error.rs index eaeafd51d4..7687fb1750 100644 --- a/winit/src/error.rs +++ b/winit/src/error.rs @@ -1,4 +1,5 @@ -use iced_futures::futures; +use crate::futures::futures; +use crate::graphics; /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] @@ -13,10 +14,10 @@ pub enum Error { /// The application graphics context could not be created. #[error("the application graphics context could not be created")] - GraphicsCreationFailed(iced_graphics::Error), + GraphicsCreationFailed(graphics::Error), } -impl From for Error { +impl From for Error { fn from(error: iced_graphics::Error) -> Error { Error::GraphicsCreationFailed(error) } diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 3a33e1741b..0d8c04d3d3 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -30,9 +30,11 @@ #![forbid(rust_2018_idioms, unsafe_code)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] - -#[doc(no_inline)] -pub use iced_native::*; +pub use iced_graphics as graphics; +pub use iced_native as native; +pub use iced_native::core; +pub use iced_native::futures; +pub use iced_style as style; pub use winit; #[cfg(feature = "application")] diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 7b9074d727..1d6c48bb25 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -1,4 +1,4 @@ -use iced_native::futures::{ +use crate::futures::futures::{ channel::mpsc, task::{Context, Poll}, Sink, diff --git a/winit/src/system.rs b/winit/src/system.rs index 8a7b2a116d..3a6a8a8ec3 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -1,8 +1,7 @@ //! Access the native system. -use crate::command::{self, Command}; -pub use iced_native::system::*; - -use iced_graphics::window::compositor; +use crate::graphics::compositor; +use crate::native::command::{self, Command}; +use crate::native::system::{Action, Information}; /// Query for available system information. pub fn fetch_information( diff --git a/winit/src/window.rs b/winit/src/window.rs index 961562bd7b..6ac58e2073 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -1,68 +1,58 @@ //! Interact with the window of your application. -use crate::command::{self, Command}; -use iced_native::window; - -pub use window::{frames, Event, Mode, RedrawRequest, UserAttention}; +use crate::core::window::{Mode, UserAttention}; +use crate::native::command::{self, Command}; +use crate::native::window::Action; /// Closes the current window and exits the application. pub fn close() -> Command { - Command::single(command::Action::Window(window::Action::Close)) + Command::single(command::Action::Window(Action::Close)) } /// Begins dragging the window while the left mouse button is held. pub fn drag() -> Command { - Command::single(command::Action::Window(window::Action::Drag)) + Command::single(command::Action::Window(Action::Drag)) } /// Resizes the window to the given logical dimensions. pub fn resize(width: u32, height: u32) -> Command { - Command::single(command::Action::Window(window::Action::Resize { - width, - height, - })) + Command::single(command::Action::Window(Action::Resize { width, height })) } /// Maximizes the window. pub fn maximize(maximized: bool) -> Command { - Command::single(command::Action::Window(window::Action::Maximize( - maximized, - ))) + Command::single(command::Action::Window(Action::Maximize(maximized))) } /// Minimes the window. pub fn minimize(minimized: bool) -> Command { - Command::single(command::Action::Window(window::Action::Minimize( - minimized, - ))) + Command::single(command::Action::Window(Action::Minimize(minimized))) } /// Moves a window to the given logical coordinates. pub fn move_to(x: i32, y: i32) -> Command { - Command::single(command::Action::Window(window::Action::Move { x, y })) + Command::single(command::Action::Window(Action::Move { x, y })) } /// Sets the [`Mode`] of the window. pub fn change_mode(mode: Mode) -> Command { - Command::single(command::Action::Window(window::Action::ChangeMode(mode))) + Command::single(command::Action::Window(Action::ChangeMode(mode))) } /// Fetches the current [`Mode`] of the window. pub fn fetch_mode( f: impl FnOnce(Mode) -> Message + 'static, ) -> Command { - Command::single(command::Action::Window(window::Action::FetchMode( - Box::new(f), - ))) + Command::single(command::Action::Window(Action::FetchMode(Box::new(f)))) } /// Toggles the window to maximized or back. pub fn toggle_maximize() -> Command { - Command::single(command::Action::Window(window::Action::ToggleMaximize)) + Command::single(command::Action::Window(Action::ToggleMaximize)) } /// Toggles the window decorations. pub fn toggle_decorations() -> Command { - Command::single(command::Action::Window(window::Action::ToggleDecorations)) + Command::single(command::Action::Window(Action::ToggleDecorations)) } /// Request user attention to the window, this has no effect if the application @@ -74,9 +64,9 @@ pub fn toggle_decorations() -> Command { pub fn request_user_attention( user_attention: Option, ) -> Command { - Command::single(command::Action::Window( - window::Action::RequestUserAttention(user_attention), - )) + Command::single(command::Action::Window(Action::RequestUserAttention( + user_attention, + ))) } /// Brings the window to the front and sets input focus. Has no effect if the window is @@ -86,21 +76,17 @@ pub fn request_user_attention( /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive /// user experience. pub fn gain_focus() -> Command { - Command::single(command::Action::Window(window::Action::GainFocus)) + Command::single(command::Action::Window(Action::GainFocus)) } /// Changes whether or not the window will always be on top of other windows. pub fn change_always_on_top(on_top: bool) -> Command { - Command::single(command::Action::Window(window::Action::ChangeAlwaysOnTop( - on_top, - ))) + Command::single(command::Action::Window(Action::ChangeAlwaysOnTop(on_top))) } /// Fetches an identifier unique to the window. pub fn fetch_id( f: impl FnOnce(u64) -> Message + 'static, ) -> Command { - Command::single(command::Action::Window(window::Action::FetchId(Box::new( - f, - )))) + Command::single(command::Action::Window(Action::FetchId(Box::new(f)))) } From 5fed065dc3aa3d2f9ff8d229cbffe003a89ba033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 4 Mar 2023 05:56:10 +0100 Subject: [PATCH 39/57] Update `glyphon` in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4cdd6c680a..50a81a9111 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -55,7 +55,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "810bc979f9005e2bd343b72b980e57e46174283f" +rev = "edd23695ad53db5f89d455c3c130172fd107d6a2" [dependencies.encase] version = "0.3.0" From f4cf488e0b083b5d7b7612c650917233163ee9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 5 Mar 2023 04:15:10 +0100 Subject: [PATCH 40/57] Remove generic `Hasher` and `Event` from `subscription::Recipe` --- examples/download_progress/Cargo.toml | 2 - examples/download_progress/src/download.rs | 2 +- examples/websocket/Cargo.toml | 2 - examples/websocket/src/echo.rs | 4 +- examples/websocket/src/echo/server.rs | 2 +- futures/Cargo.toml | 4 + futures/src/backend/native/async_std.rs | 14 +- futures/src/backend/native/smol.rs | 14 +- futures/src/backend/native/tokio.rs | 14 +- futures/src/lib.rs | 1 + futures/src/runtime.rs | 25 +- futures/src/subscription.rs | 316 +++++++++++++++++---- futures/src/subscription/tracker.rs | 51 ++-- native/src/lib.rs | 5 - native/src/runtime.rs | 18 -- native/src/subscription.rs | 242 +--------------- native/src/window.rs | 2 +- src/advanced.rs | 5 + src/lib.rs | 8 +- winit/src/application.rs | 16 +- 20 files changed, 341 insertions(+), 406 deletions(-) delete mode 100644 native/src/runtime.rs diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index f38679eac0..212832f474 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -7,8 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["tokio"] } -iced_native = { path = "../../native" } -iced_futures = { path = "../../futures" } [dependencies.reqwest] version = "0.11" diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index 39dd843fb9..5ff951b39d 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,4 +1,4 @@ -use iced_native::subscription; +use iced::subscription; use std::hash::Hash; diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index c25f067b2c..03b240c62d 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -7,8 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["tokio", "debug"] } -iced_native = { path = "../../native" } -iced_futures = { path = "../../futures" } once_cell = "1.15" [dependencies.async-tungstenite] diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs index e74768a66b..122c20dbf0 100644 --- a/examples/websocket/src/echo.rs +++ b/examples/websocket/src/echo.rs @@ -1,7 +1,7 @@ pub mod server; -use iced_futures::futures; -use iced_native::subscription::{self, Subscription}; +use iced::futures; +use iced::subscription::{self, Subscription}; use futures::channel::mpsc; use futures::sink::SinkExt; diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs index dd2349849a..168a635e01 100644 --- a/examples/websocket/src/echo/server.rs +++ b/examples/websocket/src/echo/server.rs @@ -1,4 +1,4 @@ -use iced_futures::futures; +use iced::futures; use futures::channel::mpsc; use futures::{SinkExt, StreamExt}; diff --git a/futures/Cargo.toml b/futures/Cargo.toml index e4d355ee83..411e7c2ad8 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -16,6 +16,10 @@ thread-pool = ["futures/thread-pool"] [dependencies] log = "0.4" +[dependencies.iced_core] +version = "0.8" +path = "../core" + [dependencies.futures] version = "0.3" diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs index b324dbf14c..52b0e9140d 100644 --- a/futures/src/backend/native/async_std.rs +++ b/futures/src/backend/native/async_std.rs @@ -18,28 +18,26 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; 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( + pub fn every( duration: std::time::Duration, - ) -> Subscription { + ) -> Subscription { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl subscription::Recipe for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = std::time::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::().hash(state); @@ -48,7 +46,7 @@ pub mod time { fn stream( self: Box, - _input: futures::stream::BoxStream<'static, E>, + _input: subscription::EventStream, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; diff --git a/futures/src/backend/native/smol.rs b/futures/src/backend/native/smol.rs index d5201cde0f..30bc8291a0 100644 --- a/futures/src/backend/native/smol.rs +++ b/futures/src/backend/native/smol.rs @@ -19,28 +19,26 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; 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( + pub fn every( duration: std::time::Duration, - ) -> Subscription { + ) -> Subscription { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl subscription::Recipe for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = std::time::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::().hash(state); @@ -49,7 +47,7 @@ pub mod time { fn stream( self: Box, - _input: futures::stream::BoxStream<'static, E>, + _input: subscription::EventStream, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs index dd818bd128..4698a10596 100644 --- a/futures/src/backend/native/tokio.rs +++ b/futures/src/backend/native/tokio.rs @@ -22,28 +22,26 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; 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( + pub fn every( duration: std::time::Duration, - ) -> Subscription { + ) -> Subscription { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl subscription::Recipe for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = std::time::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::().hash(state); @@ -52,7 +50,7 @@ pub mod time { fn stream( self: Box, - _input: futures::stream::BoxStream<'static, E>, + _input: subscription::EventStream, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; diff --git a/futures/src/lib.rs b/futures/src/lib.rs index c0982db7ad..39137de263 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -18,6 +18,7 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] pub use futures; +pub use iced_core as core; mod command; mod maybe_send; diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 24f9f24121..2241a49472 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,6 +1,7 @@ //! Run commands and keep track of subscriptions. +use crate::core::event::{self, Event}; use crate::subscription; -use crate::{BoxFuture, Executor, MaybeSend, Subscription}; +use crate::{BoxFuture, Executor, MaybeSend}; use futures::{channel::mpsc, Sink}; use std::marker::PhantomData; @@ -12,18 +13,15 @@ use std::marker::PhantomData; /// /// [`Command`]: crate::Command #[derive(Debug)] -pub struct Runtime { +pub struct Runtime { executor: Executor, sender: Sender, - subscriptions: subscription::Tracker, + subscriptions: subscription::Tracker, _message: PhantomData, } -impl - Runtime +impl Runtime where - Hasher: std::hash::Hasher + Default, - Event: Send + Clone + 'static, Executor: self::Executor, Sender: Sink + Unpin @@ -79,7 +77,9 @@ where /// [`Tracker::update`]: subscription::Tracker::update pub fn track( &mut self, - subscription: Subscription, + recipes: impl IntoIterator< + Item = Box>, + >, ) { let Runtime { executor, @@ -88,8 +88,9 @@ where .. } = self; - let futures = executor - .enter(|| subscriptions.update(subscription, sender.clone())); + let futures = executor.enter(|| { + subscriptions.update(recipes.into_iter(), sender.clone()) + }); for future in futures { executor.spawn(future); @@ -102,7 +103,7 @@ where /// See [`Tracker::broadcast`] to learn more. /// /// [`Tracker::broadcast`]: subscription::Tracker::broadcast - pub fn broadcast(&mut self, event: Event) { - self.subscriptions.broadcast(event); + pub fn broadcast(&mut self, event: Event, status: event::Status) { + self.subscriptions.broadcast(event, status); } } diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index fe53fd7e1e..876f29c232 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -3,7 +3,18 @@ mod tracker; pub use tracker::Tracker; -use crate::BoxStream; +use crate::core::event::{self, Event}; +use crate::core::window; +use crate::core::Hasher; +use crate::futures::{Future, Stream}; +use crate::{BoxStream, MaybeSend}; + +use std::hash::Hash; + +/// A stream of runtime events. +/// +/// It is the input of a [`Subscription`]. +pub type EventStream = BoxStream<(Event, event::Status)>; /// A request to listen to external events. /// @@ -16,19 +27,13 @@ use crate::BoxStream; /// For instance, you can use a [`Subscription`] to listen to a WebSocket /// connection, keyboard presses, mouse events, time ticks, etc. /// -/// This type is normally aliased by runtimes with a specific `Event` and/or -/// `Hasher`. -/// /// [`Command`]: crate::Command #[must_use = "`Subscription` must be returned to runtime to take effect"] -pub struct Subscription { - recipes: Vec>>, +pub struct Subscription { + recipes: Vec>>, } -impl Subscription -where - H: std::hash::Hasher, -{ +impl Subscription { /// Returns an empty [`Subscription`] that will not produce any output. pub fn none() -> Self { Self { @@ -38,7 +43,7 @@ where /// Creates a [`Subscription`] from a [`Recipe`] describing it. pub fn from_recipe( - recipe: impl Recipe + 'static, + recipe: impl Recipe + 'static, ) -> Self { Self { recipes: vec![Box::new(recipe)], @@ -48,7 +53,7 @@ where /// Batches all the provided subscriptions and returns the resulting /// [`Subscription`]. pub fn batch( - subscriptions: impl IntoIterator>, + subscriptions: impl IntoIterator>, ) -> Self { Self { recipes: subscriptions @@ -59,18 +64,16 @@ where } /// Returns the different recipes of the [`Subscription`]. - pub fn recipes(self) -> Vec>> { + pub fn into_recipes(self) -> Vec>> { self.recipes } /// Adds a value to the [`Subscription`] context. /// /// The value will be part of the identity of a [`Subscription`]. - pub fn with(mut self, value: T) -> Subscription + pub fn with(mut self, value: T) -> Subscription<(T, Message)> where - H: 'static, - E: 'static, - O: 'static, + Message: 'static, T: std::hash::Hash + Clone + Send + Sync + 'static, { Subscription { @@ -79,18 +82,16 @@ where .drain(..) .map(|recipe| { Box::new(With::new(recipe, value.clone())) - as Box> + as Box> }) .collect(), } } /// Transforms the [`Subscription`] output with the given function. - pub fn map(mut self, f: fn(O) -> A) -> Subscription + pub fn map(mut self, f: fn(Message) -> A) -> Subscription where - H: 'static, - E: 'static, - O: 'static, + Message: 'static, A: 'static, { Subscription { @@ -98,15 +99,14 @@ where .recipes .drain(..) .map(|recipe| { - Box::new(Map::new(recipe, f)) - as Box> + Box::new(Map::new(recipe, f)) as Box> }) .collect(), } } } -impl std::fmt::Debug for Subscription { +impl std::fmt::Debug for Subscription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Subscription").finish() } @@ -129,7 +129,7 @@ impl std::fmt::Debug for Subscription { /// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples /// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress /// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/examples/stopwatch -pub trait Recipe { +pub trait Recipe { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. type Output; @@ -141,45 +141,33 @@ pub trait Recipe { /// Executes the [`Recipe`] and produces the stream of events of its /// [`Subscription`]. - /// - /// It receives some stream of generic events, which is normally defined by - /// shells. - fn stream( - self: Box, - input: BoxStream, - ) -> BoxStream; + fn stream(self: Box, input: EventStream) -> BoxStream; } -struct Map { - recipe: Box>, +struct Map { + recipe: Box>, mapper: fn(A) -> B, } -impl Map { - fn new( - recipe: Box>, - mapper: fn(A) -> B, - ) -> Self { +impl Map { + fn new(recipe: Box>, mapper: fn(A) -> B) -> Self { Map { recipe, mapper } } } -impl Recipe for Map +impl Recipe for Map where A: 'static, B: 'static, - H: std::hash::Hasher, { type Output = B; - fn hash(&self, state: &mut H) { - use std::hash::Hash; - + fn hash(&self, state: &mut Hasher) { self.recipe.hash(state); self.mapper.hash(state); } - fn stream(self: Box, input: BoxStream) -> BoxStream { + fn stream(self: Box, input: EventStream) -> BoxStream { use futures::StreamExt; let mapper = self.mapper; @@ -188,34 +176,31 @@ where } } -struct With { - recipe: Box>, +struct With { + recipe: Box>, value: B, } -impl With { - fn new(recipe: Box>, value: B) -> Self { +impl With { + fn new(recipe: Box>, value: B) -> Self { With { recipe, value } } } -impl Recipe for With +impl Recipe for With where A: 'static, B: 'static + std::hash::Hash + Clone + Send + Sync, - H: std::hash::Hasher, { type Output = (B, A); - fn hash(&self, state: &mut H) { - use std::hash::Hash; - + fn hash(&self, state: &mut Hasher) { std::any::TypeId::of::().hash(state); self.value.hash(state); self.recipe.hash(state); } - fn stream(self: Box, input: BoxStream) -> BoxStream { + fn stream(self: Box, input: EventStream) -> BoxStream { use futures::StreamExt; let value = self.value; @@ -227,3 +212,222 @@ where ) } } + +/// Returns a [`Subscription`] to all the ignored runtime events. +/// +/// This subscription will notify your application of any [`Event`] that was +/// not captured by any widget. +pub fn events() -> Subscription { + events_with(|event, status| match status { + event::Status::Ignored => Some(event), + event::Status::Captured => None, + }) +} + +/// Returns a [`Subscription`] that filters all the runtime events with the +/// provided function, producing messages accordingly. +/// +/// This subscription will call the provided function for every [`Event`] +/// handled by the runtime. If the function: +/// +/// - Returns `None`, the [`Event`] will be discarded. +/// - Returns `Some` message, the `Message` will be produced. +pub fn events_with( + f: fn(Event, event::Status) -> Option, +) -> Subscription +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct EventsWith; + + Subscription::from_recipe(Runner { + id: (EventsWith, f), + spawn: move |events| { + use futures::future; + use futures::stream::StreamExt; + + events.filter_map(move |(event, status)| { + future::ready(match event { + Event::Window(window::Event::RedrawRequested(_)) => None, + _ => f(event, status), + }) + }) + }, + }) +} + +/// Returns a [`Subscription`] that produces a message for every runtime event, +/// including the redraw request events. +/// +/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in +/// an infinite loop. +pub fn raw_events( + f: fn(Event, event::Status) -> Option, +) -> Subscription +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct RawEvents; + + Subscription::from_recipe(Runner { + id: (RawEvents, f), + spawn: move |events| { + use futures::future; + use futures::stream::StreamExt; + + events.filter_map(move |(event, status)| { + future::ready(f(event, status)) + }) + }, + }) +} + +/// Returns a [`Subscription`] that will call the given function to create and +/// asynchronously run the given [`Stream`]. +pub fn run(builder: fn() -> S) -> Subscription +where + S: Stream + MaybeSend + 'static, + Message: 'static, +{ + Subscription::from_recipe(Runner { + id: builder, + spawn: move |_| builder(), + }) +} + +/// Returns a [`Subscription`] that will create and asynchronously run the +/// given [`Stream`]. +/// +/// The `id` will be used to uniquely identify the [`Subscription`]. +pub fn run_with_id(id: I, stream: S) -> Subscription +where + I: Hash + 'static, + S: Stream + MaybeSend + 'static, + Message: 'static, +{ + Subscription::from_recipe(Runner { + id, + spawn: move |_| stream, + }) +} + +/// Returns a [`Subscription`] that will create and asynchronously run a +/// [`Stream`] that will call the provided closure to produce every `Message`. +/// +/// The `id` will be used to uniquely identify the [`Subscription`]. +/// +/// # Creating an asynchronous worker with bidirectional communication +/// You can leverage this helper to create a [`Subscription`] that spawns +/// an asynchronous worker in the background and establish a channel of +/// communication with an `iced` application. +/// +/// You can achieve this by creating an `mpsc` channel inside the closure +/// and returning the `Sender` as a `Message` for the `Application`: +/// +/// ``` +/// use iced_futures::subscription::{self, Subscription}; +/// use iced_futures::futures; +/// +/// use futures::channel::mpsc; +/// +/// pub enum Event { +/// Ready(mpsc::Sender), +/// WorkFinished, +/// // ... +/// } +/// +/// enum Input { +/// DoSomeWork, +/// // ... +/// } +/// +/// enum State { +/// Starting, +/// Ready(mpsc::Receiver), +/// } +/// +/// fn some_worker() -> Subscription { +/// struct SomeWorker; +/// +/// subscription::unfold(std::any::TypeId::of::(), State::Starting, |state| async move { +/// match state { +/// State::Starting => { +/// // Create channel +/// let (sender, receiver) = mpsc::channel(100); +/// +/// (Some(Event::Ready(sender)), State::Ready(receiver)) +/// } +/// State::Ready(mut receiver) => { +/// use futures::StreamExt; +/// +/// // Read next input sent from `Application` +/// let input = receiver.select_next_some().await; +/// +/// match input { +/// Input::DoSomeWork => { +/// // Do some async work... +/// +/// // Finally, we can optionally return a message to tell the +/// // `Application` the work is done +/// (Some(Event::WorkFinished), State::Ready(receiver)) +/// } +/// } +/// } +/// } +/// }) +/// } +/// ``` +/// +/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket +/// connection open. +/// +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket +pub fn unfold( + id: I, + initial: T, + mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static, +) -> Subscription +where + I: Hash + 'static, + T: MaybeSend + 'static, + Fut: Future, T)> + MaybeSend + 'static, + Message: 'static + MaybeSend, +{ + use futures::future::{self, FutureExt}; + use futures::stream::StreamExt; + + run_with_id( + id, + futures::stream::unfold(initial, move |state| f(state).map(Some)) + .filter_map(future::ready), + ) +} + +struct Runner +where + F: FnOnce(EventStream) -> S, + S: Stream, +{ + id: I, + spawn: F, +} + +impl Recipe for Runner +where + I: Hash + 'static, + F: FnOnce(EventStream) -> S, + S: Stream + MaybeSend + 'static, +{ + type Output = Message; + + fn hash(&self, state: &mut Hasher) { + std::any::TypeId::of::().hash(state); + self.id.hash(state); + } + + fn stream(self: Box, input: EventStream) -> BoxStream { + crate::boxed_stream((self.spawn)(input)) + } +} diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 9fe110b0b5..ae71cd256f 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -1,38 +1,35 @@ -use crate::{BoxFuture, MaybeSend, Subscription}; +use crate::core::event::{self, Event}; +use crate::core::Hasher; +use crate::subscription::Recipe; +use crate::{BoxFuture, MaybeSend}; -use futures::{ - channel::mpsc, - sink::{Sink, SinkExt}, -}; -use std::{collections::HashMap, marker::PhantomData}; +use futures::channel::mpsc; +use futures::sink::{Sink, SinkExt}; + +use std::collections::HashMap; +use std::hash::Hasher as _; /// A registry of subscription streams. /// /// If you have an application that continuously returns a [`Subscription`], /// you can use a [`Tracker`] to keep track of the different recipes and keep /// its executions alive. -#[derive(Debug)] -pub struct Tracker { - subscriptions: HashMap>, - _hasher: PhantomData, +#[derive(Debug, Default)] +pub struct Tracker { + subscriptions: HashMap, } #[derive(Debug)] -pub struct Execution { +pub struct Execution { _cancel: futures::channel::oneshot::Sender<()>, - listener: Option>, + listener: Option>, } -impl Tracker -where - Hasher: std::hash::Hasher + Default, - Event: 'static + Send + Clone, -{ +impl Tracker { /// Creates a new empty [`Tracker`]. pub fn new() -> Self { Self { subscriptions: HashMap::new(), - _hasher: PhantomData, } } @@ -56,7 +53,7 @@ where /// [`Recipe`]: crate::subscription::Recipe pub fn update( &mut self, - subscription: Subscription, + recipes: impl Iterator>>, receiver: Receiver, ) -> Vec> where @@ -70,8 +67,6 @@ where use futures::stream::StreamExt; let mut futures: Vec> = Vec::new(); - - let recipes = subscription.recipes(); let mut alive = std::collections::HashSet::new(); for recipe in recipes { @@ -142,12 +137,12 @@ where /// currently open. /// /// [`Recipe::stream`]: crate::subscription::Recipe::stream - pub fn broadcast(&mut self, event: Event) { + pub fn broadcast(&mut self, event: Event, status: event::Status) { self.subscriptions .values_mut() .filter_map(|connection| connection.listener.as_mut()) .for_each(|listener| { - if let Err(error) = listener.try_send(event.clone()) { + if let Err(error) = listener.try_send((event.clone(), status)) { log::warn!( "Error sending event to subscription: {:?}", error @@ -156,13 +151,3 @@ where }); } } - -impl Default for Tracker -where - Hasher: std::hash::Hasher + Default, - Event: 'static + Send + Clone, -{ - fn default() -> Self { - Self::new() - } -} diff --git a/native/src/lib.rs b/native/src/lib.rs index 0fc4f324c1..2d2e5b3888 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -48,14 +48,11 @@ pub mod command; pub mod font; pub mod keyboard; pub mod program; -pub mod subscription; pub mod system; pub mod user_interface; pub mod widget; pub mod window; -mod runtime; - // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. #[cfg(feature = "debug")] @@ -72,6 +69,4 @@ pub use command::Command; pub use debug::Debug; pub use font::Font; pub use program::Program; -pub use runtime::Runtime; -pub use subscription::Subscription; pub use user_interface::UserInterface; diff --git a/native/src/runtime.rs b/native/src/runtime.rs deleted file mode 100644 index 1b81314f07..0000000000 --- a/native/src/runtime.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Run commands and subscriptions. -use iced_core::event::{self, Event}; -use iced_core::Hasher; - -/// A native runtime with a generic executor and receiver of results. -/// -/// It can be used by shells to easily spawn a [`Command`] or track a -/// [`Subscription`]. -/// -/// [`Command`]: crate::Command -/// [`Subscription`]: crate::Subscription -pub type Runtime = iced_futures::Runtime< - Hasher, - (Event, event::Status), - Executor, - Receiver, - Message, ->; diff --git a/native/src/subscription.rs b/native/src/subscription.rs index b16bcb0370..d4176ab565 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -3,247 +3,7 @@ use crate::core::event::{self, Event}; use crate::core::window; use crate::core::Hasher; use crate::futures::futures::{self, Future, Stream}; +use crate::futures::subscription::{EventStream, Recipe, Subscription}; use crate::futures::{BoxStream, MaybeSend}; use std::hash::Hash; - -/// 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 = - iced_futures::Subscription; - -/// A stream of runtime events. -/// -/// It is the input of a [`Subscription`] in the native runtime. -pub type EventStream = BoxStream<(Event, event::Status)>; - -/// A native [`Subscription`] tracker. -pub type Tracker = - iced_futures::subscription::Tracker; - -pub use iced_futures::subscription::Recipe; - -/// Returns a [`Subscription`] to all the ignored runtime events. -/// -/// This subscription will notify your application of any [`Event`] that was -/// not captured by any widget. -pub fn events() -> Subscription { - events_with(|event, status| match status { - event::Status::Ignored => Some(event), - event::Status::Captured => None, - }) -} - -/// Returns a [`Subscription`] that filters all the runtime events with the -/// provided function, producing messages accordingly. -/// -/// This subscription will call the provided function for every [`Event`] -/// handled by the runtime. If the function: -/// -/// - Returns `None`, the [`Event`] will be discarded. -/// - Returns `Some` message, the `Message` will be produced. -pub fn events_with( - f: fn(Event, event::Status) -> Option, -) -> Subscription -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct EventsWith; - - Subscription::from_recipe(Runner { - id: (EventsWith, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(match event { - Event::Window(window::Event::RedrawRequested(_)) => None, - _ => f(event, status), - }) - }) - }, - }) -} - -pub(crate) fn raw_events( - f: fn(Event, event::Status) -> Option, -) -> Subscription -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct RawEvents; - - Subscription::from_recipe(Runner { - id: (RawEvents, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(f(event, status)) - }) - }, - }) -} - -/// Returns a [`Subscription`] that will call the given function to create and -/// asynchronously run the given [`Stream`]. -pub fn run(builder: fn() -> S) -> Subscription -where - S: Stream + MaybeSend + 'static, - Message: 'static, -{ - Subscription::from_recipe(Runner { - id: builder, - spawn: move |_| builder(), - }) -} - -/// Returns a [`Subscription`] that will create and asynchronously run the -/// given [`Stream`]. -/// -/// The `id` will be used to uniquely identify the [`Subscription`]. -pub fn run_with_id(id: I, stream: S) -> Subscription -where - I: Hash + 'static, - S: Stream + MaybeSend + 'static, - Message: 'static, -{ - Subscription::from_recipe(Runner { - id, - spawn: move |_| stream, - }) -} - -/// Returns a [`Subscription`] that will create and asynchronously run a -/// [`Stream`] that will call the provided closure to produce every `Message`. -/// -/// The `id` will be used to uniquely identify the [`Subscription`]. -/// -/// # Creating an asynchronous worker with bidirectional communication -/// You can leverage this helper to create a [`Subscription`] that spawns -/// an asynchronous worker in the background and establish a channel of -/// communication with an `iced` application. -/// -/// You can achieve this by creating an `mpsc` channel inside the closure -/// and returning the `Sender` as a `Message` for the `Application`: -/// -/// ``` -/// use iced_native::subscription::{self, Subscription}; -/// use iced_native::futures::futures; -/// -/// use futures::channel::mpsc; -/// -/// pub enum Event { -/// Ready(mpsc::Sender), -/// WorkFinished, -/// // ... -/// } -/// -/// enum Input { -/// DoSomeWork, -/// // ... -/// } -/// -/// enum State { -/// Starting, -/// Ready(mpsc::Receiver), -/// } -/// -/// fn some_worker() -> Subscription { -/// struct SomeWorker; -/// -/// subscription::unfold(std::any::TypeId::of::(), State::Starting, |state| async move { -/// match state { -/// State::Starting => { -/// // Create channel -/// let (sender, receiver) = mpsc::channel(100); -/// -/// (Some(Event::Ready(sender)), State::Ready(receiver)) -/// } -/// State::Ready(mut receiver) => { -/// use futures::StreamExt; -/// -/// // Read next input sent from `Application` -/// let input = receiver.select_next_some().await; -/// -/// match input { -/// Input::DoSomeWork => { -/// // Do some async work... -/// -/// // Finally, we can optionally return a message to tell the -/// // `Application` the work is done -/// (Some(Event::WorkFinished), State::Ready(receiver)) -/// } -/// } -/// } -/// } -/// }) -/// } -/// ``` -/// -/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket -/// connection open. -/// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket -pub fn unfold( - id: I, - initial: T, - mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static, -) -> Subscription -where - I: Hash + 'static, - T: MaybeSend + 'static, - Fut: Future, T)> + MaybeSend + 'static, - Message: 'static + MaybeSend, -{ - use futures::future::{self, FutureExt}; - use futures::stream::StreamExt; - - run_with_id( - id, - futures::stream::unfold(initial, move |state| f(state).map(Some)) - .filter_map(future::ready), - ) -} - -struct Runner -where - F: FnOnce(EventStream) -> S, - S: Stream, -{ - id: I, - spawn: F, -} - -impl Recipe - for Runner -where - I: Hash + 'static, - F: FnOnce(EventStream) -> S, - S: Stream + MaybeSend + 'static, -{ - type Output = Message; - - fn hash(&self, state: &mut Hasher) { - std::any::TypeId::of::().hash(state); - self.id.hash(state); - } - - fn stream(self: Box, input: EventStream) -> BoxStream { - iced_futures::boxed_stream((self.spawn)(input)) - } -} diff --git a/native/src/window.rs b/native/src/window.rs index 97a96e5439..aa3f35c71b 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -5,7 +5,7 @@ pub use action::Action; use crate::core::time::Instant; use crate::core::window::Event; -use crate::subscription::{self, Subscription}; +use crate::futures::subscription::{self, Subscription}; /// Subscribes to the frames of the window of the running application. /// diff --git a/src/advanced.rs b/src/advanced.rs index 714076e0c8..9621c3bc02 100644 --- a/src/advanced.rs +++ b/src/advanced.rs @@ -7,3 +7,8 @@ pub use crate::core::svg; pub use crate::core::text::{self, Text}; pub use crate::core::widget::{self, Widget}; pub use crate::core::{Clipboard, Shell}; + +pub mod subscription { + //! Write your own subscriptions. + pub use crate::native::futures::subscription::{EventStream, Recipe}; +} diff --git a/src/lib.rs b/src/lib.rs index b71b7781d5..b9f87d5dd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,7 +193,6 @@ pub use crate::core::{ Rectangle, Size, Vector, }; pub use crate::native::Command; -pub use native::subscription; pub mod clipboard { //! Access the clipboard. @@ -233,6 +232,13 @@ pub mod mouse { pub use crate::core::mouse::{Button, Event, Interaction, ScrollDelta}; } +pub mod subscription { + //! Listen to external events in your application. + pub use iced_futures::subscription::{ + events, events_with, run, run_with_id, unfold, Subscription, + }; +} + #[cfg(feature = "system")] pub mod system { //! Retrieve system information. diff --git a/winit/src/application.rs b/winit/src/application.rs index c8162c9b53..c95dbf621d 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -14,12 +14,12 @@ use crate::core::widget::operation; use crate::core::window; use crate::core::{Event, Size}; use crate::futures::futures; -use crate::futures::Executor; +use crate::futures::{Executor, Runtime, Subscription}; use crate::graphics::compositor::{self, Compositor}; use crate::native::clipboard; use crate::native::program::Program; use crate::native::user_interface::{self, UserInterface}; -use crate::native::{Command, Debug, Runtime, Subscription}; +use crate::native::{Command, Debug}; use crate::style::application::{Appearance, StyleSheet}; use crate::{Clipboard, Error, Proxy, Settings}; @@ -316,7 +316,7 @@ async fn run_instance( &window, || compositor.fetch_information(), ); - runtime.track(application.subscription()); + runtime.track(application.subscription().into_recipes()); let mut user_interface = ManuallyDrop::new(build_user_interface( &application, @@ -360,8 +360,10 @@ async fn run_instance( debug.event_processing_finished(); - for event in events.drain(..).zip(statuses.into_iter()) { - runtime.broadcast(event); + for (event, status) in + events.drain(..).zip(statuses.into_iter()) + { + runtime.broadcast(event, status); } if !messages.is_empty() @@ -442,7 +444,7 @@ async fn run_instance( } window.request_redraw(); - runtime.broadcast((redraw_event, core::event::Status::Ignored)); + runtime.broadcast(redraw_event, core::event::Status::Ignored); let _ = control_sender.start_send(match interface_state { user_interface::State::Updated { @@ -685,7 +687,7 @@ pub fn update( } let subscription = application.subscription(); - runtime.track(subscription); + runtime.track(subscription.into_recipes()); } /// Runs the actions of a [`Command`]. From cfb8abb6f5806e08ccc3a80233e1fb1768adeaf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 5 Mar 2023 04:19:31 +0100 Subject: [PATCH 41/57] Use `no_run` for widget doc-tests --- native/src/subscription.rs | 9 --------- widget/src/button.rs | 2 +- widget/src/checkbox.rs | 2 +- widget/src/image.rs | 2 +- widget/src/pane_grid.rs | 2 +- widget/src/progress_bar.rs | 2 +- widget/src/radio.rs | 2 +- widget/src/slider.rs | 2 +- widget/src/text_input.rs | 2 +- widget/src/toggler.rs | 2 +- widget/src/vertical_slider.rs | 2 +- 11 files changed, 10 insertions(+), 19 deletions(-) delete mode 100644 native/src/subscription.rs diff --git a/native/src/subscription.rs b/native/src/subscription.rs deleted file mode 100644 index d4176ab565..0000000000 --- a/native/src/subscription.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Listen to external events in your application. -use crate::core::event::{self, Event}; -use crate::core::window; -use crate::core::Hasher; -use crate::futures::futures::{self, Future, Stream}; -use crate::futures::subscription::{EventStream, Recipe, Subscription}; -use crate::futures::{BoxStream, MaybeSend}; - -use std::hash::Hash; diff --git a/widget/src/button.rs b/widget/src/button.rs index d6fd399753..7eee69cbae 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -18,7 +18,7 @@ pub use iced_style::button::{Appearance, StyleSheet}; /// A generic widget that produces a message when pressed. /// -/// ``` +/// ```no_run /// # type Button<'a, Message> = /// # iced_widget::Button<'a, Message, iced_widget::renderer::Renderer>; /// # diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 6a062f94e6..d1f886c6bf 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -30,7 +30,7 @@ pub struct Icon { /// /// # Example /// -/// ``` +/// ```no_run /// # type Checkbox<'a, Message> = /// # iced_widget::Checkbox<'a, Message, iced_widget::renderer::Renderer>; /// # diff --git a/widget/src/image.rs b/widget/src/image.rs index 22a3a1a16e..abcb6ef22b 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -23,7 +23,7 @@ pub fn viewer(handle: Handle) -> Viewer { /// /// # Example /// -/// ``` +/// ```no_run /// # use iced_widget::image::{self, Image}; /// # /// let image = Image::::new("resources/ferris.png"); diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index a97cef6096..257c014441 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -67,7 +67,7 @@ use crate::core::{ /// /// ## Example /// -/// ``` +/// ```no_run /// # use iced_widget::{pane_grid, text}; /// # /// # type PaneGrid<'a, Message> = diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 896d40feed..ef0d87d515 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -13,7 +13,7 @@ pub use iced_style::progress_bar::{Appearance, StyleSheet}; /// A bar that displays progress. /// /// # Example -/// ``` +/// ```no_run /// # type ProgressBar = /// # iced_widget::ProgressBar>; /// # diff --git a/widget/src/radio.rs b/widget/src/radio.rs index e5c67f3d31..c2b6b017e7 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -18,7 +18,7 @@ pub use iced_style::radio::{Appearance, StyleSheet}; /// A circular button representing a choice. /// /// # Example -/// ``` +/// ```no_run /// # type Radio = /// # iced_widget::Radio>; /// # diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 70a84fc61b..e1153d2d60 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -25,7 +25,7 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; /// to 1 unit. /// /// # Example -/// ``` +/// ```no_run /// # type Slider<'a, T, Message> = /// # iced_widget::Slider<'a, Message, T, iced_widget::renderer::Renderer>; /// # diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 67d80e2b69..d1c48fbdd5 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -35,7 +35,7 @@ pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. /// /// # Example -/// ``` +/// ```no_run /// # pub type TextInput<'a, Message> = /// # iced_widget::TextInput<'a, Message, iced_widget::renderer::Renderer>; /// # diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index f23f9b27a0..713a9c302f 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -18,7 +18,7 @@ pub use crate::style::toggler::{Appearance, StyleSheet}; /// /// # Example /// -/// ``` +/// ```no_run /// # type Toggler<'a, Message> = /// # iced_widget::Toggler<'a, Message, iced_widget::renderer::Renderer>; /// # diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 64a9583c1b..62dc997f32 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -26,7 +26,7 @@ use crate::core::{ /// to 1 unit. /// /// # Example -/// ``` +/// ```no_run /// # type VerticalSlider<'a, T, Message> = /// # iced_widget::VerticalSlider<'a, T, Message, iced_widget::renderer::Renderer>; /// # From 43414bbdfb080b7aa3c702d944cc9d0c9c0fd14b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 5 Mar 2023 05:37:23 +0100 Subject: [PATCH 42/57] Fix `wasm-bindgen` backend in `iced_futures` --- futures/src/backend/wasm/wasm_bindgen.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs index b726501ad1..2666f1b42d 100644 --- a/futures/src/backend/wasm/wasm_bindgen.rs +++ b/futures/src/backend/wasm/wasm_bindgen.rs @@ -16,6 +16,7 @@ impl crate::Executor for Executor { pub mod time { //! Listen and react to time. + use crate::core::Hasher; use crate::subscription::{self, Subscription}; use crate::BoxStream; @@ -23,22 +24,19 @@ pub mod time { /// /// The first message is produced after a `duration`, and then continues to /// produce more messages every `duration` after that. - pub fn every( + pub fn every( duration: std::time::Duration, - ) -> Subscription { + ) -> Subscription { Subscription::from_recipe(Every(duration)) } #[derive(Debug)] struct Every(std::time::Duration); - impl subscription::Recipe for Every - where - H: std::hash::Hasher, - { + impl subscription::Recipe for Every { type Output = wasm_timer::Instant; - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut Hasher) { use std::hash::Hash; std::any::TypeId::of::().hash(state); @@ -47,7 +45,7 @@ pub mod time { fn stream( self: Box, - _input: BoxStream, + _input: subscription::EventStream, ) -> BoxStream { use futures::stream::StreamExt; From 8af69be47e88896b3c5f70174db609eee0c67971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 5 Mar 2023 06:23:40 +0100 Subject: [PATCH 43/57] Converge `Command` types from `iced_futures` and `iced_native` --- core/src/widget/operation.rs | 116 +++++++++++++++++++++++++- futures/src/command.rs | 70 ---------------- futures/src/lib.rs | 2 - native/src/command.rs | 59 +++++++++----- native/src/command/action.rs | 8 +- native/src/lib.rs | 1 - native/src/widget.rs | 16 ---- native/src/widget/action.rs | 153 ----------------------------------- winit/src/application.rs | 2 +- 9 files changed, 161 insertions(+), 266 deletions(-) delete mode 100644 futures/src/command.rs delete mode 100644 native/src/widget.rs delete mode 100644 native/src/widget/action.rs diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index 53688a21c0..ad188c364d 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -11,6 +11,7 @@ use crate::widget::Id; use std::any::Any; use std::fmt; +use std::rc::Rc; /// A piece of logic that can traverse the widget tree of an application in /// order to query or update some widget state. @@ -68,9 +69,122 @@ where } } +/// Maps the output of an [`Operation`] using the given function. +pub fn map( + operation: Box>, + f: impl Fn(A) -> B + 'static, +) -> impl Operation +where + A: 'static, + B: 'static, +{ + #[allow(missing_debug_implementations)] + struct Map { + operation: Box>, + f: Rc B>, + } + + impl Operation for Map + where + A: 'static, + B: 'static, + { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + struct MapRef<'a, A> { + operation: &'a mut dyn Operation, + } + + impl<'a, A, B> Operation for MapRef<'a, A> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + let Self { operation, .. } = self; + + operation.container(id, &mut |operation| { + operate_on_children(&mut MapRef { operation }); + }); + } + + fn scrollable( + &mut self, + state: &mut dyn Scrollable, + id: Option<&Id>, + ) { + self.operation.scrollable(state, id); + } + + fn focusable( + &mut self, + state: &mut dyn Focusable, + id: Option<&Id>, + ) { + self.operation.focusable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn TextInput, + id: Option<&Id>, + ) { + self.operation.text_input(state, id); + } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.operation.custom(state, id); + } + } + + let Self { operation, .. } = self; + + MapRef { + operation: operation.as_mut(), + } + .container(id, operate_on_children); + } + + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + self.operation.focusable(state, id); + } + + fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { + self.operation.scrollable(state, id); + } + + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + self.operation.text_input(state, id); + } + + fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { + self.operation.custom(state, id); + } + + fn finish(&self) -> Outcome { + match self.operation.finish() { + Outcome::None => Outcome::None, + Outcome::Some(output) => Outcome::Some((self.f)(output)), + Outcome::Chain(next) => Outcome::Chain(Box::new(Map { + operation: next, + f: self.f.clone(), + })), + } + } + } + + Map { + operation, + f: Rc::new(f), + } +} + /// Produces an [`Operation`] that applies the given [`Operation`] to the /// children of a container with the given [`Id`]. -pub fn scoped( +pub fn scope( target: Id, operation: impl Operation + 'static, ) -> impl Operation { diff --git a/futures/src/command.rs b/futures/src/command.rs deleted file mode 100644 index 3d1ec3f9fb..0000000000 --- a/futures/src/command.rs +++ /dev/null @@ -1,70 +0,0 @@ -/// A set of asynchronous actions to be performed by some runtime. -#[must_use = "`Command` must be returned to runtime to take effect"] -#[derive(Debug)] -pub struct Command(Internal); - -#[derive(Debug)] -enum Internal { - None, - Single(T), - Batch(Vec), -} - -impl Command { - /// Creates an empty [`Command`]. - /// - /// In other words, a [`Command`] that does nothing. - pub const fn none() -> Self { - Self(Internal::None) - } - - /// Creates a [`Command`] that performs a single action. - pub const fn single(action: T) -> Self { - Self(Internal::Single(action)) - } - - /// 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 { - let mut batch = Vec::new(); - - for Command(command) in commands { - match command { - Internal::None => {} - Internal::Single(command) => batch.push(command), - Internal::Batch(commands) => batch.extend(commands), - } - } - - Self(Internal::Batch(batch)) - } - - /// Applies a transformation to the result of a [`Command`]. - pub fn map(self, f: impl Fn(T) -> A) -> Command - where - T: 'static, - { - let Command(command) = self; - - match command { - Internal::None => Command::none(), - Internal::Single(action) => Command::single(f(action)), - Internal::Batch(batch) => { - Command(Internal::Batch(batch.into_iter().map(f).collect())) - } - } - } - - /// Returns all of the actions of the [`Command`]. - pub fn actions(self) -> Vec { - let Command(command) = self; - - match command { - Internal::None => Vec::new(), - Internal::Single(action) => vec![action], - Internal::Batch(batch) => batch, - } - } -} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 39137de263..397fc2d2f9 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -20,7 +20,6 @@ pub use futures; pub use iced_core as core; -mod command; mod maybe_send; mod runtime; @@ -28,7 +27,6 @@ pub mod backend; pub mod executor; pub mod subscription; -pub use command::Command; pub use executor::Executor; pub use maybe_send::MaybeSend; pub use platform::*; diff --git a/native/src/command.rs b/native/src/command.rs index 39bee8f6d8..cd4c51ff05 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -3,37 +3,39 @@ mod action; pub use action::Action; -use crate::widget; - -use iced_futures::MaybeSend; +use crate::core::widget; +use crate::futures::MaybeSend; use std::fmt; use std::future::Future; /// A set of asynchronous actions to be performed by some runtime. #[must_use = "`Command` must be returned to runtime to take effect"] -pub struct Command(iced_futures::Command>); +pub struct Command(Internal>); + +#[derive(Debug)] +enum Internal { + None, + Single(T), + Batch(Vec), +} impl Command { /// Creates an empty [`Command`]. /// /// In other words, a [`Command`] that does nothing. pub const fn none() -> Self { - Self(iced_futures::Command::none()) + Self(Internal::None) } /// Creates a [`Command`] that performs a single [`Action`]. pub const fn single(action: Action) -> Self { - Self(iced_futures::Command::single(action)) + Self(Internal::Single(action)) } /// Creates a [`Command`] that performs a [`widget::Operation`]. - pub fn widget( - operation: impl iced_core::widget::Operation + 'static, - ) -> Self { - Self(iced_futures::Command::single(Action::Widget( - widget::Action::new(operation), - ))) + pub fn widget(operation: impl widget::Operation + 'static) -> Self { + Self::single(Action::Widget(Box::new(operation))) } /// Creates a [`Command`] that performs the action of the given future. @@ -51,9 +53,17 @@ impl Command { /// /// 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), - )) + let mut batch = Vec::new(); + + for Command(command) in commands { + match command { + Internal::None => {} + Internal::Single(command) => batch.push(command), + Internal::Batch(commands) => batch.extend(commands), + } + } + + Self(Internal::Batch(batch)) } /// Applies a transformation to the result of a [`Command`]. @@ -65,16 +75,27 @@ impl Command { T: 'static, A: 'static, { - let Command(command) = self; - - Command(command.map(move |action| action.map(f.clone()))) + match self.0 { + Internal::None => Command::none(), + Internal::Single(action) => Command::single(action.map(f)), + Internal::Batch(batch) => Command(Internal::Batch( + batch + .into_iter() + .map(|action| action.map(f.clone())) + .collect(), + )), + } } /// Returns all of the actions of the [`Command`]. pub fn actions(self) -> Vec> { let Command(command) = self; - command.actions() + match command { + Internal::None => Vec::new(), + Internal::Single(action) => vec![action], + Internal::Batch(batch) => batch, + } } } diff --git a/native/src/command/action.rs b/native/src/command/action.rs index d1589c05fb..6c74f0efb2 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,7 +1,7 @@ use crate::clipboard; +use crate::core::widget; use crate::font; use crate::system; -use crate::widget; use crate::window; use iced_futures::MaybeSend; @@ -28,7 +28,7 @@ pub enum Action { System(system::Action), /// Run a widget action. - Widget(widget::Action), + Widget(Box>), /// Load a font from its bytes. LoadFont { @@ -59,7 +59,9 @@ impl Action { Self::Clipboard(action) => Action::Clipboard(action.map(f)), Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), - Self::Widget(widget) => Action::Widget(widget.map(f)), + Self::Widget(operation) => { + Action::Widget(Box::new(widget::operation::map(operation, f))) + } Self::LoadFont { bytes, tagger } => Action::LoadFont { bytes, tagger: Box::new(move |result| f(tagger(result))), diff --git a/native/src/lib.rs b/native/src/lib.rs index 2d2e5b3888..aa45e57a3a 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -50,7 +50,6 @@ pub mod keyboard; pub mod program; pub mod system; pub mod user_interface; -pub mod widget; pub mod window; // We disable debug capabilities on release builds unless the `debug` feature diff --git a/native/src/widget.rs b/native/src/widget.rs deleted file mode 100644 index 0fdade5446..0000000000 --- a/native/src/widget.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Use the built-in widgets or create your own. -//! -//! # Built-in widgets -//! Every built-in drawable widget has its own module with a `Renderer` trait -//! that must be implemented by a [renderer] before being able to use it as -//! a [`Widget`]. -//! -//! # 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. -//! -//! [renderer]: crate::renderer -mod action; - -pub use action::Action; diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs deleted file mode 100644 index f50d7aec25..0000000000 --- a/native/src/widget/action.rs +++ /dev/null @@ -1,153 +0,0 @@ -use iced_core::widget::operation::{ - self, Focusable, Operation, Scrollable, TextInput, -}; -use iced_core::widget::Id; -use iced_futures::MaybeSend; - -use std::any::Any; -use std::rc::Rc; - -/// An operation to be performed on the widget tree. -#[allow(missing_debug_implementations)] -pub struct Action(Box>); - -impl Action { - /// Creates a new [`Action`] with the given [`Operation`]. - pub fn new(operation: impl Operation + 'static) -> Self { - Self(Box::new(operation)) - } - - /// Maps the output of an [`Action`] using the given function. - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - where - T: 'static, - A: 'static, - { - Action(Box::new(Map { - operation: self.0, - f: Rc::new(f), - })) - } - - /// Consumes the [`Action`] and returns the internal [`Operation`]. - pub fn into_operation(self) -> Box> { - self.0 - } -} - -#[allow(missing_debug_implementations)] -struct Map { - operation: Box>, - f: Rc B>, -} - -impl Operation for Map -where - A: 'static, - B: 'static, -{ - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - struct MapRef<'a, A> { - operation: &'a mut dyn Operation, - } - - impl<'a, A, B> Operation for MapRef<'a, A> { - fn container( - &mut self, - id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - let Self { operation, .. } = self; - - operation.container(id, &mut |operation| { - operate_on_children(&mut MapRef { operation }); - }); - } - - fn scrollable( - &mut self, - state: &mut dyn Scrollable, - id: Option<&Id>, - ) { - self.operation.scrollable(state, id); - } - - fn focusable( - &mut self, - state: &mut dyn Focusable, - id: Option<&Id>, - ) { - self.operation.focusable(state, id); - } - - fn text_input( - &mut self, - state: &mut dyn TextInput, - id: Option<&Id>, - ) { - self.operation.text_input(state, id); - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.operation.custom(state, id); - } - } - - let Self { operation, .. } = self; - - MapRef { - operation: operation.as_mut(), - } - .container(id, operate_on_children); - } - - fn focusable( - &mut self, - state: &mut dyn operation::Focusable, - id: Option<&Id>, - ) { - self.operation.focusable(state, id); - } - - fn scrollable( - &mut self, - state: &mut dyn operation::Scrollable, - id: Option<&Id>, - ) { - self.operation.scrollable(state, id); - } - - fn text_input( - &mut self, - state: &mut dyn operation::TextInput, - id: Option<&Id>, - ) { - self.operation.text_input(state, id); - } - - fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { - self.operation.custom(state, id); - } - - fn finish(&self) -> operation::Outcome { - match self.operation.finish() { - operation::Outcome::None => operation::Outcome::None, - operation::Outcome::Some(output) => { - operation::Outcome::Some((self.f)(output)) - } - operation::Outcome::Chain(next) => { - operation::Outcome::Chain(Box::new(Map { - operation: next, - f: self.f.clone(), - })) - } - } - } -} diff --git a/winit/src/application.rs b/winit/src/application.rs index c95dbf621d..d863c84648 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -818,7 +818,7 @@ pub fn run_command( }, command::Action::Widget(action) => { let mut current_cache = std::mem::take(cache); - let mut current_operation = Some(action.into_operation()); + let mut current_operation = Some(action); let mut user_interface = build_user_interface( application, From 99e0a71504456976ba88040f5d1d3bbc347694ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 5 Mar 2023 06:35:20 +0100 Subject: [PATCH 44/57] Rename `iced_native` to `iced_runtime` --- Cargo.toml | 2 +- examples/integration/src/controls.rs | 2 +- examples/integration/src/main.rs | 4 +-- native/src/window.rs | 23 --------------- {native => runtime}/Cargo.toml | 2 +- {native => runtime}/README.md | 0 {native => runtime}/src/clipboard.rs | 15 +++++++++- {native => runtime}/src/command.rs | 0 {native => runtime}/src/command/action.rs | 0 {native => runtime}/src/debug/basic.rs | 0 {native => runtime}/src/debug/null.rs | 0 {native => runtime}/src/font.rs | 0 {native => runtime}/src/keyboard.rs | 0 {native => runtime}/src/lib.rs | 0 {native => runtime}/src/program.rs | 0 {native => runtime}/src/program/state.rs | 0 {native => runtime}/src/system.rs | 0 {native => runtime}/src/system/action.rs | 0 {native => runtime}/src/system/information.rs | 0 {native => runtime}/src/user_interface.rs | 24 ++++++++-------- {winit => runtime}/src/window.rs | 28 ++++++++++++++++--- {native => runtime}/src/window/action.rs | 0 src/advanced.rs | 2 +- src/application.rs | 2 +- src/lib.rs | 10 +++---- src/window.rs | 3 +- tiny_skia/Cargo.toml | 4 --- tiny_skia/src/text.rs | 2 +- widget/Cargo.toml | 4 +-- widget/src/helpers.rs | 2 +- widget/src/lib.rs | 4 +-- widget/src/scrollable.rs | 2 +- widget/src/text_input.rs | 2 +- winit/Cargo.toml | 6 ++-- winit/src/application.rs | 14 +++++----- winit/src/application/state.rs | 2 +- winit/src/clipboard.rs | 14 ---------- winit/src/lib.rs | 7 ++--- winit/src/system.rs | 4 +-- 39 files changed, 87 insertions(+), 97 deletions(-) delete mode 100644 native/src/window.rs rename {native => runtime}/Cargo.toml (95%) rename {native => runtime}/README.md (100%) rename {native => runtime}/src/clipboard.rs (67%) rename {native => runtime}/src/command.rs (100%) rename {native => runtime}/src/command/action.rs (100%) rename {native => runtime}/src/debug/basic.rs (100%) rename {native => runtime}/src/debug/null.rs (100%) rename {native => runtime}/src/font.rs (100%) rename {native => runtime}/src/keyboard.rs (100%) rename {native => runtime}/src/lib.rs (100%) rename {native => runtime}/src/program.rs (100%) rename {native => runtime}/src/program/state.rs (100%) rename {native => runtime}/src/system.rs (100%) rename {native => runtime}/src/system/action.rs (100%) rename {native => runtime}/src/system/information.rs (100%) rename {native => runtime}/src/user_interface.rs (96%) rename {winit => runtime}/src/window.rs (79%) rename {native => runtime}/src/window/action.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 49a52311a1..a677569a24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ members = [ "core", "futures", "graphics", - "native", + "runtime", "renderer", "style", "tiny_skia", diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 16e2170966..5849f730d1 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,7 +1,7 @@ use iced_wgpu::Renderer; use iced_widget::{slider, text_input, Column, Row, Text}; use iced_winit::core::{Alignment, Color, Element, Length}; -use iced_winit::native::{Command, Program}; +use iced_winit::runtime::{Command, Program}; use iced_winit::style::Theme; pub struct Controls { diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index c1f1f076d3..9707eda78a 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -8,8 +8,8 @@ use iced_wgpu::graphics::Viewport; use iced_wgpu::{wgpu, Backend, Renderer, Settings}; use iced_winit::core::renderer; use iced_winit::core::{Color, Size}; -use iced_winit::native::program; -use iced_winit::native::Debug; +use iced_winit::runtime::program; +use iced_winit::runtime::Debug; use iced_winit::style::Theme; use iced_winit::{conversion, futures, winit, Clipboard}; diff --git a/native/src/window.rs b/native/src/window.rs deleted file mode 100644 index aa3f35c71b..0000000000 --- a/native/src/window.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Build window-based GUI applications. -mod action; - -pub use action::Action; - -use crate::core::time::Instant; -use crate::core::window::Event; -use crate::futures::subscription::{self, Subscription}; - -/// Subscribes to the frames of the window of the running application. -/// -/// The resulting [`Subscription`] will produce items at a rate equal to the -/// refresh rate of the window. Note that this rate may be variable, as it is -/// normally managed by the graphics driver and/or the OS. -/// -/// In any case, this [`Subscription`] is useful to smoothly draw application-driven -/// animations without missing any frames. -pub fn frames() -> Subscription { - subscription::raw_events(|event, _status| match event { - iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), - _ => None, - }) -} diff --git a/native/Cargo.toml b/runtime/Cargo.toml similarity index 95% rename from native/Cargo.toml rename to runtime/Cargo.toml index bc4e7ca19c..2d3e8db3a2 100644 --- a/native/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "iced_native" +name = "iced_runtime" version = "0.9.1" authors = ["Héctor Ramón Jiménez "] edition = "2021" diff --git a/native/README.md b/runtime/README.md similarity index 100% rename from native/README.md rename to runtime/README.md diff --git a/native/src/clipboard.rs b/runtime/src/clipboard.rs similarity index 67% rename from native/src/clipboard.rs rename to runtime/src/clipboard.rs index e727c4a740..bc45091266 100644 --- a/native/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -1,5 +1,6 @@ //! Access the clipboard. -use iced_futures::MaybeSend; +use crate::command::{self, Command}; +use crate::futures::MaybeSend; use std::fmt; @@ -38,3 +39,15 @@ impl fmt::Debug for Action { } } } + +/// Read the current contents of the clipboard. +pub fn read( + f: impl Fn(Option) -> Message + 'static, +) -> Command { + Command::single(command::Action::Clipboard(Action::Read(Box::new(f)))) +} + +/// Write the given contents to the clipboard. +pub fn write(contents: String) -> Command { + Command::single(command::Action::Clipboard(Action::Write(contents))) +} diff --git a/native/src/command.rs b/runtime/src/command.rs similarity index 100% rename from native/src/command.rs rename to runtime/src/command.rs diff --git a/native/src/command/action.rs b/runtime/src/command/action.rs similarity index 100% rename from native/src/command/action.rs rename to runtime/src/command/action.rs diff --git a/native/src/debug/basic.rs b/runtime/src/debug/basic.rs similarity index 100% rename from native/src/debug/basic.rs rename to runtime/src/debug/basic.rs diff --git a/native/src/debug/null.rs b/runtime/src/debug/null.rs similarity index 100% rename from native/src/debug/null.rs rename to runtime/src/debug/null.rs diff --git a/native/src/font.rs b/runtime/src/font.rs similarity index 100% rename from native/src/font.rs rename to runtime/src/font.rs diff --git a/native/src/keyboard.rs b/runtime/src/keyboard.rs similarity index 100% rename from native/src/keyboard.rs rename to runtime/src/keyboard.rs diff --git a/native/src/lib.rs b/runtime/src/lib.rs similarity index 100% rename from native/src/lib.rs rename to runtime/src/lib.rs diff --git a/native/src/program.rs b/runtime/src/program.rs similarity index 100% rename from native/src/program.rs rename to runtime/src/program.rs diff --git a/native/src/program/state.rs b/runtime/src/program/state.rs similarity index 100% rename from native/src/program/state.rs rename to runtime/src/program/state.rs diff --git a/native/src/system.rs b/runtime/src/system.rs similarity index 100% rename from native/src/system.rs rename to runtime/src/system.rs diff --git a/native/src/system/action.rs b/runtime/src/system/action.rs similarity index 100% rename from native/src/system/action.rs rename to runtime/src/system/action.rs diff --git a/native/src/system/information.rs b/runtime/src/system/information.rs similarity index 100% rename from native/src/system/information.rs rename to runtime/src/system/information.rs diff --git a/native/src/user_interface.rs b/runtime/src/user_interface.rs similarity index 96% rename from native/src/user_interface.rs rename to runtime/src/user_interface.rs index 315027fa08..2c76fd8a8a 100644 --- a/native/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -32,7 +32,7 @@ pub struct UserInterface<'a, Message, Renderer> { impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> where - Renderer: iced_core::Renderer, + Renderer: crate::core::Renderer, { /// Builds a user interface for an [`Element`]. /// @@ -46,7 +46,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_native::core::renderer::Null as Renderer; + /// # pub use iced_runtime::core::renderer::Null as Renderer; /// # } /// # /// # pub struct Counter; @@ -56,8 +56,8 @@ where /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } - /// use iced_native::core::Size; - /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_runtime::core::Size; + /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// /// // Initialization @@ -119,7 +119,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_native::core::renderer::Null as Renderer; + /// # pub use iced_runtime::core::renderer::Null as Renderer; /// # } /// # /// # pub struct Counter; @@ -129,8 +129,8 @@ where /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } - /// use iced_native::core::{clipboard, Size, Point}; - /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_runtime::core::{clipboard, Size, Point}; + /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// /// let mut counter = Counter::new(); @@ -348,7 +348,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_native::core::renderer::Null as Renderer; + /// # pub use iced_runtime::core::renderer::Null as Renderer; /// # pub type Theme = (); /// # } /// # @@ -359,10 +359,10 @@ where /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } - /// use iced_native::core::clipboard; - /// use iced_native::core::renderer; - /// use iced_native::core::{Element, Size, Point}; - /// use iced_native::user_interface::{self, UserInterface}; + /// use iced_runtime::core::clipboard; + /// use iced_runtime::core::renderer; + /// use iced_runtime::core::{Element, Size, Point}; + /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::{Renderer, Theme}; /// /// let mut counter = Counter::new(); diff --git a/winit/src/window.rs b/runtime/src/window.rs similarity index 79% rename from winit/src/window.rs rename to runtime/src/window.rs index 6ac58e2073..236064f77c 100644 --- a/winit/src/window.rs +++ b/runtime/src/window.rs @@ -1,7 +1,27 @@ -//! Interact with the window of your application. -use crate::core::window::{Mode, UserAttention}; -use crate::native::command::{self, Command}; -use crate::native::window::Action; +//! Build window-based GUI applications. +mod action; + +pub use action::Action; + +use crate::command::{self, Command}; +use crate::core::time::Instant; +use crate::core::window::{Event, Mode, UserAttention}; +use crate::futures::subscription::{self, Subscription}; + +/// Subscribes to the frames of the window of the running application. +/// +/// The resulting [`Subscription`] will produce items at a rate equal to the +/// refresh rate of the window. Note that this rate may be variable, as it is +/// normally managed by the graphics driver and/or the OS. +/// +/// In any case, this [`Subscription`] is useful to smoothly draw application-driven +/// animations without missing any frames. +pub fn frames() -> Subscription { + subscription::raw_events(|event, _status| match event { + iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), + _ => None, + }) +} /// Closes the current window and exits the application. pub fn close() -> Command { diff --git a/native/src/window/action.rs b/runtime/src/window/action.rs similarity index 100% rename from native/src/window/action.rs rename to runtime/src/window/action.rs diff --git a/src/advanced.rs b/src/advanced.rs index 9621c3bc02..7afba85ccc 100644 --- a/src/advanced.rs +++ b/src/advanced.rs @@ -10,5 +10,5 @@ pub use crate::core::{Clipboard, Shell}; pub mod subscription { //! Write your own subscriptions. - pub use crate::native::futures::subscription::{EventStream, Recipe}; + pub use crate::runtime::futures::subscription::{EventStream, Recipe}; } diff --git a/src/application.rs b/src/application.rs index f5cf3317ea..c9ddf840c3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -215,7 +215,7 @@ pub trait Application: Sized { struct Instance(A); -impl crate::native::Program for Instance +impl crate::runtime::Program for Instance where A: Application, { diff --git a/src/lib.rs b/src/lib.rs index b9f87d5dd9..c59d505804 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,7 @@ use iced_widget::renderer; use iced_widget::style; use iced_winit as shell; use iced_winit::core; -use iced_winit::native; +use iced_winit::runtime; pub use iced_futures::futures; @@ -192,11 +192,11 @@ pub use crate::core::{ color, Alignment, Background, Color, ContentFit, Length, Padding, Point, Rectangle, Size, Vector, }; -pub use crate::native::Command; +pub use crate::runtime::Command; pub mod clipboard { //! Access the clipboard. - pub use crate::shell::clipboard::{read, write}; + pub use crate::runtime::clipboard::{read, write}; } pub mod executor { @@ -219,7 +219,7 @@ pub mod executor { pub mod font { //! Load and use fonts. pub use crate::core::font::*; - pub use crate::native::font::*; + pub use crate::runtime::font::*; } pub mod keyboard { @@ -242,7 +242,7 @@ pub mod subscription { #[cfg(feature = "system")] pub mod system { //! Retrieve system information. - pub use crate::native::system::Information; + pub use crate::runtime::system::Information; pub use crate::shell::system::*; } diff --git a/src/window.rs b/src/window.rs index 2623906552..824915b284 100644 --- a/src/window.rs +++ b/src/window.rs @@ -9,5 +9,4 @@ pub use position::Position; pub use settings::Settings; pub use crate::core::window::*; -pub use crate::native::window::*; -pub use crate::shell::window::*; +pub use crate::runtime::window::*; diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index c4c36abad5..08e79bb8c6 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -17,10 +17,6 @@ rustc-hash = "1.1" ouroboros = "0.15" kurbo = "0.9" -[dependencies.iced_native] -version = "0.9" -path = "../native" - [dependencies.iced_graphics] version = "0.7" path = "../graphics" diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 7a5034c2aa..c4edadb333 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -187,7 +187,7 @@ impl Pipeline { &self, content: &str, size: f32, - font: iced_native::Font, + font: Font, bounds: Size, point: Point, _nearest_only: bool, diff --git a/widget/Cargo.toml b/widget/Cargo.toml index fb617079c8..4c23f3e86e 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -15,9 +15,9 @@ unicode-segmentation = "1.6" num-traits = "0.2" thiserror = "1" -[dependencies.iced_native] +[dependencies.iced_runtime] version = "0.9" -path = "../native" +path = "../runtime" [dependencies.iced_renderer] version = "0.1" diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 1a73c16f13..a43e7248a3 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -5,12 +5,12 @@ use crate::container::{self, Container}; use crate::core; use crate::core::widget::operation; use crate::core::{Element, Length, Pixels}; -use crate::native::Command; use crate::overlay; use crate::pick_list::{self, PickList}; use crate::progress_bar::{self, ProgressBar}; use crate::radio::{self, Radio}; use crate::rule::{self, Rule}; +use crate::runtime::Command; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; use crate::text::{self, Text}; diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 4c1e7c1c0a..a3e7c8bc35 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -14,10 +14,10 @@ )] #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] -pub use iced_native as native; -pub use iced_native::core; pub use iced_renderer as renderer; pub use iced_renderer::graphics; +pub use iced_runtime as runtime; +pub use iced_runtime::core; pub use iced_style as style; mod column; diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 49c780deac..5a7481f727 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -13,7 +13,7 @@ use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; -use crate::native::Command; +use crate::runtime::Command; pub use crate::style::scrollable::{Scrollbar, Scroller, StyleSheet}; pub use operation::scrollable::RelativeOffset; diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index d1c48fbdd5..d066109aab 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -28,7 +28,7 @@ use crate::core::{ Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; -use crate::native::Command; +use crate::runtime::Command; pub use iced_style::text_input::{Appearance, StyleSheet}; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 21c14f68ba..bfd2209344 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -13,7 +13,7 @@ categories = ["gui"] [features] trace = ["tracing", "tracing-core", "tracing-subscriber"] chrome-trace = ["trace", "tracing-chrome"] -debug = ["iced_native/debug"] +debug = ["iced_runtime/debug"] system = ["sysinfo"] application = [] @@ -27,9 +27,9 @@ version = "0.27" git = "https://github.com/iced-rs/winit.git" rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" -[dependencies.iced_native] +[dependencies.iced_runtime] version = "0.9" -path = "../native" +path = "../runtime" [dependencies.iced_graphics] version = "0.7" diff --git a/winit/src/application.rs b/winit/src/application.rs index d863c84648..9666fcae05 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -16,10 +16,10 @@ use crate::core::{Event, Size}; use crate::futures::futures; use crate::futures::{Executor, Runtime, Subscription}; use crate::graphics::compositor::{self, Compositor}; -use crate::native::clipboard; -use crate::native::program::Program; -use crate::native::user_interface::{self, UserInterface}; -use crate::native::{Command, Debug}; +use crate::runtime::clipboard; +use crate::runtime::program::Program; +use crate::runtime::user_interface::{self, UserInterface}; +use crate::runtime::{Command, Debug}; use crate::style::application::{Appearance, StyleSheet}; use crate::{Clipboard, Error, Proxy, Settings}; @@ -709,9 +709,9 @@ pub fn run_command( E: Executor, ::Theme: StyleSheet, { - use iced_native::command; - use iced_native::system; - use iced_native::window; + use crate::runtime::command; + use crate::runtime::system; + use crate::runtime::window; for action in command.actions() { match action { diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index b727e03ca9..c37ccca614 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -3,7 +3,7 @@ use crate::conversion; use crate::core; use crate::core::{Color, Point, Size}; use crate::graphics::Viewport; -use crate::native::Debug; +use crate::runtime::Debug; use crate::Application; use std::marker::PhantomData; diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 2250913080..7271441d60 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,6 +1,4 @@ //! Access the clipboard. -use crate::native::clipboard::Action; -use crate::native::command::{self, Command}; /// A buffer for short-term storage and transfer within and between /// applications. @@ -64,15 +62,3 @@ impl crate::core::Clipboard for Clipboard { self.write(contents) } } - -/// Read the current contents of the clipboard. -pub fn read( - f: impl Fn(Option) -> Message + 'static, -) -> Command { - Command::single(command::Action::Clipboard(Action::Read(Box::new(f)))) -} - -/// Write the given contents to the clipboard. -pub fn write(contents: String) -> Command { - Command::single(command::Action::Clipboard(Action::Write(contents))) -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 0d8c04d3d3..5cde510ac4 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -31,9 +31,9 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] pub use iced_graphics as graphics; -pub use iced_native as native; -pub use iced_native::core; -pub use iced_native::futures; +pub use iced_runtime as runtime; +pub use iced_runtime::core; +pub use iced_runtime::futures; pub use iced_style as style; pub use winit; @@ -42,7 +42,6 @@ pub mod application; pub mod clipboard; pub mod conversion; pub mod settings; -pub mod window; #[cfg(feature = "system")] pub mod system; diff --git a/winit/src/system.rs b/winit/src/system.rs index 3a6a8a8ec3..069efa29fd 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -1,7 +1,7 @@ //! Access the native system. use crate::graphics::compositor; -use crate::native::command::{self, Command}; -use crate::native::system::{Action, Information}; +use crate::runtime::command::{self, Command}; +use crate::runtime::system::{Action, Information}; /// Query for available system information. pub fn fetch_information( From 1c36446115dcb46c373fbc62b9e4f1941bd5a383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 5 Mar 2023 06:36:36 +0100 Subject: [PATCH 45/57] Fix `README` of `iced_runtime` --- runtime/README.md | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/runtime/README.md b/runtime/README.md index 996daa7687..497fd145dd 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -1,22 +1,10 @@ -# `iced_native` +# `iced_runtime` [![Documentation](https://docs.rs/iced_native/badge.svg)][documentation] [![Crates.io](https://img.shields.io/crates/v/iced_native.svg)](https://crates.io/crates/iced_native) [![License](https://img.shields.io/crates/l/iced_native.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) -`iced_native` takes [`iced_core`] and builds a native runtime on top of it, featuring: -- A custom layout engine, greatly inspired by [`druid`] -- Event handling for all the built-in widgets -- A renderer-agnostic API - -To achieve this, it introduces a bunch of reusable interfaces: -- A `Widget` trait, which is used to implement new widgets: from layout requirements to event and drawing logic. -- A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic. -- A `Windowed` trait, leveraging [`raw-window-handle`], which can be implemented by graphical renderers that target _windows_. Window-based shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic. - -

- The native target -

+`iced_runtime` takes [`iced_core`] and builds a native runtime on top of it. [documentation]: https://docs.rs/iced_native [`iced_core`]: ../core @@ -25,10 +13,10 @@ To achieve this, it introduces a bunch of reusable interfaces: [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle ## Installation -Add `iced_native` as a dependency in your `Cargo.toml`: +Add `iced_runtime` as a dependency in your `Cargo.toml`: ```toml -iced_native = "0.9" +iced_runtime = "0.9" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If From 06bbcc310e6e759a0839df6ca391ea5e0f0ee609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 5 Mar 2023 06:40:20 +0100 Subject: [PATCH 46/57] Move `webgl` feature selection for `wgpu` into `iced_wgpu` --- examples/integration/Cargo.toml | 2 +- renderer/Cargo.toml | 10 ++++------ wgpu/Cargo.toml | 4 +++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index 6039024228..f429977fc4 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu", features = ["webgl"] } +iced_wgpu = { path = "../../wgpu" } iced_widget = { path = "../../widget" } env_logger = "0.8" diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index d0420ad04e..560bf2e143 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -17,12 +17,10 @@ thiserror = "1" version = "0.7" path = "../graphics" +[dependencies.iced_wgpu] +version = "0.9" +path = "../wgpu" + [dependencies.iced_tiny_skia] version = "0.1" path = "../tiny_skia" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.9", path = "../wgpu" } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.9", path = "../wgpu", features = ["webgl"] } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 50a81a9111..5601c0de43 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -23,7 +23,6 @@ dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] geometry = ["iced_graphics/geometry", "lyon"] spirv = ["wgpu/spirv"] -webgl = ["wgpu/webgl"] [dependencies] wgpu = "0.14" @@ -36,6 +35,9 @@ once_cell = "1.0" rustc-hash = "1.1" ouroboros = "0.15" +[target.'cfg(target_arch = "wasm32")'.dependencies] +wgpu = { version = "0.14", features = ["webgl"] } + [dependencies.twox-hash] version = "1.6" default-features = false From 9b4bcd287a7f4822314e158990d1dc023d5aab51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 6 Mar 2023 22:10:13 +0100 Subject: [PATCH 47/57] Introduce backend feature flags in `iced_renderer` --- Cargo.toml | 6 ++ renderer/Cargo.toml | 6 +- renderer/src/backend.rs | 76 +++++++++---------------- renderer/src/compositor.rs | 112 +++++++++++++++++++++++++++---------- renderer/src/geometry.rs | 41 +++++++++----- renderer/src/lib.rs | 3 + 6 files changed, 149 insertions(+), 95 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a677569a24..38c35f43bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,11 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +default = ["wgpu", "tiny-skia"] +# Enable the `wgpu` GPU-accelerated renderer backend +wgpu = ["iced_renderer/wgpu"] +# Enable the `tiny-skia` software renderer backend +tiny-skia = ["iced_renderer/tiny-skia"] # Enables the `Image` widget image = ["iced_widget/image", "image_rs"] # Enables the `Svg` widget @@ -58,6 +63,7 @@ members = [ [dependencies] iced_core = { version = "0.8", path = "core" } iced_futures = { version = "0.6", path = "futures" } +iced_renderer = { version = "0.1", path = "renderer" } iced_widget = { version = "0.1", path = "widget" } iced_winit = { version = "0.8", path = "winit", features = ["application"] } thiserror = "1" diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 560bf2e143..629c11bae4 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -4,9 +4,11 @@ version = "0.1.0" edition = "2021" [features] +wgpu = ["iced_wgpu"] +tiny-skia = ["iced_tiny_skia"] image = ["iced_wgpu/image", "iced_tiny_skia/image"] svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"] -geometry = ["iced_wgpu/geometry", "iced_tiny_skia/geometry"] +geometry = ["iced_graphics/geometry", "iced_wgpu?/geometry", "iced_tiny_skia?/geometry"] tracing = ["iced_wgpu/tracing"] [dependencies] @@ -20,7 +22,9 @@ path = "../graphics" [dependencies.iced_wgpu] version = "0.9" path = "../wgpu" +optional = true [dependencies.iced_tiny_skia] version = "0.1" path = "../tiny_skia" +optional = true diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs index bf5da322af..e77b708bc1 100644 --- a/renderer/src/backend.rs +++ b/renderer/src/backend.rs @@ -6,16 +6,26 @@ use std::borrow::Cow; #[allow(clippy::large_enum_variant)] pub enum Backend { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::Backend), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::Backend), } +macro_rules! delegate { + ($backend:expr, $name:ident, $body:expr) => { + match $backend { + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + #[cfg(feature = "tiny-skia")] + Self::TinySkia($name) => $body, + } + }; +} + impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { - match self { - Self::Wgpu(backend) => backend.trim_measurements(), - Self::TinySkia(backend) => backend.trim_measurements(), - } + delegate!(self, backend, backend.trim_measurements()); } } @@ -25,17 +35,11 @@ impl backend::Text for Backend { const ARROW_DOWN_ICON: char = '\u{e800}'; fn default_font(&self) -> Font { - match self { - Self::Wgpu(backend) => backend.default_font(), - Self::TinySkia(backend) => backend.default_font(), - } + delegate!(self, backend, backend.default_font()) } fn default_size(&self) -> f32 { - match self { - Self::Wgpu(backend) => backend.default_size(), - Self::TinySkia(backend) => backend.default_size(), - } + delegate!(self, backend, backend.default_size()) } fn measure( @@ -45,14 +49,7 @@ impl backend::Text for Backend { font: Font, bounds: Size, ) -> (f32, f32) { - match self { - Self::Wgpu(backend) => { - backend.measure(contents, size, font, bounds) - } - Self::TinySkia(backend) => { - backend.measure(contents, size, font, bounds) - } - } + delegate!(self, backend, backend.measure(contents, size, font, bounds)) } fn hit_test( @@ -64,45 +61,29 @@ impl backend::Text for Backend { position: Point, nearest_only: bool, ) -> Option { - match self { - Self::Wgpu(backend) => backend.hit_test( - contents, - size, - font, - bounds, - position, - nearest_only, - ), - Self::TinySkia(backend) => backend.hit_test( + delegate!( + self, + backend, + backend.hit_test( contents, size, font, bounds, position, - nearest_only, - ), - } + nearest_only + ) + ) } fn load_font(&mut self, font: Cow<'static, [u8]>) { - match self { - Self::Wgpu(backend) => { - backend.load_font(font); - } - Self::TinySkia(backend) => { - backend.load_font(font); - } - } + delegate!(self, backend, backend.load_font(font)); } } #[cfg(feature = "image")] impl backend::Image for Backend { fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { - match self { - Self::Wgpu(backend) => backend.dimensions(handle), - Self::TinySkia(backend) => backend.dimensions(handle), - } + delegate!(self, backend, backend.dimensions(handle)) } } @@ -112,9 +93,6 @@ impl backend::Svg for Backend { &self, handle: &crate::core::svg::Handle, ) -> Size { - match self { - Self::Wgpu(backend) => backend.viewport_dimensions(handle), - Self::TinySkia(backend) => backend.viewport_dimensions(handle), - } + delegate!(self, backend, backend.viewport_dimensions(handle)) } } diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index 0cdcb2938f..218e7e332e 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -1,17 +1,21 @@ use crate::core::Color; use crate::graphics::compositor::{Information, SurfaceError}; use crate::graphics::{Error, Viewport}; -use crate::{Backend, Renderer, Settings}; +use crate::{Renderer, Settings}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; pub enum Compositor { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Compositor), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::window::Compositor), } pub enum Surface { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::window::Surface), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::window::Surface), } @@ -22,32 +26,65 @@ impl crate::graphics::Compositor for Compositor { fn new( settings: Self::Settings, - _compatible_window: Option<&W>, + compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - //let (compositor, backend) = iced_wgpu::window::compositor::new( - // iced_wgpu::Settings { - // default_font: settings.default_font, - // default_text_size: settings.default_text_size, - // antialiasing: settings.antialiasing, - // ..iced_wgpu::Settings::from_env() - // }, - // compatible_window, - //)?; - - //Ok(( - // Self::Wgpu(compositor), - // Renderer::new(Backend::Wgpu(backend)), - //)) - let (compositor, backend) = - iced_tiny_skia::window::compositor::new(iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }); - - Ok(( - Self::TinySkia(compositor), - Renderer::new(Backend::TinySkia(backend)), - )) + #[cfg(feature = "wgpu")] + let new_wgpu = |settings: Self::Settings, compatible_window| { + let (compositor, backend) = iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + compatible_window, + )?; + + Ok(( + Self::Wgpu(compositor), + Renderer::new(crate::Backend::Wgpu(backend)), + )) + }; + + #[cfg(feature = "tiny-skia")] + let new_tiny_skia = |settings: Self::Settings, _compatible_window| { + let (compositor, backend) = iced_tiny_skia::window::compositor::new( + iced_tiny_skia::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + }, + ); + + Ok(( + Self::TinySkia(compositor), + Renderer::new(crate::Backend::TinySkia(backend)), + )) + }; + + let fail = |_, _| Err(Error::GraphicsAdapterNotFound); + + let candidates = &[ + #[cfg(feature = "wgpu")] + new_wgpu, + #[cfg(feature = "tiny-skia")] + new_tiny_skia, + fail, + ]; + + let mut error = Error::GraphicsAdapterNotFound; + + for candidate in candidates { + match candidate(settings, compatible_window) { + Ok((compositor, renderer)) => { + return Ok((compositor, renderer)) + } + Err(new_error) => { + error = new_error; + } + } + } + + Err(error) } fn create_surface( @@ -57,9 +94,11 @@ impl crate::graphics::Compositor for Compositor { height: u32, ) -> Surface { match self { + #[cfg(feature = "wgpu")] Self::Wgpu(compositor) => { Surface::Wgpu(compositor.create_surface(window, width, height)) } + #[cfg(feature = "tiny-skia")] Self::TinySkia(compositor) => Surface::TinySkia( compositor.create_surface(window, width, height), ), @@ -73,19 +112,26 @@ impl crate::graphics::Compositor for Compositor { height: u32, ) { match (self, surface) { + #[cfg(feature = "wgpu")] (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { compositor.configure_surface(surface, width, height); } + #[cfg(feature = "tiny-skia")] (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { compositor.configure_surface(surface, width, height); } - _ => unreachable!(), + #[allow(unreachable_patterns)] + _ => panic!( + "The provided surface is not compatible with the compositor." + ), } } fn fetch_information(&self) -> Information { match self { + #[cfg(feature = "wgpu")] Self::Wgpu(compositor) => compositor.fetch_information(), + #[cfg(feature = "tiny-skia")] Self::TinySkia(compositor) => compositor.fetch_information(), } } @@ -100,9 +146,10 @@ impl crate::graphics::Compositor for Compositor { ) -> Result<(), SurfaceError> { renderer.with_primitives(|backend, primitives| { match (self, backend, surface) { + #[cfg(feature = "wgpu")] ( Self::Wgpu(compositor), - Backend::Wgpu(backend), + crate::Backend::Wgpu(backend), Surface::Wgpu(surface), ) => iced_wgpu::window::compositor::present( compositor, @@ -113,9 +160,10 @@ impl crate::graphics::Compositor for Compositor { background_color, overlay, ), + #[cfg(feature = "tiny-skia")] ( Self::TinySkia(compositor), - Backend::TinySkia(backend), + crate::Backend::TinySkia(backend), Surface::TinySkia(surface), ) => iced_tiny_skia::window::compositor::present( compositor, @@ -126,7 +174,11 @@ impl crate::graphics::Compositor for Compositor { background_color, overlay, ), - _ => unreachable!(), + #[allow(unreachable_patterns)] + _ => panic!( + "The provided renderer or surface are not compatible \ + with the compositor." + ), } }) } diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs index 361fc86bcb..21ef2c0671 100644 --- a/renderer/src/geometry.rs +++ b/renderer/src/geometry.rs @@ -7,14 +7,18 @@ use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; use crate::Backend; pub enum Frame { + #[cfg(feature = "wgpu")] Wgpu(iced_wgpu::geometry::Frame), + #[cfg(feature = "tiny-skia")] TinySkia(iced_tiny_skia::geometry::Frame), } macro_rules! delegate { - ($frame:expr, $name:ident => $body:expr) => { + ($frame:expr, $name:ident, $body:expr) => { match $frame { + #[cfg(feature = "wgpu")] Self::Wgpu($name) => $body, + #[cfg(feature = "tiny-skia")] Self::TinySkia($name) => $body, } }; @@ -23,9 +27,11 @@ macro_rules! delegate { impl Frame { pub fn new(renderer: &crate::Renderer, size: Size) -> Self { match renderer.backend() { + #[cfg(feature = "wgpu")] Backend::Wgpu(_) => { Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) } + #[cfg(feature = "tiny-skia")] Backend::TinySkia(_) => { Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) } @@ -35,31 +41,31 @@ impl Frame { /// Returns the width of the [`Frame`]. #[inline] pub fn width(&self) -> f32 { - delegate!(self, frame => frame.width()) + delegate!(self, frame, frame.width()) } /// Returns the height of the [`Frame`]. #[inline] pub fn height(&self) -> f32 { - delegate!(self, frame => frame.height()) + delegate!(self, frame, frame.height()) } /// Returns the dimensions of the [`Frame`]. #[inline] pub fn size(&self) -> Size { - delegate!(self, frame => frame.size()) + delegate!(self, frame, frame.size()) } /// Returns the coordinate of the center of the [`Frame`]. #[inline] pub fn center(&self) -> Point { - delegate!(self, frame => frame.center()) + delegate!(self, frame, frame.center()) } /// Draws the given [`Path`] on the [`Frame`] by filling it with the /// provided style. pub fn fill(&mut self, path: &Path, fill: impl Into) { - delegate!(self, frame => frame.fill(path, fill)); + delegate!(self, frame, frame.fill(path, fill)); } /// Draws an axis-aligned rectangle given its top-left corner coordinate and @@ -70,13 +76,13 @@ impl Frame { size: Size, fill: impl Into, ) { - delegate!(self, frame => frame.fill_rectangle(top_left, size, fill)); + delegate!(self, frame, frame.fill_rectangle(top_left, size, fill)); } /// Draws the stroke of the given [`Path`] on the [`Frame`] with the /// provided style. pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - delegate!(self, frame => frame.stroke(path, stroke)); + delegate!(self, frame, frame.stroke(path, stroke)); } /// Draws the characters of the given [`Text`] on the [`Frame`], filling @@ -95,7 +101,7 @@ impl Frame { /// /// [`Canvas`]: crate::widget::Canvas pub fn fill_text(&mut self, text: impl Into) { - delegate!(self, frame => frame.fill_text(text)); + delegate!(self, frame, frame.fill_text(text)); } /// Stores the current transform of the [`Frame`] and executes the given @@ -105,11 +111,11 @@ impl Frame { /// operations in different coordinate systems. #[inline] pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { - delegate!(self, frame => frame.push_transform()); + delegate!(self, frame, frame.push_transform()); f(self); - delegate!(self, frame => frame.pop_transform()); + delegate!(self, frame, frame.pop_transform()); } /// Executes the given drawing operations within a [`Rectangle`] region, @@ -121,9 +127,11 @@ impl Frame { #[inline] pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { let mut frame = match self { + #[cfg(feature = "wgpu")] Self::Wgpu(_) => { Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) } + #[cfg(feature = "tiny-skia")] Self::TinySkia(_) => Self::TinySkia( iced_tiny_skia::geometry::Frame::new(region.size()), ), @@ -134,12 +142,15 @@ impl Frame { let translation = Vector::new(region.x, region.y); match (self, frame) { + #[cfg(feature = "wgpu")] (Self::Wgpu(target), Self::Wgpu(frame)) => { target.clip(frame, translation); } + #[cfg(feature = "tiny-skia")] (Self::TinySkia(target), Self::TinySkia(frame)) => { target.clip(frame, translation); } + #[allow(unreachable_patterns)] _ => unreachable!(), }; } @@ -147,22 +158,22 @@ impl Frame { /// Applies a translation to the current transform of the [`Frame`]. #[inline] pub fn translate(&mut self, translation: Vector) { - delegate!(self, frame => frame.translate(translation)); + delegate!(self, frame, frame.translate(translation)); } /// Applies a rotation in radians to the current transform of the [`Frame`]. #[inline] pub fn rotate(&mut self, angle: f32) { - delegate!(self, frame => frame.rotate(angle)); + delegate!(self, frame, frame.rotate(angle)); } /// Applies a scaling to the current transform of the [`Frame`]. #[inline] pub fn scale(&mut self, scale: f32) { - delegate!(self, frame => frame.scale(scale)); + delegate!(self, frame, frame.scale(scale)); } pub fn into_geometry(self) -> Geometry { - Geometry(delegate!(self, frame => frame.into_primitive())) + Geometry(delegate!(self, frame, frame.into_primitive())) } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 22ec7bd1d8..ba737b112d 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))] +compile_error!("No backend selected. Enable at least one backend feature: `wgpu` or `tiny-skia`."); + pub mod compositor; #[cfg(feature = "geometry")] From 3a26baa564524b0f25c5cb180b592c8b004b68a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 03:47:49 +0100 Subject: [PATCH 48/57] Remove `image` abstractions in `iced_graphics` --- graphics/Cargo.toml | 23 +-- graphics/src/image.rs | 98 +++++++++- graphics/src/image/raster.rs | 242 ------------------------- graphics/src/lib.rs | 4 +- wgpu/Cargo.toml | 20 +- wgpu/src/backend.rs | 2 +- wgpu/src/image.rs | 39 ++-- wgpu/src/image/atlas.rs | 190 ++++++++++--------- wgpu/src/image/atlas/entry.rs | 6 +- wgpu/src/image/raster.rs | 121 +++++++++++++ {graphics => wgpu}/src/image/vector.rs | 43 ++--- 11 files changed, 352 insertions(+), 436 deletions(-) delete mode 100644 graphics/src/image/raster.rs create mode 100644 wgpu/src/image/raster.rs rename {graphics => wgpu}/src/image/vector.rs (86%) diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 4bb22b6ce8..a08c670735 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -11,22 +11,9 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -svg = ["resvg"] -image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] -png = ["image_rs/png"] -jpeg = ["image_rs/jpeg"] -jpeg_rayon = ["image_rs/jpeg_rayon"] -gif = ["image_rs/gif"] -webp = ["image_rs/webp"] -pnm = ["image_rs/pnm"] -ico = ["image_rs/ico"] -bmp = ["image_rs/bmp"] -hdr = ["image_rs/hdr"] -dds = ["image_rs/dds"] -farbfeld = ["image_rs/farbfeld"] geometry = ["lyon_path"] opengl = [] -image_rs = ["kamadak-exif"] +image = ["dep:image", "kamadak-exif"] [dependencies] glam = "0.21.3" @@ -47,14 +34,8 @@ path = "../core" version = "0.8" optional = true -[dependencies.image_rs] +[dependencies.image] version = "0.24" -package = "image" -default-features = false -optional = true - -[dependencies.resvg] -version = "0.29" optional = true [dependencies.kamadak-exif] diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 04f4ff9d0a..2f634252cb 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -1,10 +1,94 @@ -//! Render images. -#[cfg(feature = "image_rs")] -pub mod raster; +//! Load and operate on images. +use crate::core::image::{Data, Handle}; -#[cfg(feature = "svg")] -pub mod vector; +use bitflags::bitflags; -pub mod storage; +pub use ::image as image_rs; -pub use storage::Storage; +pub fn load(handle: &Handle) -> image_rs::ImageResult { + match handle.data() { + Data::Path(path) => { + let image = ::image::open(path)?; + + let operation = std::fs::File::open(path) + .ok() + .map(std::io::BufReader::new) + .and_then(|mut reader| Operation::from_exif(&mut reader).ok()) + .unwrap_or_else(Operation::empty); + + Ok(operation.perform(image)) + } + Data::Bytes(bytes) => { + let image = ::image::load_from_memory(bytes)?; + let operation = + Operation::from_exif(&mut std::io::Cursor::new(bytes)) + .ok() + .unwrap_or_else(Operation::empty); + + Ok(operation.perform(image)) + } + Data::Rgba { + width, + height, + pixels, + } => { + if let Some(image) = image_rs::ImageBuffer::from_vec( + *width, + *height, + pixels.to_vec(), + ) { + Ok(image_rs::DynamicImage::ImageRgba8(image)) + } else { + Err(image_rs::error::ImageError::Limits( + image_rs::error::LimitError::from_kind( + image_rs::error::LimitErrorKind::DimensionError, + ), + )) + } + } + } +} + +bitflags! { + struct Operation: u8 { + const FLIP_HORIZONTALLY = 0b001; + const ROTATE_180 = 0b010; + const FLIP_DIAGONALLY = 0b100; + } +} + +impl Operation { + // Meaning of the returned value is described e.g. at: + // https://magnushoff.com/articles/jpeg-orientation/ + fn from_exif(reader: &mut R) -> Result + where + R: std::io::BufRead + std::io::Seek, + { + let exif = exif::Reader::new().read_from_container(reader)?; + + Ok(exif + .get_field(exif::Tag::Orientation, exif::In::PRIMARY) + .and_then(|field| field.value.get_uint(0)) + .and_then(|value| u8::try_from(value).ok()) + .and_then(|value| Self::from_bits(value.saturating_sub(1))) + .unwrap_or_else(Self::empty)) + } + + fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage { + use image::imageops; + + if self.contains(Self::FLIP_DIAGONALLY) { + imageops::flip_vertical_in_place(&mut image) + } + + if self.contains(Self::ROTATE_180) { + imageops::rotate180_in_place(&mut image); + } + + if self.contains(Self::FLIP_HORIZONTALLY) { + imageops::flip_horizontal_in_place(&mut image); + } + + image + } +} diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs deleted file mode 100644 index 03211160e8..0000000000 --- a/graphics/src/image/raster.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Raster image loading and caching. -use crate::image::Storage; - -use iced_core::image; -use iced_core::Size; - -use bitflags::bitflags; -use std::collections::{HashMap, HashSet}; - -/// Entry in cache corresponding to an image handle -#[derive(Debug)] -pub enum Memory { - /// Image data on host - Host(::image_rs::ImageBuffer<::image_rs::Rgba, Vec>), - /// Storage entry - Device(T::Entry), - /// Image not found - NotFound, - /// Invalid image data - Invalid, -} - -impl Memory { - /// Width and height of image - pub fn dimensions(&self) -> Size { - use crate::image::storage::Entry; - - match self { - Memory::Host(image) => { - let (width, height) = image.dimensions(); - - Size::new(width, height) - } - Memory::Device(entry) => entry.size(), - Memory::NotFound => Size::new(1, 1), - Memory::Invalid => Size::new(1, 1), - } - } -} - -/// Caches image raster data -#[derive(Debug)] -pub struct Cache { - map: HashMap>, - hits: HashSet, -} - -impl Cache { - /// Load image - pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { - if self.contains(handle) { - return self.get(handle).unwrap(); - } - - let memory = match handle.data() { - image::Data::Path(path) => { - if let Ok(image) = image_rs::open(path) { - let operation = std::fs::File::open(path) - .ok() - .map(std::io::BufReader::new) - .and_then(|mut reader| { - Operation::from_exif(&mut reader).ok() - }) - .unwrap_or_else(Operation::empty); - - Memory::Host(operation.perform(image.to_rgba8())) - } else { - Memory::NotFound - } - } - image::Data::Bytes(bytes) => { - if let Ok(image) = image_rs::load_from_memory(bytes) { - let operation = - Operation::from_exif(&mut std::io::Cursor::new(bytes)) - .ok() - .unwrap_or_else(Operation::empty); - - Memory::Host(operation.perform(image.to_rgba8())) - } else { - Memory::Invalid - } - } - image::Data::Rgba { - width, - height, - pixels, - } => { - if let Some(image) = image_rs::ImageBuffer::from_vec( - *width, - *height, - pixels.to_vec(), - ) { - Memory::Host(image) - } else { - Memory::Invalid - } - } - }; - - self.insert(handle, memory); - self.get(handle).unwrap() - } - - /// Load image and upload raster data - pub fn upload( - &mut self, - handle: &image::Handle, - state: &mut T::State<'_>, - storage: &mut T, - ) -> Option<&T::Entry> { - let memory = self.load(handle); - - if let Memory::Host(image) = memory { - let (width, height) = image.dimensions(); - - let entry = storage.upload(width, height, image, state)?; - - *memory = Memory::Device(entry); - } - - if let Memory::Device(allocation) = memory { - Some(allocation) - } else { - None - } - } - - /// Trim cache misses from cache - pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { - let hits = &self.hits; - - self.map.retain(|k, memory| { - let retain = hits.contains(k); - - if !retain { - if let Memory::Device(entry) = memory { - storage.remove(entry, state); - } - } - - retain - }); - - self.hits.clear(); - } - - fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { - let _ = self.hits.insert(handle.id()); - - self.map.get_mut(&handle.id()) - } - - fn insert(&mut self, handle: &image::Handle, memory: Memory) { - let _ = self.map.insert(handle.id(), memory); - } - - fn contains(&self, handle: &image::Handle) -> bool { - self.map.contains_key(&handle.id()) - } -} - -impl Default for Cache { - fn default() -> Self { - Self { - map: HashMap::new(), - hits: HashSet::new(), - } - } -} - -bitflags! { - struct Operation: u8 { - const FLIP_HORIZONTALLY = 0b001; - const ROTATE_180 = 0b010; - const FLIP_DIAGONALLY = 0b100; - } -} - -impl Operation { - // Meaning of the returned value is described e.g. at: - // https://magnushoff.com/articles/jpeg-orientation/ - fn from_exif(reader: &mut R) -> Result - where - R: std::io::BufRead + std::io::Seek, - { - let exif = exif::Reader::new().read_from_container(reader)?; - - Ok(exif - .get_field(exif::Tag::Orientation, exif::In::PRIMARY) - .and_then(|field| field.value.get_uint(0)) - .and_then(|value| u8::try_from(value).ok()) - .and_then(|value| Self::from_bits(value.saturating_sub(1))) - .unwrap_or_else(Self::empty)) - } - - fn perform

( - self, - image: image_rs::ImageBuffer>, - ) -> image_rs::ImageBuffer> - where - P: image_rs::Pixel + 'static, - { - use image_rs::imageops; - - let mut image = if self.contains(Self::FLIP_DIAGONALLY) { - flip_diagonally(image) - } else { - image - }; - - if self.contains(Self::ROTATE_180) { - imageops::rotate180_in_place(&mut image); - } - - if self.contains(Self::FLIP_HORIZONTALLY) { - imageops::flip_horizontal_in_place(&mut image); - } - - image - } -} - -fn flip_diagonally( - image: I, -) -> image_rs::ImageBuffer::Subpixel>> -where - I: image_rs::GenericImage, - I::Pixel: 'static, -{ - let (width, height) = image.dimensions(); - let mut out = image_rs::ImageBuffer::new(height, width); - - for x in 0..width { - for y in 0..height { - let p = image.get_pixel(x, y); - - out.put_pixel(y, x, p); - } - } - - out -} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index c6f9cf5717..0c50db52d5 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -28,13 +28,15 @@ mod viewport; pub mod backend; pub mod compositor; -pub mod image; pub mod primitive; pub mod renderer; #[cfg(feature = "geometry")] pub mod geometry; +#[cfg(feature = "image")] +pub mod image; + pub use antialiasing::Antialiasing; pub use backend::Backend; pub use compositor::Compositor; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 5601c0de43..6a313d4f69 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,21 +8,9 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [features] -svg = ["iced_graphics/svg"] -image = ["iced_graphics/image"] -png = ["iced_graphics/png"] -jpeg = ["iced_graphics/jpeg"] -jpeg_rayon = ["iced_graphics/jpeg_rayon"] -gif = ["iced_graphics/gif"] -webp = ["iced_graphics/webp"] -pnm = ["iced_graphics/pnm"] -ico = ["iced_graphics/ico"] -bmp = ["iced_graphics/bmp"] -hdr = ["iced_graphics/hdr"] -dds = ["iced_graphics/dds"] -farbfeld = ["iced_graphics/farbfeld"] geometry = ["iced_graphics/geometry", "lyon"] -spirv = ["wgpu/spirv"] +image = ["iced_graphics/image"] +svg = ["resvg"] [dependencies] wgpu = "0.14" @@ -70,6 +58,10 @@ version = "0.21.3" version = "1.0" optional = true +[dependencies.resvg] +version = "0.29" +optional = true + [dependencies.tracing] version = "0.1.6" optional = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9c9a1b7637..88c585545c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -119,7 +119,7 @@ impl Backend { self.triangle_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.end_frame(device, queue, encoder); + self.image_pipeline.end_frame(); } fn prepare_text( diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 5eaa9a8651..4163e3382f 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,5 +1,11 @@ mod atlas; +#[cfg(feature = "image")] +mod raster; + +#[cfg(feature = "svg")] +mod vector; + use atlas::Atlas; use crate::core::{Rectangle, Size}; @@ -7,12 +13,6 @@ use crate::graphics::Transformation; use crate::layer; use crate::Buffer; -#[cfg(feature = "image")] -use crate::graphics::image::raster; - -#[cfg(feature = "svg")] -use crate::graphics::image::vector; - use std::cell::RefCell; use std::mem; @@ -30,9 +30,9 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image")] - raster_cache: RefCell>, + raster_cache: RefCell, #[cfg(feature = "svg")] - vector_cache: RefCell>, + vector_cache: RefCell, pipeline: wgpu::RenderPipeline, vertices: wgpu::Buffer, @@ -368,8 +368,10 @@ impl Pipeline { #[cfg(feature = "image")] layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( + device, + queue, + encoder, handle, - &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -392,11 +394,13 @@ impl Pipeline { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = vector_cache.upload( + device, + queue, + encoder, handle, *color, size, _scale, - &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -477,21 +481,12 @@ impl Pipeline { } } - pub fn end_frame( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - encoder: &mut wgpu::CommandEncoder, - ) { + pub fn end_frame(&mut self) { #[cfg(feature = "image")] - self.raster_cache - .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); + self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); #[cfg(feature = "svg")] - self.vector_cache - .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); + self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); self.prepare_layer = 0; } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 0a17ca33bf..c00b8cef34 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -13,7 +13,6 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; use crate::core::Size; -use crate::graphics::image; use std::num::NonZeroU32; @@ -64,6 +63,97 @@ impl Atlas { self.layers.len() } + pub fn upload( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + width: u32, + height: u32, + data: &[u8], + ) -> Option { + let entry = { + let current_size = self.layers.len(); + let entry = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + entry + }; + + log::info!("Allocated atlas entry: {:?}", entry); + + // It is a webgpu requirement that: + // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 + // So we calculate padded_width by rounding width up to the next + // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; + let padding = (align - (4 * width) % align) % align; + let padded_width = (4 * width + padding) as usize; + let padded_data_size = padded_width * height as usize; + + let mut padded_data = vec![0; padded_data_size]; + + for row in 0..height as usize { + let offset = row * padded_width; + + padded_data[offset..offset + 4 * width as usize].copy_from_slice( + &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], + ) + } + + match &entry { + Entry::Contiguous(allocation) => { + self.upload_allocation( + &padded_data, + width, + height, + padding, + 0, + allocation, + queue, + ); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.position; + let offset = (y * padded_width as u32 + 4 * x) as usize; + + self.upload_allocation( + &padded_data, + width, + height, + padding, + offset, + &fragment.allocation, + queue, + ); + } + } + } + + log::info!("Current atlas: {:?}", self); + + Some(entry) + } + + pub fn remove(&mut self, entry: &Entry) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } + fn allocate(&mut self, width: u32, height: u32) -> Option { // Allocate one layer if texture fits perfectly if width == SIZE && height == SIZE { @@ -296,101 +386,3 @@ impl Atlas { }); } } - -impl image::Storage for Atlas { - type Entry = Entry; - type State<'a> = ( - &'a wgpu::Device, - &'a wgpu::Queue, - &'a mut wgpu::CommandEncoder, - ); - - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - (device, queue, encoder): &mut Self::State<'_>, - ) -> Option { - let entry = { - let current_size = self.layers.len(); - let entry = self.allocate(width, height)?; - - // We grow the internal texture after allocating if necessary - let new_layers = self.layers.len() - current_size; - self.grow(new_layers, device, encoder); - - entry - }; - - log::info!("Allocated atlas entry: {:?}", entry); - - // It is a webgpu requirement that: - // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate padded_width by rounding width up to the next - // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; - let padding = (align - (4 * width) % align) % align; - let padded_width = (4 * width + padding) as usize; - let padded_data_size = padded_width * height as usize; - - let mut padded_data = vec![0; padded_data_size]; - - for row in 0..height as usize { - let offset = row * padded_width; - - padded_data[offset..offset + 4 * width as usize].copy_from_slice( - &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], - ) - } - - match &entry { - Entry::Contiguous(allocation) => { - self.upload_allocation( - &padded_data, - width, - height, - padding, - 0, - allocation, - queue, - ); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - let (x, y) = fragment.position; - let offset = (y * padded_width as u32 + 4 * x) as usize; - - self.upload_allocation( - &padded_data, - width, - height, - padding, - offset, - &fragment.allocation, - queue, - ); - } - } - } - - log::info!("Current atlas: {:?}", self); - - Some(entry) - } - - fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) { - log::info!("Removing atlas entry: {:?}", entry); - - match entry { - Entry::Contiguous(allocation) => { - self.deallocate(allocation); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - self.deallocate(&fragment.allocation); - } - } - } - } -} diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 4b06bd951d..7e4c92a2c7 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,5 +1,4 @@ use crate::core::Size; -use crate::graphics::image; use crate::image::atlas; #[derive(Debug)] @@ -11,8 +10,9 @@ pub enum Entry { }, } -impl image::storage::Entry for Entry { - fn size(&self) -> Size { +impl Entry { + #[cfg(feature = "image")] + pub fn size(&self) -> Size { match self { Entry::Contiguous(allocation) => allocation.size(), Entry::Fragmented { size, .. } => *size, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs new file mode 100644 index 0000000000..9b38dce4a2 --- /dev/null +++ b/wgpu/src/image/raster.rs @@ -0,0 +1,121 @@ +use crate::core::image; +use crate::core::Size; +use crate::graphics; +use crate::graphics::image::image_rs; +use crate::image::atlas::{self, Atlas}; + +use std::collections::{HashMap, HashSet}; + +/// Entry in cache corresponding to an image handle +#[derive(Debug)] +pub enum Memory { + /// Image data on host + Host(image_rs::ImageBuffer, Vec>), + /// Storage entry + Device(atlas::Entry), + /// Image not found + NotFound, + /// Invalid image data + Invalid, +} + +impl Memory { + /// Width and height of image + pub fn dimensions(&self) -> Size { + match self { + Memory::Host(image) => { + let (width, height) = image.dimensions(); + + Size::new(width, height) + } + Memory::Device(entry) => entry.size(), + Memory::NotFound => Size::new(1, 1), + Memory::Invalid => Size::new(1, 1), + } + } +} + +/// Caches image raster data +#[derive(Debug, Default)] +pub struct Cache { + map: HashMap, + hits: HashSet, +} + +impl Cache { + /// Load image + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { + if self.contains(handle) { + return self.get(handle).unwrap(); + } + + let memory = match graphics::image::load(handle) { + Ok(image) => Memory::Host(image.to_rgba8()), + Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound, + Err(_) => Memory::Invalid, + }; + + self.insert(handle, memory); + self.get(handle).unwrap() + } + + /// Load image and upload raster data + pub fn upload( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + handle: &image::Handle, + atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { + let memory = self.load(handle); + + if let Memory::Host(image) = memory { + let (width, height) = image.dimensions(); + + let entry = + atlas.upload(device, queue, encoder, width, height, image)?; + + *memory = Memory::Device(entry); + } + + if let Memory::Device(allocation) = memory { + Some(allocation) + } else { + None + } + } + + /// Trim cache misses from cache + pub fn trim(&mut self, atlas: &mut Atlas) { + let hits = &self.hits; + + self.map.retain(|k, memory| { + let retain = hits.contains(k); + + if !retain { + if let Memory::Device(entry) = memory { + atlas.remove(entry); + } + } + + retain + }); + + self.hits.clear(); + } + + fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { + let _ = self.hits.insert(handle.id()); + + self.map.get_mut(&handle.id()) + } + + fn insert(&mut self, handle: &image::Handle, memory: Memory) { + let _ = self.map.insert(handle.id(), memory); + } + + fn contains(&self, handle: &image::Handle) -> bool { + self.map.contains_key(&handle.id()) + } +} diff --git a/graphics/src/image/vector.rs b/wgpu/src/image/vector.rs similarity index 86% rename from graphics/src/image/vector.rs rename to wgpu/src/image/vector.rs index 32729acdaa..3624e46be6 100644 --- a/graphics/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,8 +1,6 @@ -//! Vector image loading and caching -use crate::image::Storage; - -use iced_core::svg; -use iced_core::{Color, Size}; +use crate::core::svg; +use crate::core::{Color, Size}; +use crate::image::atlas::{self, Atlas}; use resvg::tiny_skia; use resvg::usvg; @@ -32,17 +30,17 @@ impl Svg { } /// Caches svg vector and raster data -#[derive(Debug)] -pub struct Cache { +#[derive(Debug, Default)] +pub struct Cache { svgs: HashMap, - rasterized: HashMap<(u64, u32, u32, ColorFilter), T::Entry>, + rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, } type ColorFilter = Option<[u8; 4]>; -impl Cache { +impl Cache { /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { if self.svgs.contains_key(&handle.id()) { @@ -73,13 +71,15 @@ impl Cache { /// Load svg and upload raster data pub fn upload( &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, handle: &svg::Handle, color: Option, [width, height]: [f32; 2], scale: f32, - state: &mut T::State<'_>, - storage: &mut T, - ) -> Option<&T::Entry> { + atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { let id = handle.id(); let (width, height) = ( @@ -136,7 +136,9 @@ impl Cache { }); } - let allocation = storage.upload(width, height, &rgba, state)?; + let allocation = atlas + .upload(device, queue, encoder, width, height, &rgba)?; + log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); @@ -150,7 +152,7 @@ impl Cache { } /// Load svg and upload raster data - pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { + pub fn trim(&mut self, atlas: &mut Atlas) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; @@ -159,7 +161,7 @@ impl Cache { let retain = rasterized_hits.contains(k); if !retain { - storage.remove(entry, state); + atlas.remove(entry); } retain @@ -169,17 +171,6 @@ impl Cache { } } -impl Default for Cache { - fn default() -> Self { - Self { - svgs: HashMap::new(), - rasterized: HashMap::new(), - svg_hits: HashSet::new(), - rasterized_hits: HashSet::new(), - } - } -} - impl std::fmt::Debug for Svg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From bb49e17cabd45f3a21af98b4c5ecdddd507fd427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 05:06:26 +0100 Subject: [PATCH 49/57] Implement `raster` pipeline in `iced_tiny_skia` --- tiny_skia/Cargo.toml | 2 +- tiny_skia/src/backend.rs | 28 ++++++++-- tiny_skia/src/lib.rs | 3 ++ tiny_skia/src/raster.rs | 107 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 tiny_skia/src/raster.rs diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 08e79bb8c6..647ec12acc 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [features] -image = [] +image = ["iced_graphics/image"] svg = [] geometry = ["iced_graphics/geometry"] diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index d364e36a6b..d894ab957c 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -11,6 +11,9 @@ pub struct Backend { default_font: Font, default_text_size: f32, text_pipeline: crate::text::Pipeline, + + #[cfg(feature = "image")] + raster_pipeline: crate::raster::Pipeline, } impl Backend { @@ -19,6 +22,9 @@ impl Backend { default_font: settings.default_font, default_text_size: settings.default_text_size, text_pipeline: crate::text::Pipeline::new(), + + #[cfg(feature = "image")] + raster_pipeline: crate::raster::Pipeline::new(), } } @@ -159,8 +165,21 @@ impl Backend { clip_bounds.map(|_| clip_mask as &_), ); } - Primitive::Image { .. } => { - // TODO + #[cfg(feature = "image")] + Primitive::Image { handle, bounds } => { + let transform = tiny_skia::Transform::from_translate( + translation.x, + translation.y, + ) + .post_scale(scale_factor, scale_factor); + + self.raster_pipeline.draw( + handle, + *bounds, + pixels, + transform, + clip_bounds.map(|_| clip_mask as &_), + ); } Primitive::Svg { .. } => { // TODO @@ -490,9 +509,8 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, _handle: &crate::core::image::Handle) -> Size { - // TODO - Size::new(0, 0) + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size { + self.raster_pipeline.dimensions(handle) } } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index bf83e4004d..d03bdcc2a0 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -4,6 +4,9 @@ mod backend; mod settings; mod text; +#[cfg(feature = "image")] +mod raster; + #[cfg(feature = "geometry")] pub mod geometry; diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs new file mode 100644 index 0000000000..e57f0e50c2 --- /dev/null +++ b/tiny_skia/src/raster.rs @@ -0,0 +1,107 @@ +use crate::core::image as raster; +use crate::core::{Rectangle, Size}; +use crate::graphics; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::cell::RefCell; +use std::collections::hash_map; + +pub struct Pipeline { + cache: RefCell, +} + +impl Pipeline { + pub fn new() -> Self { + Self { + cache: RefCell::new(Cache::default()), + } + } + + pub fn dimensions(&self, handle: &raster::Handle) -> Size { + if let Some(image) = self.cache.borrow_mut().allocate(handle) { + Size::new(image.width(), image.height()) + } else { + Size::new(0, 0) + } + } + + pub fn draw( + &mut self, + handle: &raster::Handle, + bounds: Rectangle, + pixels: &mut tiny_skia::PixmapMut<'_>, + transform: tiny_skia::Transform, + clip_mask: Option<&tiny_skia::ClipMask>, + ) { + if let Some(image) = self.cache.borrow_mut().allocate(handle) { + let width_scale = bounds.width / image.width() as f32; + let height_scale = bounds.height / image.height() as f32; + + let transform = transform.pre_scale(width_scale, height_scale); + + pixels.draw_pixmap( + (bounds.x / width_scale) as i32, + (bounds.y / height_scale) as i32, + image, + &tiny_skia::PixmapPaint { + quality: tiny_skia::FilterQuality::Bilinear, + ..Default::default() + }, + transform, + clip_mask, + ); + } + } +} + +#[derive(Default)] +struct Cache { + entries: FxHashMap>, + hits: FxHashSet, +} + +impl Cache { + pub fn allocate( + &mut self, + handle: &raster::Handle, + ) -> Option> { + let id = handle.id(); + + if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) { + let image = graphics::image::load(handle).ok()?.into_rgba8(); + + let mut buffer = + vec![0u32; image.width() as usize * image.height() as usize]; + + for (i, pixel) in image.pixels().enumerate() { + let [r, g, b, a] = pixel.0; + + buffer[i] = tiny_skia::ColorU8::from_rgba(b, g, r, a) + .premultiply() + .get(); + } + + entry.insert(Some(Entry { + width: image.width(), + height: image.height(), + pixels: buffer, + })); + } + + self.hits.insert(id); + self.entries.get(&id).unwrap().as_ref().map(|entry| { + tiny_skia::PixmapRef::from_bytes( + bytemuck::cast_slice(&entry.pixels), + entry.width, + entry.height, + ) + .expect("Build pixmap from image bytes") + }) + } +} + +struct Entry { + width: u32, + height: u32, + pixels: Vec, +} From 5b3977daf6df624ca5d5e1a21ce282161234b22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 06:09:51 +0100 Subject: [PATCH 50/57] Implement `vector` pipeline in `iced_tiny_skia` --- core/src/size.rs | 2 +- examples/svg/Cargo.toml | 2 +- tiny_skia/Cargo.toml | 6 +- tiny_skia/src/backend.rs | 30 ++++++-- tiny_skia/src/lib.rs | 3 + tiny_skia/src/text.rs | 2 +- tiny_skia/src/vector.rs | 149 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 tiny_skia/src/vector.rs diff --git a/core/src/size.rs b/core/src/size.rs index fbe940efe4..7ef2f602ee 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -1,7 +1,7 @@ use crate::{Padding, Vector}; /// An amount of space in 2 dimensions. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Size { /// The width. pub width: T, diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index f5a6eaa22c..7b45c672ec 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["svg"] } +iced = { path = "../..", features = ["svg", "tiny-skia", "debug"], default-features = false } diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index 647ec12acc..691975896d 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [features] image = ["iced_graphics/image"] -svg = [] +svg = ["resvg"] geometry = ["iced_graphics/geometry"] [dependencies] @@ -34,3 +34,7 @@ default-features = false [target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] version = "1.6.1" features = ["std"] + +[dependencies.resvg] +version = "0.29" +optional = true diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index d894ab957c..3c2a97b9b2 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -14,6 +14,9 @@ pub struct Backend { #[cfg(feature = "image")] raster_pipeline: crate::raster::Pipeline, + + #[cfg(feature = "svg")] + vector_pipeline: crate::vector::Pipeline, } impl Backend { @@ -25,6 +28,9 @@ impl Backend { #[cfg(feature = "image")] raster_pipeline: crate::raster::Pipeline::new(), + + #[cfg(feature = "svg")] + vector_pipeline: crate::vector::Pipeline::new(), } } @@ -78,7 +84,10 @@ impl Backend { ); } - self.text_pipeline.end_frame(); + self.text_pipeline.trim_cache(); + + #[cfg(feature = "svg")] + self.vector_pipeline.trim_cache(); } fn draw_primitive( @@ -181,8 +190,18 @@ impl Backend { clip_bounds.map(|_| clip_mask as &_), ); } - Primitive::Svg { .. } => { - // TODO + #[cfg(feature = "svg")] + Primitive::Svg { + handle, + bounds, + color: _, // TODO: Implement color filter + } => { + self.vector_pipeline.draw( + handle, + (*bounds + translation) * scale_factor, + pixels, + clip_bounds.map(|_| clip_mask as &_), + ); } Primitive::Fill { path, @@ -518,9 +537,8 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - _handle: &crate::core::svg::Handle, + handle: &crate::core::svg::Handle, ) -> Size { - // TODO - Size::new(0, 0) + self.vector_pipeline.viewport_dimensions(handle) } } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index d03bdcc2a0..83baef1c6e 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -7,6 +7,9 @@ mod text; #[cfg(feature = "image")] mod raster; +#[cfg(feature = "svg")] +mod vector; + #[cfg(feature = "geometry")] pub mod geometry; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index c4edadb333..bfe5da9ddc 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -143,7 +143,7 @@ impl Pipeline { }); } - pub fn end_frame(&mut self) { + pub fn trim_cache(&mut self) { self.system .as_mut() .unwrap() diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs new file mode 100644 index 0000000000..fd9cfdc501 --- /dev/null +++ b/tiny_skia/src/vector.rs @@ -0,0 +1,149 @@ +use crate::core::svg::{Data, Handle}; +use crate::core::{Rectangle, Size}; + +use resvg::usvg; +use rustc_hash::{FxHashMap, FxHashSet}; + +use std::cell::RefCell; +use std::collections::hash_map; +use std::fs; + +pub struct Pipeline { + cache: RefCell, +} + +impl Pipeline { + pub fn new() -> Self { + Self { + cache: RefCell::new(Cache::default()), + } + } + + pub fn viewport_dimensions(&self, handle: &Handle) -> Size { + self.cache + .borrow_mut() + .viewport_dimensions(handle) + .unwrap_or(Size::new(0, 0)) + } + + pub fn draw( + &mut self, + handle: &Handle, + bounds: Rectangle, + pixels: &mut tiny_skia::PixmapMut<'_>, + clip_mask: Option<&tiny_skia::ClipMask>, + ) { + if let Some(image) = self + .cache + .borrow_mut() + .draw(handle, Size::new(bounds.width as u32, bounds.height as u32)) + { + pixels.draw_pixmap( + bounds.x as i32, + bounds.y as i32, + image, + &tiny_skia::PixmapPaint::default(), + tiny_skia::Transform::identity(), + clip_mask, + ); + } + } + + pub fn trim_cache(&mut self) { + self.cache.borrow_mut().trim(); + } +} + +#[derive(Default)] +struct Cache { + trees: FxHashMap>, + tree_hits: FxHashSet, + rasters: FxHashMap<(u64, Size), tiny_skia::Pixmap>, + raster_hits: FxHashSet<(u64, Size)>, +} + +impl Cache { + fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> { + let id = handle.id(); + + if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) { + let svg = match handle.data() { + Data::Path(path) => { + fs::read_to_string(path).ok().and_then(|contents| { + usvg::Tree::from_str( + &contents, + &usvg::Options::default(), + ) + .ok() + }) + } + Data::Bytes(bytes) => { + usvg::Tree::from_data(bytes, &usvg::Options::default()).ok() + } + }; + + entry.insert(svg); + } + + self.tree_hits.insert(id); + self.trees.get(&id).unwrap().as_ref() + } + + fn viewport_dimensions(&mut self, handle: &Handle) -> Option> { + let tree = self.load(handle)?; + + Some(Size::new( + tree.size.width() as u32, + tree.size.height() as u32, + )) + } + + fn draw( + &mut self, + handle: &Handle, + size: Size, + ) -> Option> { + if size.width == 0 || size.height == 0 { + return None; + } + + let id = handle.id(); + + if !self.rasters.contains_key(&(id, size)) { + let tree = self.load(handle)?; + + let mut image = tiny_skia::Pixmap::new(size.width, size.height)?; + + resvg::render( + tree, + if size.width > size.height { + usvg::FitTo::Width(size.width) + } else { + usvg::FitTo::Height(size.height) + }, + tiny_skia::Transform::default(), + image.as_mut(), + )?; + + // Swap R and B channels for `softbuffer` presentation + for pixel in bytemuck::cast_slice_mut::(image.data_mut()) { + *pixel = *pixel & 0xFF00FF00 + | ((0x000000FF & *pixel) << 16) + | ((0x00FF0000 & *pixel) >> 16); + } + + self.rasters.insert((id, size), image); + } + + self.raster_hits.insert((id, size)); + self.rasters.get(&(id, size)).map(tiny_skia::Pixmap::as_ref) + } + + fn trim(&mut self) { + self.trees.retain(|key, _| self.tree_hits.contains(key)); + self.rasters.retain(|key, _| self.raster_hits.contains(key)); + + self.tree_hits.clear(); + self.raster_hits.clear(); + } +} From a8d55ceb829377725b4e7632702894fed6867eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 06:15:05 +0100 Subject: [PATCH 51/57] Trim `raster` cache in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 3 +++ tiny_skia/src/raster.rs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 3c2a97b9b2..b3c7d2bc7e 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -86,6 +86,9 @@ impl Backend { self.text_pipeline.trim_cache(); + #[cfg(feature = "image")] + self.raster_pipeline.trim_cache(); + #[cfg(feature = "svg")] self.vector_pipeline.trim_cache(); } diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index e57f0e50c2..2fd73f8c7d 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -52,6 +52,10 @@ impl Pipeline { ); } } + + pub fn trim_cache(&mut self) { + self.cache.borrow_mut().trim(); + } } #[derive(Default)] @@ -98,6 +102,11 @@ impl Cache { .expect("Build pixmap from image bytes") }) } + + fn trim(&mut self) { + self.entries.retain(|key, _| self.hits.contains(key)); + self.hits.clear(); + } } struct Entry { From 81d154d63a637d69f0c710780e2bbcd45ef4683c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 06:15:38 +0100 Subject: [PATCH 52/57] Use default features in `svg` example --- examples/svg/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index 7b45c672ec..f5a6eaa22c 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["svg", "tiny-skia", "debug"], default-features = false } +iced = { path = "../..", features = ["svg"] } From 0850f52d8c06bd4c5ee80609758197a093939d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 06:23:09 +0100 Subject: [PATCH 53/57] Use `ceil` to avoid cut text in `iced_tiny_skia` This won't be necessary once we reuse the buffers from layouting by leveraging layout linearity. --- tiny_skia/src/text.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index bfe5da9ddc..f2935efab4 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -84,7 +84,12 @@ impl Pipeline { ) { self.system.as_mut().unwrap().with_mut(|fields| { let key = Key { - bounds: bounds.size(), + bounds: { + let size = bounds.size(); + + // TODO: Reuse buffers from layouting + Size::new(size.width.ceil(), size.height.ceil()) + }, content, font, size, From 24c3d20a76e45feddac67ed62249796c774ce330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 06:34:27 +0100 Subject: [PATCH 54/57] Tell `clippy` to go learn the borrow rules --- tiny_skia/src/vector.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index fd9cfdc501..89063c4cdc 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -109,6 +109,7 @@ impl Cache { let id = handle.id(); + #[allow(clippy::map_entry)] if !self.rasters.contains_key(&(id, size)) { let tree = self.load(handle)?; From d3900e067361c82fd857fc92b81284146140bc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 06:41:41 +0100 Subject: [PATCH 55/57] Enable renderer backends in `integration` example --- .github/workflows/test.yml | 2 +- examples/integration/Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 390e7bf303..a9a9b3f9ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,5 +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 + - name: Check compilation of `integration` example run: cargo build --package integration --target wasm32-unknown-unknown diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index f429977fc4..f6863cd326 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -9,6 +9,7 @@ publish = false iced_winit = { path = "../../winit" } iced_wgpu = { path = "../../wgpu" } iced_widget = { path = "../../widget" } +iced_renderer = { path = "../../renderer", features = ["wgpu", "tiny-skia"] } env_logger = "0.8" [target.'cfg(target_arch = "wasm32")'.dependencies] From df68cca0c9dda051ae979ccc2f5ca8d37c3e3cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 7 Mar 2023 07:22:48 +0100 Subject: [PATCH 56/57] Update `sysinfo` to `0.28` --- winit/Cargo.toml | 2 +- winit/src/system.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 60e464c6ca..dd5c12c27d 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -65,5 +65,5 @@ version = "0.3" features = ["Document", "Window"] [dependencies.sysinfo] -version = "0.23" +version = "0.28" optional = true diff --git a/winit/src/system.rs b/winit/src/system.rs index 619086b80a..8d8b018c71 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -16,11 +16,11 @@ pub fn fetch_information( pub(crate) fn information( graphics_info: compositor::Information, ) -> Information { - use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt}; + use sysinfo::{CpuExt, ProcessExt, System, SystemExt}; let mut system = System::new_all(); system.refresh_all(); - let cpu = system.global_processor_info(); + let cpu = system.global_cpu_info(); let memory_used = sysinfo::get_current_pid() .and_then(|pid| system.process(pid).ok_or("Process not found")) From 424ac8177309440bbd8efe0dd9f7622cb10807ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 9 Mar 2023 04:48:35 +0100 Subject: [PATCH 57/57] Implement color filter support for `Primitive::Svg` in `iced_tiny_skia` --- tiny_skia/src/backend.rs | 3 +- tiny_skia/src/vector.rs | 66 +++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b3c7d2bc7e..ba063f4ec9 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -197,10 +197,11 @@ impl Backend { Primitive::Svg { handle, bounds, - color: _, // TODO: Implement color filter + color, } => { self.vector_pipeline.draw( handle, + *color, (*bounds + translation) * scale_factor, pixels, clip_bounds.map(|_| clip_mask as &_), diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 89063c4cdc..8509b761e6 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -1,5 +1,5 @@ use crate::core::svg::{Data, Handle}; -use crate::core::{Rectangle, Size}; +use crate::core::{Color, Rectangle, Size}; use resvg::usvg; use rustc_hash::{FxHashMap, FxHashSet}; @@ -29,15 +29,16 @@ impl Pipeline { pub fn draw( &mut self, handle: &Handle, + color: Option, bounds: Rectangle, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: Option<&tiny_skia::ClipMask>, ) { - if let Some(image) = self - .cache - .borrow_mut() - .draw(handle, Size::new(bounds.width as u32, bounds.height as u32)) - { + if let Some(image) = self.cache.borrow_mut().draw( + handle, + color, + Size::new(bounds.width as u32, bounds.height as u32), + ) { pixels.draw_pixmap( bounds.x as i32, bounds.y as i32, @@ -58,8 +59,15 @@ impl Pipeline { struct Cache { trees: FxHashMap>, tree_hits: FxHashSet, - rasters: FxHashMap<(u64, Size), tiny_skia::Pixmap>, - raster_hits: FxHashSet<(u64, Size)>, + rasters: FxHashMap, + raster_hits: FxHashSet, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct RasterKey { + id: u64, + color: Option<[u8; 4]>, + size: Size, } impl Cache { @@ -101,16 +109,21 @@ impl Cache { fn draw( &mut self, handle: &Handle, + color: Option, size: Size, ) -> Option> { if size.width == 0 || size.height == 0 { return None; } - let id = handle.id(); + let key = RasterKey { + id: handle.id(), + color: color.map(Color::into_rgba8), + size, + }; #[allow(clippy::map_entry)] - if !self.rasters.contains_key(&(id, size)) { + if !self.rasters.contains_key(&key) { let tree = self.load(handle)?; let mut image = tiny_skia::Pixmap::new(size.width, size.height)?; @@ -126,18 +139,35 @@ impl Cache { image.as_mut(), )?; - // Swap R and B channels for `softbuffer` presentation - for pixel in bytemuck::cast_slice_mut::(image.data_mut()) { - *pixel = *pixel & 0xFF00FF00 - | ((0x000000FF & *pixel) << 16) - | ((0x00FF0000 & *pixel) >> 16); + if let Some([r, g, b, a]) = key.color { + // TODO: Blend alpha + let color = tiny_skia::ColorU8::from_rgba(b, g, r, a) + .premultiply() + .get() + & 0x00FFFFFF; + + // Apply color filter + for pixel in + bytemuck::cast_slice_mut::(image.data_mut()) + { + *pixel = *pixel & 0xFF000000 | color; + } + } else { + // Swap R and B channels for `softbuffer` presentation + for pixel in + bytemuck::cast_slice_mut::(image.data_mut()) + { + *pixel = *pixel & 0xFF00FF00 + | ((0x000000FF & *pixel) << 16) + | ((0x00FF0000 & *pixel) >> 16); + } } - self.rasters.insert((id, size), image); + self.rasters.insert(key, image); } - self.raster_hits.insert((id, size)); - self.rasters.get(&(id, size)).map(tiny_skia::Pixmap::as_ref) + self.raster_hits.insert(key); + self.rasters.get(&key).map(tiny_skia::Pixmap::as_ref) } fn trim(&mut self) {