diff options
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | core/src/image.rs | 12 | ||||
-rw-r--r-- | core/src/rectangle.rs | 48 | ||||
-rw-r--r-- | core/src/svg.rs | 4 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 159 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 2 | ||||
-rw-r--r-- | runtime/src/debug/basic.rs | 2 | ||||
-rw-r--r-- | tiny_skia/src/backend.rs | 361 | ||||
-rw-r--r-- | tiny_skia/src/primitive.rs | 82 | ||||
-rw-r--r-- | tiny_skia/src/text.rs | 16 | ||||
-rw-r--r-- | tiny_skia/src/window/compositor.rs | 14 | ||||
-rw-r--r-- | widget/src/text_input.rs | 2 |
12 files changed, 540 insertions, 165 deletions
@@ -86,3 +86,6 @@ incremental = false opt-level = 3 overflow-checks = false strip = "debuginfo" + +[patch.crates-io] +tiny-skia = { version = "0.8", git = "https://github.com/hecrj/tiny-skia.git", rev = "213890dcbb3754d51533f5b558d9f5ffa3bf6da1" } 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/graphics/src/primitive.rs b/graphics/src/primitive.rs index 195b62da..01546dcb 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,161 @@ 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.0) + } + Self::Quad { bounds, .. } + | Self::Image { bounds, .. } + | Self::Svg { bounds, .. } => bounds.expand(1.0), + Self::Clip { bounds, .. } => *bounds, + 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(), + } + } + + pub fn damage(&self, other: &Self) -> Vec<Rectangle> { + match (self, other) { + ( + Primitive::Group { + primitives: primitives_a, + }, + Primitive::Group { + primitives: primitives_b, + }, + ) => return Self::damage_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 content_a + .damage(content_b) + .into_iter() + .filter_map(|r| r.intersection(bounds_a)) + .collect(); + } else { + return vec![*bounds_a, *bounds_b]; + } + } + ( + Primitive::Translate { + translation: translation_a, + content: content_a, + }, + Primitive::Translate { + translation: translation_b, + content: content_b, + }, + ) => { + if translation_a == translation_b { + return content_a + .damage(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 self == other => return vec![], + _ => {} + } + + let bounds_a = self.bounds(); + let bounds_b = other.bounds(); + + if bounds_a == bounds_b { + vec![bounds_a] + } else { + vec![bounds_a, bounds_b] + } + } + + pub fn damage_list(previous: &[Self], current: &[Self]) -> Vec<Rectangle> { + let damage = + previous.iter().zip(current).flat_map(|(a, b)| a.damage(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() + } + } } /// 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 +313,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 +321,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/runtime/src/debug/basic.rs b/runtime/src/debug/basic.rs index 32f725a1..e9be4c84 100644 --- a/runtime/src/debug/basic.rs +++ b/runtime/src/debug/basic.rs @@ -129,7 +129,7 @@ impl Debug { pub fn render_finished(&mut self) { self.render_durations - .push(time::Instant::now() - self.render_start); + .push(dbg!(time::Instant::now() - self.render_start)); } pub fn log_message<Message: std::fmt::Debug>(&mut self, message: &Message) { diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 58076b84..974faa74 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -17,6 +17,10 @@ pub struct Backend { #[cfg(feature = "svg")] vector_pipeline: crate::vector::Pipeline, + + last_primitives: Vec<Primitive>, + last_background_color: Color, + last_size: Size<u32>, } impl Backend { @@ -31,6 +35,10 @@ impl Backend { #[cfg(feature = "svg")] vector_pipeline: crate::vector::Pipeline::new(), + + last_primitives: Vec::new(), + last_background_color: Color::BLACK, + last_size: Size::new(0, 0), } } @@ -42,48 +50,151 @@ impl Backend { viewport: &Viewport, background_color: Color, overlay: &[T], - ) { - pixels.fill(into_color(background_color)); + ) -> bool { + let physical_size = viewport.physical_size(); + + let damage = if self.last_background_color == background_color + && self.last_size == physical_size + { + Primitive::damage_list(&self.last_primitives, primitives) + } else { + vec![Rectangle::with_size(viewport.logical_size())] + }; + + if damage.is_empty() { + return false; + } + + self.last_primitives = primitives.to_vec(); + self.last_background_color = background_color; + self.last_size = physical_size; let scale_factor = viewport.scale_factor() as f32; - for primitive in primitives { - self.draw_primitive( - primitive, - pixels, - clip_mask, + let damage = group_damage(damage, scale_factor, physical_size); + + 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, pixels, 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, + ); + } } + //if !overlay.is_empty() { + // let bounds = Rectangle { + // x: 0.0, + // y: 0.0, + // width: viewport.physical_width() as f32, + // height: viewport.physical_height() as f32, + // }; + + // adjust_clip_mask(clip_mask, pixels, bounds); + + // 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: bounds.width - 1.0, + // height: bounds.height - 1.0, + // }, + // color: Color::BLACK, + // font: Font::MONOSPACE, + // horizontal_alignment: alignment::Horizontal::Left, + // vertical_alignment: alignment::Vertical::Top, + // }, + // pixels, + // clip_mask, + // bounds, + // scale_factor, + // Vector::ZERO, + // ); + // } + //} + self.text_pipeline.trim_cache(); #[cfg(feature = "image")] @@ -91,6 +202,8 @@ impl Backend { #[cfg(feature = "svg")] self.vector_pipeline.trim_cache(); + + true } fn draw_primitive( @@ -98,7 +211,7 @@ impl Backend { primitive: &Primitive, pixels: &mut tiny_skia::PixmapMut<'_>, clip_mask: &mut tiny_skia::ClipMask, - clip_bounds: Option<Rectangle>, + clip_bounds: Rectangle, scale_factor: f32, translation: Vector, ) { @@ -110,6 +223,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 +239,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 +286,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 +305,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 +334,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 +357,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 +381,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 +390,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 +414,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 +445,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; - } + 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, pixels, bounds); + adjust_clip_mask(clip_mask, pixels, 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, pixels, clip_bounds); } } Primitive::Cache { content } => { @@ -480,11 +667,57 @@ fn adjust_clip_mask( pixels.height(), &path, tiny_skia::FillRule::EvenOdd, - true, + false, ) .expect("Set path of clipping area"); } +fn group_damage( + 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(®ion); + + if union.area() - current.area() - region.area() <= AREA_THRESHOLD { + current = union; + } else { + output.push(current); + current = region; + } + } + + output.push(current); + } + + output +} + impl iced_graphics::Backend for Backend { fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache(); 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/text.rs b/tiny_skia/src/text.rs index e0e893bd..f5994d09 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -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/window/compositor.rs b/tiny_skia/src/window/compositor.rs index cea1cabf..6e4bb6ef 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -106,7 +106,7 @@ pub fn present<Theme, T: AsRef<str>>( ) -> Result<(), compositor::SurfaceError> { let physical_size = viewport.physical_size(); - backend.draw( + let drawn = backend.draw( &mut tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut surface.buffer), physical_size.width, @@ -120,11 +120,13 @@ pub fn present<Theme, T: AsRef<str>>( overlay, ); - surface.window.set_buffer( - &surface.buffer, - physical_size.width as u16, - physical_size.height as u16, - ); + if drawn { + surface.window.set_buffer( + &surface.buffer, + physical_size.width as u16, + physical_size.height as u16, + ); + } Ok(()) } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index d066109a..4f018284 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -953,6 +953,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 { |