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 { | 
