diff options
author | 2023-05-11 16:45:08 +0200 | |
---|---|---|
committer | 2023-05-11 16:45:08 +0200 | |
commit | 669f7cc74b2e7918e86a8197916f503f2d3d9b93 (patch) | |
tree | acb365358235be6ce115b50db9404d890b6e77a6 /renderer | |
parent | bc62013b6cde52174bf4c4286939cf170bfa7760 (diff) | |
parent | 63d3fc6996b848e10e77e6924bfebdf6ba82852e (diff) | |
download | iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.tar.gz iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.tar.bz2 iced-669f7cc74b2e7918e86a8197916f503f2d3d9b93.zip |
Merge pull request #1830 from iced-rs/advanced-text
Advanced text
Diffstat (limited to 'renderer')
-rw-r--r-- | renderer/Cargo.toml | 28 | ||||
-rw-r--r-- | renderer/src/backend.rs | 106 | ||||
-rw-r--r-- | renderer/src/compositor.rs | 214 | ||||
-rw-r--r-- | renderer/src/geometry.rs | 174 | ||||
-rw-r--r-- | renderer/src/geometry/cache.rs | 87 | ||||
-rw-r--r-- | renderer/src/lib.rs | 19 | ||||
-rw-r--r-- | renderer/src/settings.rs | 31 | ||||
-rw-r--r-- | renderer/src/widget.rs | 11 |
8 files changed, 670 insertions, 0 deletions
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml new file mode 100644 index 00000000..640ac996 --- /dev/null +++ b/renderer/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "iced_renderer" +version = "0.1.0" +edition = "2021" + +[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"] +tracing = ["iced_wgpu?/tracing"] + +[dependencies] +raw-window-handle = "0.5" +thiserror = "1" + +[dependencies.iced_graphics] +version = "0.8" +path = "../graphics" + +[dependencies.iced_tiny_skia] +version = "0.1" +path = "../tiny_skia" + +[dependencies.iced_wgpu] +version = "0.10" +path = "../wgpu" +optional = true diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs new file mode 100644 index 00000000..c9d79851 --- /dev/null +++ b/renderer/src/backend.rs @@ -0,0 +1,106 @@ +use crate::core::text; +use crate::core::{Font, Point, Size}; +use crate::graphics::backend; + +use std::borrow::Cow; + +#[allow(clippy::large_enum_variant)] +pub enum Backend { + TinySkia(iced_tiny_skia::Backend), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::Backend), +} + +macro_rules! delegate { + ($backend:expr, $name:ident, $body:expr) => { + match $backend { + Self::TinySkia($name) => $body, + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + } + }; +} + +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + delegate!(self, backend, backend.trim_measurements()); + } +} + +impl backend::Text for Backend { + const ICON_FONT: Font = Font::with_name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + delegate!(self, backend, backend.default_font()) + } + + fn default_size(&self) -> f32 { + delegate!(self, backend, backend.default_size()) + } + + fn measure( + &self, + contents: &str, + size: f32, + line_height: text::LineHeight, + font: Font, + bounds: Size, + shaping: text::Shaping, + ) -> (f32, f32) { + delegate!( + self, + backend, + backend.measure(contents, size, line_height, font, bounds, shaping) + ) + } + + fn hit_test( + &self, + contents: &str, + size: f32, + line_height: text::LineHeight, + font: Font, + bounds: Size, + shaping: text::Shaping, + position: Point, + nearest_only: bool, + ) -> Option<text::Hit> { + delegate!( + self, + backend, + backend.hit_test( + contents, + size, + line_height, + font, + bounds, + shaping, + position, + nearest_only, + ) + ) + } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + delegate!(self, backend, backend.load_font(font)); + } +} + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> { + delegate!(self, backend, backend.dimensions(handle)) + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + handle: &crate::core::svg::Handle, + ) -> Size<u32> { + delegate!(self, backend, backend.viewport_dimensions(handle)) + } +} diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs new file mode 100644 index 00000000..a353b8e4 --- /dev/null +++ b/renderer/src/compositor.rs @@ -0,0 +1,214 @@ +use crate::core::Color; +use crate::graphics::compositor::{Information, SurfaceError}; +use crate::graphics::{Error, Viewport}; +use crate::{Renderer, Settings}; + +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::env; + +pub enum Compositor<Theme> { + TinySkia(iced_tiny_skia::window::Compositor<Theme>), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::window::Compositor<Theme>), +} + +pub enum Surface { + TinySkia(iced_tiny_skia::window::Surface), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::window::Surface), +} + +impl<Theme> crate::graphics::Compositor for Compositor<Theme> { + type Settings = Settings; + type Renderer = Renderer<Theme>; + type Surface = Surface; + + fn new<W: HasRawWindowHandle + HasRawDisplayHandle>( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error> { + let candidates = + Candidate::list_from_env().unwrap_or(Candidate::default_list()); + + let mut error = Error::GraphicsAdapterNotFound; + + for candidate in candidates { + match candidate.build(settings, compatible_window) { + Ok((compositor, renderer)) => { + return Ok((compositor, renderer)) + } + Err(new_error) => { + error = new_error; + } + } + } + + Err(error) + } + + fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( + &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> { + renderer.with_primitives(|backend, primitives| { + match (self, backend, surface) { + ( + Self::TinySkia(_compositor), + crate::Backend::TinySkia(backend), + Surface::TinySkia(surface), + ) => iced_tiny_skia::window::compositor::present( + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ), + #[cfg(feature = "wgpu")] + ( + Self::Wgpu(compositor), + crate::Backend::Wgpu(backend), + Surface::Wgpu(surface), + ) => 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." + ), + } + }) + } +} + +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(), + ) + } + + fn build<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>( + self, + settings: Settings, + _compatible_window: Option<&W>, + ) -> Result<(Compositor<Theme>, Renderer<Theme>), Error> { + match self { + Self::TinySkia => { + let (compositor, backend) = + iced_tiny_skia::window::compositor::new( + iced_tiny_skia::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + }, + ); + + Ok(( + Compositor::TinySkia(compositor), + Renderer::new(crate::Backend::TinySkia(backend)), + )) + } + #[cfg(feature = "wgpu")] + Self::Wgpu => { + let (compositor, backend) = iced_wgpu::window::compositor::new( + iced_wgpu::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + ..iced_wgpu::Settings::from_env() + }, + _compatible_window, + )?; + + Ok(( + Compositor::Wgpu(compositor), + Renderer::new(crate::Backend::Wgpu(backend)), + )) + } + #[cfg(not(feature = "wgpu"))] + Self::Wgpu => { + panic!("`wgpu` feature was not enabled in `iced_renderer`") + } + } + } +} diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs new file mode 100644 index 00000000..26e2fed0 --- /dev/null +++ b/renderer/src/geometry.rs @@ -0,0 +1,174 @@ +mod cache; + +pub use cache::Cache; + +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; +use crate::Backend; + +pub enum Frame { + TinySkia(iced_tiny_skia::geometry::Frame), + #[cfg(feature = "wgpu")] + Wgpu(iced_wgpu::geometry::Frame), +} + +macro_rules! delegate { + ($frame:expr, $name:ident, $body:expr) => { + match $frame { + Self::TinySkia($name) => $body, + #[cfg(feature = "wgpu")] + Self::Wgpu($name) => $body, + } + }; +} + +impl Frame { + pub fn new<Theme>(renderer: &crate::Renderer<Theme>, size: Size) -> Self { + match renderer.backend() { + Backend::TinySkia(_) => { + Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) + } + #[cfg(feature = "wgpu")] + Backend::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. + /// + /// [`Canvas`]: crate::widget::Canvas + 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(&mut self, f: impl FnOnce(&mut Frame)) { + delegate!(self, frame, frame.push_transform()); + + f(self); + + delegate!(self, frame, frame.pop_transform()); + } + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { + let mut frame = match self { + Self::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())) + } + }; + + 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!(), + }; + } + + /// Applies a translation to the current transform of the [`Frame`]. + #[inline] + pub fn translate(&mut self, translation: Vector) { + delegate!(self, frame, frame.translate(translation)); + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + #[inline] + pub fn rotate(&mut self, angle: f32) { + delegate!(self, frame, frame.rotate(angle)); + } + + /// Applies a scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale(&mut self, scale: f32) { + delegate!(self, frame, frame.scale(scale)); + } + + pub fn into_geometry(self) -> Geometry { + Geometry(delegate!(self, frame, frame.into_primitive())) + } +} diff --git a/renderer/src/geometry/cache.rs b/renderer/src/geometry/cache.rs new file mode 100644 index 00000000..2a3534d0 --- /dev/null +++ b/renderer/src/geometry/cache.rs @@ -0,0 +1,87 @@ +use crate::core::Size; +use crate::geometry::{Frame, Geometry}; +use crate::graphics::Primitive; +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: Arc<Primitive>, + }, +} + +impl Cache { + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: Default::default(), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw<Theme>( + &self, + renderer: &Renderer<Theme>, + bounds: Size, + draw_fn: impl FnOnce(&mut Frame), + ) -> Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + primitive, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry(Primitive::Cache { + content: primitive.clone(), + }); + } + } + + let mut frame = Frame::new(renderer, bounds); + draw_fn(&mut frame); + + let primitive = { + let geometry = frame.into_geometry(); + + Arc::new(geometry.0) + }; + + *self.state.borrow_mut() = State::Filled { + bounds, + primitive: primitive.clone(), + }; + + Geometry(Primitive::Cache { content: primitive }) + } +} diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs new file mode 100644 index 00000000..22ec7bd1 --- /dev/null +++ b/renderer/src/lib.rs @@ -0,0 +1,19 @@ +pub mod compositor; + +#[cfg(feature = "geometry")] +pub mod geometry; + +mod backend; +mod settings; + +pub use iced_graphics as graphics; +pub use iced_graphics::core; + +pub use backend::Backend; +pub use compositor::Compositor; +pub use settings::Settings; + +/// The default graphics renderer for [`iced`]. +/// +/// [`iced`]: https://github.com/iced-rs/iced +pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>; diff --git a/renderer/src/settings.rs b/renderer/src/settings.rs new file mode 100644 index 00000000..2e51f339 --- /dev/null +++ b/renderer/src/settings.rs @@ -0,0 +1,31 @@ +use crate::core::Font; +use crate::graphics::Antialiasing; + +/// The settings of a [`Backend`]. +/// +/// [`Backend`]: crate::Backend +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Settings { + /// The default [`Font`] to use. + pub default_font: Font, + + /// The default size of text. + /// + /// By default, it will be set to `16.0`. + pub default_text_size: f32, + + /// The antialiasing strategy that will be used for triangle primitives. + /// + /// By default, it is `None`. + pub antialiasing: Option<Antialiasing>, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: Font::default(), + default_text_size: 16.0, + antialiasing: None, + } + } +} diff --git a/renderer/src/widget.rs b/renderer/src/widget.rs new file mode 100644 index 00000000..6c0c2a83 --- /dev/null +++ b/renderer/src/widget.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "canvas")] +pub mod canvas; + +#[cfg(feature = "canvas")] +pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +pub use qr_code::QRCode; |