Merge pull request #1932 from iced-rs/generic-graphics-primitive

Backend-specific primitives
This commit is contained in:
Héctor Ramón 2023-06-29 08:09:45 +02:00 committed by GitHub
commit c6b583113d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 869 additions and 482 deletions

View file

@ -5,26 +5,13 @@ mod null;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub use null::Null; pub use null::Null;
use crate::layout; use crate::{Background, BorderRadius, Color, Rectangle, Vector};
use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
/// A component that can be used by widgets to draw themselves on a screen. /// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized { pub trait Renderer: Sized {
/// The supported theme of the [`Renderer`]. /// The supported theme of the [`Renderer`].
type Theme; type Theme;
/// Lays out the elements of a user interface.
///
/// You should override this if you need to perform any operations before or
/// after layouting. For instance, trimming the measurements cache.
fn layout<Message>(
&mut self,
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
element.as_widget().layout(self, limits)
}
/// Draws the primitives recorded in the given closure in a new layer. /// Draws the primitives recorded in the given closure in a new layer.
/// ///
/// The layer will clip its contents to the provided `bounds`. /// The layer will clip its contents to the provided `bounds`.

View file

@ -7,4 +7,3 @@ publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["advanced"] } iced = { path = "../..", features = ["advanced"] }
iced_graphics = { path = "../../graphics" }

View file

@ -1,8 +1,6 @@
//! This example showcases a simple native custom widget that renders using //! This example showcases a simple native custom widget that renders using
//! arbitrary low-level geometry. //! arbitrary low-level geometry.
mod rainbow { mod rainbow {
use iced_graphics::primitive::{ColoredVertex2D, Primitive};
use iced::advanced::graphics::color; use iced::advanced::graphics::color;
use iced::advanced::layout::{self, Layout}; use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer; use iced::advanced::renderer;
@ -46,8 +44,8 @@ mod rainbow {
cursor: mouse::Cursor, cursor: mouse::Cursor,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) {
use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D};
use iced::advanced::Renderer as _; use iced::advanced::Renderer as _;
use iced_graphics::primitive::Mesh2D;
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -78,43 +76,43 @@ mod rainbow {
let posn_bl = [0.0, bounds.height]; let posn_bl = [0.0, bounds.height];
let posn_l = [0.0, bounds.height / 2.0]; let posn_l = [0.0, bounds.height / 2.0];
let mesh = Primitive::SolidMesh { let mesh = Mesh::Solid {
size: bounds.size(), size: bounds.size(),
buffers: Mesh2D { buffers: mesh::Indexed {
vertices: vec![ vertices: vec![
ColoredVertex2D { SolidVertex2D {
position: posn_center, position: posn_center,
color: color::pack([1.0, 1.0, 1.0, 1.0]), color: color::pack([1.0, 1.0, 1.0, 1.0]),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_tl, position: posn_tl,
color: color::pack(color_r), color: color::pack(color_r),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_t, position: posn_t,
color: color::pack(color_o), color: color::pack(color_o),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_tr, position: posn_tr,
color: color::pack(color_y), color: color::pack(color_y),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_r, position: posn_r,
color: color::pack(color_g), color: color::pack(color_g),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_br, position: posn_br,
color: color::pack(color_gb), color: color::pack(color_gb),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_b, position: posn_b,
color: color::pack(color_b), color: color::pack(color_b),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_bl, position: posn_bl,
color: color::pack(color_i), color: color::pack(color_i),
}, },
ColoredVertex2D { SolidVertex2D {
position: posn_l, position: posn_l,
color: color::pack(color_v), color: color::pack(color_v),
}, },
@ -135,7 +133,7 @@ mod rainbow {
renderer.with_translation( renderer.with_translation(
Vector::new(bounds.x, bounds.y), Vector::new(bounds.x, bounds.y),
|renderer| { |renderer| {
renderer.draw_primitive(mesh); renderer.draw_mesh(mesh);
}, },
); );
} }

View file

@ -358,7 +358,9 @@ where
renderer.with_translation( renderer.with_translation(
Vector::new(bounds.x, bounds.y), Vector::new(bounds.x, bounds.y),
|renderer| { |renderer| {
renderer.draw_primitive(geometry.0); use iced::advanced::graphics::geometry::Renderer as _;
renderer.draw(vec![geometry]);
}, },
); );
} }

View file

@ -32,10 +32,6 @@ features = ["derive"]
version = "0.9" version = "0.9"
path = "../core" path = "../core"
[dependencies.tiny-skia]
version = "0.9"
optional = true
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
optional = true optional = true

View file

@ -6,6 +6,14 @@ use iced_core::{Font, Point, Size};
use std::borrow::Cow; use std::borrow::Cow;
/// The graphics backend of a [`Renderer`].
///
/// [`Renderer`]: crate::Renderer
pub trait Backend {
/// The custom kind of primitives this [`Backend`] supports.
type Primitive;
}
/// A graphics backend that supports text rendering. /// A graphics backend that supports text rendering.
pub trait Text { pub trait Text {
/// The icon font of the backend. /// The icon font of the backend.

View file

@ -1,11 +1,66 @@
//! Track and compute the damage of graphical primitives. //! Track and compute the damage of graphical primitives.
use crate::core::alignment;
use crate::core::{Rectangle, Size}; use crate::core::{Rectangle, Size};
use crate::Primitive; use crate::Primitive;
use std::sync::Arc; use std::sync::Arc;
/// Computes the damage regions between the two given primitives. /// A type that has some damage bounds.
pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> { pub trait Damage: PartialEq {
/// Returns the bounds of the [`Damage`].
fn bounds(&self) -> Rectangle;
}
impl<T: Damage> Damage for Primitive<T> {
fn bounds(&self) -> Rectangle {
match self {
Self::Text {
bounds,
horizontal_alignment,
vertical_alignment,
..
} => {
let mut bounds = *bounds;
bounds.x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => {
bounds.x - bounds.width / 2.0
}
alignment::Horizontal::Right => bounds.x - bounds.width,
};
bounds.y = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => {
bounds.y - bounds.height / 2.0
}
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
bounds.expand(1.5)
}
Self::Quad { bounds, .. }
| Self::Image { bounds, .. }
| Self::Svg { bounds, .. } => bounds.expand(1.0),
Self::Clip { bounds, .. } => bounds.expand(1.0),
Self::Group { primitives } => primitives
.iter()
.map(Self::bounds)
.fold(Rectangle::with_size(Size::ZERO), |a, b| {
Rectangle::union(&a, &b)
}),
Self::Translate {
translation,
content,
} => content.bounds() + *translation,
Self::Cache { content } => content.bounds(),
Self::Custom(custom) => custom.bounds(),
}
}
}
fn regions<T: Damage>(a: &Primitive<T>, b: &Primitive<T>) -> Vec<Rectangle> {
match (a, b) { match (a, b) {
( (
Primitive::Group { Primitive::Group {
@ -76,7 +131,10 @@ pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> {
} }
/// Computes the damage regions between the two given lists of primitives. /// Computes the damage regions between the two given lists of primitives.
pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> { pub fn list<T: Damage>(
previous: &[Primitive<T>],
current: &[Primitive<T>],
) -> Vec<Rectangle> {
let damage = previous let damage = previous
.iter() .iter()
.zip(current) .zip(current)
@ -93,7 +151,7 @@ pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> {
// Extend damage by the added/removed primitives // Extend damage by the added/removed primitives
damage damage
.chain(bigger[smaller.len()..].iter().map(Primitive::bounds)) .chain(bigger[smaller.len()..].iter().map(Damage::bounds))
.collect() .collect()
} }
} }

View file

@ -14,20 +14,11 @@ pub use text::Text;
pub use crate::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);
impl From<Geometry> for Primitive {
fn from(geometry: Geometry) -> Self {
geometry.0
}
}
/// A renderer capable of drawing some [`Geometry`]. /// A renderer capable of drawing some [`Geometry`].
pub trait Renderer: crate::core::Renderer { pub trait Renderer: crate::core::Renderer {
/// The kind of geometry this renderer can draw.
type Geometry;
/// Draws the given layers of [`Geometry`]. /// Draws the given layers of [`Geometry`].
fn draw(&mut self, layers: Vec<Geometry>); fn draw(&mut self, layers: Vec<Self::Geometry>);
} }

View file

@ -7,6 +7,7 @@ use crate::color;
use crate::core::gradient::ColorStop; use crate::core::gradient::ColorStop;
use crate::core::{self, Color, Point, Rectangle}; use crate::core::{self, Color, Point, Rectangle};
use bytemuck::{Pod, Zeroable};
use half::f16; use half::f16;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -135,7 +136,7 @@ impl Linear {
} }
/// Packed [`Gradient`] data for use in shader code. /// Packed [`Gradient`] data for use in shader code.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Zeroable, Pod)]
#[repr(C)] #[repr(C)]
pub struct Packed { pub struct Packed {
// 8 colors, each channel = 16 bit float, 2 colors packed into 1 u32 // 8 colors, each channel = 16 bit float, 2 colors packed into 1 u32

View file

@ -23,6 +23,7 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod antialiasing; mod antialiasing;
mod error; mod error;
mod primitive;
mod transformation; mod transformation;
mod viewport; mod viewport;
@ -31,7 +32,7 @@ pub mod color;
pub mod compositor; pub mod compositor;
pub mod damage; pub mod damage;
pub mod gradient; pub mod gradient;
pub mod primitive; pub mod mesh;
pub mod renderer; pub mod renderer;
#[cfg(feature = "geometry")] #[cfg(feature = "geometry")]
@ -41,15 +42,15 @@ pub mod geometry;
pub mod image; pub mod image;
pub use antialiasing::Antialiasing; pub use antialiasing::Antialiasing;
pub use backend::Backend;
pub use compositor::Compositor; pub use compositor::Compositor;
pub use damage::Damage;
pub use error::Error; pub use error::Error;
pub use gradient::Gradient; pub use gradient::Gradient;
pub use mesh::Mesh;
pub use primitive::Primitive; pub use primitive::Primitive;
pub use renderer::Renderer; pub use renderer::Renderer;
pub use transformation::Transformation; pub use transformation::Transformation;
pub use viewport::Viewport; pub use viewport::Viewport;
#[cfg(feature = "geometry")]
pub use geometry::Geometry;
pub use iced_core as core; pub use iced_core as core;

76
graphics/src/mesh.rs Normal file
View file

@ -0,0 +1,76 @@
//! Draw triangles!
use crate::color;
use crate::core::{Rectangle, Size};
use crate::gradient;
use crate::Damage;
use bytemuck::{Pod, Zeroable};
/// A low-level primitive to render a mesh of triangles.
#[derive(Debug, Clone, PartialEq)]
pub enum Mesh {
/// A mesh with a solid color.
Solid {
/// The vertices and indices of the mesh.
buffers: Indexed<SolidVertex2D>,
/// The size of the drawable region of the mesh.
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
},
/// A mesh with a gradient.
Gradient {
/// The vertices and indices of the mesh.
buffers: Indexed<GradientVertex2D>,
/// The size of the drawable region of the mesh.
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
},
}
impl Damage for Mesh {
fn bounds(&self) -> Rectangle {
match self {
Self::Solid { size, .. } | Self::Gradient { size, .. } => {
Rectangle::with_size(*size)
}
}
}
}
/// A set of [`Vertex2D`] and indices representing a list of triangles.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Indexed<T> {
/// The vertices of the mesh
pub vertices: Vec<T>,
/// The list of vertex indices that defines the triangles of the mesh.
///
/// Therefore, this list should always have a length that is a multiple of 3.
pub indices: Vec<u32>,
}
/// A two-dimensional vertex with a color.
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
pub struct SolidVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
/// The color of the vertex in __linear__ RGBA.
pub color: color::Packed,
}
/// A vertex which contains 2D position & packed gradient data.
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
pub struct GradientVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
/// The packed vertex data of the gradient.
pub gradient: gradient::Packed,
}

View file

@ -1,19 +1,15 @@
//! Draw using different graphical primitives. //! Draw using different graphical primitives.
use crate::color;
use crate::core::alignment; use crate::core::alignment;
use crate::core::image; use crate::core::image;
use crate::core::svg; use crate::core::svg;
use crate::core::text; use crate::core::text;
use crate::core::{Background, Color, Font, Rectangle, Size, Vector}; use crate::core::{Background, Color, Font, Rectangle, Vector};
use crate::gradient;
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
/// A rendering primitive. /// A rendering primitive.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive] pub enum Primitive<T> {
pub enum Primitive {
/// A text primitive /// A text primitive
Text { Text {
/// The contents of the text /// The contents of the text
@ -66,65 +62,17 @@ pub enum Primitive {
/// The bounds of the viewport /// The bounds of the viewport
bounds: Rectangle, bounds: Rectangle,
}, },
/// A low-level primitive to render a mesh of triangles with a solid color.
///
/// It can be used to render many kinds of geometry freely.
SolidMesh {
/// The vertices and indices of the mesh.
buffers: Mesh2D<ColoredVertex2D>,
/// The size of the drawable region of the mesh.
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
},
/// A low-level primitive to render a mesh of triangles with a gradient.
///
/// It can be used to render many kinds of geometry freely.
GradientMesh {
/// The vertices and indices of the mesh.
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,
},
/// 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 /// A group of primitives
Group { Group {
/// The primitives of the group /// The primitives of the group
primitives: Vec<Primitive>, primitives: Vec<Primitive<T>>,
}, },
/// A clip primitive /// A clip primitive
Clip { Clip {
/// The bounds of the clip /// The bounds of the clip
bounds: Rectangle, bounds: Rectangle,
/// The content of the clip /// The content of the clip
content: Box<Primitive>, content: Box<Primitive<T>>,
}, },
/// A primitive that applies a translation /// A primitive that applies a translation
Translate { Translate {
@ -132,7 +80,7 @@ pub enum Primitive {
translation: Vector, translation: Vector,
/// The primitive to translate /// The primitive to translate
content: Box<Primitive>, content: Box<Primitive<T>>,
}, },
/// A cached primitive. /// A cached primitive.
/// ///
@ -140,11 +88,13 @@ pub enum Primitive {
/// generation is expensive. /// generation is expensive.
Cache { Cache {
/// The cached primitive /// The cached primitive
content: Arc<Primitive>, content: Arc<Primitive<T>>,
}, },
/// A backend-specific primitive.
Custom(T),
} }
impl Primitive { impl<T> Primitive<T> {
/// Creates a [`Primitive::Group`]. /// Creates a [`Primitive::Group`].
pub fn group(primitives: Vec<Self>) -> Self { pub fn group(primitives: Vec<Self>) -> Self {
Self::Group { primitives } Self::Group { primitives }
@ -165,112 +115,4 @@ impl Primitive {
content: Box::new(self), content: Box::new(self),
} }
} }
/// Returns the bounds of the [`Primitive`].
pub fn bounds(&self) -> Rectangle {
match self {
Self::Text {
bounds,
horizontal_alignment,
vertical_alignment,
..
} => {
let mut bounds = *bounds;
bounds.x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => {
bounds.x - bounds.width / 2.0
}
alignment::Horizontal::Right => bounds.x - bounds.width,
};
bounds.y = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => {
bounds.y - bounds.height / 2.0
}
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
bounds.expand(1.5)
}
Self::Quad { bounds, .. }
| Self::Image { bounds, .. }
| Self::Svg { bounds, .. } => bounds.expand(1.0),
Self::Clip { bounds, .. } => bounds.expand(1.0),
Self::SolidMesh { size, .. } | Self::GradientMesh { size, .. } => {
Rectangle::with_size(*size)
}
#[cfg(feature = "tiny-skia")]
Self::Fill { path, .. } | Self::Stroke { path, .. } => {
let bounds = path.bounds();
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
}
.expand(1.0)
}
Self::Group { primitives } => primitives
.iter()
.map(Self::bounds)
.fold(Rectangle::with_size(Size::ZERO), |a, b| {
Rectangle::union(&a, &b)
}),
Self::Translate {
translation,
content,
} => content.bounds() + *translation,
Self::Cache { content } => content.bounds(),
}
}
}
/// A set of [`Vertex2D`] and indices representing a list of triangles.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Mesh2D<T> {
/// The vertices of the mesh
pub vertices: Vec<T>,
/// The list of vertex indices that defines the triangles of the mesh.
///
/// Therefore, this list should always have a length that is a multiple of 3.
pub indices: Vec<u32>,
}
/// A two-dimensional vertex with a color.
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
pub struct ColoredVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
/// The color of the vertex in __linear__ RGBA.
pub color: color::Packed,
}
/// A vertex which contains 2D position & packed gradient data.
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub struct GradientVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
/// 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![] }
}
} }

View file

@ -1,28 +1,25 @@
//! Create a renderer from a [`Backend`]. //! Create a renderer from a [`Backend`].
use crate::backend; use crate::backend::{self, Backend};
use crate::Primitive; use crate::Primitive;
use iced_core::image; use iced_core::image;
use iced_core::layout;
use iced_core::renderer; use iced_core::renderer;
use iced_core::svg; use iced_core::svg;
use iced_core::text::{self, Text}; use iced_core::text::{self, Text};
use iced_core::{ use iced_core::{Background, Color, Font, Point, Rectangle, Size, Vector};
Background, Color, Element, Font, Point, Rectangle, Size, Vector,
};
use std::borrow::Cow; use std::borrow::Cow;
use std::marker::PhantomData; use std::marker::PhantomData;
/// A backend-agnostic renderer that supports all the built-in widgets. /// A backend-agnostic renderer that supports all the built-in widgets.
#[derive(Debug)] #[derive(Debug)]
pub struct Renderer<B, Theme> { pub struct Renderer<B: Backend, Theme> {
backend: B, backend: B,
primitives: Vec<Primitive>, primitives: Vec<Primitive<B::Primitive>>,
theme: PhantomData<Theme>, theme: PhantomData<Theme>,
} }
impl<B, T> Renderer<B, T> { impl<B: Backend, T> Renderer<B, T> {
/// Creates a new [`Renderer`] from the given [`Backend`]. /// Creates a new [`Renderer`] from the given [`Backend`].
pub fn new(backend: B) -> Self { pub fn new(backend: B) -> Self {
Self { Self {
@ -38,7 +35,7 @@ impl<B, T> Renderer<B, T> {
} }
/// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing. /// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing.
pub fn draw_primitive(&mut self, primitive: Primitive) { pub fn draw_primitive(&mut self, primitive: Primitive<B::Primitive>) {
self.primitives.push(primitive); self.primitives.push(primitive);
} }
@ -46,31 +43,54 @@ impl<B, T> Renderer<B, T> {
/// of the [`Renderer`]. /// of the [`Renderer`].
pub fn with_primitives<O>( pub fn with_primitives<O>(
&mut self, &mut self,
f: impl FnOnce(&mut B, &[Primitive]) -> O, f: impl FnOnce(&mut B, &[Primitive<B::Primitive>]) -> O,
) -> O { ) -> O {
f(&mut self.backend, &self.primitives) f(&mut self.backend, &self.primitives)
} }
}
impl<B, T> iced_core::Renderer for Renderer<B, T> { /// Starts recording a new layer.
type Theme = T; pub fn start_layer(&mut self) -> Vec<Primitive<B::Primitive>> {
std::mem::take(&mut self.primitives)
fn layout<Message>(
&mut self,
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
element.as_widget().layout(self, limits)
} }
/// 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);
self.primitives.push(Primitive::group(layer).clip(bounds));
}
/// Starts recording a translation.
pub fn start_translation(&mut self) -> Vec<Primitive<B::Primitive>> {
std::mem::take(&mut self.primitives)
}
/// Ends the recording of a translation.
pub fn end_translation(
&mut self,
primitives: Vec<Primitive<B::Primitive>>,
translation: Vector,
) {
let layer = std::mem::replace(&mut self.primitives, primitives);
self.primitives
.push(Primitive::group(layer).translate(translation));
}
}
impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> {
type Theme = T;
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) { fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
let current = std::mem::take(&mut self.primitives); let current = self.start_layer();
f(self); f(self);
let layer = std::mem::replace(&mut self.primitives, current); self.end_layer(current, bounds);
self.primitives.push(Primitive::group(layer).clip(bounds));
} }
fn with_translation( fn with_translation(
@ -78,14 +98,11 @@ impl<B, T> iced_core::Renderer for Renderer<B, T> {
translation: Vector, translation: Vector,
f: impl FnOnce(&mut Self), f: impl FnOnce(&mut Self),
) { ) {
let current = std::mem::take(&mut self.primitives); let current = self.start_translation();
f(self); f(self);
let layer = std::mem::replace(&mut self.primitives, current); self.end_translation(current, translation);
self.primitives
.push(Primitive::group(layer).translate(translation));
} }
fn fill_quad( fn fill_quad(
@ -109,7 +126,7 @@ impl<B, T> iced_core::Renderer for Renderer<B, T> {
impl<B, T> text::Renderer for Renderer<B, T> impl<B, T> text::Renderer for Renderer<B, T>
where where
B: backend::Text, B: Backend + backend::Text,
{ {
type Font = Font; type Font = Font;
@ -188,7 +205,7 @@ where
impl<B, T> image::Renderer for Renderer<B, T> impl<B, T> image::Renderer for Renderer<B, T>
where where
B: backend::Image, B: Backend + backend::Image,
{ {
type Handle = image::Handle; type Handle = image::Handle;
@ -203,7 +220,7 @@ where
impl<B, T> svg::Renderer for Renderer<B, T> impl<B, T> svg::Renderer for Renderer<B, T>
where where
B: backend::Svg, B: Backend + backend::Svg,
{ {
fn dimensions(&self, handle: &svg::Handle) -> Size<u32> { fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
self.backend().viewport_dimensions(handle) self.backend().viewport_dimensions(handle)
@ -222,11 +239,3 @@ where
}) })
} }
} }
#[cfg(feature = "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));
}
}

View file

@ -14,6 +14,7 @@ web-colors = ["iced_wgpu?/web-colors"]
[dependencies] [dependencies]
raw-window-handle = "0.5" raw-window-handle = "0.5"
thiserror = "1" thiserror = "1"
log = "0.4"
[dependencies.iced_graphics] [dependencies.iced_graphics]
version = "0.8" version = "0.8"

View file

@ -100,26 +100,28 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
background_color: Color, background_color: Color,
overlay: &[T], overlay: &[T],
) -> Result<(), SurfaceError> { ) -> Result<(), SurfaceError> {
renderer.with_primitives(|backend, primitives| { match (self, renderer, surface) {
match (self, backend, surface) { (
( Self::TinySkia(_compositor),
Self::TinySkia(_compositor), crate::Renderer::TinySkia(renderer),
crate::Backend::TinySkia(backend), Surface::TinySkia(surface),
Surface::TinySkia(surface), ) => renderer.with_primitives(|backend, primitives| {
) => iced_tiny_skia::window::compositor::present( iced_tiny_skia::window::compositor::present(
backend, backend,
surface, surface,
primitives, primitives,
viewport, viewport,
background_color, background_color,
overlay, overlay,
), )
#[cfg(feature = "wgpu")] }),
( #[cfg(feature = "wgpu")]
Self::Wgpu(compositor), (
crate::Backend::Wgpu(backend), Self::Wgpu(compositor),
Surface::Wgpu(surface), crate::Renderer::Wgpu(renderer),
) => iced_wgpu::window::compositor::present( Surface::Wgpu(surface),
) => renderer.with_primitives(|backend, primitives| {
iced_wgpu::window::compositor::present(
compositor, compositor,
backend, backend,
surface, surface,
@ -127,14 +129,14 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
viewport, viewport,
background_color, background_color,
overlay, overlay,
), )
#[allow(unreachable_patterns)] }),
_ => panic!( #[allow(unreachable_patterns)]
"The provided renderer or surface are not compatible \ _ => panic!(
"The provided renderer or surface are not compatible \
with the compositor." with the compositor."
), ),
} }
})
} }
fn screenshot<T: AsRef<str>>( fn screenshot<T: AsRef<str>>(
@ -145,12 +147,27 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
background_color: Color, background_color: Color,
overlay: &[T], overlay: &[T],
) -> Vec<u8> { ) -> Vec<u8> {
renderer.with_primitives(|backend, primitives| match (self, backend, surface) { match (self, renderer, surface) {
(Self::TinySkia(_compositor), crate::Backend::TinySkia(backend), Surface::TinySkia(surface)) => { (
iced_tiny_skia::window::compositor::screenshot(surface, backend, primitives, viewport, background_color, overlay) 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")] #[cfg(feature = "wgpu")]
(Self::Wgpu(compositor), crate::Backend::Wgpu(backend), Surface::Wgpu(_)) => { (
Self::Wgpu(compositor),
Renderer::Wgpu(renderer),
Surface::Wgpu(_),
) => renderer.with_primitives(|backend, primitives| {
iced_wgpu::window::compositor::screenshot( iced_wgpu::window::compositor::screenshot(
compositor, compositor,
backend, backend,
@ -159,12 +176,13 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
background_color, background_color,
overlay, overlay,
) )
}, }),
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
_ => panic!( _ => panic!(
"The provided renderer or backend are not compatible with the compositor." "The provided renderer or backend are not compatible \
with the compositor."
), ),
}) }
} }
} }
@ -215,7 +233,7 @@ impl Candidate {
Ok(( Ok((
Compositor::TinySkia(compositor), Compositor::TinySkia(compositor),
Renderer::new(crate::Backend::TinySkia(backend)), Renderer::TinySkia(iced_tiny_skia::Renderer::new(backend)),
)) ))
} }
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
@ -232,7 +250,7 @@ impl Candidate {
Ok(( Ok((
Compositor::Wgpu(compositor), Compositor::Wgpu(compositor),
Renderer::new(crate::Backend::Wgpu(backend)), Renderer::Wgpu(iced_wgpu::Renderer::new(backend)),
)) ))
} }
#[cfg(not(feature = "wgpu"))] #[cfg(not(feature = "wgpu"))]

View file

@ -3,8 +3,8 @@ mod cache;
pub use cache::Cache; pub use cache::Cache;
use crate::core::{Point, Rectangle, Size, Vector}; use crate::core::{Point, Rectangle, Size, Vector};
use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text}; use crate::graphics::geometry::{Fill, Path, Stroke, Text};
use crate::Backend; use crate::Renderer;
pub enum Frame { pub enum Frame {
TinySkia(iced_tiny_skia::geometry::Frame), TinySkia(iced_tiny_skia::geometry::Frame),
@ -12,6 +12,12 @@ pub enum Frame {
Wgpu(iced_wgpu::geometry::Frame), Wgpu(iced_wgpu::geometry::Frame),
} }
pub enum Geometry {
TinySkia(iced_tiny_skia::Primitive),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::Primitive),
}
macro_rules! delegate { macro_rules! delegate {
($frame:expr, $name:ident, $body:expr) => { ($frame:expr, $name:ident, $body:expr) => {
match $frame { match $frame {
@ -23,13 +29,13 @@ macro_rules! delegate {
} }
impl Frame { impl Frame {
pub fn new<Theme>(renderer: &crate::Renderer<Theme>, size: Size) -> Self { pub fn new<Theme>(renderer: &Renderer<Theme>, size: Size) -> Self {
match renderer.backend() { match renderer {
Backend::TinySkia(_) => { Renderer::TinySkia(_) => {
Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size)) Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size))
} }
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
Backend::Wgpu(_) => { Renderer::Wgpu(_) => {
Frame::Wgpu(iced_wgpu::geometry::Frame::new(size)) Frame::Wgpu(iced_wgpu::geometry::Frame::new(size))
} }
} }
@ -169,6 +175,10 @@ impl Frame {
} }
pub fn into_geometry(self) -> Geometry { pub fn into_geometry(self) -> Geometry {
Geometry(delegate!(self, frame, frame.into_primitive())) match self {
Self::TinySkia(frame) => Geometry::TinySkia(frame.into_primitive()),
#[cfg(feature = "wgpu")]
Self::Wgpu(frame) => Geometry::Wgpu(frame.into_primitive()),
}
} }
} }

View file

@ -1,6 +1,5 @@
use crate::core::Size; use crate::core::Size;
use crate::geometry::{Frame, Geometry}; use crate::geometry::{Frame, Geometry};
use crate::graphics::Primitive;
use crate::Renderer; use crate::Renderer;
use std::cell::RefCell; use std::cell::RefCell;
@ -21,10 +20,17 @@ enum State {
Empty, Empty,
Filled { Filled {
bounds: Size, bounds: Size,
primitive: Arc<Primitive>, primitive: Internal,
}, },
} }
#[derive(Debug, Clone)]
enum Internal {
TinySkia(Arc<iced_tiny_skia::Primitive>),
#[cfg(feature = "wgpu")]
Wgpu(Arc<iced_wgpu::Primitive>),
}
impl Cache { impl Cache {
/// Creates a new empty [`Cache`]. /// Creates a new empty [`Cache`].
pub fn new() -> Self { pub fn new() -> Self {
@ -62,9 +68,21 @@ impl Cache {
} = self.state.borrow().deref() } = self.state.borrow().deref()
{ {
if *cached_bounds == bounds { if *cached_bounds == bounds {
return Geometry(Primitive::Cache { match primitive {
content: primitive.clone(), 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(),
});
}
}
} }
} }
@ -74,7 +92,15 @@ impl Cache {
let primitive = { let primitive = {
let geometry = frame.into_geometry(); let geometry = frame.into_geometry();
Arc::new(geometry.0) 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 { *self.state.borrow_mut() = State::Filled {
@ -82,6 +108,18 @@ impl Cache {
primitive: primitive.clone(), primitive: primitive.clone(),
}; };
Geometry(Primitive::Cache { content: primitive }) 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,
})
}
}
} }
} }

View file

@ -3,17 +3,267 @@ pub mod compositor;
#[cfg(feature = "geometry")] #[cfg(feature = "geometry")]
pub mod geometry; pub mod geometry;
mod backend;
mod settings; mod settings;
pub use iced_graphics as graphics; pub use iced_graphics as graphics;
pub use iced_graphics::core; pub use iced_graphics::core;
pub use backend::Backend;
pub use compositor::Compositor; pub use compositor::Compositor;
pub use settings::Settings; 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, Font, Point, Rectangle, Size, Vector};
use crate::graphics::Mesh;
use std::borrow::Cow;
/// The default graphics renderer for [`iced`]. /// The default graphics renderer for [`iced`].
/// ///
/// [`iced`]: https://github.com/iced-rs/iced /// [`iced`]: https://github.com/iced-rs/iced
pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>; pub enum Renderer<Theme> {
TinySkia(iced_tiny_skia::Renderer<Theme>),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::Renderer<Theme>),
}
macro_rules! delegate {
($renderer:expr, $name:ident, $body:expr) => {
match $renderer {
Self::TinySkia($name) => $body,
#[cfg(feature = "wgpu")]
Self::Wgpu($name) => $body,
}
};
}
impl<T> Renderer<T> {
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<T> core::Renderer for Renderer<T> {
type Theme = T;
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_translation(
&mut self,
translation: Vector,
f: impl FnOnce(&mut Self),
) {
match self {
Self::TinySkia(renderer) => {
let primitives = renderer.start_translation();
f(self);
match self {
Self::TinySkia(renderer) => {
renderer.end_translation(primitives, translation);
}
#[cfg(feature = "wgpu")]
_ => unreachable!(),
}
}
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
let primitives = renderer.start_translation();
f(self);
match self {
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
renderer.end_translation(primitives, translation);
}
_ => 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<T> text::Renderer for Renderer<T> {
type Font = Font;
const ICON_FONT: Font = iced_tiny_skia::Renderer::<T>::ICON_FONT;
const CHECKMARK_ICON: char = iced_tiny_skia::Renderer::<T>::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char =
iced_tiny_skia::Renderer::<T>::ARROW_DOWN_ICON;
fn default_font(&self) -> Self::Font {
delegate!(self, renderer, renderer.default_font())
}
fn default_size(&self) -> f32 {
delegate!(self, renderer, renderer.default_size())
}
fn measure(
&self,
content: &str,
size: f32,
line_height: text::LineHeight,
font: Font,
bounds: Size,
shaping: text::Shaping,
) -> Size {
delegate!(
self,
renderer,
renderer.measure(content, size, line_height, font, bounds, shaping)
)
}
fn hit_test(
&self,
content: &str,
size: f32,
line_height: text::LineHeight,
font: Font,
bounds: Size,
shaping: text::Shaping,
point: Point,
nearest_only: bool,
) -> Option<text::Hit> {
delegate!(
self,
renderer,
renderer.hit_test(
content,
size,
line_height,
font,
bounds,
shaping,
point,
nearest_only
)
)
}
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
delegate!(self, renderer, renderer.load_font(bytes));
}
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
delegate!(self, renderer, renderer.fill_text(text));
}
}
#[cfg(feature = "image")]
impl<T> crate::core::image::Renderer for Renderer<T> {
type Handle = crate::core::image::Handle;
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
delegate!(self, renderer, renderer.dimensions(handle))
}
fn draw(&mut self, handle: crate::core::image::Handle, bounds: Rectangle) {
delegate!(self, renderer, renderer.draw(handle, bounds));
}
}
#[cfg(feature = "svg")]
impl<T> crate::core::svg::Renderer for Renderer<T> {
fn dimensions(&self, handle: &crate::core::svg::Handle) -> 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(feature = "geometry")]
impl<T> crate::graphics::geometry::Renderer for Renderer<T> {
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);
}
_ => unreachable!(),
}
}
}
#[cfg(feature = "wgpu")]
Self::Wgpu(renderer) => {
for layer in layers {
match layer {
crate::Geometry::Wgpu(primitive) => {
renderer.draw_primitive(primitive);
}
_ => unreachable!(),
}
}
}
}
}
}

View file

@ -95,8 +95,9 @@ where
let Cache { mut state } = cache; let Cache { mut state } = cache;
state.diff(root.as_widget()); state.diff(root.as_widget());
let base = let base = root
renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); .as_widget()
.layout(renderer, &layout::Limits::new(Size::ZERO, bounds));
UserInterface { UserInterface {
root, root,
@ -226,8 +227,8 @@ where
if shell.is_layout_invalid() { if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay); let _ = ManuallyDrop::into_inner(manual_overlay);
self.base = renderer.layout( self.base = self.root.as_widget().layout(
&self.root, renderer,
&layout::Limits::new(Size::ZERO, self.bounds), &layout::Limits::new(Size::ZERO, self.bounds),
); );
@ -322,8 +323,8 @@ where
} }
shell.revalidate_layout(|| { shell.revalidate_layout(|| {
self.base = renderer.layout( self.base = self.root.as_widget().layout(
&self.root, renderer,
&layout::Limits::new(Size::ZERO, self.bounds), &layout::Limits::new(Size::ZERO, self.bounds),
); );

View file

@ -20,7 +20,6 @@ log = "0.4"
[dependencies.iced_graphics] [dependencies.iced_graphics]
version = "0.8" version = "0.8"
path = "../graphics" path = "../graphics"
features = ["tiny-skia"]
[dependencies.cosmic-text] [dependencies.cosmic-text]
git = "https://github.com/hecrj/cosmic-text.git" git = "https://github.com/hecrj/cosmic-text.git"

View file

@ -2,7 +2,8 @@ use crate::core::text;
use crate::core::Gradient; use crate::core::Gradient;
use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
use crate::graphics::backend; use crate::graphics::backend;
use crate::graphics::{Primitive, Viewport}; use crate::graphics::{Damage, Viewport};
use crate::primitive::{self, Primitive};
use crate::Settings; use crate::Settings;
use std::borrow::Cow; use std::borrow::Cow;
@ -419,6 +420,13 @@ impl Backend {
self.raster_pipeline self.raster_pipeline
.draw(handle, *bounds, pixels, transform, clip_mask); .draw(handle, *bounds, pixels, transform, clip_mask);
} }
#[cfg(not(feature = "image"))]
Primitive::Image { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {:?}",
primitive
);
}
#[cfg(feature = "svg")] #[cfg(feature = "svg")]
Primitive::Svg { Primitive::Svg {
handle, handle,
@ -442,12 +450,19 @@ impl Backend {
clip_mask, clip_mask,
); );
} }
Primitive::Fill { #[cfg(not(feature = "svg"))]
Primitive::Svg { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {:?}",
primitive
);
}
Primitive::Custom(primitive::Custom::Fill {
path, path,
paint, paint,
rule, rule,
transform, transform,
} => { }) => {
let bounds = path.bounds(); let bounds = path.bounds();
let physical_bounds = (Rectangle { let physical_bounds = (Rectangle {
@ -475,12 +490,12 @@ impl Backend {
clip_mask, clip_mask,
); );
} }
Primitive::Stroke { Primitive::Custom(primitive::Custom::Stroke {
path, path,
paint, paint,
stroke, stroke,
transform, transform,
} => { }) => {
let bounds = path.bounds(); let bounds = path.bounds();
let physical_bounds = (Rectangle { let physical_bounds = (Rectangle {
@ -580,21 +595,6 @@ impl Backend {
translation, translation,
); );
} }
Primitive::SolidMesh { .. } | Primitive::GradientMesh { .. } => {
// Not supported!
// TODO: Draw a placeholder (?)
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {:?}",
primitive
);
}
_ => {
// Not supported!
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {:?}",
primitive
);
}
} }
} }
} }
@ -766,6 +766,10 @@ fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
); );
} }
impl iced_graphics::Backend for Backend {
type Primitive = primitive::Custom;
}
impl backend::Text for Backend { impl backend::Text for Backend {
const ICON_FONT: Font = Font::with_name("Iced-Icons"); const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}'; const CHECKMARK_ICON: char = '\u{f00c}';

View file

@ -3,7 +3,7 @@ use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke}; use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{Path, Style, Text}; use crate::graphics::geometry::{Path, Style, Text};
use crate::graphics::Gradient; use crate::graphics::Gradient;
use crate::graphics::Primitive; use crate::primitive::{self, Primitive};
pub struct Frame { pub struct Frame {
size: Size, size: Size,
@ -42,12 +42,13 @@ impl Frame {
let Some(path) = convert_path(path) else { return }; let Some(path) = convert_path(path) else { return };
let fill = fill.into(); let fill = fill.into();
self.primitives.push(Primitive::Fill { self.primitives
path, .push(Primitive::Custom(primitive::Custom::Fill {
paint: into_paint(fill.style), path,
rule: into_fill_rule(fill.rule), paint: into_paint(fill.style),
transform: self.transform, rule: into_fill_rule(fill.rule),
}); transform: self.transform,
}));
} }
pub fn fill_rectangle( pub fn fill_rectangle(
@ -59,15 +60,16 @@ impl Frame {
let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { return }; let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { return };
let fill = fill.into(); let fill = fill.into();
self.primitives.push(Primitive::Fill { self.primitives
path, .push(Primitive::Custom(primitive::Custom::Fill {
paint: tiny_skia::Paint { path,
anti_alias: false, paint: tiny_skia::Paint {
..into_paint(fill.style) anti_alias: false,
}, ..into_paint(fill.style)
rule: into_fill_rule(fill.rule), },
transform: self.transform, rule: into_fill_rule(fill.rule),
}); transform: self.transform,
}));
} }
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
@ -76,12 +78,13 @@ impl Frame {
let stroke = stroke.into(); let stroke = stroke.into();
let skia_stroke = into_stroke(&stroke); let skia_stroke = into_stroke(&stroke);
self.primitives.push(Primitive::Stroke { self.primitives
path, .push(Primitive::Custom(primitive::Custom::Stroke {
paint: into_paint(stroke.style), path,
stroke: skia_stroke, paint: into_paint(stroke.style),
transform: self.transform, stroke: skia_stroke,
}); transform: self.transform,
}));
} }
pub fn fill_text(&mut self, text: impl Into<Text>) { pub fn fill_text(&mut self, text: impl Into<Text>) {

View file

@ -1,6 +1,7 @@
pub mod window; pub mod window;
mod backend; mod backend;
mod primitive;
mod settings; mod settings;
mod text; mod text;
@ -17,6 +18,7 @@ pub use iced_graphics as graphics;
pub use iced_graphics::core; pub use iced_graphics::core;
pub use backend::Backend; pub use backend::Backend;
pub use primitive::Primitive;
pub use settings::Settings; pub use settings::Settings;
/// A [`tiny-skia`] graphics renderer for [`iced`]. /// A [`tiny-skia`] graphics renderer for [`iced`].

View file

@ -0,0 +1,48 @@
use crate::core::Rectangle;
use crate::graphics::Damage;
pub type Primitive = crate::graphics::Primitive<Custom>;
#[derive(Debug, Clone, PartialEq)]
pub enum Custom {
/// A path filled with some paint.
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 path stroked with some paint.
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,
},
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Fill { path, .. } | Self::Stroke { path, .. } => {
let bounds = path.bounds();
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
}
.expand(1.0)
}
}
}
}

View file

@ -1,8 +1,8 @@
use crate::core::{Color, Rectangle, Size}; use crate::core::{Color, Rectangle, Size};
use crate::graphics::compositor::{self, Information}; use crate::graphics::compositor::{self, Information};
use crate::graphics::damage; use crate::graphics::damage;
use crate::graphics::{Error, Primitive, Viewport}; use crate::graphics::{Error, Viewport};
use crate::{Backend, Renderer, Settings}; use crate::{Backend, Primitive, Renderer, Settings};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::marker::PhantomData; use std::marker::PhantomData;

View file

@ -2,7 +2,8 @@ use crate::core;
use crate::core::{Color, Font, Point, Size}; use crate::core::{Color, Font, Point, Size};
use crate::graphics::backend; use crate::graphics::backend;
use crate::graphics::color; use crate::graphics::color;
use crate::graphics::{Primitive, Transformation, Viewport}; use crate::graphics::{Transformation, Viewport};
use crate::primitive::{self, Primitive};
use crate::quad; use crate::quad;
use crate::text; use crate::text;
use crate::triangle; use crate::triangle;
@ -334,6 +335,10 @@ impl Backend {
} }
} }
impl crate::graphics::Backend for Backend {
type Primitive = primitive::Custom;
}
impl backend::Text for Backend { impl backend::Text for Backend {
const ICON_FONT: Font = Font::with_name("Iced-Icons"); const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}'; const CHECKMARK_ICON: char = '\u{f00c}';

View file

@ -5,10 +5,10 @@ use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{ use crate::graphics::geometry::{
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
}; };
use crate::graphics::primitive::{self, Primitive}; use crate::graphics::gradient::{self, Gradient};
use crate::graphics::Gradient; use crate::graphics::mesh::{self, Mesh};
use crate::primitive::{self, Primitive};
use iced_graphics::gradient;
use lyon::geom::euclid; use lyon::geom::euclid;
use lyon::tessellation; use lyon::tessellation;
use std::borrow::Cow; use std::borrow::Cow;
@ -25,8 +25,8 @@ pub struct Frame {
} }
enum Buffer { enum Buffer {
Solid(tessellation::VertexBuffers<primitive::ColoredVertex2D, u32>), Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
Gradient(tessellation::VertexBuffers<primitive::GradientVertex2D, u32>), Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
} }
struct BufferStack { struct BufferStack {
@ -464,24 +464,28 @@ impl Frame {
match buffer { match buffer {
Buffer::Solid(buffer) => { Buffer::Solid(buffer) => {
if !buffer.indices.is_empty() { if !buffer.indices.is_empty() {
self.primitives.push(Primitive::SolidMesh { self.primitives.push(Primitive::Custom(
buffers: primitive::Mesh2D { primitive::Custom::Mesh(Mesh::Solid {
vertices: buffer.vertices, buffers: mesh::Indexed {
indices: buffer.indices, vertices: buffer.vertices,
}, indices: buffer.indices,
size: self.size, },
}) size: self.size,
}),
))
} }
} }
Buffer::Gradient(buffer) => { Buffer::Gradient(buffer) => {
if !buffer.indices.is_empty() { if !buffer.indices.is_empty() {
self.primitives.push(Primitive::GradientMesh { self.primitives.push(Primitive::Custom(
buffers: primitive::Mesh2D { primitive::Custom::Mesh(Mesh::Gradient {
vertices: buffer.vertices, buffers: mesh::Indexed {
indices: buffer.indices, vertices: buffer.vertices,
}, indices: buffer.indices,
size: self.size, },
}) size: self.size,
}),
))
} }
} }
} }
@ -495,32 +499,32 @@ struct GradientVertex2DBuilder {
gradient: gradient::Packed, gradient: gradient::Packed,
} }
impl tessellation::FillVertexConstructor<primitive::GradientVertex2D> impl tessellation::FillVertexConstructor<mesh::GradientVertex2D>
for GradientVertex2DBuilder for GradientVertex2DBuilder
{ {
fn new_vertex( fn new_vertex(
&mut self, &mut self,
vertex: tessellation::FillVertex<'_>, vertex: tessellation::FillVertex<'_>,
) -> primitive::GradientVertex2D { ) -> mesh::GradientVertex2D {
let position = vertex.position(); let position = vertex.position();
primitive::GradientVertex2D { mesh::GradientVertex2D {
position: [position.x, position.y], position: [position.x, position.y],
gradient: self.gradient, gradient: self.gradient,
} }
} }
} }
impl tessellation::StrokeVertexConstructor<primitive::GradientVertex2D> impl tessellation::StrokeVertexConstructor<mesh::GradientVertex2D>
for GradientVertex2DBuilder for GradientVertex2DBuilder
{ {
fn new_vertex( fn new_vertex(
&mut self, &mut self,
vertex: tessellation::StrokeVertex<'_, '_>, vertex: tessellation::StrokeVertex<'_, '_>,
) -> primitive::GradientVertex2D { ) -> mesh::GradientVertex2D {
let position = vertex.position(); let position = vertex.position();
primitive::GradientVertex2D { mesh::GradientVertex2D {
position: [position.x, position.y], position: [position.x, position.y],
gradient: self.gradient, gradient: self.gradient,
} }
@ -529,32 +533,32 @@ impl tessellation::StrokeVertexConstructor<primitive::GradientVertex2D>
struct TriangleVertex2DBuilder(color::Packed); struct TriangleVertex2DBuilder(color::Packed);
impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D> impl tessellation::FillVertexConstructor<mesh::SolidVertex2D>
for TriangleVertex2DBuilder for TriangleVertex2DBuilder
{ {
fn new_vertex( fn new_vertex(
&mut self, &mut self,
vertex: tessellation::FillVertex<'_>, vertex: tessellation::FillVertex<'_>,
) -> primitive::ColoredVertex2D { ) -> mesh::SolidVertex2D {
let position = vertex.position(); let position = vertex.position();
primitive::ColoredVertex2D { mesh::SolidVertex2D {
position: [position.x, position.y], position: [position.x, position.y],
color: self.0, color: self.0,
} }
} }
} }
impl tessellation::StrokeVertexConstructor<primitive::ColoredVertex2D> impl tessellation::StrokeVertexConstructor<mesh::SolidVertex2D>
for TriangleVertex2DBuilder for TriangleVertex2DBuilder
{ {
fn new_vertex( fn new_vertex(
&mut self, &mut self,
vertex: tessellation::StrokeVertex<'_, '_>, vertex: tessellation::StrokeVertex<'_, '_>,
) -> primitive::ColoredVertex2D { ) -> mesh::SolidVertex2D {
let position = vertex.position(); let position = vertex.position();
primitive::ColoredVertex2D { mesh::SolidVertex2D {
position: [position.x, position.y], position: [position.x, position.y],
color: self.0, color: self.0,
} }

View file

@ -11,8 +11,10 @@ pub use text::Text;
use crate::core; use crate::core;
use crate::core::alignment; use crate::core::alignment;
use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; use crate::core::{Color, Font, Point, Rectangle, Size, Vector};
use crate::graphics;
use crate::graphics::color; use crate::graphics::color;
use crate::graphics::{Primitive, Viewport}; use crate::graphics::Viewport;
use crate::primitive::{self, Primitive};
use crate::quad::{self, Quad}; use crate::quad::{self, Quad};
/// A group of primitives that should be clipped together. /// A group of primitives that should be clipped together.
@ -179,40 +181,6 @@ impl<'a> Layer<'a> {
bounds: *bounds + translation, bounds: *bounds + translation,
}); });
} }
Primitive::SolidMesh { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
Point::new(translation.x, translation.y),
*size,
);
// Only draw visible content
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
layer.meshes.push(Mesh::Solid {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
});
}
}
Primitive::GradientMesh { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
Point::new(translation.x, translation.y),
*size,
);
// Only draw visible content
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
layer.meshes.push(Mesh::Gradient {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
});
}
}
Primitive::Group { primitives } => { Primitive::Group { primitives } => {
// TODO: Inspect a bit and regroup (?) // TODO: Inspect a bit and regroup (?)
for primitive in primitives { for primitive in primitives {
@ -262,13 +230,54 @@ impl<'a> Layer<'a> {
current_layer, current_layer,
); );
} }
_ => { Primitive::Custom(custom) => match custom {
// Not supported! primitive::Custom::Mesh(mesh) => match mesh {
log::warn!( graphics::Mesh::Solid { buffers, size } => {
"Unsupported primitive in `iced_wgpu`: {:?}", let layer = &mut layers[current_layer];
primitive
); let bounds = Rectangle::new(
} Point::new(translation.x, translation.y),
*size,
);
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.meshes.push(Mesh::Solid {
origin: Point::new(
translation.x,
translation.y,
),
buffers,
clip_bounds,
});
}
}
graphics::Mesh::Gradient { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
Point::new(translation.x, translation.y),
*size,
);
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.meshes.push(Mesh::Gradient {
origin: Point::new(
translation.x,
translation.y,
),
buffers,
clip_bounds,
});
}
}
},
},
} }
} }
} }

View file

@ -1,6 +1,6 @@
//! A collection of triangle primitives. //! A collection of triangle primitives.
use crate::core::{Point, Rectangle}; use crate::core::{Point, Rectangle};
use crate::graphics::primitive; use crate::graphics::mesh;
/// A mesh of triangles. /// A mesh of triangles.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -11,7 +11,7 @@ pub enum Mesh<'a> {
origin: Point, origin: Point,
/// The vertex and index buffers of the [`Mesh`]. /// The vertex and index buffers of the [`Mesh`].
buffers: &'a primitive::Mesh2D<primitive::ColoredVertex2D>, buffers: &'a mesh::Indexed<mesh::SolidVertex2D>,
/// The clipping bounds of the [`Mesh`]. /// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>, clip_bounds: Rectangle<f32>,
@ -22,7 +22,7 @@ pub enum Mesh<'a> {
origin: Point, origin: Point,
/// The vertex and index buffers of the [`Mesh`]. /// The vertex and index buffers of the [`Mesh`].
buffers: &'a primitive::Mesh2D<primitive::GradientVertex2D>, buffers: &'a mesh::Indexed<mesh::GradientVertex2D>,
/// The clipping bounds of the [`Mesh`]. /// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>, clip_bounds: Rectangle<f32>,

View file

@ -38,6 +38,7 @@
#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![allow(clippy::inherent_to_string, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod layer; pub mod layer;
pub mod primitive;
pub mod settings; pub mod settings;
pub mod window; pub mod window;
@ -60,6 +61,7 @@ pub use wgpu;
pub use backend::Backend; pub use backend::Backend;
pub use layer::Layer; pub use layer::Layer;
pub use primitive::Primitive;
pub use settings::Settings; pub use settings::Settings;
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]

21
wgpu/src/primitive.rs Normal file
View file

@ -0,0 +1,21 @@
//! Draw using different graphical primitives.
use crate::core::Rectangle;
use crate::graphics::{Damage, Mesh};
/// The graphical primitives supported by `iced_wgpu`.
pub type Primitive = crate::graphics::Primitive<Custom>;
/// The custom primitives supported by `iced_wgpu`.
#[derive(Debug, Clone, PartialEq)]
pub enum Custom {
/// A mesh primitive.
Mesh(Mesh),
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Mesh(mesh) => mesh.bounds(),
}
}
}

View file

@ -393,7 +393,7 @@ impl Uniforms {
} }
mod solid { mod solid {
use crate::graphics::primitive; use crate::graphics::mesh;
use crate::graphics::Antialiasing; use crate::graphics::Antialiasing;
use crate::triangle; use crate::triangle;
use crate::Buffer; use crate::Buffer;
@ -406,7 +406,7 @@ mod solid {
#[derive(Debug)] #[derive(Debug)]
pub struct Layer { pub struct Layer {
pub vertices: Buffer<primitive::ColoredVertex2D>, pub vertices: Buffer<mesh::SolidVertex2D>,
pub uniforms: Buffer<triangle::Uniforms>, pub uniforms: Buffer<triangle::Uniforms>,
pub constants: wgpu::BindGroup, pub constants: wgpu::BindGroup,
} }
@ -493,38 +493,40 @@ mod solid {
), ),
}); });
let pipeline = device.create_render_pipeline( let pipeline =
&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(
label: Some("iced_wgpu::triangle::solid pipeline"), &wgpu::RenderPipelineDescriptor {
layout: Some(&layout), label: Some("iced_wgpu::triangle::solid pipeline"),
vertex: wgpu::VertexState { layout: Some(&layout),
module: &shader, vertex: wgpu::VertexState {
entry_point: "solid_vs_main", module: &shader,
buffers: &[wgpu::VertexBufferLayout { entry_point: "solid_vs_main",
array_stride: std::mem::size_of::< buffers: &[wgpu::VertexBufferLayout {
primitive::ColoredVertex2D, array_stride: std::mem::size_of::<
>() mesh::SolidVertex2D,
as u64, >(
step_mode: wgpu::VertexStepMode::Vertex, )
attributes: &wgpu::vertex_attr_array!( as u64,
// Position step_mode: wgpu::VertexStepMode::Vertex,
0 => Float32x2, attributes: &wgpu::vertex_attr_array!(
// Color // Position
1 => Float32x4, 0 => Float32x2,
), // Color
}], 1 => Float32x4,
),
}],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "solid_fs_main",
targets: &[triangle::fragment_target(format)],
}),
primitive: triangle::primitive_state(),
depth_stencil: None,
multisample: triangle::multisample_state(antialiasing),
multiview: None,
}, },
fragment: Some(wgpu::FragmentState { );
module: &shader,
entry_point: "solid_fs_main",
targets: &[triangle::fragment_target(format)],
}),
primitive: triangle::primitive_state(),
depth_stencil: None,
multisample: triangle::multisample_state(antialiasing),
multiview: None,
},
);
Self { Self {
pipeline, pipeline,
@ -535,7 +537,8 @@ mod solid {
} }
mod gradient { mod gradient {
use crate::graphics::{primitive, Antialiasing}; use crate::graphics::mesh;
use crate::graphics::Antialiasing;
use crate::triangle; use crate::triangle;
use crate::Buffer; use crate::Buffer;
@ -547,7 +550,7 @@ mod gradient {
#[derive(Debug)] #[derive(Debug)]
pub struct Layer { pub struct Layer {
pub vertices: Buffer<primitive::GradientVertex2D>, pub vertices: Buffer<mesh::GradientVertex2D>,
pub uniforms: Buffer<triangle::Uniforms>, pub uniforms: Buffer<triangle::Uniforms>,
pub constants: wgpu::BindGroup, pub constants: wgpu::BindGroup,
} }
@ -645,7 +648,7 @@ mod gradient {
entry_point: "gradient_vs_main", entry_point: "gradient_vs_main",
buffers: &[wgpu::VertexBufferLayout { buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::< array_stride: std::mem::size_of::<
primitive::GradientVertex2D, mesh::GradientVertex2D,
>() >()
as u64, as u64,
step_mode: wgpu::VertexStepMode::Vertex, step_mode: wgpu::VertexStepMode::Vertex,

View file

@ -3,8 +3,8 @@ use crate::core::{Color, Size};
use crate::graphics; use crate::graphics;
use crate::graphics::color; use crate::graphics::color;
use crate::graphics::compositor; use crate::graphics::compositor;
use crate::graphics::{Error, Primitive, Viewport}; use crate::graphics::{Error, Viewport};
use crate::{Backend, Renderer, Settings}; use crate::{Backend, Primitive, Renderer, Settings};
use futures::stream::{self, StreamExt}; use futures::stream::{self, StreamExt};

View file

@ -1,7 +1,7 @@
use crate::canvas::event::{self, Event}; use crate::canvas::event::{self, Event};
use crate::canvas::mouse; use crate::canvas::mouse;
use crate::core::Rectangle; use crate::core::Rectangle;
use crate::graphics::geometry::{self, Geometry}; use crate::graphics::geometry;
/// The state and logic of a [`Canvas`]. /// The state and logic of a [`Canvas`].
/// ///
@ -51,7 +51,7 @@ where
theme: &Renderer::Theme, theme: &Renderer::Theme,
bounds: Rectangle, bounds: Rectangle,
cursor: mouse::Cursor, cursor: mouse::Cursor,
) -> Vec<Geometry>; ) -> Vec<Renderer::Geometry>;
/// Returns the current mouse interaction of the [`Program`]. /// Returns the current mouse interaction of the [`Program`].
/// ///
@ -93,7 +93,7 @@ where
theme: &Renderer::Theme, theme: &Renderer::Theme,
bounds: Rectangle, bounds: Rectangle,
cursor: mouse::Cursor, cursor: mouse::Cursor,
) -> Vec<Geometry> { ) -> Vec<Renderer::Geometry> {
T::draw(self, state, renderer, theme, bounds, cursor) T::draw(self, state, renderer, theme, bounds, cursor)
} }

View file

@ -7,6 +7,7 @@ use crate::core::widget::Tree;
use crate::core::{ use crate::core::{
Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
}; };
use crate::graphics::geometry::Renderer as _;
use crate::Renderer; use crate::Renderer;
use thiserror::Error; use thiserror::Error;
@ -121,7 +122,7 @@ impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> {
let translation = Vector::new(bounds.x, bounds.y); let translation = Vector::new(bounds.x, bounds.y);
renderer.with_translation(translation, |renderer| { renderer.with_translation(translation, |renderer| {
renderer.draw_primitive(geometry.0); renderer.draw(vec![geometry]);
}); });
} }
} }