diff options
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/Cargo.toml | 4 | ||||
-rw-r--r-- | graphics/src/backend.rs | 12 | ||||
-rw-r--r-- | graphics/src/color.rs | 46 | ||||
-rw-r--r-- | graphics/src/compositor.rs | 15 | ||||
-rw-r--r-- | graphics/src/damage.rs | 5 | ||||
-rw-r--r-- | graphics/src/geometry.rs | 17 | ||||
-rw-r--r-- | graphics/src/geometry/fill.rs | 14 | ||||
-rw-r--r-- | graphics/src/geometry/path.rs | 2 | ||||
-rw-r--r-- | graphics/src/geometry/style.rs | 3 | ||||
-rw-r--r-- | graphics/src/gradient.rs | 195 | ||||
-rw-r--r-- | graphics/src/image.rs | 1 | ||||
-rw-r--r-- | graphics/src/lib.rs | 8 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 49 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 32 | ||||
-rw-r--r-- | graphics/src/triangle.rs | 1 |
15 files changed, 338 insertions, 66 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 125ea17d..02621695 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -14,9 +14,11 @@ categories = ["gui"] geometry = ["lyon_path"] opengl = [] image = ["dep:image", "kamadak-exif"] +web-colors = [] [dependencies] -glam = "0.21.3" +glam = "0.24" +half = "2.2.1" log = "0.4" raw-window-handle = "0.5" thiserror = "1.0" diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index ae89da06..6a082576 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -6,18 +6,6 @@ use iced_core::{Font, Point, Size}; use std::borrow::Cow; -/// The graphics backend of a [`Renderer`]. -/// -/// [`Renderer`]: crate::Renderer -pub trait Backend { - /// Trims the measurements cache. - /// - /// This method is currently necessary to properly trim the text cache in - /// `iced_wgpu` and `iced_glow` because of limitations in the text rendering - /// pipeline. It will be removed in the future. - fn trim_measurements(&mut self) {} -} - /// A graphics backend that supports text rendering. pub trait Text { /// The icon font of the backend. diff --git a/graphics/src/color.rs b/graphics/src/color.rs new file mode 100644 index 00000000..92448a68 --- /dev/null +++ b/graphics/src/color.rs @@ -0,0 +1,46 @@ +//! Manage colors for shaders. +use crate::core::Color; + +use bytemuck::{Pod, Zeroable}; + +/// A color packed as 4 floats representing RGBA channels. +#[derive(Debug, Clone, Copy, PartialEq, Zeroable, Pod)] +#[repr(C)] +pub struct Packed([f32; 4]); + +impl Packed { + /// Returns the internal components of the [`Packed`] color. + pub fn components(self) -> [f32; 4] { + self.0 + } +} + +/// A flag that indicates whether the renderer should perform gamma correction. +pub const GAMMA_CORRECTION: bool = internal::GAMMA_CORRECTION; + +/// Packs a [`Color`]. +pub fn pack(color: impl Into<Color>) -> Packed { + Packed(internal::pack(color.into())) +} + +#[cfg(not(feature = "web-colors"))] +mod internal { + use crate::core::Color; + + pub const GAMMA_CORRECTION: bool = true; + + pub fn pack(color: Color) -> [f32; 4] { + color.into_linear() + } +} + +#[cfg(feature = "web-colors")] +mod internal { + use crate::core::Color; + + pub const GAMMA_CORRECTION: bool = false; + + pub fn pack(color: Color) -> [f32; 4] { + [color.r, color.g, color.b, color.a] + } +} diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index d55e801a..f7b86045 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -59,6 +59,19 @@ pub trait Compositor: Sized { background_color: Color, overlay: &[T], ) -> Result<(), SurfaceError>; + + /// Screenshots the current [`Renderer`] primitives to an offscreen texture, and returns the bytes of + /// the texture ordered as `RGBA` in the sRGB color space. + /// + /// [`Renderer`]: Self::Renderer; + fn screenshot<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Vec<u8>; } /// Result of an unsuccessful call to [`Compositor::present`]. @@ -82,7 +95,7 @@ pub enum SurfaceError { OutOfMemory, } -/// Contains informations about the graphics (e.g. graphics adapter, graphics backend). +/// Contains information about the graphics (e.g. graphics adapter, graphics backend). #[derive(Debug)] pub struct Information { /// Contains the graphics adapter. diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 5aab06b1..c6b0f759 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -1,8 +1,10 @@ +//! Track and compute the damage of graphical primitives. use crate::core::{Rectangle, Size}; use crate::Primitive; use std::sync::Arc; +/// Computes the damage regions between the two given primitives. pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> { match (a, b) { ( @@ -73,6 +75,7 @@ pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> { } } +/// Computes the damage regions between the two given lists of primitives. pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> { let damage = previous .iter() @@ -95,6 +98,8 @@ pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> { } } +/// Groups the given damage regions that are close together inside the given +/// bounds. pub fn group( mut damage: Vec<Rectangle>, scale_factor: f32, diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs index 8db1594a..729c3d44 100644 --- a/graphics/src/geometry.rs +++ b/graphics/src/geometry.rs @@ -1,8 +1,4 @@ -//! Draw 2D graphics for your users. -//! -//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a -//! [`Frame`]. It can be used for animation, data visualization, game graphics, -//! and more! +//! Build and draw geometry. pub mod fill; pub mod path; pub mod stroke; @@ -16,10 +12,11 @@ pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; pub use text::Text; -pub use iced_core::gradient::{self, Gradient}; +pub use crate::gradient::{self, Gradient}; use crate::Primitive; +/// A bunch of shapes that can be drawn. #[derive(Debug, Clone)] pub struct Geometry(pub Primitive); @@ -29,8 +26,8 @@ impl From<Geometry> for Primitive { } } -pub trait Renderer: iced_core::Renderer { - type Geometry; - - fn draw(&mut self, geometry: Vec<Self::Geometry>); +/// A renderer capable of drawing some [`Geometry`]. +pub trait Renderer: crate::core::Renderer { + /// Draws the given layers of [`Geometry`]. + fn draw(&mut self, layers: Vec<Geometry>); } diff --git a/graphics/src/geometry/fill.rs b/graphics/src/geometry/fill.rs index 2e8c1669..b773c99b 100644 --- a/graphics/src/geometry/fill.rs +++ b/graphics/src/geometry/fill.rs @@ -1,8 +1,9 @@ //! Fill [crate::widget::canvas::Geometry] with a certain style. -use iced_core::{Color, Gradient}; - pub use crate::geometry::Style; +use crate::core::Color; +use crate::gradient::{self, Gradient}; + /// The style used to fill geometry. #[derive(Debug, Clone)] pub struct Fill { @@ -49,6 +50,15 @@ impl From<Gradient> for Fill { } } +impl From<gradient::Linear> for Fill { + fn from(gradient: gradient::Linear) -> Self { + Fill { + style: Style::Gradient(Gradient::Linear(gradient)), + ..Default::default() + } + } +} + /// The fill rule defines how to determine what is inside and what is outside of /// a shape. /// diff --git a/graphics/src/geometry/path.rs b/graphics/src/geometry/path.rs index c3127bdf..3d8fc6fa 100644 --- a/graphics/src/geometry/path.rs +++ b/graphics/src/geometry/path.rs @@ -53,11 +53,13 @@ impl Path { Self::new(|p| p.circle(center, radius)) } + /// Returns the internal [`lyon_path::Path`]. #[inline] pub fn raw(&self) -> &lyon_path::Path { &self.raw } + /// Returns the current [`Path`] with the given transform applied to it. #[inline] pub fn transform(&self, transform: &lyon_path::math::Transform) -> Path { Path { diff --git a/graphics/src/geometry/style.rs b/graphics/src/geometry/style.rs index be9ee376..a0f4b08a 100644 --- a/graphics/src/geometry/style.rs +++ b/graphics/src/geometry/style.rs @@ -1,4 +1,5 @@ -use iced_core::{Color, Gradient}; +use crate::core::Color; +use crate::geometry::Gradient; /// The coloring style of some drawing. #[derive(Debug, Clone, PartialEq)] diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs new file mode 100644 index 00000000..3f5d0509 --- /dev/null +++ b/graphics/src/gradient.rs @@ -0,0 +1,195 @@ +//! A gradient that can be used as a [`Fill`] for some geometry. +//! +//! For a gradient that you can use as a background variant for a widget, see [`Gradient`]. +//! +//! [`Gradient`]: crate::core::Gradient; +use crate::color; +use crate::core::gradient::ColorStop; +use crate::core::{self, Color, Point, Rectangle}; + +use half::f16; +use std::cmp::Ordering; + +#[derive(Debug, Clone, PartialEq)] +/// A fill which linearly interpolates colors along a direction. +/// +/// For a gradient which can be used as a fill for a background of a widget, see [`crate::core::Gradient`]. +pub enum Gradient { + /// A linear gradient interpolates colors along a direction from its `start` to its `end` + /// point. + Linear(Linear), +} + +impl From<Linear> for Gradient { + fn from(gradient: Linear) -> Self { + Self::Linear(gradient) + } +} + +impl Gradient { + /// Packs the [`Gradient`] for use in shader code. + pub fn pack(&self) -> Packed { + match self { + Gradient::Linear(linear) => linear.pack(), + } + } +} + +/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`]. +/// +/// [`Fill`]: crate::geometry::Fill; +/// [`Stroke`]: crate::geometry::Stroke; +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Linear { + /// The absolute starting position of the gradient. + pub start: Point, + + /// The absolute ending position of the gradient. + pub end: Point, + + /// [`ColorStop`]s along the linear gradient direction. + pub stops: [Option<ColorStop>; 8], +} + +impl Linear { + /// Creates a new [`Builder`]. + pub fn new(start: Point, end: Point) -> Self { + Self { + start, + end, + stops: [None; 8], + } + } + + /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient. + /// + /// Any `offset` that is not within `0.0..=1.0` will be silently ignored. + /// + /// Any stop added after the 8th will be silently ignored. + pub fn add_stop(mut self, offset: f32, color: Color) -> Self { + if offset.is_finite() && (0.0..=1.0).contains(&offset) { + let (Ok(index) | Err(index)) = + self.stops.binary_search_by(|stop| match stop { + None => Ordering::Greater, + Some(stop) => stop.offset.partial_cmp(&offset).unwrap(), + }); + + if index < 8 { + self.stops[index] = Some(ColorStop { offset, color }); + } + } else { + log::warn!("Gradient: ColorStop must be within 0.0..=1.0 range."); + }; + + self + } + + /// Adds multiple [`ColorStop`]s to the gradient. + /// + /// Any stop added after the 8th will be silently ignored. + pub fn add_stops( + mut self, + stops: impl IntoIterator<Item = ColorStop>, + ) -> Self { + for stop in stops.into_iter() { + self = self.add_stop(stop.offset, stop.color) + } + + self + } + + /// Packs the [`Gradient`] for use in shader code. + pub fn pack(&self) -> Packed { + let mut colors = [[0u32; 2]; 8]; + let mut offsets = [f16::from(0u8); 8]; + + for (index, stop) in self.stops.iter().enumerate() { + let [r, g, b, a] = + color::pack(stop.map_or(Color::default(), |s| s.color)) + .components(); + + colors[index] = [ + pack_f16s([f16::from_f32(r), f16::from_f32(g)]), + pack_f16s([f16::from_f32(b), f16::from_f32(a)]), + ]; + + offsets[index] = + stop.map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset)); + } + + let offsets = [ + pack_f16s([offsets[0], offsets[1]]), + pack_f16s([offsets[2], offsets[3]]), + pack_f16s([offsets[4], offsets[5]]), + pack_f16s([offsets[6], offsets[7]]), + ]; + + let direction = [self.start.x, self.start.y, self.end.x, self.end.y]; + + Packed { + colors, + offsets, + direction, + } + } +} + +/// Packed [`Gradient`] data for use in shader code. +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct Packed { + // 8 colors, each channel = 16 bit float, 2 colors packed into 1 u32 + colors: [[u32; 2]; 8], + // 8 offsets, 8x 16 bit floats packed into 4 u32s + offsets: [u32; 4], + direction: [f32; 4], +} + +/// Creates a new [`Packed`] gradient for use in shader code. +pub fn pack(gradient: &core::Gradient, bounds: Rectangle) -> Packed { + match gradient { + core::Gradient::Linear(linear) => { + let mut colors = [[0u32; 2]; 8]; + let mut offsets = [f16::from(0u8); 8]; + + for (index, stop) in linear.stops.iter().enumerate() { + let [r, g, b, a] = + color::pack(stop.map_or(Color::default(), |s| s.color)) + .components(); + + colors[index] = [ + pack_f16s([f16::from_f32(r), f16::from_f32(g)]), + pack_f16s([f16::from_f32(b), f16::from_f32(a)]), + ]; + + offsets[index] = stop + .map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset)); + } + + let offsets = [ + pack_f16s([offsets[0], offsets[1]]), + pack_f16s([offsets[2], offsets[3]]), + pack_f16s([offsets[4], offsets[5]]), + pack_f16s([offsets[6], offsets[7]]), + ]; + + let (start, end) = linear.angle.to_distance(&bounds); + + let direction = [start.x, start.y, end.x, end.y]; + + Packed { + colors, + offsets, + direction, + } + } + } +} + +/// Packs two f16s into one u32. +fn pack_f16s(f: [f16; 2]) -> u32 { + let one = (f[0].to_bits() as u32) << 16; + let two = f[1].to_bits() as u32; + + one | two +} diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 2f634252..6b43f4a8 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -5,6 +5,7 @@ use bitflags::bitflags; pub use ::image as image_rs; +/// Tries to load an image by its [`Handle`]. pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> { match handle.data() { Data::Path(path) => { diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index e3de4025..226b245b 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,7 +9,7 @@ )] #![deny( missing_debug_implementations, - //missing_docs, + missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -20,15 +20,17 @@ )] #![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] mod antialiasing; mod error; mod transformation; mod viewport; pub mod backend; +pub mod color; pub mod compositor; pub mod damage; +pub mod gradient; pub mod primitive; pub mod renderer; @@ -39,9 +41,9 @@ pub mod geometry; pub mod image; pub use antialiasing::Antialiasing; -pub use backend::Backend; pub use compositor::Compositor; pub use error::Error; +pub use gradient::Gradient; pub use primitive::Primitive; pub use renderer::Renderer; pub use transformation::Transformation; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index d814c757..187c6c6c 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -1,8 +1,11 @@ +//! Draw using different graphical primitives. +use crate::color; use crate::core::alignment; use crate::core::image; use crate::core::svg; use crate::core::text; -use crate::core::{Background, Color, Font, Gradient, Rectangle, Size, Vector}; +use crate::core::{Background, Color, Font, Rectangle, Size, Vector}; +use crate::gradient; use bytemuck::{Pod, Zeroable}; use std::sync::Arc; @@ -38,7 +41,7 @@ pub enum Primitive { bounds: Rectangle, /// The background of the quad background: Background, - /// The border radius of the quad + /// The border radii of the quad border_radius: [f32; 4], /// The border width of the quad border_width: f32, @@ -80,28 +83,35 @@ pub enum Primitive { /// It can be used to render many kinds of geometry freely. GradientMesh { /// The vertices and indices of the mesh. - buffers: Mesh2D<Vertex2D>, + buffers: Mesh2D<GradientVertex2D>, /// The size of the drawable region of the mesh. /// /// Any geometry that falls out of this region will be clipped. size: Size, - - /// The [`Gradient`] to apply to the mesh. - gradient: Gradient, }, + /// A [`tiny_skia`] path filled with some paint. #[cfg(feature = "tiny-skia")] Fill { + /// The path to fill. path: tiny_skia::Path, + /// The paint to use. paint: tiny_skia::Paint<'static>, + /// The fill rule to follow. rule: tiny_skia::FillRule, + /// The transform to apply to the path. transform: tiny_skia::Transform, }, + /// A [`tiny_skia`] path stroked with some paint. #[cfg(feature = "tiny-skia")] Stroke { + /// The path to stroke. path: tiny_skia::Path, + /// The paint to use. paint: tiny_skia::Paint<'static>, + /// The stroke settings. stroke: tiny_skia::Stroke, + /// The transform to apply to the path. transform: tiny_skia::Transform, }, /// A group of primitives @@ -135,10 +145,12 @@ pub enum Primitive { } impl Primitive { + /// Creates a [`Primitive::Group`]. pub fn group(primitives: Vec<Self>) -> Self { Self::Group { primitives } } + /// Creates a [`Primitive::Clip`]. pub fn clip(self, bounds: Rectangle) -> Self { Self::Clip { bounds, @@ -146,6 +158,7 @@ impl Primitive { } } + /// Creates a [`Primitive::Translate`]. pub fn translate(self, translation: Vector) -> Self { Self::Translate { translation, @@ -153,6 +166,7 @@ impl Primitive { } } + /// Returns the bounds of the [`Primitive`]. pub fn bounds(&self) -> Rectangle { match self { Self::Text { @@ -227,25 +241,34 @@ pub struct Mesh2D<T> { pub indices: Vec<u32>, } -/// A two-dimensional vertex. +/// A two-dimensional vertex with a color. #[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)] #[repr(C)] -pub struct Vertex2D { +pub struct ColoredVertex2D { /// The vertex position in 2D space. pub position: [f32; 2], + + /// The color of the vertex in __linear__ RGBA. + pub color: color::Packed, } -/// A two-dimensional vertex with a color. -#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)] +/// A vertex which contains 2D position & packed gradient data. +#[derive(Copy, Clone, Debug, PartialEq)] #[repr(C)] -pub struct ColoredVertex2D { +pub struct GradientVertex2D { /// The vertex position in 2D space. pub position: [f32; 2], - /// The color of the vertex in __linear__ RGBA. - pub color: [f32; 4], + /// The packed vertex data of the gradient. + pub gradient: gradient::Packed, } +#[allow(unsafe_code)] +unsafe impl Zeroable for GradientVertex2D {} + +#[allow(unsafe_code)] +unsafe impl Pod for GradientVertex2D {} + impl From<()> for Primitive { fn from(_: ()) -> Self { Self::Group { primitives: vec![] } diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index aaf1737a..4241d45c 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,5 +1,5 @@ //! Create a renderer from a [`Backend`]. -use crate::backend::{self, Backend}; +use crate::backend; use crate::Primitive; use iced_core::image; @@ -16,13 +16,13 @@ use std::marker::PhantomData; /// A backend-agnostic renderer that supports all the built-in widgets. #[derive(Debug)] -pub struct Renderer<B: Backend, Theme> { +pub struct Renderer<B, Theme> { backend: B, primitives: Vec<Primitive>, theme: PhantomData<Theme>, } -impl<B: Backend, T> Renderer<B, T> { +impl<B, T> Renderer<B, T> { /// Creates a new [`Renderer`] from the given [`Backend`]. pub fn new(backend: B) -> Self { Self { @@ -52,10 +52,7 @@ impl<B: Backend, T> Renderer<B, T> { } } -impl<B, T> iced_core::Renderer for Renderer<B, T> -where - B: Backend, -{ +impl<B, T> iced_core::Renderer for Renderer<B, T> { type Theme = T; fn layout<Message>( @@ -63,11 +60,7 @@ where element: &Element<'_, Message, Self>, limits: &layout::Limits, ) -> layout::Node { - let layout = element.as_widget().layout(self, limits); - - self.backend.trim_measurements(); - - layout + element.as_widget().layout(self, limits) } fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { @@ -116,7 +109,7 @@ where impl<B, T> text::Renderer for Renderer<B, T> where - B: Backend + backend::Text, + B: backend::Text, { type Font = Font; @@ -195,7 +188,7 @@ where impl<B, T> image::Renderer for Renderer<B, T> where - B: Backend + backend::Image, + B: backend::Image, { type Handle = image::Handle; @@ -210,7 +203,7 @@ where impl<B, T> svg::Renderer for Renderer<B, T> where - B: Backend + backend::Svg, + B: backend::Svg, { fn dimensions(&self, handle: &svg::Handle) -> Size<u32> { self.backend().viewport_dimensions(handle) @@ -231,13 +224,8 @@ where } #[cfg(feature = "geometry")] -impl<B, T> crate::geometry::Renderer for Renderer<B, T> -where - B: Backend, -{ - type Geometry = crate::Geometry; - - fn draw(&mut self, layers: Vec<Self::Geometry>) { +impl<B, T> crate::geometry::Renderer for Renderer<B, T> { + fn draw(&mut self, layers: Vec<crate::Geometry>) { self.primitives .extend(layers.into_iter().map(crate::Geometry::into)); } diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs deleted file mode 100644 index 09b61767..00000000 --- a/graphics/src/triangle.rs +++ /dev/null @@ -1 +0,0 @@ -//! Draw geometry using meshes of triangles. |