summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/image.rs12
-rw-r--r--core/src/rectangle.rs48
-rw-r--r--core/src/svg.rs4
-rw-r--r--examples/integration/src/main.rs3
-rw-r--r--graphics/Cargo.toml2
-rw-r--r--graphics/src/damage.rs142
-rw-r--r--graphics/src/lib.rs1
-rw-r--r--graphics/src/primitive.rs69
-rw-r--r--graphics/src/renderer.rs2
-rw-r--r--renderer/src/compositor.rs133
-rw-r--r--tiny_skia/Cargo.toml4
-rw-r--r--tiny_skia/src/backend.rs278
-rw-r--r--tiny_skia/src/primitive.rs82
-rw-r--r--tiny_skia/src/raster.rs2
-rw-r--r--tiny_skia/src/text.rs18
-rw-r--r--tiny_skia/src/vector.rs8
-rw-r--r--tiny_skia/src/window/compositor.rs56
-rw-r--r--wgpu/Cargo.toml4
-rw-r--r--wgpu/src/image/vector.rs6
-rw-r--r--wgpu/src/settings.rs17
-rw-r--r--wgpu/src/triangle.rs5
-rw-r--r--wgpu/src/window/compositor.rs11
-rw-r--r--widget/src/text_input.rs2
23 files changed, 640 insertions, 269 deletions
diff --git a/core/src/image.rs b/core/src/image.rs
index 70fbade0..85d9d475 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -6,7 +6,7 @@ use std::path::PathBuf;
use std::sync::Arc;
/// A handle of some image data.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
id: u64,
data: Data,
@@ -110,6 +110,14 @@ impl std::hash::Hash for Bytes {
}
}
+impl PartialEq for Bytes {
+ fn eq(&self, other: &Self) -> bool {
+ self.as_ref() == other.as_ref()
+ }
+}
+
+impl Eq for Bytes {}
+
impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_ref()
@@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes {
}
/// The data of a raster image.
-#[derive(Clone, Hash)]
+#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Data {
/// File data
Path(PathBuf),
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs
index 4fe91519..7ff324cb 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -66,6 +66,11 @@ impl Rectangle<f32> {
Size::new(self.width, self.height)
}
+ /// Returns the area of the [`Rectangle`].
+ pub fn area(&self) -> f32 {
+ self.width * self.height
+ }
+
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
pub fn contains(&self, point: Point) -> bool {
self.x <= point.x
@@ -74,6 +79,15 @@ impl Rectangle<f32> {
&& point.y <= self.y + self.height
}
+ /// Returns true if the current [`Rectangle`] is completely within the given
+ /// `container`.
+ pub fn is_within(&self, container: &Rectangle) -> bool {
+ container.contains(self.position())
+ && container.contains(
+ self.position() + Vector::new(self.width, self.height),
+ )
+ }
+
/// Computes the intersection with the given [`Rectangle`].
pub fn intersection(
&self,
@@ -100,6 +114,30 @@ impl Rectangle<f32> {
}
}
+ /// Returns whether the [`Rectangle`] intersects with the given one.
+ pub fn intersects(&self, other: &Self) -> bool {
+ self.intersection(other).is_some()
+ }
+
+ /// Computes the union with the given [`Rectangle`].
+ pub fn union(&self, other: &Self) -> Self {
+ let x = self.x.min(other.x);
+ let y = self.y.min(other.y);
+
+ let lower_right_x = (self.x + self.width).max(other.x + other.width);
+ let lower_right_y = (self.y + self.height).max(other.y + other.height);
+
+ let width = lower_right_x - x;
+ let height = lower_right_y - y;
+
+ Rectangle {
+ x,
+ y,
+ width,
+ height,
+ }
+ }
+
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
pub fn snap(self) -> Rectangle<u32> {
Rectangle {
@@ -109,6 +147,16 @@ impl Rectangle<f32> {
height: self.height as u32,
}
}
+
+ /// Expands the [`Rectangle`] a given amount.
+ pub fn expand(self, amount: f32) -> Self {
+ Self {
+ x: self.x - amount,
+ y: self.y - amount,
+ width: self.width + amount * 2.0,
+ height: self.height + amount * 2.0,
+ }
+ }
}
impl std::ops::Mul<f32> for Rectangle<f32> {
diff --git a/core/src/svg.rs b/core/src/svg.rs
index 9b98877a..54e9434e 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -7,7 +7,7 @@ use std::path::PathBuf;
use std::sync::Arc;
/// A handle of Svg data.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
id: u64,
data: Arc<Data>,
@@ -57,7 +57,7 @@ impl Hash for Handle {
}
/// The data of a vectorial image.
-#[derive(Clone, Hash)]
+#[derive(Clone, Hash, PartialEq, Eq)]
pub enum Data {
/// File data
Path(PathBuf),
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 98d2bc59..d9aae7b9 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -36,7 +36,8 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
- .and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())?
+ .and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
+ .expect("Get canvas element")
};
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index f1ce6b3a..125ea17d 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -31,7 +31,7 @@ version = "0.9"
path = "../core"
[dependencies.tiny-skia]
-version = "0.8"
+version = "0.9"
optional = true
[dependencies.image]
diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs
new file mode 100644
index 00000000..5aab06b1
--- /dev/null
+++ b/graphics/src/damage.rs
@@ -0,0 +1,142 @@
+use crate::core::{Rectangle, Size};
+use crate::Primitive;
+
+use std::sync::Arc;
+
+pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> {
+ match (a, b) {
+ (
+ Primitive::Group {
+ primitives: primitives_a,
+ },
+ Primitive::Group {
+ primitives: primitives_b,
+ },
+ ) => return list(primitives_a, primitives_b),
+ (
+ Primitive::Clip {
+ bounds: bounds_a,
+ content: content_a,
+ ..
+ },
+ Primitive::Clip {
+ bounds: bounds_b,
+ content: content_b,
+ ..
+ },
+ ) => {
+ if bounds_a == bounds_b {
+ return regions(content_a, content_b)
+ .into_iter()
+ .filter_map(|r| r.intersection(&bounds_a.expand(1.0)))
+ .collect();
+ } else {
+ return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)];
+ }
+ }
+ (
+ Primitive::Translate {
+ translation: translation_a,
+ content: content_a,
+ },
+ Primitive::Translate {
+ translation: translation_b,
+ content: content_b,
+ },
+ ) => {
+ if translation_a == translation_b {
+ return regions(content_a, content_b)
+ .into_iter()
+ .map(|r| r + *translation_a)
+ .collect();
+ }
+ }
+ (
+ Primitive::Cache { content: content_a },
+ Primitive::Cache { content: content_b },
+ ) => {
+ if Arc::ptr_eq(content_a, content_b) {
+ return vec![];
+ }
+ }
+ _ if a == b => return vec![],
+ _ => {}
+ }
+
+ let bounds_a = a.bounds();
+ let bounds_b = b.bounds();
+
+ if bounds_a == bounds_b {
+ vec![bounds_a]
+ } else {
+ vec![bounds_a, bounds_b]
+ }
+}
+
+pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> {
+ let damage = previous
+ .iter()
+ .zip(current)
+ .flat_map(|(a, b)| regions(a, b));
+
+ if previous.len() == current.len() {
+ damage.collect()
+ } else {
+ let (smaller, bigger) = if previous.len() < current.len() {
+ (previous, current)
+ } else {
+ (current, previous)
+ };
+
+ // Extend damage by the added/removed primitives
+ damage
+ .chain(bigger[smaller.len()..].iter().map(Primitive::bounds))
+ .collect()
+ }
+}
+
+pub fn group(
+ mut damage: Vec<Rectangle>,
+ scale_factor: f32,
+ bounds: Size<u32>,
+) -> Vec<Rectangle> {
+ use std::cmp::Ordering;
+
+ const AREA_THRESHOLD: f32 = 20_000.0;
+
+ let bounds = Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: bounds.width as f32,
+ height: bounds.height as f32,
+ };
+
+ damage.sort_by(|a, b| {
+ a.x.partial_cmp(&b.x)
+ .unwrap_or(Ordering::Equal)
+ .then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal))
+ });
+
+ let mut output = Vec::new();
+ let mut scaled = damage
+ .into_iter()
+ .filter_map(|region| (region * scale_factor).intersection(&bounds))
+ .filter(|region| region.width >= 1.0 && region.height >= 1.0);
+
+ if let Some(mut current) = scaled.next() {
+ for region in scaled {
+ let union = current.union(&region);
+
+ if union.area() - current.area() - region.area() <= AREA_THRESHOLD {
+ current = union;
+ } else {
+ output.push(current);
+ current = region;
+ }
+ }
+
+ output.push(current);
+ }
+
+ output
+}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index 0c50db52..e3de4025 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -28,6 +28,7 @@ mod viewport;
pub mod backend;
pub mod compositor;
+pub mod damage;
pub mod primitive;
pub mod renderer;
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index 195b62da..d6a2c4c4 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -7,7 +7,7 @@ use bytemuck::{Pod, Zeroable};
use std::sync::Arc;
/// A rendering primitive.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Primitive {
/// A text primitive
@@ -147,10 +147,71 @@ impl Primitive {
content: Box::new(self),
}
}
+
+ 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)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Mesh2D<T> {
/// The vertices of the mesh
pub vertices: Vec<T>,
@@ -162,7 +223,7 @@ pub struct Mesh2D<T> {
}
/// A two-dimensional vertex.
-#[derive(Copy, Clone, Debug, Zeroable, Pod)]
+#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
pub struct Vertex2D {
/// The vertex position in 2D space.
@@ -170,7 +231,7 @@ pub struct Vertex2D {
}
/// A two-dimensional vertex with a color.
-#[derive(Copy, Clone, Debug, Zeroable, Pod)]
+#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
pub struct ColoredVertex2D {
/// The vertex position in 2D space.
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 7bc462ef..23e594be 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -32,7 +32,7 @@ impl<B: Backend, T> Renderer<B, T> {
}
}
- /// Returns the [`Backend`] of the [`Renderer`].
+ /// Returns a reference to the [`Backend`] of the [`Renderer`].
pub fn backend(&self) -> &B {
&self.backend
}
diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index 218e7e33..48ed4b1f 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -4,6 +4,7 @@ use crate::graphics::{Error, Viewport};
use crate::{Renderer, Settings};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
+use std::env;
pub enum Compositor<Theme> {
#[cfg(feature = "wgpu")]
@@ -28,53 +29,13 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
settings: Self::Settings,
compatible_window: Option<&W>,
) -> Result<(Self, Self::Renderer), Error> {
- #[cfg(feature = "wgpu")]
- let new_wgpu = |settings: Self::Settings, compatible_window| {
- let (compositor, backend) = iced_wgpu::window::compositor::new(
- iced_wgpu::Settings {
- default_font: settings.default_font,
- default_text_size: settings.default_text_size,
- antialiasing: settings.antialiasing,
- ..iced_wgpu::Settings::from_env()
- },
- compatible_window,
- )?;
-
- Ok((
- Self::Wgpu(compositor),
- Renderer::new(crate::Backend::Wgpu(backend)),
- ))
- };
-
- #[cfg(feature = "tiny-skia")]
- let new_tiny_skia = |settings: Self::Settings, _compatible_window| {
- let (compositor, backend) = iced_tiny_skia::window::compositor::new(
- iced_tiny_skia::Settings {
- default_font: settings.default_font,
- default_text_size: settings.default_text_size,
- },
- );
-
- Ok((
- Self::TinySkia(compositor),
- Renderer::new(crate::Backend::TinySkia(backend)),
- ))
- };
-
- let fail = |_, _| Err(Error::GraphicsAdapterNotFound);
-
- let candidates = &[
- #[cfg(feature = "wgpu")]
- new_wgpu,
- #[cfg(feature = "tiny-skia")]
- new_tiny_skia,
- fail,
- ];
+ let candidates =
+ Candidate::list_from_env().unwrap_or(Candidate::default_list());
let mut error = Error::GraphicsAdapterNotFound;
for candidate in candidates {
- match candidate(settings, compatible_window) {
+ match candidate.build(settings, compatible_window) {
Ok((compositor, renderer)) => {
return Ok((compositor, renderer))
}
@@ -162,11 +123,10 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
),
#[cfg(feature = "tiny-skia")]
(
- Self::TinySkia(compositor),
+ Self::TinySkia(_compositor),
crate::Backend::TinySkia(backend),
Surface::TinySkia(surface),
) => iced_tiny_skia::window::compositor::present(
- compositor,
backend,
surface,
primitives,
@@ -183,3 +143,86 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
})
}
}
+
+enum Candidate {
+ Wgpu,
+ TinySkia,
+}
+
+impl Candidate {
+ fn default_list() -> Vec<Self> {
+ vec![
+ #[cfg(feature = "wgpu")]
+ Self::Wgpu,
+ #[cfg(feature = "tiny-skia")]
+ 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 {
+ #[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(feature = "tiny-skia")]
+ 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(not(feature = "wgpu"))]
+ Self::Wgpu => {
+ panic!("`wgpu` feature was not enabled in `iced_renderer`")
+ }
+ #[cfg(not(feature = "tiny-skia"))]
+ Self::TinySkia => {
+ panic!(
+ "`tiny-skia` feature was not enabled in `iced_renderer`"
+ );
+ }
+ }
+ }
+}
diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml
index f629dab9..a3bddc93 100644
--- a/tiny_skia/Cargo.toml
+++ b/tiny_skia/Cargo.toml
@@ -11,7 +11,7 @@ geometry = ["iced_graphics/geometry"]
[dependencies]
raw-window-handle = "0.5"
softbuffer = "0.2"
-tiny-skia = "0.8"
+tiny-skia = "0.9"
cosmic-text = "0.8"
bytemuck = "1"
rustc-hash = "1.1"
@@ -32,5 +32,5 @@ version = "1.6.1"
features = ["std"]
[dependencies.resvg]
-version = "0.29"
+version = "0.32"
optional = true
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index 58076b84..9c69e1d2 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -1,4 +1,3 @@
-use crate::core::alignment;
use crate::core::text;
use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
use crate::graphics::backend;
@@ -37,51 +36,99 @@ impl Backend {
pub fn draw<T: AsRef<str>>(
&mut self,
pixels: &mut tiny_skia::PixmapMut<'_>,
- clip_mask: &mut tiny_skia::ClipMask,
+ clip_mask: &mut tiny_skia::Mask,
primitives: &[Primitive],
viewport: &Viewport,
+ damage: &[Rectangle],
background_color: Color,
overlay: &[T],
) {
- pixels.fill(into_color(background_color));
-
+ let physical_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
- for primitive in primitives {
- self.draw_primitive(
- primitive,
- pixels,
- clip_mask,
+ if !overlay.is_empty() {
+ let path = tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ 0.0,
+ 0.0,
+ physical_size.width as f32,
+ physical_size.height as f32,
+ )
+ .expect("Create damage rectangle"),
+ );
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(Color {
+ a: 0.1,
+ ..background_color
+ })),
+ anti_alias: false,
+ ..Default::default()
+ },
+ tiny_skia::FillRule::default(),
+ tiny_skia::Transform::identity(),
None,
- scale_factor,
- Vector::ZERO,
);
}
- for (i, text) in overlay.iter().enumerate() {
- const OVERLAY_TEXT_SIZE: f32 = 20.0;
-
- self.draw_primitive(
- &Primitive::Text {
- content: text.as_ref().to_owned(),
- size: OVERLAY_TEXT_SIZE,
- bounds: Rectangle {
- x: 10.0,
- y: 10.0 + i as f32 * OVERLAY_TEXT_SIZE * 1.2,
- width: f32::INFINITY,
- height: f32::INFINITY,
- },
- color: Color::BLACK,
- font: Font::MONOSPACE,
- horizontal_alignment: alignment::Horizontal::Left,
- vertical_alignment: alignment::Vertical::Top,
+ for &region in damage {
+ let path = tiny_skia::PathBuilder::from_rect(
+ tiny_skia::Rect::from_xywh(
+ region.x,
+ region.y,
+ region.width,
+ region.height,
+ )
+ .expect("Create damage rectangle"),
+ );
+
+ pixels.fill_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(
+ background_color,
+ )),
+ anti_alias: false,
+ ..Default::default()
},
- pixels,
- clip_mask,
+ tiny_skia::FillRule::default(),
+ tiny_skia::Transform::identity(),
None,
- scale_factor,
- Vector::ZERO,
);
+
+ adjust_clip_mask(clip_mask, region);
+
+ for primitive in primitives {
+ self.draw_primitive(
+ primitive,
+ pixels,
+ clip_mask,
+ region,
+ scale_factor,
+ Vector::ZERO,
+ );
+ }
+
+ if !overlay.is_empty() {
+ pixels.stroke_path(
+ &path,
+ &tiny_skia::Paint {
+ shader: tiny_skia::Shader::SolidColor(into_color(
+ Color::from_rgb(1.0, 0.0, 0.0),
+ )),
+ anti_alias: false,
+ ..tiny_skia::Paint::default()
+ },
+ &tiny_skia::Stroke {
+ width: 1.0,
+ ..tiny_skia::Stroke::default()
+ },
+ tiny_skia::Transform::identity(),
+ None,
+ );
+ }
}
self.text_pipeline.trim_cache();
@@ -97,8 +144,8 @@ impl Backend {
&mut self,
primitive: &Primitive,
pixels: &mut tiny_skia::PixmapMut<'_>,
- clip_mask: &mut tiny_skia::ClipMask,
- clip_bounds: Option<Rectangle>,
+ clip_mask: &mut tiny_skia::Mask,
+ clip_bounds: Rectangle,
scale_factor: f32,
translation: Vector,
) {
@@ -110,6 +157,15 @@ impl Backend {
border_width,
border_color,
} => {
+ let physical_bounds = (*bounds + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
let transform = tiny_skia::Transform::from_translate(
translation.x,
translation.y,
@@ -117,7 +173,6 @@ impl Backend {
.post_scale(scale_factor, scale_factor);
let path = rounded_rectangle(*bounds, *border_radius);
- let clip_mask = clip_bounds.map(|_| clip_mask as &_);
pixels.fill_path(
&path,
@@ -165,6 +220,16 @@ impl Backend {
horizontal_alignment,
vertical_alignment,
} => {
+ let physical_bounds =
+ (primitive.bounds() + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
self.text_pipeline.draw(
content,
(*bounds + translation) * scale_factor,
@@ -174,24 +239,28 @@ impl Backend {
*horizontal_alignment,
*vertical_alignment,
pixels,
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
#[cfg(feature = "image")]
Primitive::Image { handle, bounds } => {
+ let physical_bounds = (*bounds + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
let transform = tiny_skia::Transform::from_translate(
translation.x,
translation.y,
)
.post_scale(scale_factor, scale_factor);
- self.raster_pipeline.draw(
- handle,
- *bounds,
- pixels,
- transform,
- clip_bounds.map(|_| clip_mask as &_),
- );
+ self.raster_pipeline
+ .draw(handle, *bounds, pixels, transform, clip_mask);
}
#[cfg(feature = "svg")]
Primitive::Svg {
@@ -199,12 +268,21 @@ impl Backend {
bounds,
color,
} => {
+ let physical_bounds = (*bounds + translation) * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
self.vector_pipeline.draw(
handle,
*color,
(*bounds + translation) * scale_factor,
pixels,
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
Primitive::Fill {
@@ -213,6 +291,23 @@ impl Backend {
rule,
transform,
} => {
+ let bounds = path.bounds();
+
+ let physical_bounds = (Rectangle {
+ x: bounds.x(),
+ y: bounds.y(),
+ width: bounds.width(),
+ height: bounds.height(),
+ } + translation)
+ * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
pixels.fill_path(
path,
paint,
@@ -220,7 +315,7 @@ impl Backend {
transform
.post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor),
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
Primitive::Stroke {
@@ -229,6 +324,23 @@ impl Backend {
stroke,
transform,
} => {
+ let bounds = path.bounds();
+
+ let physical_bounds = (Rectangle {
+ x: bounds.x(),
+ y: bounds.y(),
+ width: bounds.width(),
+ height: bounds.height(),
+ } + translation)
+ * scale_factor;
+
+ if !clip_bounds.intersects(&physical_bounds) {
+ return;
+ }
+
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
pixels.stroke_path(
path,
paint,
@@ -236,7 +348,7 @@ impl Backend {
transform
.post_translate(translation.x, translation.y)
.post_scale(scale_factor, scale_factor),
- clip_bounds.map(|_| clip_mask as &_),
+ clip_mask,
);
}
Primitive::Group { primitives } => {
@@ -267,29 +379,38 @@ impl Backend {
Primitive::Clip { bounds, content } => {
let bounds = (*bounds + translation) * scale_factor;
- if bounds.x + bounds.width <= 0.0
- || bounds.y + bounds.height <= 0.0
- || bounds.x as u32 >= pixels.width()
- || bounds.y as u32 >= pixels.height()
- {
- return;
- }
-
- adjust_clip_mask(clip_mask, pixels, bounds);
+ if bounds == clip_bounds {
+ self.draw_primitive(
+ content,
+ pixels,
+ clip_mask,
+ bounds,
+ scale_factor,
+ translation,
+ );
+ } else if let Some(bounds) = clip_bounds.intersection(&bounds) {
+ if bounds.x + bounds.width <= 0.0
+ || bounds.y + bounds.height <= 0.0
+ || bounds.x as u32 >= pixels.width()
+ || bounds.y as u32 >= pixels.height()
+ || bounds.width <= 1.0
+ || bounds.height <= 1.0
+ {
+ return;
+ }
+
+ adjust_clip_mask(clip_mask, bounds);
- self.draw_primitive(
- content,
- pixels,
- clip_mask,
- Some(bounds),
- scale_factor,
- translation,
- );
+ self.draw_primitive(
+ content,
+ pixels,
+ clip_mask,
+ bounds,
+ scale_factor,
+ translation,
+ );
- if let Some(bounds) = clip_bounds {
- adjust_clip_mask(clip_mask, pixels, bounds);
- } else {
- clip_mask.clear();
+ adjust_clip_mask(clip_mask, clip_bounds);
}
}
Primitive::Cache { content } => {
@@ -462,11 +583,9 @@ fn arc_to(
}
}
-fn adjust_clip_mask(
- clip_mask: &mut tiny_skia::ClipMask,
- pixels: &tiny_skia::PixmapMut<'_>,
- bounds: Rectangle,
-) {
+fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
+ clip_mask.clear();
+
let path = {
let mut builder = tiny_skia::PathBuilder::new();
builder.push_rect(bounds.x, bounds.y, bounds.width, bounds.height);
@@ -474,15 +593,12 @@ fn adjust_clip_mask(
builder.finish().unwrap()
};
- clip_mask
- .set_path(
- pixels.width(),
- pixels.height(),
- &path,
- tiny_skia::FillRule::EvenOdd,
- true,
- )
- .expect("Set path of clipping area");
+ clip_mask.fill_path(
+ &path,
+ tiny_skia::FillRule::EvenOdd,
+ false,
+ tiny_skia::Transform::default(),
+ );
}
impl iced_graphics::Backend for Backend {
diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs
deleted file mode 100644
index 22daaedc..00000000
--- a/tiny_skia/src/primitive.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-use crate::{Rectangle, Vector};
-
-use std::sync::Arc;
-
-#[derive(Debug, Clone)]
-pub enum Primitive {
- /// A group of primitives
- Group {
- /// The primitives of the group
- primitives: Vec<Primitive>,
- },
- /// A clip primitive
- Clip {
- /// The bounds of the clip
- bounds: Rectangle,
- /// The content of the clip
- content: Box<Primitive>,
- },
- /// A primitive that applies a translation
- Translate {
- /// The translation vector
- translation: Vector,
-
- /// The primitive to translate
- content: Box<Primitive>,
- },
- /// A cached primitive.
- ///
- /// This can be useful if you are implementing a widget where primitive
- /// generation is expensive.
- Cached {
- /// The cached primitive
- cache: Arc<Primitive>,
- },
- /// A basic primitive.
- Basic(iced_graphics::Primitive),
-}
-
-impl iced_graphics::backend::Primitive for Primitive {
- fn translate(self, translation: Vector) -> Self {
- Self::Translate {
- translation,
- content: Box::new(self),
- }
- }
-
- fn clip(self, bounds: Rectangle) -> Self {
- Self::Clip {
- bounds,
- content: Box::new(self),
- }
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Recording(pub(crate) Vec<Primitive>);
-
-impl iced_graphics::backend::Recording for Recording {
- type Primitive = Primitive;
-
- fn push(&mut self, primitive: Primitive) {
- self.0.push(primitive);
- }
-
- fn push_basic(&mut self, basic: iced_graphics::Primitive) {
- self.0.push(Primitive::Basic(basic));
- }
-
- fn group(self) -> Self::Primitive {
- Primitive::Group { primitives: self.0 }
- }
-
- fn clear(&mut self) {
- self.0.clear();
- }
-}
-
-impl Recording {
- pub fn primitives(&self) -> &[Primitive] {
- &self.0
- }
-}
diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs
index 2fd73f8c..3887ec8d 100644
--- a/tiny_skia/src/raster.rs
+++ b/tiny_skia/src/raster.rs
@@ -31,7 +31,7 @@ impl Pipeline {
bounds: Rectangle,
pixels: &mut tiny_skia::PixmapMut<'_>,
transform: tiny_skia::Transform,
- clip_mask: Option<&tiny_skia::ClipMask>,
+ clip_mask: Option<&tiny_skia::Mask>,
) {
if let Some(image) = self.cache.borrow_mut().allocate(handle) {
let width_scale = bounds.width / image.width() as f32;
diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs
index e0e893bd..1246bbd5 100644
--- a/tiny_skia/src/text.rs
+++ b/tiny_skia/src/text.rs
@@ -50,7 +50,7 @@ impl Pipeline {
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
pixels: &mut tiny_skia::PixmapMut<'_>,
- clip_mask: Option<&tiny_skia::ClipMask>,
+ clip_mask: Option<&tiny_skia::Mask>,
) {
let font_system = self.font_system.get_mut();
let key = Key {
@@ -336,6 +336,7 @@ struct Cache {
entries: FxHashMap<KeyHash, cosmic_text::Buffer>,
recently_used: FxHashSet<KeyHash>,
hasher: HashBuilder,
+ trim_count: usize,
}
#[cfg(not(target_arch = "wasm32"))]
@@ -345,11 +346,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64;
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
impl Cache {
+ const TRIM_INTERVAL: usize = 300;
+
fn new() -> Self {
Self {
entries: FxHashMap::default(),
recently_used: FxHashSet::default(),
hasher: HashBuilder::default(),
+ trim_count: 0,
}
}
@@ -397,10 +401,16 @@ impl Cache {
}
fn trim(&mut self) {
- self.entries
- .retain(|key, _| self.recently_used.contains(key));
+ if self.trim_count > Self::TRIM_INTERVAL {
+ self.entries
+ .retain(|key, _| self.recently_used.contains(key));
- self.recently_used.clear();
+ self.recently_used.clear();
+
+ self.trim_count = 0;
+ } else {
+ self.trim_count += 1;
+ }
}
}
diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs
index 8509b761..fc411fdd 100644
--- a/tiny_skia/src/vector.rs
+++ b/tiny_skia/src/vector.rs
@@ -32,7 +32,7 @@ impl Pipeline {
color: Option<Color>,
bounds: Rectangle,
pixels: &mut tiny_skia::PixmapMut<'_>,
- clip_mask: Option<&tiny_skia::ClipMask>,
+ clip_mask: Option<&tiny_skia::Mask>,
) {
if let Some(image) = self.cache.borrow_mut().draw(
handle,
@@ -72,6 +72,8 @@ struct RasterKey {
impl Cache {
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
+ use usvg::TreeParsing;
+
let id = handle.id();
if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
@@ -131,9 +133,9 @@ impl Cache {
resvg::render(
tree,
if size.width > size.height {
- usvg::FitTo::Width(size.width)
+ resvg::FitTo::Width(size.width)
} else {
- usvg::FitTo::Height(size.height)
+ resvg::FitTo::Height(size.height)
},
tiny_skia::Transform::default(),
image.as_mut(),
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index cea1cabf..9999a188 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -1,5 +1,6 @@
-use crate::core::Color;
+use crate::core::{Color, Rectangle};
use crate::graphics::compositor::{self, Information, SurfaceError};
+use crate::graphics::damage;
use crate::graphics::{Error, Primitive, Viewport};
use crate::{Backend, Renderer, Settings};
@@ -7,13 +8,15 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::marker::PhantomData;
pub struct Compositor<Theme> {
- clip_mask: tiny_skia::ClipMask,
_theme: PhantomData<Theme>,
}
pub struct Surface {
window: softbuffer::GraphicsContext,
buffer: Vec<u32>,
+ clip_mask: tiny_skia::Mask,
+ primitives: Option<Vec<Primitive>>,
+ background_color: Color,
}
impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
@@ -43,6 +46,10 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
Surface {
window,
buffer: vec![0; width as usize * height as usize],
+ clip_mask: tiny_skia::Mask::new(width, height)
+ .expect("Create clip mask"),
+ primitives: None,
+ background_color: Color::BLACK,
}
}
@@ -53,6 +60,9 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
height: u32,
) {
surface.buffer.resize((width * height) as usize, 0);
+ surface.clip_mask =
+ tiny_skia::Mask::new(width, height).expect("Create clip mask");
+ surface.primitives = None;
}
fn fetch_information(&self) -> Information {
@@ -72,7 +82,6 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
) -> Result<(), SurfaceError> {
renderer.with_primitives(|backend, primitives| {
present(
- self,
backend,
surface,
primitives,
@@ -85,18 +94,15 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
}
pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) {
- // TOD
(
Compositor {
- clip_mask: tiny_skia::ClipMask::new(),
_theme: PhantomData,
},
Backend::new(settings),
)
}
-pub fn present<Theme, T: AsRef<str>>(
- compositor: &mut Compositor<Theme>,
+pub fn present<T: AsRef<str>>(
backend: &mut Backend,
surface: &mut Surface,
primitives: &[Primitive],
@@ -105,17 +111,39 @@ pub fn present<Theme, T: AsRef<str>>(
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size();
+ let scale_factor = viewport.scale_factor() as f32;
+
+ let mut pixels = tiny_skia::PixmapMut::from_bytes(
+ bytemuck::cast_slice_mut(&mut surface.buffer),
+ physical_size.width,
+ physical_size.height,
+ )
+ .expect("Create pixel map");
+
+ let damage = surface
+ .primitives
+ .as_deref()
+ .and_then(|last_primitives| {
+ (surface.background_color == background_color)
+ .then(|| damage::list(last_primitives, primitives))
+ })
+ .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
+
+ if damage.is_empty() {
+ return Ok(());
+ }
+
+ surface.primitives = Some(primitives.to_vec());
+ surface.background_color = background_color;
+
+ let damage = damage::group(damage, scale_factor, physical_size);
backend.draw(
- &mut tiny_skia::PixmapMut::from_bytes(
- bytemuck::cast_slice_mut(&mut surface.buffer),
- physical_size.width,
- physical_size.height,
- )
- .expect("Create pixel map"),
- &mut compositor.clip_mask,
+ &mut pixels,
+ &mut surface.clip_mask,
primitives,
viewport,
+ &damage,
background_color,
overlay,
);
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 14dcd550..9f9bd066 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -23,7 +23,7 @@ once_cell = "1.0"
rustc-hash = "1.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
-wgpu = { version = "0.14", features = ["webgl"] }
+wgpu = { version = "0.15", features = ["webgl"] }
[dependencies.twox-hash]
version = "1.6"
@@ -58,7 +58,7 @@ version = "1.0"
optional = true
[dependencies.resvg]
-version = "0.29"
+version = "0.32"
optional = true
[dependencies.tracing]
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
index 3624e46b..58bdf64a 100644
--- a/wgpu/src/image/vector.rs
+++ b/wgpu/src/image/vector.rs
@@ -43,6 +43,8 @@ type ColorFilter = Option<[u8; 4]>;
impl Cache {
/// Load svg
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
+ use usvg::TreeParsing;
+
if self.svgs.contains_key(&handle.id()) {
return self.svgs.get(&handle.id()).unwrap();
}
@@ -116,9 +118,9 @@ impl Cache {
resvg::render(
tree,
if width > height {
- usvg::FitTo::Width(width)
+ resvg::FitTo::Width(width)
} else {
- usvg::FitTo::Height(height)
+ resvg::FitTo::Height(height)
},
tiny_skia::Transform::default(),
img.as_mut(),
diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs
index ff041bdf..266a2c87 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -46,7 +46,7 @@ impl Settings {
/// - `primary`
pub fn from_env() -> Self {
Settings {
- internal_backend: backend_from_env()
+ internal_backend: wgpu::util::backend_bits_from_env()
.unwrap_or(wgpu::Backends::all()),
..Self::default()
}
@@ -64,18 +64,3 @@ impl Default for Settings {
}
}
}
-
-fn backend_from_env() -> Option<wgpu::Backends> {
- std::env::var("WGPU_BACKEND").ok().map(|backend| {
- match backend.to_lowercase().as_str() {
- "vulkan" => wgpu::Backends::VULKAN,
- "metal" => wgpu::Backends::METAL,
- "dx12" => wgpu::Backends::DX12,
- "dx11" => wgpu::Backends::DX11,
- "gl" => wgpu::Backends::GL,
- "webgpu" => wgpu::Backends::BROWSER_WEBGPU,
- "primary" => wgpu::Backends::PRIMARY,
- other => panic!("Unknown backend: {other}"),
- }
- })
-}
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index 0df8dd02..eb15a458 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -2,10 +2,13 @@
mod msaa;
use crate::buffer::r#static::Buffer;
-use crate::core::{Gradient, Size};
+use crate::core::Size;
use crate::graphics::{Antialiasing, Transformation};
use crate::layer::mesh::{self, Mesh};
+#[cfg(not(target_arch = "wasm32"))]
+use crate::core::Gradient;
+
#[cfg(feature = "tracing")]
use tracing::info_span;
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 54ab83ea..c55ffa46 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -53,11 +53,12 @@ impl<Theme> Compositor<Theme> {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: if settings.antialiasing.is_none() {
- wgpu::PowerPreference::LowPower
- } else {
- wgpu::PowerPreference::HighPerformance
- },
+ power_preference: wgpu::util::power_preference_from_env()
+ .unwrap_or(if settings.antialiasing.is_none() {
+ wgpu::PowerPreference::LowPower
+ } else {
+ wgpu::PowerPreference::HighPerformance
+ }),
compatible_surface: compatible_surface.as_ref(),
force_fallback_adapter: false,
})
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 0faa51c0..9db382f7 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -1084,6 +1084,8 @@ pub fn draw<Renderer>(
let render = |renderer: &mut Renderer| {
if let Some((cursor, color)) = cursor {
renderer.fill_quad(cursor, color);
+ } else {
+ renderer.with_translation(Vector::ZERO, |_| {});
}
renderer.fill_text(Text {