diff options
author | 2024-03-25 21:36:44 +0100 | |
---|---|---|
committer | 2024-03-25 21:36:44 +0100 | |
commit | a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286 (patch) | |
tree | e6c24928a42e23ff91eea0fc30b4fbbcb6da024b | |
parent | 3013463baa71504488a20436beb3db87ecb66df0 (diff) | |
parent | 6a4f5ac2081699f7cf20c917b367366ab49eeef1 (diff) | |
download | iced-a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286.tar.gz iced-a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286.tar.bz2 iced-a2a8381a49ac2dd1cd65eb382b9ee02bbfa17286.zip |
Merge pull request #2351 from iced-rs/custom-renderer-injection
Type-Driven Renderer Fallback
56 files changed, 1886 insertions, 1447 deletions
@@ -18,9 +18,11 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "fira-sans", "auto-detect-theme"] +default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"] # Enable the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] +# Enable the `tiny-skia` software renderer backend +tiny-skia = ["iced_renderer/tiny-skia"] # Enables the `Image` widget image = ["iced_widget/image", "dep:image"] # Enables the `Svg` widget diff --git a/core/src/element.rs b/core/src/element.rs index 989eaa3b..7d918a2e 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -95,7 +95,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// /// ```no_run /// # mod iced { - /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>; + /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// # /// # pub mod widget { /// # pub fn row<'a, Message>(iter: impl IntoIterator<Item = super::Element<'a, Message>>) -> super::Element<'a, Message> { @@ -109,7 +109,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> { /// # pub enum Message {} /// # pub struct Counter; /// # - /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, iced_core::renderer::Null>; + /// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>; /// # /// # impl Counter { /// # pub fn view(&self) -> Element<Message> { diff --git a/core/src/image.rs b/core/src/image.rs index e5fdcd83..32b95f03 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -186,11 +186,11 @@ pub trait Renderer: crate::Renderer { type Handle: Clone + Hash; /// Returns the dimensions of an image for the given [`Handle`]. - fn dimensions(&self, handle: &Self::Handle) -> Size<u32>; + fn measure_image(&self, handle: &Self::Handle) -> Size<u32>; /// Draws an image with the given [`Handle`] and inside the provided /// `bounds`. - fn draw( + fn draw_image( &mut self, handle: Self::Handle, filter_method: FilterMethod, diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 1139b41c..6712314e 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -2,26 +2,47 @@ #[cfg(debug_assertions)] mod null; -#[cfg(debug_assertions)] -pub use null::Null; - use crate::{ Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector, }; /// A component that can be used by widgets to draw themselves on a screen. -pub trait Renderer: Sized { +pub trait Renderer { + /// Starts recording a new layer. + fn start_layer(&mut self); + + /// Ends recording a new layer. + /// + /// The new layer will clip its contents to the provided `bounds`. + fn end_layer(&mut self, bounds: Rectangle); + /// Draws the primitives recorded in the given closure in a new layer. /// /// The layer will clip its contents to the provided `bounds`. - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)); + fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { + self.start_layer(); + f(self); + self.end_layer(bounds); + } + + /// Starts recording with a new [`Transformation`]. + fn start_transformation(&mut self); + + /// Ends recording a new layer. + /// + /// The new layer will clip its contents to the provided `bounds`. + fn end_transformation(&mut self, transformation: Transformation); /// Applies a [`Transformation`] to the primitives recorded in the given closure. fn with_transformation( &mut self, transformation: Transformation, f: impl FnOnce(&mut Self), - ); + ) { + self.start_transformation(); + f(self); + self.end_transformation(transformation); + } /// Applies a translation to the primitives recorded in the given closure. fn with_translation( diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 83688ff7..c26ce1a5 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,5 +1,7 @@ use crate::alignment; +use crate::image; use crate::renderer::{self, Renderer}; +use crate::svg; use crate::text::{self, Text}; use crate::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, @@ -7,28 +9,14 @@ use crate::{ use std::borrow::Cow; -/// A renderer that does nothing. -/// -/// It can be useful if you are writing tests! -#[derive(Debug, Clone, Copy, Default)] -pub struct Null; +impl Renderer for () { + fn start_layer(&mut self) {} -impl Null { - /// Creates a new [`Null`] renderer. - pub fn new() -> Null { - Null - } -} + fn end_layer(&mut self, _bounds: Rectangle) {} -impl Renderer for Null { - fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {} + fn start_transformation(&mut self) {} - fn with_transformation( - &mut self, - _transformation: Transformation, - _f: impl FnOnce(&mut Self), - ) { - } + fn end_transformation(&mut self, _transformation: Transformation) {} fn clear(&mut self) {} @@ -40,7 +28,7 @@ impl Renderer for Null { } } -impl text::Renderer for Null { +impl text::Renderer for () { type Font = Font; type Paragraph = (); type Editor = (); @@ -174,3 +162,33 @@ impl text::Editor for () { ) { } } + +impl image::Renderer for () { + type Handle = (); + + fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> { + Size::default() + } + + fn draw_image( + &mut self, + _handle: Self::Handle, + _filter_method: image::FilterMethod, + _bounds: Rectangle, + ) { + } +} + +impl svg::Renderer for () { + fn measure_svg(&self, _handle: &svg::Handle) -> Size<u32> { + Size::default() + } + + fn draw_svg( + &mut self, + _handle: svg::Handle, + _color: Option<Color>, + _bounds: Rectangle, + ) { + } +} diff --git a/core/src/size.rs b/core/src/size.rs index 267fc90e..55db759d 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -1,7 +1,7 @@ use crate::Vector; /// An amount of space in 2 dimensions. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct Size<T = f32> { /// The width. pub width: T, diff --git a/core/src/svg.rs b/core/src/svg.rs index d63e3c95..ab207cca 100644 --- a/core/src/svg.rs +++ b/core/src/svg.rs @@ -91,8 +91,13 @@ impl std::fmt::Debug for Data { /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. - fn dimensions(&self, handle: &Handle) -> Size<u32>; + fn measure_svg(&self, handle: &Handle) -> Size<u32>; /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. - fn draw(&mut self, handle: Handle, color: Option<Color>, bounds: Rectangle); + fn draw_svg( + &mut self, + handle: Handle, + color: Option<Color>, + bounds: Rectangle, + ); } diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index cf70bd40..289c919b 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -143,23 +143,18 @@ mod bezier { bounds: Rectangle, cursor: mouse::Cursor, ) -> Vec<Geometry> { - let content = self.state.cache.draw( - renderer, - bounds.size(), - |frame: &mut Frame| { + let content = + self.state.cache.draw(renderer, bounds.size(), |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(renderer, bounds, cursor); - - vec![content, pending_curve] + vec![content, pending.draw(renderer, bounds, cursor)] } else { vec![content] } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 2b0fae0b..0716b2a4 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -602,9 +602,7 @@ mod grid { frame.into_geometry() }; - if self.scaling < 0.2 || !self.show_lines { - vec![life, overlay] - } else { + if self.scaling >= 0.2 && self.show_lines { let grid = self.grid_cache.draw(renderer, bounds.size(), |frame| { frame.translate(center); @@ -641,6 +639,8 @@ mod grid { }); vec![life, grid, overlay] + } else { + vec![life, overlay] } } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 63efcbdd..16cdb86f 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -44,7 +44,9 @@ mod rainbow { cursor: mouse::Cursor, _viewport: &Rectangle, ) { - use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D}; + use iced::advanced::graphics::mesh::{ + self, Mesh, Renderer as _, SolidVertex2D, + }; use iced::advanced::Renderer as _; let bounds = layout.bounds(); diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 12670ed1..de728af2 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -358,7 +358,7 @@ where |renderer| { use iced::advanced::graphics::geometry::Renderer as _; - renderer.draw(vec![geometry]); + renderer.draw_geometry(geometry); }, ); } diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 07ae05d6..9cd6237f 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -1,6 +1,6 @@ use iced::mouse; use iced::widget::canvas::event::{self, Event}; -use iced::widget::canvas::{self, Canvas}; +use iced::widget::canvas::{self, Canvas, Geometry}; use iced::widget::{column, row, slider, text}; use iced::{Color, Length, Point, Rectangle, Renderer, Size, Theme}; @@ -111,7 +111,7 @@ impl canvas::Program<Message> for SierpinskiGraph { _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<canvas::Geometry> { + ) -> Vec<Geometry> { let geom = self.cache.draw(renderer, bounds.size(), |frame| { frame.stroke( &canvas::Path::rectangle(Point::ORIGIN, frame.size()), diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index b5228f09..deb211d8 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -10,7 +10,7 @@ use iced::mouse; use iced::widget::canvas; use iced::widget::canvas::gradient; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::Path; +use iced::widget::canvas::{Geometry, Path}; use iced::window; use iced::{ Color, Element, Length, Point, Rectangle, Renderer, Size, Subscription, @@ -130,7 +130,7 @@ impl<Message> canvas::Program<Message> for State { _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, - ) -> Vec<canvas::Geometry> { + ) -> Vec<Geometry> { use std::f32::consts::PI; let background = diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 10eb337f..7abc42c5 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -2,15 +2,19 @@ use crate::core::image; use crate::core::svg; use crate::core::Size; +use crate::{Compositor, Mesh, Renderer}; use std::borrow::Cow; /// The graphics backend of a [`Renderer`]. /// /// [`Renderer`]: crate::Renderer -pub trait Backend { +pub trait Backend: Sized { /// The custom kind of primitives this [`Backend`] supports. - type Primitive; + type Primitive: TryFrom<Mesh, Error = &'static str>; + + /// The default compositor of this [`Backend`]. + type Compositor: Compositor<Renderer = Renderer<Self>>; } /// A graphics backend that supports text rendering. diff --git a/graphics/src/cached.rs b/graphics/src/cached.rs new file mode 100644 index 00000000..b52f9d9d --- /dev/null +++ b/graphics/src/cached.rs @@ -0,0 +1,42 @@ +use crate::Primitive; + +use std::sync::Arc; + +/// A piece of data that can be cached. +pub trait Cached: Sized { + /// The type of cache produced. + type Cache; + + /// Loads the [`Cache`] into a proper instance. + /// + /// [`Cache`]: Self::Cache + fn load(cache: &Self::Cache) -> Self; + + /// Caches this value, producing its corresponding [`Cache`]. + /// + /// [`Cache`]: Self::Cache + fn cache(self) -> Self::Cache; +} + +impl<T> Cached for Primitive<T> { + type Cache = Arc<Self>; + + fn load(cache: &Arc<Self>) -> Self { + Self::Cache { + content: cache.clone(), + } + } + + fn cache(self) -> Arc<Self> { + Arc::new(self) + } +} + +#[cfg(debug_assertions)] +impl Cached for () { + type Cache = (); + + fn load(_cache: &Self::Cache) -> Self {} + + fn cache(self) -> Self::Cache {} +} diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 91951a8e..86472a58 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -1,9 +1,8 @@ //! A compositor is responsible for initializing a renderer and managing window //! surfaces. -use crate::{Error, Viewport}; - use crate::core::Color; use crate::futures::{MaybeSend, MaybeSync}; +use crate::{Error, Settings, Viewport}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::future::Future; @@ -11,19 +10,28 @@ use thiserror::Error; /// A graphics compositor that can draw to windows. pub trait Compositor: Sized { - /// The settings of the backend. - type Settings: Default; - /// The iced renderer of the backend. - type Renderer: iced_core::Renderer; + type Renderer; /// The surface of the backend. type Surface; /// Creates a new [`Compositor`]. fn new<W: Window + Clone>( - settings: Self::Settings, + settings: Settings, compatible_window: W, + ) -> impl Future<Output = Result<Self, Error>> { + Self::with_backend(settings, compatible_window, None) + } + + /// Creates a new [`Compositor`] with a backend preference. + /// + /// If the backend does not match the preference, it will return + /// [`Error::GraphicsAdapterNotFound`]. + fn with_backend<W: Window + Clone>( + _settings: Settings, + _compatible_window: W, + _backend: Option<&str>, ) -> impl Future<Output = Result<Self, Error>>; /// Creates a [`Self::Renderer`] for the [`Compositor`]. @@ -93,6 +101,12 @@ impl<T> Window for T where { } +/// Defines the default compositor of a renderer. +pub trait Default { + /// The compositor of the renderer. + type Compositor: Compositor<Renderer = Self>; +} + /// Result of an unsuccessful call to [`Compositor::present`]. #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum SurfaceError { @@ -122,3 +136,69 @@ pub struct Information { /// Contains the graphics backend. pub backend: String, } + +#[cfg(debug_assertions)] +impl Compositor for () { + type Renderer = (); + type Surface = (); + + async fn with_backend<W: Window + Clone>( + _settings: Settings, + _compatible_window: W, + _preffered_backend: Option<&str>, + ) -> Result<Self, Error> { + Ok(()) + } + + fn create_renderer(&self) -> Self::Renderer {} + + fn create_surface<W: Window + Clone>( + &mut self, + _window: W, + _width: u32, + _height: u32, + ) -> Self::Surface { + } + + fn configure_surface( + &mut self, + _surface: &mut Self::Surface, + _width: u32, + _height: u32, + ) { + } + + fn fetch_information(&self) -> Information { + Information { + adapter: String::from("Null Renderer"), + backend: String::from("Null"), + } + } + + fn present<T: AsRef<str>>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Result<(), SurfaceError> { + Ok(()) + } + + fn screenshot<T: AsRef<str>>( + &mut self, + _renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + _viewport: &Viewport, + _background_color: Color, + _overlay: &[T], + ) -> Vec<u8> { + vec![] + } +} + +#[cfg(debug_assertions)] +impl Default for () { + type Compositor = (); +} diff --git a/graphics/src/error.rs b/graphics/src/error.rs index c6ea98a3..6ea1d3a4 100644 --- a/graphics/src/error.rs +++ b/graphics/src/error.rs @@ -1,5 +1,7 @@ +//! See what can go wrong when creating graphical backends. + /// An error that occurred while creating an application's graphical context. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum Error { /// The requested backend version is not supported. #[error("the requested backend version is not supported")] @@ -11,9 +13,30 @@ pub enum Error { /// A suitable graphics adapter or device could not be found. #[error("a suitable graphics adapter or device could not be found")] - GraphicsAdapterNotFound, + GraphicsAdapterNotFound { + /// The name of the backend where the error happened + backend: &'static str, + /// The reason why this backend could not be used + reason: Reason, + }, /// An error occurred in the context's internal backend #[error("an error occurred in the context's internal backend")] BackendError(String), + + /// Multiple errors occurred + #[error("multiple errors occurred: {0:?}")] + List(Vec<Self>), +} + +/// The reason why a graphics adapter could not be found +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Reason { + /// The backend did not match the preference + DidNotMatch { + /// The preferred backend + preferred_backend: String, + }, + /// The request to create the backend failed + RequestFailed(String), } diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index d7d6a0aa..d251efb8 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -1,12 +1,16 @@ //! Build and draw geometry. pub mod fill; +pub mod frame; pub mod path; pub mod stroke; +mod cache; mod style; mod text; +pub use cache::Cache; pub use fill::Fill; +pub use frame::Frame; pub use path::Path; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; @@ -14,11 +18,39 @@ pub use text::Text; pub use crate::gradient::{self, Gradient}; +use crate::core::{self, Size}; +use crate::Cached; + /// A renderer capable of drawing some [`Self::Geometry`]. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer { /// The kind of geometry this renderer can draw. - type Geometry; + type Geometry: Cached; + + /// The kind of [`Frame`] this renderer supports. + type Frame: frame::Backend<Geometry = Self::Geometry>; + + /// Creates a new [`Self::Frame`]. + fn new_frame(&self, size: Size) -> Self::Frame; + + /// Draws the given [`Self::Geometry`]. + fn draw_geometry(&mut self, geometry: Self::Geometry); +} + +/// The graphics backend of a geometry renderer. +pub trait Backend { + /// The kind of [`Frame`] this backend supports. + type Frame: frame::Backend; + + /// Creates a new [`Self::Frame`]. + fn new_frame(&self, size: Size) -> Self::Frame; +} + +#[cfg(debug_assertions)] +impl Renderer for () { + type Geometry = (); + type Frame = (); + + fn new_frame(&self, _size: Size) -> Self::Frame {} - /// Draws the given layers of [`Self::Geometry`]. - fn draw(&mut self, layers: Vec<Self::Geometry>); + fn draw_geometry(&mut self, _geometry: Self::Geometry) {} } diff --git a/graphics/src/geometry/cache.rs b/graphics/src/geometry/cache.rs new file mode 100644 index 00000000..37d433c2 --- /dev/null +++ b/graphics/src/geometry/cache.rs @@ -0,0 +1,108 @@ +use crate::core::Size; +use crate::geometry::{self, Frame}; +use crate::Cached; + +use std::cell::RefCell; + +/// 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. +pub struct Cache<Renderer> +where + Renderer: geometry::Renderer, +{ + state: RefCell<State<Renderer::Geometry>>, +} + +impl<Renderer> Cache<Renderer> +where + Renderer: geometry::Renderer, +{ + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: RefCell::new(State::Empty), + } + } + + /// 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<Renderer>), + ) -> Renderer::Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + geometry, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Cached::load(geometry); + } + } + + let mut frame = Frame::new(renderer, bounds); + draw_fn(&mut frame); + + let geometry = frame.into_geometry().cache(); + let result = Cached::load(&geometry); + + *self.state.borrow_mut() = State::Filled { bounds, geometry }; + + result + } +} + +impl<Renderer> std::fmt::Debug for Cache<Renderer> +where + Renderer: geometry::Renderer, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let state = self.state.borrow(); + + match *state { + State::Empty => write!(f, "Cache::Empty"), + State::Filled { bounds, .. } => { + write!(f, "Cache::Filled {{ bounds: {bounds:?} }}") + } + } + } +} + +impl<Renderer> Default for Cache<Renderer> +where + Renderer: geometry::Renderer, +{ + fn default() -> Self { + Self::new() + } +} + +enum State<Geometry> +where + Geometry: Cached, +{ + Empty, + Filled { + bounds: Size, + geometry: Geometry::Cache, + }, +} diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs new file mode 100644 index 00000000..ad35e8ec --- /dev/null +++ b/graphics/src/geometry/frame.rs @@ -0,0 +1,251 @@ +//! Draw and generate geometry. +use crate::core::{Point, Radians, Rectangle, Size, Vector}; +use crate::geometry::{self, Fill, Path, Stroke, Text}; + +/// The region of a surface that can be used to draw geometry. +#[allow(missing_debug_implementations)] +pub struct Frame<Renderer> +where + Renderer: geometry::Renderer, +{ + raw: Renderer::Frame, +} + +impl<Renderer> Frame<Renderer> +where + Renderer: geometry::Renderer, +{ + /// Creates a new [`Frame`] with the given dimensions. + pub fn new(renderer: &Renderer, size: Size) -> Self { + Self { + raw: renderer.new_frame(size), + } + } + + /// Returns the width of the [`Frame`]. + pub fn width(&self) -> f32 { + self.raw.width() + } + + /// Returns the height of the [`Frame`]. + pub fn height(&self) -> f32 { + self.raw.height() + } + + /// Returns the dimensions of the [`Frame`]. + pub fn size(&self) -> Size { + self.raw.size() + } + + /// Returns the coordinate of the center of the [`Frame`]. + pub fn center(&self) -> Point { + self.raw.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<Fill>) { + self.raw.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<Fill>, + ) { + self.raw.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<Stroke<'a>>) { + self.raw.stroke(path, stroke); + } + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ 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. + pub fn fill_text(&mut self, text: impl Into<Text>) { + self.raw.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<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + self.push_transform(); + + let result = f(self); + + self.pop_transform(); + + result + } + + /// Pushes the current transform in the transform stack. + pub fn push_transform(&mut self) { + self.raw.push_transform(); + } + + /// Pops a transform from the transform stack and sets it as the current transform. + pub fn pop_transform(&mut self) { + self.raw.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<R>( + &mut self, + region: Rectangle, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let mut frame = self.draft(region.size()); + + let result = f(&mut frame); + + let origin = Point::new(region.x, region.y); + + self.paste(frame, origin); + + result + } + + /// Creates a new [`Frame`] with the given [`Size`]. + /// + /// Draw its contents back to this [`Frame`] with [`paste`]. + /// + /// [`paste`]: Self::paste + pub fn draft(&mut self, size: Size) -> Self { + Self { + raw: self.raw.draft(size), + } + } + + /// Draws the contents of the given [`Frame`] with origin at the given [`Point`]. + pub fn paste(&mut self, frame: Self, at: Point) { + self.raw.paste(frame.raw, at); + } + + /// Applies a translation to the current transform of the [`Frame`]. + pub fn translate(&mut self, translation: Vector) { + self.raw.translate(translation); + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + pub fn rotate(&mut self, angle: impl Into<Radians>) { + self.raw.rotate(angle); + } + + /// Applies a uniform scaling to the current transform of the [`Frame`]. + pub fn scale(&mut self, scale: impl Into<f32>) { + self.raw.scale(scale); + } + + /// Applies a non-uniform scaling to the current transform of the [`Frame`]. + pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + self.raw.scale_nonuniform(scale); + } + + /// Turns the [`Frame`] into its underlying geometry. + pub fn into_geometry(self) -> Renderer::Geometry { + self.raw.into_geometry() + } +} + +/// The internal implementation of a [`Frame`]. +/// +/// Analogous to [`Frame`]. See [`Frame`] for the documentation +/// of each method. +#[allow(missing_docs)] +pub trait Backend: Sized { + type Geometry; + + fn width(&self) -> f32; + fn height(&self) -> f32; + fn size(&self) -> Size; + fn center(&self) -> Point; + + fn push_transform(&mut self); + fn pop_transform(&mut self); + + fn translate(&mut self, translation: Vector); + fn rotate(&mut self, angle: impl Into<Radians>); + fn scale(&mut self, scale: impl Into<f32>); + fn scale_nonuniform(&mut self, scale: impl Into<Vector>); + + fn draft(&mut self, size: Size) -> Self; + fn paste(&mut self, frame: Self, at: Point); + + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>); + + fn fill(&mut self, path: &Path, fill: impl Into<Fill>); + fn fill_text(&mut self, text: impl Into<Text>); + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ); + + fn into_geometry(self) -> Self::Geometry; +} + +#[cfg(debug_assertions)] +impl Backend for () { + type Geometry = (); + + fn width(&self) -> f32 { + 0.0 + } + + fn height(&self) -> f32 { + 0.0 + } + + fn size(&self) -> Size { + Size::ZERO + } + + fn center(&self) -> Point { + Point::ORIGIN + } + + fn push_transform(&mut self) {} + fn pop_transform(&mut self) {} + + fn translate(&mut self, _translation: Vector) {} + fn rotate(&mut self, _angle: impl Into<Radians>) {} + fn scale(&mut self, _scale: impl Into<f32>) {} + fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {} + + fn draft(&mut self, _size: Size) -> Self {} + fn paste(&mut self, _frame: Self, _at: Point) {} + + fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {} + + fn fill(&mut self, _path: &Path, _fill: impl Into<Fill>) {} + fn fill_text(&mut self, _text: impl Into<Text>) {} + fn fill_rectangle( + &mut self, + _top_left: Point, + _size: Size, + _fill: impl Into<Fill>, + ) { + } + + fn into_geometry(self) -> Self::Geometry {} +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index aa9d00e8..d7f2f439 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -17,14 +17,16 @@ )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; -mod error; +mod cached; mod primitive; +mod settings; mod viewport; pub mod backend; pub mod color; pub mod compositor; pub mod damage; +pub mod error; pub mod gradient; pub mod mesh; pub mod renderer; @@ -38,6 +40,7 @@ pub mod image; pub use antialiasing::Antialiasing; pub use backend::Backend; +pub use cached::Cached; pub use compositor::Compositor; pub use damage::Damage; pub use error::Error; @@ -45,6 +48,7 @@ pub use gradient::Gradient; pub use mesh::Mesh; pub use primitive::Primitive; pub use renderer::Renderer; +pub use settings::Settings; pub use viewport::Viewport; pub use iced_core as core; diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 041986cf..20692b07 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -74,3 +74,9 @@ pub struct GradientVertex2D { /// The packed vertex data of the gradient. pub gradient: gradient::Packed, } + +/// A renderer capable of drawing a [`Mesh`]. +pub trait Renderer { + /// Draws the given [`Mesh`]. + fn draw_mesh(&mut self, mesh: Mesh); +} diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 143f348b..f517ff3e 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,5 +1,6 @@ //! Create a renderer from a [`Backend`]. use crate::backend::{self, Backend}; +use crate::compositor; use crate::core; use crate::core::image; use crate::core::renderer; @@ -8,8 +9,9 @@ use crate::core::text::Text; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; +use crate::mesh; use crate::text; -use crate::Primitive; +use crate::{Mesh, Primitive}; use std::borrow::Cow; @@ -20,6 +22,7 @@ pub struct Renderer<B: Backend> { default_font: Font, default_text_size: Pixels, primitives: Vec<Primitive<B::Primitive>>, + stack: Vec<Vec<Primitive<B::Primitive>>>, } impl<B: Backend> Renderer<B> { @@ -34,6 +37,7 @@ impl<B: Backend> Renderer<B> { default_font, default_text_size, primitives: Vec::new(), + stack: Vec::new(), } } @@ -55,61 +59,35 @@ impl<B: Backend> Renderer<B> { ) -> O { f(&mut self.backend, &self.primitives) } +} - /// Starts recording a new layer. - pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> { - std::mem::take(&mut self.primitives) +impl<B: Backend> iced_core::Renderer for Renderer<B> { + fn start_layer(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); } - /// Ends the recording of a layer. - pub fn end_layer( - &mut self, - primitives: Vec<Primitive<B::Primitive>>, - bounds: Rectangle, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); + fn end_layer(&mut self, bounds: Rectangle) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); self.primitives.push(Primitive::group(layer).clip(bounds)); } - /// Starts recording a translation. - pub fn start_transformation(&mut self) -> Vec<Primitive<B::Primitive>> { - std::mem::take(&mut self.primitives) + fn start_transformation(&mut self) { + self.stack.push(std::mem::take(&mut self.primitives)); } - /// Ends the recording of a translation. - pub fn end_transformation( - &mut self, - primitives: Vec<Primitive<B::Primitive>>, - transformation: Transformation, - ) { - let layer = std::mem::replace(&mut self.primitives, primitives); + fn end_transformation(&mut self, transformation: Transformation) { + let layer = std::mem::replace( + &mut self.primitives, + self.stack.pop().expect("a layer should be recording"), + ); self.primitives .push(Primitive::group(layer).transform(transformation)); } -} - -impl<B: Backend> iced_core::Renderer for Renderer<B> { - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - let current = self.start_layer(); - - f(self); - - self.end_layer(current, bounds); - } - - fn with_transformation( - &mut self, - transformation: Transformation, - f: impl FnOnce(&mut Self), - ) { - let current = self.start_transformation(); - - f(self); - - self.end_transformation(current, transformation); - } fn fill_quad( &mut self, @@ -211,11 +189,11 @@ where { type Handle = image::Handle; - fn dimensions(&self, handle: &image::Handle) -> Size<u32> { + fn measure_image(&self, handle: &image::Handle) -> Size<u32> { self.backend().dimensions(handle) } - fn draw( + fn draw_image( &mut self, handle: image::Handle, filter_method: image::FilterMethod, @@ -233,11 +211,11 @@ impl<B> svg::Renderer for Renderer<B> where B: Backend + backend::Svg, { - fn dimensions(&self, handle: &svg::Handle) -> Size<u32> { + fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> { self.backend().viewport_dimensions(handle) } - fn draw( + fn draw_svg( &mut self, handle: svg::Handle, color: Option<Color>, @@ -250,3 +228,42 @@ where }); } } + +impl<B: Backend> mesh::Renderer for Renderer<B> { + fn draw_mesh(&mut self, mesh: Mesh) { + match B::Primitive::try_from(mesh) { + Ok(primitive) => { + self.draw_primitive(Primitive::Custom(primitive)); + } + Err(error) => { + log::warn!("mesh primitive could not be drawn: {error:?}"); + } + } + } +} + +#[cfg(feature = "geometry")] +impl<B> crate::geometry::Renderer for Renderer<B> +where + B: Backend + crate::geometry::Backend, + B::Frame: + crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>, +{ + type Frame = B::Frame; + type Geometry = Primitive<B::Primitive>; + + fn new_frame(&self, size: Size) -> Self::Frame { + self.backend.new_frame(size) + } + + fn draw_geometry(&mut self, geometry: Self::Geometry) { + self.draw_primitive(geometry); + } +} + +impl<B> compositor::Default for Renderer<B> +where + B: Backend, +{ + type Compositor = B::Compositor; +} diff --git a/renderer/src/settings.rs b/graphics/src/settings.rs index 432eb8a0..68673536 100644 --- a/renderer/src/settings.rs +++ b/graphics/src/settings.rs @@ -1,5 +1,5 @@ use crate::core::{Font, Pixels}; -use crate::graphics::Antialiasing; +use crate::Antialiasing; /// The settings of a Backend. #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 5cce2427..39c19fa3 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -12,9 +12,10 @@ keywords.workspace = true [features] wgpu = ["iced_wgpu"] -image = ["iced_tiny_skia/image", "iced_wgpu?/image"] -svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"] -geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"] +tiny-skia = ["iced_tiny_skia"] +image = ["iced_tiny_skia?/image", "iced_wgpu?/image"] +svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"] +geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"] tracing = ["iced_wgpu?/tracing"] web-colors = ["iced_wgpu?/web-colors"] webgl = ["iced_wgpu?/webgl"] @@ -22,7 +23,9 @@ fira-sans = ["iced_graphics/fira-sans"] [dependencies] iced_graphics.workspace = true + iced_tiny_skia.workspace = true +iced_tiny_skia.optional = true iced_wgpu.workspace = true iced_wgpu.optional = true diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs index c23a814c..8b137891 100644 --- a/renderer/src/compositor.rs +++ b/renderer/src/compositor.rs @@ -1,270 +1 @@ -use crate::core::Color; -use crate::graphics::compositor::{Information, SurfaceError, Window}; -use crate::graphics::{Error, Viewport}; -use crate::{Renderer, Settings}; -use std::env; -use std::future::Future; - -pub enum Compositor { - TinySkia(iced_tiny_skia::window::Compositor), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::window::Compositor), -} - -pub enum Surface { - TinySkia(iced_tiny_skia::window::Surface), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::window::Surface<'static>), -} - -impl crate::graphics::Compositor for Compositor { - type Settings = Settings; - type Renderer = Renderer; - type Surface = Surface; - - fn new<W: Window + Clone>( - settings: Self::Settings, - compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - let candidates = - Candidate::list_from_env().unwrap_or(Candidate::default_list()); - - async move { - let mut error = Error::GraphicsAdapterNotFound; - - for candidate in candidates { - match candidate.build(settings, compatible_window.clone()).await - { - Ok(compositor) => return Ok(compositor), - Err(new_error) => { - error = new_error; - } - } - } - - Err(error) - } - } - - fn create_renderer(&self) -> Self::Renderer { - match self { - Compositor::TinySkia(compositor) => { - Renderer::TinySkia(compositor.create_renderer()) - } - #[cfg(feature = "wgpu")] - Compositor::Wgpu(compositor) => { - Renderer::Wgpu(compositor.create_renderer()) - } - } - } - - fn create_surface<W: Window + Clone>( - &mut self, - window: W, - width: u32, - height: u32, - ) -> Surface { - match self { - Self::TinySkia(compositor) => Surface::TinySkia( - compositor.create_surface(window, width, height), - ), - #[cfg(feature = "wgpu")] - Self::Wgpu(compositor) => { - Surface::Wgpu(compositor.create_surface(window, width, height)) - } - } - } - - fn configure_surface( - &mut self, - surface: &mut Surface, - width: u32, - height: u32, - ) { - match (self, surface) { - (Self::TinySkia(compositor), Surface::TinySkia(surface)) => { - compositor.configure_surface(surface, width, height); - } - #[cfg(feature = "wgpu")] - (Self::Wgpu(compositor), Surface::Wgpu(surface)) => { - compositor.configure_surface(surface, width, height); - } - #[allow(unreachable_patterns)] - _ => panic!( - "The provided surface is not compatible with the compositor." - ), - } - } - - fn fetch_information(&self) -> Information { - match self { - Self::TinySkia(compositor) => compositor.fetch_information(), - #[cfg(feature = "wgpu")] - Self::Wgpu(compositor) => compositor.fetch_information(), - } - } - - fn present<T: AsRef<str>>( - &mut self, - renderer: &mut Self::Renderer, - surface: &mut Self::Surface, - viewport: &Viewport, - background_color: Color, - overlay: &[T], - ) -> Result<(), SurfaceError> { - match (self, renderer, surface) { - ( - Self::TinySkia(_compositor), - crate::Renderer::TinySkia(renderer), - Surface::TinySkia(surface), - ) => renderer.with_primitives(|backend, primitives| { - iced_tiny_skia::window::compositor::present( - backend, - surface, - primitives, - viewport, - background_color, - overlay, - ) - }), - #[cfg(feature = "wgpu")] - ( - Self::Wgpu(compositor), - crate::Renderer::Wgpu(renderer), - Surface::Wgpu(surface), - ) => renderer.with_primitives(|backend, primitives| { - iced_wgpu::window::compositor::present( - compositor, - backend, - surface, - primitives, - viewport, - background_color, - overlay, - ) - }), - #[allow(unreachable_patterns)] - _ => panic!( - "The provided renderer or surface are not compatible \ - with the compositor." - ), - } - } - - fn screenshot<T: AsRef<str>>( - &mut self, - renderer: &mut Self::Renderer, - surface: &mut Self::Surface, - viewport: &Viewport, - background_color: Color, - overlay: &[T], - ) -> Vec<u8> { - match (self, renderer, surface) { - ( - Self::TinySkia(_compositor), - Renderer::TinySkia(renderer), - Surface::TinySkia(surface), - ) => renderer.with_primitives(|backend, primitives| { - iced_tiny_skia::window::compositor::screenshot( - surface, - backend, - primitives, - viewport, - background_color, - overlay, - ) - }), - #[cfg(feature = "wgpu")] - ( - Self::Wgpu(compositor), - Renderer::Wgpu(renderer), - Surface::Wgpu(_), - ) => renderer.with_primitives(|backend, primitives| { - iced_wgpu::window::compositor::screenshot( - compositor, - backend, - primitives, - viewport, - background_color, - overlay, - ) - }), - #[allow(unreachable_patterns)] - _ => panic!( - "The provided renderer or backend are not compatible \ - with the compositor." - ), - } - } -} - -enum Candidate { - Wgpu, - TinySkia, -} - -impl Candidate { - fn default_list() -> Vec<Self> { - vec![ - #[cfg(feature = "wgpu")] - Self::Wgpu, - Self::TinySkia, - ] - } - - fn list_from_env() -> Option<Vec<Self>> { - let backends = env::var("ICED_BACKEND").ok()?; - - Some( - backends - .split(',') - .map(str::trim) - .map(|backend| match backend { - "wgpu" => Self::Wgpu, - "tiny-skia" => Self::TinySkia, - _ => panic!("unknown backend value: \"{backend}\""), - }) - .collect(), - ) - } - - async fn build<W: Window>( - self, - settings: Settings, - _compatible_window: W, - ) -> Result<Compositor, Error> { - match self { - Self::TinySkia => { - let compositor = iced_tiny_skia::window::compositor::new( - iced_tiny_skia::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - }, - _compatible_window, - ); - - Ok(Compositor::TinySkia(compositor)) - } - #[cfg(feature = "wgpu")] - Self::Wgpu => { - let compositor = 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, - ) - .await?; - - Ok(Compositor::Wgpu(compositor)) - } - #[cfg(not(feature = "wgpu"))] - Self::Wgpu => { - panic!("`wgpu` feature was not enabled in `iced_renderer`") - } - } - } -} diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs new file mode 100644 index 00000000..ef9cc9a9 --- /dev/null +++ b/renderer/src/fallback.rs @@ -0,0 +1,570 @@ +use crate::core::image; +use crate::core::renderer; +use crate::core::svg; +use crate::core::{ + self, Background, Color, Point, Rectangle, Size, Transformation, +}; +use crate::graphics; +use crate::graphics::compositor; +use crate::graphics::mesh; + +pub enum Renderer<L, R> { + Left(L), + Right(R), +} + +macro_rules! delegate { + ($renderer:expr, $name:ident, $body:expr) => { + match $renderer { + Self::Left($name) => $body, + Self::Right($name) => $body, + } + }; +} + +impl<L, R> core::Renderer for Renderer<L, R> +where + L: core::Renderer, + R: core::Renderer, +{ + fn fill_quad( + &mut self, + quad: renderer::Quad, + background: impl Into<Background>, + ) { + delegate!(self, renderer, renderer.fill_quad(quad, background.into())); + } + + fn clear(&mut self) { + delegate!(self, renderer, renderer.clear()); + } + + fn start_layer(&mut self) { + delegate!(self, renderer, renderer.start_layer()); + } + + fn end_layer(&mut self, bounds: Rectangle) { + delegate!(self, renderer, renderer.end_layer(bounds)); + } + + fn start_transformation(&mut self) { + delegate!(self, renderer, renderer.start_transformation()); + } + + fn end_transformation(&mut self, transformation: Transformation) { + delegate!(self, renderer, renderer.end_transformation(transformation)); + } +} + +impl<L, R> core::text::Renderer for Renderer<L, R> +where + L: core::text::Renderer, + R: core::text::Renderer< + Font = L::Font, + Paragraph = L::Paragraph, + Editor = L::Editor, + >, +{ + type Font = L::Font; + type Paragraph = L::Paragraph; + type Editor = L::Editor; + + const ICON_FONT: Self::Font = L::ICON_FONT; + const CHECKMARK_ICON: char = L::CHECKMARK_ICON; + const ARROW_DOWN_ICON: char = L::ARROW_DOWN_ICON; + + fn default_font(&self) -> Self::Font { + delegate!(self, renderer, renderer.default_font()) + } + + fn default_size(&self) -> core::Pixels { + delegate!(self, renderer, renderer.default_size()) + } + + fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) { + delegate!(self, renderer, renderer.load_font(font)); + } + + fn fill_paragraph( + &mut self, + text: &Self::Paragraph, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.fill_paragraph(text, position, color, clip_bounds) + ); + } + + fn fill_editor( + &mut self, + editor: &Self::Editor, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.fill_editor(editor, position, color, clip_bounds) + ); + } + + fn fill_text( + &mut self, + text: core::Text<'_, Self::Font>, + position: Point, + color: Color, + clip_bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.fill_text(text, position, color, clip_bounds) + ); + } +} + +impl<L, R> image::Renderer for Renderer<L, R> +where + L: image::Renderer, + R: image::Renderer<Handle = L::Handle>, +{ + type Handle = L::Handle; + + fn measure_image(&self, handle: &Self::Handle) -> Size<u32> { + delegate!(self, renderer, renderer.measure_image(handle)) + } + + fn draw_image( + &mut self, + handle: Self::Handle, + filter_method: image::FilterMethod, + bounds: Rectangle, + ) { + delegate!( + self, + renderer, + renderer.draw_image(handle, filter_method, bounds) + ); + } +} + +impl<L, R> svg::Renderer for Renderer<L, R> +where + L: svg::Renderer, + R: svg::Renderer, +{ + fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> { + delegate!(self, renderer, renderer.measure_svg(handle)) + } + + fn draw_svg( + &mut self, + handle: svg::Handle, + color: Option<Color>, + bounds: Rectangle, + ) { + delegate!(self, renderer, renderer.draw_svg(handle, color, bounds)); + } +} + +impl<L, R> mesh::Renderer for Renderer<L, R> +where + L: mesh::Renderer, + R: mesh::Renderer, +{ + fn draw_mesh(&mut self, mesh: graphics::Mesh) { + delegate!(self, renderer, renderer.draw_mesh(mesh)); + } +} + +pub enum Compositor<L, R> +where + L: graphics::Compositor, + R: graphics::Compositor, +{ + Left(L), + Right(R), +} + +pub enum Surface<L, R> { + Left(L), + Right(R), +} + +impl<L, R> graphics::Compositor for Compositor<L, R> +where + L: graphics::Compositor, + R: graphics::Compositor, +{ + type Renderer = Renderer<L::Renderer, R::Renderer>; + type Surface = Surface<L::Surface, R::Surface>; + + async fn with_backend<W: compositor::Window + Clone>( + settings: graphics::Settings, + compatible_window: W, + backend: Option<&str>, + ) -> Result<Self, graphics::Error> { + use std::env; + + let backends = backend + .map(str::to_owned) + .or_else(|| env::var("ICED_BACKEND").ok()); + + let mut candidates: Vec<_> = backends + .map(|backends| { + backends + .split(',') + .filter(|candidate| !candidate.is_empty()) + .map(str::to_owned) + .map(Some) + .collect() + }) + .unwrap_or_default(); + + if candidates.is_empty() { + candidates.push(None); + } + + let mut errors = vec![]; + + for backend in candidates.iter().map(Option::as_deref) { + match L::with_backend(settings, compatible_window.clone(), backend) + .await + { + Ok(compositor) => return Ok(Self::Left(compositor)), + Err(error) => { + errors.push(error); + } + } + + match R::with_backend(settings, compatible_window.clone(), backend) + .await + { + Ok(compositor) => return Ok(Self::Right(compositor)), + Err(error) => { + errors.push(error); + } + } + } + + Err(graphics::Error::List(errors)) + } + + fn create_renderer(&self) -> Self::Renderer { + match self { + Self::Left(compositor) => { + Renderer::Left(compositor.create_renderer()) + } + Self::Right(compositor) => { + Renderer::Right(compositor.create_renderer()) + } + } + } + + fn create_surface<W: compositor::Window + Clone>( + &mut self, + window: W, + width: u32, + height: u32, + ) -> Self::Surface { + match self { + Self::Left(compositor) => { + Surface::Left(compositor.create_surface(window, width, height)) + } + Self::Right(compositor) => { + Surface::Right(compositor.create_surface(window, width, height)) + } + } + } + + fn configure_surface( + &mut self, + surface: &mut Self::Surface, + width: u32, + height: u32, + ) { + match (self, surface) { + (Self::Left(compositor), Surface::Left(surface)) => { + compositor.configure_surface(surface, width, height); + } + (Self::Right(compositor), Surface::Right(surface)) => { + compositor.configure_surface(surface, width, height); + } + _ => unreachable!(), + } + } + + fn fetch_information(&self) -> compositor::Information { + delegate!(self, compositor, compositor.fetch_information()) + } + + fn present<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &graphics::Viewport, + background_color: Color, + overlay: &[T], + ) -> Result<(), compositor::SurfaceError> { + match (self, renderer, surface) { + ( + Self::Left(compositor), + Renderer::Left(renderer), + Surface::Left(surface), + ) => compositor.present( + renderer, + surface, + viewport, + background_color, + overlay, + ), + ( + Self::Right(compositor), + Renderer::Right(renderer), + Surface::Right(surface), + ) => compositor.present( + renderer, + surface, + viewport, + background_color, + overlay, + ), + _ => unreachable!(), + } + } + + fn screenshot<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &graphics::Viewport, + background_color: Color, + overlay: &[T], + ) -> Vec<u8> { + match (self, renderer, surface) { + ( + Self::Left(compositor), + Renderer::Left(renderer), + Surface::Left(surface), + ) => compositor.screenshot( + renderer, + surface, + viewport, + background_color, + overlay, + ), + ( + Self::Right(compositor), + Renderer::Right(renderer), + Surface::Right(surface), + ) => compositor.screenshot( + renderer, + surface, + viewport, + background_color, + overlay, + ), + _ => unreachable!(), + } + } +} + +#[cfg(feature = "wgpu")] +impl<L, R> iced_wgpu::primitive::pipeline::Renderer for Renderer<L, R> +where + L: iced_wgpu::primitive::pipeline::Renderer, + R: core::Renderer, +{ + fn draw_pipeline_primitive( + &mut self, + bounds: Rectangle, + primitive: impl iced_wgpu::primitive::pipeline::Primitive, + ) { + match self { + Self::Left(renderer) => { + renderer.draw_pipeline_primitive(bounds, primitive); + } + Self::Right(_) => { + log::warn!( + "Custom shader primitive is not supported with this renderer." + ); + } + } + } +} + +#[cfg(feature = "geometry")] +mod geometry { + use super::Renderer; + use crate::core::{Point, Radians, Size, Vector}; + use crate::graphics::geometry::{self, Fill, Path, Stroke, Text}; + use crate::graphics::Cached; + + impl<L, R> geometry::Renderer for Renderer<L, R> + where + L: geometry::Renderer, + R: geometry::Renderer, + { + type Geometry = Geometry<L::Geometry, R::Geometry>; + type Frame = Frame<L::Frame, R::Frame>; + + fn new_frame(&self, size: iced_graphics::core::Size) -> Self::Frame { + match self { + Self::Left(renderer) => Frame::Left(renderer.new_frame(size)), + Self::Right(renderer) => Frame::Right(renderer.new_frame(size)), + } + } + + fn draw_geometry(&mut self, geometry: Self::Geometry) { + match (self, geometry) { + (Self::Left(renderer), Geometry::Left(geometry)) => { + renderer.draw_geometry(geometry); + } + (Self::Right(renderer), Geometry::Right(geometry)) => { + renderer.draw_geometry(geometry); + } + _ => unreachable!(), + } + } + } + + pub enum Geometry<L, R> { + Left(L), + Right(R), + } + + impl<L, R> Cached for Geometry<L, R> + where + L: Cached, + R: Cached, + { + type Cache = Geometry<L::Cache, R::Cache>; + + fn load(cache: &Self::Cache) -> Self { + match cache { + Geometry::Left(cache) => Self::Left(L::load(cache)), + Geometry::Right(cache) => Self::Right(R::load(cache)), + } + } + + fn cache(self) -> Self::Cache { + match self { + Self::Left(geometry) => Geometry::Left(geometry.cache()), + Self::Right(geometry) => Geometry::Right(geometry.cache()), + } + } + } + + pub enum Frame<L, R> { + Left(L), + Right(R), + } + + impl<L, R> geometry::frame::Backend for Frame<L, R> + where + L: geometry::frame::Backend, + R: geometry::frame::Backend, + { + type Geometry = Geometry<L::Geometry, R::Geometry>; + + fn width(&self) -> f32 { + delegate!(self, frame, frame.width()) + } + + fn height(&self) -> f32 { + delegate!(self, frame, frame.height()) + } + + fn size(&self) -> Size { + delegate!(self, frame, frame.size()) + } + + fn center(&self) -> Point { + delegate!(self, frame, frame.center()) + } + + fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { + delegate!(self, frame, frame.fill(path, fill)); + } + + fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into<Fill>, + ) { + delegate!(self, frame, frame.fill_rectangle(top_left, size, fill)); + } + + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { + delegate!(self, frame, frame.stroke(path, stroke)); + } + + fn fill_text(&mut self, text: impl Into<Text>) { + delegate!(self, frame, frame.fill_text(text)); + } + + fn push_transform(&mut self) { + delegate!(self, frame, frame.push_transform()); + } + + fn pop_transform(&mut self) { + delegate!(self, frame, frame.pop_transform()); + } + + fn draft(&mut self, size: Size) -> Self { + match self { + Self::Left(frame) => Self::Left(frame.draft(size)), + Self::Right(frame) => Self::Right(frame.draft(size)), + } + } + + fn paste(&mut self, frame: Self, at: Point) { + match (self, frame) { + (Self::Left(target), Self::Left(source)) => { + target.paste(source, at); + } + (Self::Right(target), Self::Right(source)) => { + target.paste(source, at); + } + _ => unreachable!(), + } + } + + fn translate(&mut self, translation: Vector) { + delegate!(self, frame, frame.translate(translation)); + } + + fn rotate(&mut self, angle: impl Into<Radians>) { + delegate!(self, frame, frame.rotate(angle)); + } + + fn scale(&mut self, scale: impl Into<f32>) { + delegate!(self, frame, frame.scale(scale)); + } + + fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + delegate!(self, frame, frame.scale_nonuniform(scale)); + } + + fn into_geometry(self) -> Self::Geometry { + match self { + Frame::Left(frame) => Geometry::Left(frame.into_geometry()), + Frame::Right(frame) => Geometry::Right(frame.into_geometry()), + } + } + } +} + +impl<L, R> compositor::Default for Renderer<L, R> +where + L: compositor::Default, + R: compositor::Default, +{ + type Compositor = Compositor<L::Compositor, R::Compositor>; +} diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs deleted file mode 100644 index 36435148..00000000 --- a/renderer/src/geometry.rs +++ /dev/null @@ -1,210 +0,0 @@ -mod cache; - -pub use cache::Cache; - -use crate::core::{Point, Radians, Rectangle, Size, Transformation, Vector}; -use crate::graphics::geometry::{Fill, Path, Stroke, Text}; -use crate::Renderer; - -macro_rules! delegate { - ($frame:expr, $name:ident, $body:expr) => { - match $frame { - Self::TinySkia($name) => $body, - #[cfg(feature = "wgpu")] - Self::Wgpu($name) => $body, - } - }; -} - -pub enum Geometry { - TinySkia(iced_tiny_skia::Primitive), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::Primitive), -} - -impl Geometry { - pub fn transform(self, transformation: Transformation) -> Self { - match self { - Self::TinySkia(primitive) => { - Self::TinySkia(primitive.transform(transformation)) - } - #[cfg(feature = "wgpu")] - Self::Wgpu(primitive) => { - Self::Wgpu(primitive.transform(transformation)) - } - } - } -} - -pub enum Frame { - TinySkia(iced_tiny_skia::geometry::Frame), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::geometry::Frame), -} - -impl Frame { - pub fn new(renderer: &Renderer, size: Size) -> Self { - match renderer { - Renderer::TinySkia(_) => { - Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) - } - #[cfg(feature = "wgpu")] - Renderer::Wgpu(_) => { - Frame::Wgpu(iced_wgpu::geometry::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<Fill>) { - 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<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<Stroke<'a>>) { - 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. - pub fn fill_text(&mut self, text: impl Into<Text>) { - 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<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R { - delegate!(self, frame, frame.push_transform()); - - let result = f(self); - - delegate!(self, frame, frame.pop_transform()); - - result - } - - /// 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<R>( - &mut self, - region: Rectangle, - f: impl FnOnce(&mut Frame) -> R, - ) -> R { - let mut frame = match self { - Self::TinySkia(_) => Self::TinySkia( - iced_tiny_skia::geometry::Frame::new(region.size()), - ), - #[cfg(feature = "wgpu")] - Self::Wgpu(_) => { - Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size())) - } - }; - - let result = f(&mut frame); - - let origin = Point::new(region.x, region.y); - - match (self, frame) { - (Self::TinySkia(target), Self::TinySkia(frame)) => { - target.clip(frame, origin); - } - #[cfg(feature = "wgpu")] - (Self::Wgpu(target), Self::Wgpu(frame)) => { - target.clip(frame, origin); - } - #[allow(unreachable_patterns)] - _ => unreachable!(), - }; - - result - } - - /// 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: impl Into<Radians>) { - delegate!(self, frame, frame.rotate(angle)); - } - - /// Applies a uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: impl Into<f32>) { - delegate!(self, frame, frame.scale(scale)); - } - - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { - delegate!(self, frame, frame.scale_nonuniform(scale)); - } - - pub fn into_geometry(self) -> Geometry { - match self { - Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()), - #[cfg(feature = "wgpu")] - Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()), - } - } -} diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs deleted file mode 100644 index 3aff76b9..00000000 --- a/renderer/src/geometry/cache.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::core::Size; -use crate::geometry::{Frame, Geometry}; -use crate::Renderer; - -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<State>, -} - -#[derive(Debug, Default)] -enum State { - #[default] - Empty, - Filled { - bounds: Size, - primitive: Internal, - }, -} - -#[derive(Debug, Clone)] -enum Internal { - TinySkia(Arc<iced_tiny_skia::Primitive>), - #[cfg(feature = "wgpu")] - Wgpu(Arc<iced_wgpu::Primitive>), -} - -impl Cache { - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: RefCell::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 { - match primitive { - Internal::TinySkia(primitive) => { - return Geometry::TinySkia( - iced_tiny_skia::Primitive::Cache { - content: primitive.clone(), - }, - ); - } - #[cfg(feature = "wgpu")] - Internal::Wgpu(primitive) => { - return Geometry::Wgpu(iced_wgpu::Primitive::Cache { - content: primitive.clone(), - }); - } - } - } - } - - let mut frame = Frame::new(renderer, bounds); - draw_fn(&mut frame); - - let primitive = { - let geometry = frame.into_geometry(); - - match geometry { - Geometry::TinySkia(primitive) => { - Internal::TinySkia(Arc::new(primitive)) - } - #[cfg(feature = "wgpu")] - Geometry::Wgpu(primitive) => { - Internal::Wgpu(Arc::new(primitive)) - } - } - }; - - *self.state.borrow_mut() = State::Filled { - bounds, - primitive: primitive.clone(), - }; - - match primitive { - Internal::TinySkia(primitive) => { - Geometry::TinySkia(iced_tiny_skia::Primitive::Cache { - content: primitive, - }) - } - #[cfg(feature = "wgpu")] - Internal::Wgpu(primitive) => { - Geometry::Wgpu(iced_wgpu::Primitive::Cache { - content: primitive, - }) - } - } - } -} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 757c264d..7c48995d 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -4,299 +4,51 @@ #[cfg(feature = "wgpu")] pub use iced_wgpu as wgpu; -pub mod compositor; - -#[cfg(feature = "geometry")] -pub mod geometry; - -mod settings; +pub mod fallback; pub use iced_graphics as graphics; pub use iced_graphics::core; -pub use compositor::Compositor; -pub use settings::Settings; - #[cfg(feature = "geometry")] -pub use geometry::Geometry; - -use crate::core::renderer; -use crate::core::text::{self, Text}; -use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Transformation, -}; -use crate::graphics::text::Editor; -use crate::graphics::text::Paragraph; -use crate::graphics::Mesh; - -use std::borrow::Cow; +pub use iced_graphics::geometry; /// The default graphics renderer for [`iced`]. /// /// [`iced`]: https://github.com/iced-rs/iced -pub enum Renderer { - TinySkia(iced_tiny_skia::Renderer), - #[cfg(feature = "wgpu")] - Wgpu(iced_wgpu::Renderer), -} - -macro_rules! delegate { - ($renderer:expr, $name:ident, $body:expr) => { - match $renderer { - Self::TinySkia($name) => $body, - #[cfg(feature = "wgpu")] - Self::Wgpu($name) => $body, - } - }; -} - -impl Renderer { - pub fn draw_mesh(&mut self, mesh: Mesh) { - match self { - Self::TinySkia(_) => { - log::warn!("Unsupported mesh primitive: {mesh:?}"); - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - renderer.draw_primitive(iced_wgpu::Primitive::Custom( - iced_wgpu::primitive::Custom::Mesh(mesh), - )); - } - } - } -} - -impl core::Renderer for Renderer { - fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { - match self { - Self::TinySkia(renderer) => { - let primitives = renderer.start_layer(); - - f(self); - - match self { - Self::TinySkia(renderer) => { - renderer.end_layer(primitives, bounds); - } - #[cfg(feature = "wgpu")] - _ => unreachable!(), - } - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - let primitives = renderer.start_layer(); - - f(self); - - match self { - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - renderer.end_layer(primitives, bounds); - } - _ => unreachable!(), - } - } - } - } - - fn with_transformation( - &mut self, - transformation: Transformation, - f: impl FnOnce(&mut Self), - ) { - match self { - Self::TinySkia(renderer) => { - let primitives = renderer.start_transformation(); - - f(self); - - match self { - Self::TinySkia(renderer) => { - renderer.end_transformation(primitives, transformation); - } - #[cfg(feature = "wgpu")] - _ => unreachable!(), - } - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - let primitives = renderer.start_transformation(); - - f(self); +pub type Renderer = renderer::Renderer; - match self { - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - renderer.end_transformation(primitives, transformation); - } - _ => unreachable!(), - } - } - } - } - - fn fill_quad( - &mut self, - quad: renderer::Quad, - background: impl Into<Background>, - ) { - delegate!(self, renderer, renderer.fill_quad(quad, background)); - } - - fn clear(&mut self) { - delegate!(self, renderer, renderer.clear()); - } -} - -impl text::Renderer for Renderer { - type Font = Font; - type Paragraph = Paragraph; - type Editor = Editor; - - const ICON_FONT: Font = iced_tiny_skia::Renderer::ICON_FONT; - const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = iced_tiny_skia::Renderer::ARROW_DOWN_ICON; - - fn default_font(&self) -> Self::Font { - delegate!(self, renderer, renderer.default_font()) - } - - fn default_size(&self) -> Pixels { - delegate!(self, renderer, renderer.default_size()) - } - - fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - delegate!(self, renderer, renderer.load_font(bytes)); - } - - fn fill_paragraph( - &mut self, - paragraph: &Self::Paragraph, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - delegate!( - self, - renderer, - renderer.fill_paragraph(paragraph, position, color, clip_bounds) - ); - } - - fn fill_editor( - &mut self, - editor: &Self::Editor, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - delegate!( - self, - renderer, - renderer.fill_editor(editor, position, color, clip_bounds) - ); - } - - fn fill_text( - &mut self, - text: Text<'_, Self::Font>, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - delegate!( - self, - renderer, - renderer.fill_text(text, position, color, clip_bounds) - ); - } -} - -#[cfg(feature = "image")] -impl crate::core::image::Renderer for Renderer { - type Handle = crate::core::image::Handle; +/// The default graphics compositor for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +pub type Compositor = renderer::Compositor; - fn dimensions( - &self, - handle: &crate::core::image::Handle, - ) -> core::Size<u32> { - delegate!(self, renderer, renderer.dimensions(handle)) - } +#[cfg(all(feature = "wgpu", feature = "tiny-skia"))] +mod renderer { + pub type Renderer = crate::fallback::Renderer< + iced_wgpu::Renderer, + iced_tiny_skia::Renderer, + >; - fn draw( - &mut self, - handle: crate::core::image::Handle, - filter_method: crate::core::image::FilterMethod, - bounds: Rectangle, - ) { - delegate!(self, renderer, renderer.draw(handle, filter_method, bounds)); - } + pub type Compositor = crate::fallback::Compositor< + iced_wgpu::window::Compositor, + iced_tiny_skia::window::Compositor, + >; } -#[cfg(feature = "svg")] -impl crate::core::svg::Renderer for Renderer { - fn dimensions(&self, handle: &crate::core::svg::Handle) -> core::Size<u32> { - delegate!(self, renderer, renderer.dimensions(handle)) - } - - fn draw( - &mut self, - handle: crate::core::svg::Handle, - color: Option<crate::core::Color>, - bounds: Rectangle, - ) { - delegate!(self, renderer, renderer.draw(handle, color, bounds)); - } +#[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))] +mod renderer { + pub type Renderer = iced_wgpu::Renderer; + pub type Compositor = iced_wgpu::window::Compositor; } -#[cfg(feature = "geometry")] -impl crate::graphics::geometry::Renderer for Renderer { - type Geometry = crate::Geometry; - - fn draw(&mut self, layers: Vec<Self::Geometry>) { - match self { - Self::TinySkia(renderer) => { - for layer in layers { - match layer { - crate::Geometry::TinySkia(primitive) => { - renderer.draw_primitive(primitive); - } - #[cfg(feature = "wgpu")] - crate::Geometry::Wgpu(_) => unreachable!(), - } - } - } - #[cfg(feature = "wgpu")] - Self::Wgpu(renderer) => { - for layer in layers { - match layer { - crate::Geometry::Wgpu(primitive) => { - renderer.draw_primitive(primitive); - } - crate::Geometry::TinySkia(_) => unreachable!(), - } - } - } - } - } +#[cfg(all(not(feature = "wgpu"), feature = "tiny-skia"))] +mod renderer { + pub type Renderer = iced_tiny_skia::Renderer; + pub type Compositor = iced_tiny_skia::window::Compositor; } -#[cfg(feature = "wgpu")] -impl iced_wgpu::primitive::pipeline::Renderer for Renderer { - fn draw_pipeline_primitive( - &mut self, - bounds: Rectangle, - primitive: impl wgpu::primitive::pipeline::Primitive, - ) { - match self { - Self::TinySkia(_renderer) => { - log::warn!( - "Custom shader primitive is unavailable with tiny-skia." - ); - } - Self::Wgpu(renderer) => { - renderer.draw_pipeline_primitive(bounds, primitive); - } - } - } +#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))] +mod renderer { + pub type Renderer = (); + pub type Compositor = (); } diff --git a/runtime/src/program.rs b/runtime/src/program.rs index 6c1b8f07..0ea94d3b 100644 --- a/runtime/src/program.rs +++ b/runtime/src/program.rs @@ -2,7 +2,7 @@ use crate::Command; use iced_core::text; -use iced_core::{Element, Renderer}; +use iced_core::Element; mod state; @@ -11,7 +11,7 @@ pub use state::State; /// The core of a user interface application following The Elm Architecture. pub trait Program: Sized { /// The graphics backend to use to draw the [`Program`]. - type Renderer: Renderer + text::Renderer; + type Renderer: text::Renderer; /// The theme used to draw the [`Program`]. type Theme; diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 748fb651..006225ed 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -45,7 +45,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Renderer = (); /// # } /// # /// # pub struct Counter; @@ -62,7 +62,7 @@ where /// // Initialization /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); + /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// /// // Application loop @@ -121,7 +121,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Renderer = (); /// # } /// # /// # pub struct Counter; @@ -139,7 +139,7 @@ where /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); + /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; @@ -374,7 +374,7 @@ where /// /// ```no_run /// # mod iced_wgpu { - /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Renderer = (); /// # pub type Theme = (); /// # } /// # @@ -394,7 +394,7 @@ where /// /// let mut counter = Counter::new(); /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); + /// let mut renderer = Renderer::default(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; diff --git a/src/application.rs b/src/application.rs index 8317abcb..9197834b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,6 @@ //! Build interactive cross-platform applications. +use crate::core::text; +use crate::graphics::compositor; use crate::shell::application; use crate::{Command, Element, Executor, Settings, Subscription}; @@ -60,7 +62,7 @@ pub use application::{Appearance, DefaultStyle}; /// ```no_run /// use iced::advanced::Application; /// use iced::executor; -/// use iced::{Command, Element, Settings, Theme}; +/// use iced::{Command, Element, Settings, Theme, Renderer}; /// /// pub fn main() -> iced::Result { /// Hello::run(Settings::default()) @@ -73,6 +75,7 @@ pub use application::{Appearance, DefaultStyle}; /// type Flags = (); /// type Message = (); /// type Theme = Theme; +/// type Renderer = Renderer; /// /// fn new(_flags: ()) -> (Hello, Command<Self::Message>) { /// (Hello, Command::none()) @@ -109,6 +112,9 @@ where /// The theme of your [`Application`]. type Theme: Default; + /// The renderer of your [`Application`]. + type Renderer: text::Renderer + compositor::Default; + /// The data needed to initialize your [`Application`]. type Flags; @@ -142,7 +148,7 @@ where /// Returns the widgets to display in the [`Application`]. /// /// These widgets can produce __messages__ based on user interaction. - fn view(&self) -> Element<'_, Self::Message, Self::Theme, crate::Renderer>; + fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer>; /// Returns the current [`Theme`] of the [`Application`]. /// @@ -195,7 +201,7 @@ where Self: 'static, { #[allow(clippy::needless_update)] - let renderer_settings = crate::renderer::Settings { + let renderer_settings = crate::graphics::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, antialiasing: if settings.antialiasing { @@ -203,13 +209,13 @@ where } else { None }, - ..crate::renderer::Settings::default() + ..crate::graphics::Settings::default() }; let run = crate::shell::application::run::< Instance<Self>, Self::Executor, - crate::renderer::Compositor, + <Self::Renderer as compositor::Default>::Compositor, >(settings.into(), renderer_settings); #[cfg(target_arch = "wasm32")] @@ -241,7 +247,7 @@ where { type Message = A::Message; type Theme = A::Theme; - type Renderer = crate::Renderer; + type Renderer = A::Renderer; fn update(&mut self, message: Self::Message) -> Command<Self::Message> { self.0.update(message) @@ -372,15 +372,16 @@ pub type Result = std::result::Result<(), Error>; /// ] /// } /// ``` -pub fn run<State, Message, Theme>( +pub fn run<State, Message, Theme, Renderer>( title: impl program::Title<State> + 'static, update: impl program::Update<State, Message> + 'static, - view: impl for<'a> program::View<'a, State, Message, Theme> + 'static, + view: impl for<'a> program::View<'a, State, Message, Theme, Renderer> + 'static, ) -> Result where State: Default + 'static, Message: std::fmt::Debug + Send + 'static, Theme: Default + program::DefaultStyle + 'static, + Renderer: program::Renderer + 'static, { program(title, update, view).run() } diff --git a/src/multi_window.rs b/src/multi_window.rs index fca0be46..b81297dc 100644 --- a/src/multi_window.rs +++ b/src/multi_window.rs @@ -174,7 +174,7 @@ where Self: 'static, { #[allow(clippy::needless_update)] - let renderer_settings = crate::renderer::Settings { + let renderer_settings = crate::graphics::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, antialiasing: if settings.antialiasing { @@ -182,7 +182,7 @@ where } else { None }, - ..crate::renderer::Settings::default() + ..crate::graphics::Settings::default() }; Ok(crate::shell::multi_window::run::< diff --git a/src/program.rs b/src/program.rs index 7a366585..d4c2a266 100644 --- a/src/program.rs +++ b/src/program.rs @@ -31,7 +31,9 @@ //! } //! ``` use crate::application::Application; +use crate::core::text; use crate::executor::{self, Executor}; +use crate::graphics::compositor; use crate::window; use crate::{Command, Element, Font, Result, Settings, Size, Subscription}; @@ -67,37 +69,41 @@ use std::borrow::Cow; /// ] /// } /// ``` -pub fn program<State, Message, Theme>( +pub fn program<State, Message, Theme, Renderer>( title: impl Title<State>, update: impl Update<State, Message>, - view: impl for<'a> self::View<'a, State, Message, Theme>, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, ) -> Program<impl Definition<State = State, Message = Message, Theme = Theme>> where State: 'static, Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, + Renderer: self::Renderer, { use std::marker::PhantomData; - struct Application<State, Message, Theme, Update, View> { + struct Application<State, Message, Theme, Renderer, Update, View> { update: Update, view: View, _state: PhantomData<State>, _message: PhantomData<Message>, _theme: PhantomData<Theme>, + _renderer: PhantomData<Renderer>, } - impl<State, Message, Theme, Update, View> Definition - for Application<State, Message, Theme, Update, View> + impl<State, Message, Theme, Renderer, Update, View> Definition + for Application<State, Message, Theme, Renderer, Update, View> where Message: Send + std::fmt::Debug, Theme: Default + DefaultStyle, + Renderer: self::Renderer, Update: self::Update<State, Message>, - View: for<'a> self::View<'a, State, Message, Theme>, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, { type State = State; type Message = Message; type Theme = Theme; + type Renderer = Renderer; type Executor = executor::Default; fn load(&self) -> Command<Self::Message> { @@ -115,7 +121,7 @@ where fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.view.view(state).into() } } @@ -127,6 +133,7 @@ where _state: PhantomData, _message: PhantomData, _theme: PhantomData, + _renderer: PhantomData, }, settings: Settings::default(), } @@ -184,6 +191,7 @@ impl<P: Definition> Program<P> { impl<P: Definition, I: Fn() -> P::State> Application for Instance<P, I> { type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Flags = (P, I); type Executor = P::Executor; @@ -216,7 +224,7 @@ impl<P: Definition> Program<P> { fn view( &self, - ) -> crate::Element<'_, Self::Message, Self::Theme, crate::Renderer> + ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> { self.program.view(&self.state) } @@ -417,6 +425,9 @@ pub trait Definition: Sized { /// The theme of the program. type Theme: Default + DefaultStyle; + /// The renderer of the program. + type Renderer: Renderer; + /// The executor of the program. type Executor: Executor; @@ -431,7 +442,7 @@ pub trait Definition: Sized { fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme>; + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; fn title(&self, _state: &Self::State) -> String { String::from("A cool iced application!") @@ -470,6 +481,7 @@ fn with_title<P: Definition>( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = P::Executor; fn load(&self) -> Command<Self::Message> { @@ -491,7 +503,7 @@ fn with_title<P: Definition>( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -534,6 +546,7 @@ fn with_load<P: Definition>( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = executor::Default; fn load(&self) -> Command<Self::Message> { @@ -551,7 +564,7 @@ fn with_load<P: Definition>( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -598,6 +611,7 @@ fn with_subscription<P: Definition>( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = executor::Default; fn subscription( @@ -622,7 +636,7 @@ fn with_subscription<P: Definition>( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -665,6 +679,7 @@ fn with_theme<P: Definition>( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = P::Executor; fn theme(&self, state: &Self::State) -> Self::Theme { @@ -690,7 +705,7 @@ fn with_theme<P: Definition>( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -729,6 +744,7 @@ fn with_style<P: Definition>( type State = P::State; type Message = P::Message; type Theme = P::Theme; + type Renderer = P::Renderer; type Executor = P::Executor; fn style( @@ -758,7 +774,7 @@ fn with_style<P: Definition>( fn view<'a>( &self, state: &'a Self::State, - ) -> Element<'a, Self::Message, Self::Theme> { + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { self.program.view(state) } @@ -834,18 +850,30 @@ where /// /// This trait allows the [`program`] builder to take any closure that /// returns any `Into<Element<'_, Message>>`. -pub trait View<'a, State, Message, Theme> { +pub trait View<'a, State, Message, Theme, Renderer> { /// Produces the widget of the [`Program`]. - fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>>; + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>>; } -impl<'a, T, State, Message, Theme, Widget> View<'a, State, Message, Theme> for T +impl<'a, T, State, Message, Theme, Renderer, Widget> + View<'a, State, Message, Theme, Renderer> for T where T: Fn(&'a State) -> Widget, State: 'static, - Widget: Into<Element<'a, Message, Theme>>, + Widget: Into<Element<'a, Message, Theme, Renderer>>, { - fn view(&self, state: &'a State) -> impl Into<Element<'a, Message, Theme>> { + fn view( + &self, + state: &'a State, + ) -> impl Into<Element<'a, Message, Theme, Renderer>> { self(state) } } + +/// The renderer of some [`Program`]. +pub trait Renderer: text::Renderer + compositor::Default {} + +impl<T> Renderer for T where T: text::Renderer + compositor::Default {} diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index b6487b38..8c8781e3 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -5,6 +5,7 @@ use crate::graphics::backend; use crate::graphics::text; use crate::graphics::{Damage, Viewport}; use crate::primitive::{self, Primitive}; +use crate::window; use std::borrow::Cow; @@ -989,8 +990,9 @@ fn rounded_box_sdf( (x.powf(2.0) + y.powf(2.0)).sqrt() - radius } -impl iced_graphics::Backend for Backend { +impl backend::Backend for Backend { type Primitive = primitive::Custom; + type Compositor = window::Compositor; } impl backend::Text for Backend { @@ -1018,3 +1020,12 @@ impl backend::Svg for Backend { self.vector_pipeline.viewport_dimensions(handle) } } + +#[cfg(feature = "geometry")] +impl crate::graphics::geometry::Backend for Backend { + type Frame = crate::geometry::Frame; + + fn new_frame(&self, size: Size) -> Self::Frame { + crate::geometry::Frame::new(size) + } +} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 16787f89..76482e12 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -4,7 +4,7 @@ use crate::core::{ }; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::stroke::{self, Stroke}; -use crate::graphics::geometry::{Path, Style, Text}; +use crate::graphics::geometry::{self, Path, Style, Text}; use crate::graphics::Gradient; use crate::primitive::{self, Primitive}; @@ -25,23 +25,36 @@ impl Frame { } } - pub fn width(&self) -> f32 { + pub fn into_primitive(self) -> Primitive { + Primitive::Clip { + bounds: Rectangle::new(Point::ORIGIN, self.size), + content: Box::new(Primitive::Group { + primitives: self.primitives, + }), + } + } +} + +impl geometry::frame::Backend for Frame { + type Geometry = Primitive; + + fn width(&self) -> f32 { self.size.width } - pub fn height(&self) -> f32 { + fn height(&self) -> f32 { self.size.height } - pub fn size(&self) -> Size { + fn size(&self) -> Size { self.size } - pub fn center(&self) -> Point { + 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<Fill>) { + fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { @@ -61,7 +74,7 @@ impl Frame { })); } - pub fn fill_rectangle( + fn fill_rectangle( &mut self, top_left: Point, size: Size, @@ -89,7 +102,7 @@ impl Frame { })); } - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { @@ -110,7 +123,7 @@ impl Frame { })); } - pub fn fill_text(&mut self, text: impl Into<Text>) { + fn fill_text(&mut self, text: impl Into<Text>) { let text = text.into(); let (scale_x, scale_y) = self.transform.get_scale(); @@ -174,51 +187,50 @@ impl Frame { } } - pub fn push_transform(&mut self) { + fn push_transform(&mut self) { self.stack.push(self.transform); } - pub fn pop_transform(&mut self) { + fn pop_transform(&mut self) { self.transform = self.stack.pop().expect("Pop transform"); } - pub fn clip(&mut self, frame: Self, at: Point) { + fn draft(&mut self, size: Size) -> Self { + Self::new(size) + } + + fn paste(&mut self, frame: Self, at: Point) { self.primitives.push(Primitive::Transform { transformation: Transformation::translate(at.x, at.y), content: Box::new(frame.into_primitive()), }); } - pub fn translate(&mut self, translation: Vector) { + fn translate(&mut self, translation: Vector) { self.transform = self.transform.pre_translate(translation.x, translation.y); } - pub fn rotate(&mut self, angle: impl Into<Radians>) { + fn rotate(&mut self, angle: impl Into<Radians>) { self.transform = self.transform.pre_concat( tiny_skia::Transform::from_rotate(angle.into().0.to_degrees()), ); } - pub fn scale(&mut self, scale: impl Into<f32>) { + fn scale(&mut self, scale: impl Into<f32>) { let scale = scale.into(); self.scale_nonuniform(Vector { x: scale, y: scale }); } - pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { let scale = scale.into(); self.transform = self.transform.pre_scale(scale.x, scale.y); } - pub fn into_primitive(self) -> Primitive { - Primitive::Clip { - bounds: Rectangle::new(Point::ORIGIN, self.size), - content: Box::new(Primitive::Group { - primitives: self.primitives, - }), - } + fn into_geometry(self) -> Self::Geometry { + self.into_primitive() } } diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs index 7718d542..b7c428e4 100644 --- a/tiny_skia/src/primitive.rs +++ b/tiny_skia/src/primitive.rs @@ -1,5 +1,5 @@ use crate::core::Rectangle; -use crate::graphics::Damage; +use crate::graphics::{Damage, Mesh}; pub type Primitive = crate::graphics::Primitive<Custom>; @@ -42,3 +42,11 @@ impl Damage for Custom { } } } + +impl TryFrom<Mesh> for Custom { + type Error = &'static str; + + fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> { + Err("unsupported") + } +} diff --git a/tiny_skia/src/settings.rs b/tiny_skia/src/settings.rs index ec27b218..01d015b4 100644 --- a/tiny_skia/src/settings.rs +++ b/tiny_skia/src/settings.rs @@ -1,4 +1,5 @@ use crate::core::{Font, Pixels}; +use crate::graphics; /// The settings of a [`Backend`]. /// @@ -22,3 +23,12 @@ impl Default for Settings { } } } + +impl From<graphics::Settings> for Settings { + fn from(settings: graphics::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + } + } +} diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index a98825f1..25c57dc1 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -1,11 +1,11 @@ use crate::core::{Color, Rectangle, Size}; use crate::graphics::compositor::{self, Information}; use crate::graphics::damage; -use crate::graphics::{Error, Viewport}; +use crate::graphics::error::{self, Error}; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; use std::collections::VecDeque; -use std::future::{self, Future}; use std::num::NonZeroU32; pub struct Compositor { @@ -25,15 +25,25 @@ pub struct Surface { } impl crate::graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = Surface; - fn new<W: compositor::Window>( - settings: Self::Settings, + async fn with_backend<W: compositor::Window>( + settings: graphics::Settings, compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - future::ready(Ok(new(settings, compatible_window))) + backend: Option<&str>, + ) -> Result<Self, Error> { + match backend { + None | Some("tiny-skia") | Some("tiny_skia") => { + Ok(new(settings.into(), compatible_window)) + } + Some(backend) => Err(Error::GraphicsAdapterNotFound { + backend: "tiny-skia", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4a0d89f0..f6162e0f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -32,6 +32,7 @@ glyphon.workspace = true guillotiere.workspace = true log.workspace = true once_cell.workspace = true +thiserror.workspace = true wgpu.workspace = true lyon.workspace = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 09ddbe4d..5019191c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -7,6 +7,7 @@ use crate::primitive::{self, Primitive}; use crate::quad; use crate::text; use crate::triangle; +use crate::window; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -371,8 +372,9 @@ impl Backend { } } -impl crate::graphics::Backend for Backend { +impl backend::Backend for Backend { type Primitive = primitive::Custom; + type Compositor = window::Compositor; } impl backend::Text for Backend { @@ -397,3 +399,12 @@ impl backend::Svg for Backend { self.image_pipeline.viewport_dimensions(handle) } } + +#[cfg(feature = "geometry")] +impl crate::graphics::geometry::Backend for Backend { + type Frame = crate::geometry::Frame; + + fn new_frame(&self, size: Size) -> Self::Frame { + crate::geometry::Frame::new(size) + } +} diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f4e0fbda..ba56c59d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -6,7 +6,7 @@ use crate::core::{ use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ - LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, + self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; @@ -14,6 +14,7 @@ use crate::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; + use std::borrow::Cow; /// A frame for drawing some geometry. @@ -27,191 +28,87 @@ pub struct Frame { stroke_tessellator: tessellation::StrokeTessellator, } -enum Buffer { - Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), - Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), -} - -struct BufferStack { - stack: Vec<Buffer>, -} - -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } - } - - fn get_mut(&mut self, style: &Style) -> &mut Buffer { - match style { - Style::Solid(_) => match self.stack.last() { - Some(Buffer::Solid(_)) => {} - _ => { - self.stack.push(Buffer::Solid( - tessellation::VertexBuffers::new(), - )); - } - }, - Style::Gradient(_) => match self.stack.last() { - Some(Buffer::Gradient(_)) => {} - _ => { - self.stack.push(Buffer::Gradient( - tessellation::VertexBuffers::new(), - )); - } +impl Frame { + /// Creates a new [`Frame`] with the given [`Size`]. + pub fn new(size: Size) -> Frame { + Frame { + size, + buffers: BufferStack::new(), + primitives: Vec::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform(lyon::math::Transform::identity()), }, + fill_tessellator: tessellation::FillTessellator::new(), + stroke_tessellator: tessellation::StrokeTessellator::new(), } - - self.stack.last_mut().unwrap() } - fn get_fill<'a>( - &'a mut self, - style: &Style, - ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) + fn into_primitives(mut self) -> Vec<Primitive> { + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )); + } + } + Buffer::Gradient(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )); + } + } } - _ => unreachable!(), } - } - fn get_stroke<'a>( - &'a mut self, - style: &Style, - ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) - } - _ => unreachable!(), - } + self.primitives } } -#[derive(Debug)] -struct Transforms { - previous: Vec<Transform>, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform(lyon::math::Transform); - -impl Transform { - fn is_identity(&self) -> bool { - self.0 == lyon::math::Transform::identity() - } - - fn is_scale_translation(&self) -> bool { - self.0.m12.abs() < 2.0 * f32::EPSILON - && self.0.m21.abs() < 2.0 * f32::EPSILON - } - - fn scale(&self) -> (f32, f32) { - (self.0.m11, self.0.m22) - } - - fn transform_point(&self, point: Point) -> Point { - let transformed = self - .0 - .transform_point(euclid::Point2D::new(point.x, point.y)); - - Point { - x: transformed.x, - y: transformed.y, - } - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - match &mut gradient { - Gradient::Linear(linear) => { - linear.start = self.transform_point(linear.start); - linear.end = self.transform_point(linear.end); - } - } - - gradient - } -} +impl geometry::frame::Backend for Frame { + type Geometry = Primitive; -impl Frame { /// Creates a new empty [`Frame`] with the given dimensions. /// /// The default coordinate system of a [`Frame`] has its origin at the /// top-left corner of its bounds. - pub fn new(size: Size) -> Frame { - Frame { - size, - buffers: BufferStack::new(), - primitives: Vec::new(), - transforms: Transforms { - previous: Vec::new(), - current: Transform(lyon::math::Transform::identity()), - }, - fill_tessellator: tessellation::FillTessellator::new(), - stroke_tessellator: tessellation::StrokeTessellator::new(), - } - } - /// Returns the width of the [`Frame`]. #[inline] - pub fn width(&self) -> f32 { + fn width(&self) -> f32 { self.size.width } - /// Returns the height of the [`Frame`]. #[inline] - pub fn height(&self) -> f32 { + fn height(&self) -> f32 { self.size.height } - /// Returns the dimensions of the [`Frame`]. #[inline] - pub fn size(&self) -> Size { + fn size(&self) -> Size { self.size } - /// Returns the coordinate of the center of the [`Frame`]. #[inline] - pub fn center(&self) -> Point { + fn center(&self) -> Point { Point::new(self.size.width / 2.0, self.size.height / 2.0) } - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { + fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { let Fill { style, rule } = fill.into(); let mut buffer = self @@ -239,9 +136,7 @@ impl Frame { .expect("Tessellate path."); } - /// 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( + fn fill_rectangle( &mut self, top_left: Point, size: Size, @@ -276,9 +171,7 @@ impl Frame { .expect("Fill rectangle"); } - /// 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<Stroke<'a>>) { + fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { let stroke = stroke.into(); let mut buffer = self @@ -315,20 +208,7 @@ impl Frame { .expect("Stroke path"); } - /// 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. - pub fn fill_text(&mut self, text: impl Into<Text>) { + fn fill_text(&mut self, text: impl Into<Text>) { let text = text.into(); let (scale_x, scale_y) = self.transforms.current.scale(); @@ -384,57 +264,55 @@ impl Frame { } } - /// 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<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R { - self.push_transform(); - - let result = f(self); - - self.pop_transform(); - - result + fn translate(&mut self, translation: Vector) { + self.transforms.current.0 = + self.transforms + .current + .0 + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); } - /// Pushes the current transform in the transform stack. - pub fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); + #[inline] + fn rotate(&mut self, angle: impl Into<Radians>) { + self.transforms.current.0 = self + .transforms + .current + .0 + .pre_rotate(lyon::math::Angle::radians(angle.into().0)); } - /// Pops a transform from the transform stack and sets it as the current transform. - pub fn pop_transform(&mut self) { - self.transforms.current = self.transforms.previous.pop().unwrap(); + #[inline] + fn scale(&mut self, scale: impl Into<f32>) { + let scale = scale.into(); + + self.scale_nonuniform(Vector { x: scale, y: scale }); } - /// 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<R>( - &mut self, - region: Rectangle, - f: impl FnOnce(&mut Frame) -> R, - ) -> R { - let mut frame = Frame::new(region.size()); + fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { + let scale = scale.into(); - let result = f(&mut frame); + self.transforms.current.0 = + self.transforms.current.0.pre_scale(scale.x, scale.y); + } - let origin = Point::new(region.x, region.y); + fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } - self.clip(frame, origin); + fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } - result + fn draft(&mut self, size: Size) -> Frame { + Frame::new(size) } - /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. - pub fn clip(&mut self, frame: Frame, at: Point) { + fn paste(&mut self, frame: Frame, at: Point) { let size = frame.size(); let primitives = frame.into_primitives(); let transformation = Transformation::translate(at.x, at.y); @@ -462,89 +340,151 @@ impl Frame { }); } - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { - self.transforms.current.0 = - self.transforms - .current - .0 - .pre_translate(lyon::math::Vector::new( - translation.x, - translation.y, - )); + fn into_geometry(self) -> Self::Geometry { + Primitive::Group { + primitives: self.into_primitives(), + } } +} - /// Applies a rotation in radians to the current transform of the [`Frame`]. - #[inline] - pub fn rotate(&mut self, angle: impl Into<Radians>) { - self.transforms.current.0 = self - .transforms - .current - .0 - .pre_rotate(lyon::math::Angle::radians(angle.into().0)); +enum Buffer { + Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), + Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), +} + +struct BufferStack { + stack: Vec<Buffer>, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } } - /// Applies a uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: impl Into<f32>) { - let scale = scale.into(); + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(_) => match self.stack.last() { + Some(Buffer::Gradient(_)) => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + )); + } + }, + } - self.scale_nonuniform(Vector { x: scale, y: scale }); + self.stack.last_mut().unwrap() } - /// Applies a non-uniform scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { - let scale = scale.into(); + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } + } - self.transforms.current.0 = - self.transforms.current.0.pre_scale(scale.x, scale.y); + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color::pack(*color)), + )) + } + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, + )) + } + _ => unreachable!(), + } } +} - /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. - pub fn into_primitive(self) -> Primitive { - Primitive::Group { - primitives: self.into_primitives(), +#[derive(Debug)] +struct Transforms { + previous: Vec<Transform>, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform(lyon::math::Transform); + +impl Transform { + fn is_identity(&self) -> bool { + self.0 == lyon::math::Transform::identity() + } + + fn is_scale_translation(&self) -> bool { + self.0.m12.abs() < 2.0 * f32::EPSILON + && self.0.m21.abs() < 2.0 * f32::EPSILON + } + + fn scale(&self) -> (f32, f32) { + (self.0.m11, self.0.m22) + } + + fn transform_point(&self, point: Point) -> Point { + let transformed = self + .0 + .transform_point(euclid::Point2D::new(point.x, point.y)); + + Point { + x: transformed.x, + y: transformed.y, } } - fn into_primitives(mut self) -> Vec<Primitive> { - for buffer in self.buffers.stack { - match buffer { - Buffer::Solid(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Custom( - primitive::Custom::Mesh(Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }), - )); - } - } - Buffer::Gradient(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Custom( - primitive::Custom::Mesh(Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }), - )); - } - } + fn transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) } } + } - self.primitives + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + match &mut gradient { + Gradient::Linear(linear) => { + linear.start = self.transform_point(linear.start); + linear.end = self.transform_point(linear.end); + } + } + + gradient } } - struct GradientVertex2DBuilder { gradient: gradient::Packed, } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index fff927ea..ee9af93c 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -28,3 +28,11 @@ impl Damage for Custom { } } } + +impl TryFrom<Mesh> for Custom { + type Error = &'static str; + + fn try_from(mesh: Mesh) -> Result<Self, Self::Error> { + Ok(Custom::Mesh(mesh)) + } +} diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index c6b7c5e2..814440ba 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,5 +1,5 @@ //! Draw primitives using custom pipelines. -use crate::core::{Rectangle, Size}; +use crate::core::{self, Rectangle, Size}; use std::any::{Any, TypeId}; use std::collections::HashMap; @@ -58,7 +58,7 @@ pub trait Primitive: Debug + Send + Sync + 'static { } /// A renderer than can draw custom pipeline primitives. -pub trait Renderer: crate::core::Renderer { +pub trait Renderer: core::Renderer { /// Draws a custom pipeline primitive. fn draw_pipeline_primitive( &mut self, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index c9338fec..828d9e09 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,6 +1,6 @@ //! Configure a renderer. use crate::core::{Font, Pixels}; -use crate::graphics::Antialiasing; +use crate::graphics::{self, Antialiasing}; /// The settings of a [`Backend`]. /// @@ -29,30 +29,6 @@ pub struct Settings { pub antialiasing: Option<Antialiasing>, } -impl Settings { - /// Creates new [`Settings`] using environment configuration. - /// - /// Specifically: - /// - /// - The `internal_backend` can be configured using the `WGPU_BACKEND` - /// environment variable. If the variable is not set, the primary backend - /// will be used. The following values are allowed: - /// - `vulkan` - /// - `metal` - /// - `dx12` - /// - `dx11` - /// - `gl` - /// - `webgpu` - /// - `primary` - pub fn from_env() -> Self { - Settings { - internal_backend: wgpu::util::backend_bits_from_env() - .unwrap_or(wgpu::Backends::all()), - ..Self::default() - } - } -} - impl Default for Settings { fn default() -> Settings { Settings { @@ -64,3 +40,14 @@ impl Default for Settings { } } } + +impl From<graphics::Settings> for Settings { + fn from(settings: graphics::Settings) -> Self { + Self { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..Settings::default() + } + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index fa6b9373..9a3e3b34 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,13 +1,11 @@ //! Connect a window with a renderer. use crate::core::{Color, Size}; -use crate::graphics; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{Error, Viewport}; +use crate::graphics::error; +use crate::graphics::{self, Viewport}; use crate::{Backend, Primitive, Renderer, Settings}; -use std::future::Future; - /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] pub struct Compositor { @@ -20,6 +18,32 @@ pub struct Compositor { alpha_mode: wgpu::CompositeAlphaMode, } +/// A compositor error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + /// The surface creation failed. + #[error("the surface creation failed: {0}")] + SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError), + /// The surface is not compatible. + #[error("the surface is not compatible")] + IncompatibleSurface, + /// No adapter was found for the options requested. + #[error("no adapter was found for the options requested: {0:?}")] + NoAdapterFound(String), + /// No device request succeeded. + #[error("no device request succeeded: {0:?}")] + RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), +} + +impl From<Error> for graphics::Error { + fn from(error: Error) -> Self { + Self::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::RequestFailed(error.to_string()), + } + } +} + impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// @@ -27,7 +51,7 @@ impl Compositor { pub async fn request<W: compositor::Window>( settings: Settings, compatible_window: Option<W>, - ) -> Option<Self> { + ) -> Result<Self, Error> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: settings.internal_backend, ..Default::default() @@ -49,23 +73,27 @@ impl Compositor { let compatible_surface = compatible_window .and_then(|window| instance.create_surface(window).ok()); + let adapter_options = wgpu::RequestAdapterOptions { + power_preference: wgpu::util::power_preference_from_env() + .unwrap_or(if settings.antialiasing.is_none() { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + }), + compatible_surface: compatible_surface.as_ref(), + force_fallback_adapter: false, + }; + let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::util::power_preference_from_env() - .unwrap_or(if settings.antialiasing.is_none() { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }), - compatible_surface: compatible_surface.as_ref(), - force_fallback_adapter: false, - }) - .await?; + .request_adapter(&adapter_options) + .await + .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?; log::info!("Selected: {:#?}", adapter.get_info()); - let (format, alpha_mode) = - compatible_surface.as_ref().and_then(|surface| { + let (format, alpha_mode) = compatible_surface + .as_ref() + .and_then(|surface| { let capabilities = surface.get_capabilities(&adapter); let mut formats = capabilities.formats.iter().copied(); @@ -101,7 +129,8 @@ impl Compositor { }; format.zip(Some(preferred_alpha)) - })?; + }) + .ok_or(Error::IncompatibleSurface)?; log::info!( "Selected format: {format:?} with alpha mode: {alpha_mode:?}" @@ -115,39 +144,46 @@ impl Compositor { let limits = [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; - let mut limits = limits.into_iter().map(|limits| wgpu::Limits { + let limits = limits.into_iter().map(|limits| wgpu::Limits { max_bind_groups: 2, ..limits }); - let (device, queue) = - loop { - let required_limits = limits.next()?; - let device = adapter.request_device( + let mut errors = Vec::new(); + + for required_limits in limits { + let result = adapter + .request_device( &wgpu::DeviceDescriptor { label: Some( "iced_wgpu::window::compositor device descriptor", ), required_features: wgpu::Features::empty(), - required_limits, + required_limits: required_limits.clone(), }, None, - ).await.ok(); - - if let Some(device) = device { - break Some(device); + ) + .await; + + match result { + Ok((device, queue)) => { + return Ok(Compositor { + instance, + settings, + adapter, + device, + queue, + format, + alpha_mode, + }) } - }?; - - Some(Compositor { - instance, - settings, - adapter, - device, - queue, - format, - alpha_mode, - }) + Err(error) => { + errors.push((required_limits, error)); + } + } + } + + Err(Error::RequestDeviceFailed(errors)) } /// Creates a new rendering [`Backend`] for this [`Compositor`]. @@ -168,9 +204,7 @@ pub async fn new<W: compositor::Window>( settings: Settings, compatible_window: W, ) -> Result<Compositor, Error> { - Compositor::request(settings, Some(compatible_window)) - .await - .ok_or(Error::GraphicsAdapterNotFound) + Compositor::request(settings, Some(compatible_window)).await } /// Presents the given primitives with the given [`Compositor`] and [`Backend`]. @@ -229,15 +263,31 @@ pub fn present<T: AsRef<str>>( } impl graphics::Compositor for Compositor { - type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface<'static>; - fn new<W: compositor::Window>( - settings: Self::Settings, + async fn with_backend<W: compositor::Window>( + settings: graphics::Settings, compatible_window: W, - ) -> impl Future<Output = Result<Self, Error>> { - new(settings, compatible_window) + backend: Option<&str>, + ) -> Result<Self, graphics::Error> { + match backend { + None | Some("wgpu") => Ok(new( + Settings { + internal_backend: wgpu::util::backend_bits_from_env() + .unwrap_or(wgpu::Backends::all()), + ..settings.into() + }, + compatible_window, + ) + .await?), + Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { + backend: "wgpu", + reason: error::Reason::DidNotMatch { + preferred_backend: backend.to_owned(), + }, + }), + } } fn create_renderer(&self) -> Self::Renderer { diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 0eda0191..7a21895a 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -6,8 +6,10 @@ mod program; pub use event::Event; pub use program::Program; -pub use crate::graphics::geometry::*; -pub use crate::renderer::geometry::*; +pub use crate::graphics::geometry::{ + fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin, + Path, Stroke, Style, Text, +}; use crate::core; use crate::core::layout::{self, Layout}; @@ -21,6 +23,19 @@ use crate::graphics::geometry; use std::marker::PhantomData; +/// 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. +pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>; + +/// The geometry supported by a renderer. +pub type Geometry<Renderer = crate::Renderer> = + <Renderer as geometry::Renderer>::Geometry; + +/// The frame supported by a renderer. +pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>; + /// A widget capable of drawing 2D graphics. /// /// ## Drawing a simple circle @@ -42,7 +57,7 @@ use std::marker::PhantomData; /// impl Program<()> for Circle { /// type State = (); /// -/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry>{ +/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry> { /// // We prepare a new `Frame` /// let mut frame = Frame::new(renderer, bounds.size()); /// @@ -210,9 +225,12 @@ where renderer.with_transformation( Transformation::translate(bounds.x, bounds.y), |renderer| { - renderer.draw( - self.program.draw(state, renderer, theme, bounds, cursor), - ); + let layers = + self.program.draw(state, renderer, theme, bounds, cursor); + + for layer in layers { + renderer.draw_geometry(layer); + } }, ); } diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index 0bff4bda..a7ded0f4 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,5 +1,6 @@ use crate::canvas::event::{self, Event}; use crate::canvas::mouse; +use crate::canvas::Geometry; use crate::core::Rectangle; use crate::graphics::geometry; @@ -52,7 +53,7 @@ where theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec<Renderer::Geometry>; + ) -> Vec<Geometry<Renderer>>; /// Returns the current mouse interaction of the [`Program`]. /// @@ -94,7 +95,7 @@ where theme: &Theme, bounds: Rectangle, cursor: mouse::Cursor, - ) -> Vec<Renderer::Geometry> { + ) -> Vec<Geometry<Renderer>> { T::draw(self, state, renderer, theme, bounds, cursor) } diff --git a/widget/src/image.rs b/widget/src/image.rs index ccf1f175..f673c7b3 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -93,7 +93,7 @@ where { // The raw w/h of the underlying image let image_size = { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); Size::new(width as f32, height as f32) }; @@ -130,7 +130,7 @@ pub fn draw<Renderer, Handle>( Renderer: image::Renderer<Handle = Handle>, Handle: Clone + Hash, { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); @@ -148,7 +148,11 @@ pub fn draw<Renderer, Handle>( ..bounds }; - renderer.draw(handle.clone(), filter_method, drawing_bounds + offset); + renderer.draw_image( + handle.clone(), + filter_method, + drawing_bounds + offset, + ); }; if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 2e3fd713..5f7bb345 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -117,7 +117,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_image(&self.handle); let mut size = limits.resolve( self.width, @@ -335,8 +335,7 @@ where renderer.with_layer(bounds, |renderer| { renderer.with_translation(translation, |renderer| { - image::Renderer::draw( - renderer, + renderer.draw_image( self.handle.clone(), self.filter_method, Rectangle { @@ -421,7 +420,7 @@ pub fn image_size<Renderer>( where Renderer: image::Renderer, { - let Size { width, height } = renderer.dimensions(handle); + let Size { width, height } = renderer.measure_image(handle); let (width, height) = { let dimensions = (width as f32, height as f32); diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 90c0c970..601e5808 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -8,7 +8,6 @@ use crate::core::{ Color, Element, Layout, Length, Point, Rectangle, Size, Theme, Vector, Widget, }; -use crate::graphics::geometry::Renderer as _; use crate::Renderer; use std::cell::RefCell; @@ -142,7 +141,9 @@ impl<'a, Message, Theme> Widget<Message, Theme, Renderer> renderer.with_translation( bounds.position() - Point::ORIGIN, |renderer| { - renderer.draw(vec![geometry]); + use crate::graphics::geometry::Renderer as _; + + renderer.draw_geometry(geometry); }, ); } @@ -161,11 +162,11 @@ where /// The data of a [`QRCode`]. /// /// It stores the contents that will be displayed. -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct Data { contents: Vec<qrcode::Color>, width: usize, - cache: canvas::Cache, + cache: canvas::Cache<Renderer>, } impl Data { diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 1ac07ade..53c45bcb 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -108,7 +108,7 @@ where limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); // The size to be available to the widget prior to `Shrink`ing @@ -142,7 +142,7 @@ where cursor: mouse::Cursor, _viewport: &Rectangle, ) { - let Size { width, height } = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.measure_svg(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); @@ -169,7 +169,7 @@ where let appearance = (self.style)(theme, status); - renderer.draw( + renderer.draw_svg( self.handle.clone(), appearance.color, drawing_bounds + offset, diff --git a/winit/src/application.rs b/winit/src/application.rs index 13d9282d..d68523fa 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -13,6 +13,7 @@ use crate::core::window; use crate::core::{Color, Event, Point, Size, Theme}; use crate::futures::futures; use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics; use crate::graphics::compositor::{self, Compositor}; use crate::runtime::clipboard; use crate::runtime::program::Program; @@ -130,7 +131,7 @@ pub fn default(theme: &Theme) -> Appearance { /// settings. pub async fn run<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: C::Settings, + graphics_settings: graphics::Settings, ) -> Result<(), Error> where A: Application + 'static, @@ -219,7 +220,7 @@ where }; } - let compositor = C::new(compositor_settings, window.clone()).await?; + let compositor = C::new(graphics_settings, window.clone()).await?; let mut renderer = compositor.create_renderer(); for font in settings.fonts { diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 18db1fb5..b4c25411 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -16,6 +16,7 @@ use crate::futures::futures::executor; use crate::futures::futures::task; use crate::futures::futures::{Future, StreamExt}; use crate::futures::{Executor, Runtime, Subscription}; +use crate::graphics; use crate::graphics::{compositor, Compositor}; use crate::multi_window::window_manager::WindowManager; use crate::runtime::command::{self, Command}; @@ -105,7 +106,7 @@ where /// settings. pub fn run<A, E, C>( settings: Settings<A::Flags>, - compositor_settings: C::Settings, + graphics_settings: graphics::Settings, ) -> Result<(), Error> where A: Application + 'static, @@ -187,7 +188,7 @@ where } let mut compositor = - executor::block_on(C::new(compositor_settings, main_window.clone()))?; + executor::block_on(C::new(graphics_settings, main_window.clone()))?; let mut window_manager = WindowManager::new(); let _ = window_manager.insert( |