Move core::Image::clip_bounds to graphics::Image

This commit is contained in:
Héctor Ramón Jiménez 2025-10-28 19:44:46 +01:00
parent 3cc5bd5dbc
commit 7c11ccb046
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
16 changed files with 189 additions and 88 deletions

View file

@ -16,22 +16,17 @@ pub struct Image<H = Handle> {
/// The handle of the image. /// The handle of the image.
pub handle: H, pub handle: H,
/// The clip bounds of the [`Image`].
///
/// Anything outside this [`Rectangle`] will not be drawn.
pub clip_bounds: Rectangle,
/// The border radius of the [`Image`].
///
/// Currently, this will only be applied to the `clip_bounds`.
pub border_radius: border::Radius,
/// The filter method of the image. /// The filter method of the image.
pub filter_method: FilterMethod, pub filter_method: FilterMethod,
/// The rotation to be applied to the image; on its center. /// The rotation to be applied to the image; on its center.
pub rotation: Radians, pub rotation: Radians,
/// The border radius of the [`Image`].
///
/// Currently, this will only be applied to the `clip_bounds`.
pub border_radius: border::Radius,
/// The opacity of the image. /// The opacity of the image.
/// ///
/// 0 means transparent. 1 means opaque. /// 0 means transparent. 1 means opaque.
@ -49,10 +44,9 @@ impl Image<Handle> {
pub fn new(handle: impl Into<Handle>) -> Self { pub fn new(handle: impl Into<Handle>) -> Self {
Self { Self {
handle: handle.into(), handle: handle.into(),
clip_bounds: Rectangle::INFINITE,
border_radius: border::Radius::default(),
filter_method: FilterMethod::default(), filter_method: FilterMethod::default(),
rotation: Radians(0.0), rotation: Radians(0.0),
border_radius: border::Radius::default(),
opacity: 1.0, opacity: 1.0,
snap: false, snap: false,
} }
@ -307,5 +301,10 @@ pub trait Renderer: crate::Renderer {
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>; fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an [`Image`] inside the provided `bounds`. /// Draws an [`Image`] inside the provided `bounds`.
fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle); fn draw_image(
&mut self,
image: Image<Self::Handle>,
bounds: Rectangle,
clip_bounds: Rectangle,
);
} }

View file

@ -217,7 +217,13 @@ impl image::Renderer for () {
Size::default() Size::default()
} }
fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {} fn draw_image(
&mut self,
_image: Image,
_bounds: Rectangle,
_clip_bounds: Rectangle,
) {
}
} }
impl svg::Renderer for () { impl svg::Renderer for () {
@ -225,5 +231,11 @@ impl svg::Renderer for () {
Size::default() Size::default()
} }
fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle) {} fn draw_svg(
&mut self,
_svg: svg::Svg,
_bounds: Rectangle,
_clip_bounds: Rectangle,
) {
}
} }

View file

@ -155,5 +155,5 @@ pub trait Renderer: crate::Renderer {
fn measure_svg(&self, handle: &Handle) -> Size<u32>; fn measure_svg(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`. /// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle); fn draw_svg(&mut self, svg: Svg, bounds: Rectangle, clip_bounds: Rectangle);
} }

View file

@ -7,21 +7,32 @@ use crate::core::image;
use crate::core::svg; use crate::core::svg;
/// A raster or vector image. /// A raster or vector image.
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Image { pub enum Image {
/// A raster image. /// A raster image.
Raster(image::Image, Rectangle), Raster {
image: image::Image,
bounds: Rectangle,
clip_bounds: Rectangle,
},
/// A vector image. /// A vector image.
Vector(svg::Svg, Rectangle), Vector {
svg: svg::Svg,
bounds: Rectangle,
clip_bounds: Rectangle,
},
} }
impl Image { impl Image {
/// Returns the bounds of the [`Image`]. /// Returns the bounds of the [`Image`].
pub fn bounds(&self) -> Rectangle { pub fn bounds(&self) -> Rectangle {
match self { match self {
Image::Raster(image, bounds) => bounds.rotate(image.rotation), Image::Raster { image, bounds, .. } => {
Image::Vector(svg, bounds) => bounds.rotate(svg.rotation), bounds.rotate(image.rotation)
}
Image::Vector { svg, bounds, .. } => bounds.rotate(svg.rotation),
} }
} }
} }

View file

@ -158,8 +158,17 @@ where
delegate!(self, renderer, renderer.measure_image(handle)) delegate!(self, renderer, renderer.measure_image(handle))
} }
fn draw_image(&mut self, image: Image<A::Handle>, bounds: Rectangle) { fn draw_image(
delegate!(self, renderer, renderer.draw_image(image, bounds)); &mut self,
image: Image<A::Handle>,
bounds: Rectangle,
clip_bounds: Rectangle,
) {
delegate!(
self,
renderer,
renderer.draw_image(image, bounds, clip_bounds)
);
} }
} }
@ -172,8 +181,13 @@ where
delegate!(self, renderer, renderer.measure_svg(handle)) delegate!(self, renderer, renderer.measure_svg(handle))
} }
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle) { fn draw_svg(
delegate!(self, renderer, renderer.draw_svg(svg, bounds)); &mut self,
svg: Svg,
bounds: Rectangle,
clip_bounds: Rectangle,
) {
delegate!(self, renderer, renderer.draw_svg(svg, bounds, clip_bounds));
} }
} }

View file

@ -550,7 +550,7 @@ impl Engine {
) { ) {
match image { match image {
#[cfg(feature = "image")] #[cfg(feature = "image")]
Image::Raster(raster, bounds) => { Image::Raster { image, bounds, .. } => {
let physical_bounds = *bounds * _transformation; let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) { if !_clip_bounds.intersects(&physical_bounds) {
@ -561,7 +561,7 @@ impl Engine {
.then_some(_clip_mask as &_); .then_some(_clip_mask as &_);
let center = physical_bounds.center(); let center = physical_bounds.center();
let radians = f32::from(raster.rotation); let radians = f32::from(image.rotation);
let transform = into_transform(_transformation).post_rotate_at( let transform = into_transform(_transformation).post_rotate_at(
radians.to_degrees(), radians.to_degrees(),
@ -570,17 +570,17 @@ impl Engine {
); );
self.raster_pipeline.draw( self.raster_pipeline.draw(
&raster.handle, &image.handle,
raster.filter_method, image.filter_method,
*bounds, *bounds,
raster.opacity, image.opacity,
_pixels, _pixels,
transform, transform,
clip_mask, clip_mask,
); );
} }
#[cfg(feature = "svg")] #[cfg(feature = "svg")]
Image::Vector(svg, bounds) => { Image::Vector { svg, bounds, .. } => {
let physical_bounds = *bounds * _transformation; let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) { if !_clip_bounds.intersects(&physical_bounds) {

View file

@ -307,7 +307,11 @@ impl geometry::frame::Backend for Frame {
image.rotation += external_rotation; image.rotation += external_rotation;
self.images.push(graphics::Image::Raster(image, bounds)); self.images.push(graphics::Image::Raster {
image,
bounds,
clip_bounds: self.clip_bounds,
});
} }
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) { fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
@ -318,7 +322,11 @@ impl geometry::frame::Backend for Frame {
svg.rotation += external_rotation; svg.rotation += external_rotation;
self.images.push(Image::Vector(svg, bounds)); self.images.push(Image::Vector {
svg,
bounds,
clip_bounds: self.clip_bounds,
});
} }
} }

View file

@ -117,11 +117,19 @@ impl Layer {
pub fn draw_image(&mut self, image: Image, transformation: Transformation) { pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
match image { match image {
Image::Raster(raster, bounds) => { Image::Raster {
self.draw_raster(raster, bounds, transformation); image,
bounds,
clip_bounds,
} => {
self.draw_raster(image, bounds, clip_bounds, transformation);
} }
Image::Vector(svg, bounds) => { Image::Vector {
self.draw_svg(svg, bounds, transformation); svg,
bounds,
clip_bounds,
} => {
self.draw_svg(svg, bounds, clip_bounds, transformation);
} }
} }
} }
@ -130,17 +138,18 @@ impl Layer {
&mut self, &mut self,
image: core::Image, image: core::Image,
bounds: Rectangle, bounds: Rectangle,
clip_bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
) { ) {
let image = Image::Raster( let image = Image::Raster {
core::Image { image: core::Image {
clip_bounds: image.clip_bounds * transformation,
border_radius: image.border_radius border_radius: image.border_radius
* transformation.scale_factor(), * transformation.scale_factor(),
..image ..image
}, },
bounds * transformation, bounds: bounds * transformation,
); clip_bounds: clip_bounds * transformation,
};
self.images.push(image); self.images.push(image);
} }
@ -149,9 +158,14 @@ impl Layer {
&mut self, &mut self,
svg: Svg, svg: Svg,
bounds: Rectangle, bounds: Rectangle,
clip_bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
) { ) {
let svg = Image::Vector(svg, bounds * transformation); let svg = Image::Vector {
svg,
bounds: bounds * transformation,
clip_bounds: clip_bounds * transformation,
};
self.images.push(svg); self.images.push(svg);
} }

View file

@ -364,9 +364,14 @@ impl core::image::Renderer for Renderer {
self.engine.raster_pipeline.dimensions(handle) self.engine.raster_pipeline.dimensions(handle)
} }
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) { fn draw_image(
&mut self,
image: core::Image,
bounds: Rectangle,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_raster(image, bounds, transformation); layer.draw_raster(image, bounds, clip_bounds, transformation);
} }
} }
@ -379,9 +384,14 @@ impl core::svg::Renderer for Renderer {
self.engine.vector_pipeline.viewport_dimensions(handle) self.engine.vector_pipeline.viewport_dimensions(handle)
} }
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) { fn draw_svg(
&mut self,
svg: core::Svg,
bounds: Rectangle,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(svg, bounds, transformation); layer.draw_svg(svg, bounds, clip_bounds, transformation);
} }
} }

View file

@ -431,8 +431,14 @@ impl geometry::frame::Backend for Frame {
self.transforms.current.transform_rectangle(bounds); self.transforms.current.transform_rectangle(bounds);
image.rotation += external_rotation; image.rotation += external_rotation;
image.border_radius =
image.border_radius * self.transforms.current.scale().0;
self.images.push(Image::Raster(image, bounds)); self.images.push(Image::Raster {
image,
bounds,
clip_bounds: self.clip_bounds,
});
} }
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) { fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
@ -443,7 +449,11 @@ impl geometry::frame::Backend for Frame {
svg.rotation += external_rotation; svg.rotation += external_rotation;
self.images.push(Image::Vector(svg, bounds)); self.images.push(Image::Vector {
svg,
bounds,
clip_bounds: self.clip_bounds,
});
} }
} }

View file

@ -261,7 +261,11 @@ impl State {
for image in images { for image in images {
match &image { match &image {
#[cfg(feature = "image")] #[cfg(feature = "image")]
Image::Raster(image, bounds) => { Image::Raster {
image,
bounds,
clip_bounds,
} => {
if let Some((atlas_entry, bind_group)) = cache if let Some((atlas_entry, bind_group)) = cache
.upload_raster(device, encoder, belt, &image.handle) .upload_raster(device, encoder, belt, &image.handle)
{ {
@ -283,7 +287,7 @@ impl State {
add_instances( add_instances(
*bounds, *bounds,
image.clip_bounds, *clip_bounds,
image.border_radius, image.border_radius,
f32::from(image.rotation), f32::from(image.rotation),
image.opacity, image.opacity,
@ -304,7 +308,11 @@ impl State {
Image::Raster { .. } => continue, Image::Raster { .. } => continue,
#[cfg(feature = "svg")] #[cfg(feature = "svg")]
Image::Vector(svg, bounds) => { Image::Vector {
svg,
bounds,
clip_bounds,
} => {
if let Some((atlas_entry, bind_group)) = cache if let Some((atlas_entry, bind_group)) = cache
.upload_vector( .upload_vector(
device, device,
@ -334,7 +342,7 @@ impl State {
add_instances( add_instances(
*bounds, *bounds,
Rectangle::INFINITE, *clip_bounds,
border::radius(0), border::radius(0),
f32::from(svg.rotation), f32::from(svg.rotation),
svg.opacity, svg.opacity,

View file

@ -128,11 +128,19 @@ impl Layer {
pub fn draw_image(&mut self, image: Image, transformation: Transformation) { pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
match image { match image {
Image::Raster(image, bounds) => { Image::Raster {
self.draw_raster(image, bounds, transformation); image,
bounds,
clip_bounds,
} => {
self.draw_raster(image, bounds, clip_bounds, transformation);
} }
Image::Vector(svg, bounds) => { Image::Vector {
self.draw_svg(svg, bounds, transformation); svg,
bounds,
clip_bounds,
} => {
self.draw_svg(svg, bounds, clip_bounds, transformation);
} }
} }
} }
@ -141,17 +149,18 @@ impl Layer {
&mut self, &mut self,
image: core::Image, image: core::Image,
bounds: Rectangle, bounds: Rectangle,
clip_bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
) { ) {
let image = Image::Raster( let image = Image::Raster {
core::Image { image: core::Image {
clip_bounds: image.clip_bounds * transformation,
border_radius: image.border_radius border_radius: image.border_radius
* transformation.scale_factor(), * transformation.scale_factor(),
..image ..image
}, },
bounds * transformation, bounds: bounds * transformation,
); clip_bounds: clip_bounds * transformation,
};
self.images.push(image); self.images.push(image);
} }
@ -160,9 +169,14 @@ impl Layer {
&mut self, &mut self,
svg: Svg, svg: Svg,
bounds: Rectangle, bounds: Rectangle,
clip_bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
) { ) {
let svg = Image::Vector(svg, bounds * transformation); let svg = Image::Vector {
svg,
bounds: bounds * transformation,
clip_bounds: clip_bounds * transformation,
};
self.images.push(svg); self.images.push(svg);
} }

View file

@ -777,9 +777,14 @@ impl core::image::Renderer for Renderer {
self.image_cache.borrow_mut().measure_image(handle) self.image_cache.borrow_mut().measure_image(handle)
} }
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) { fn draw_image(
&mut self,
image: core::Image,
bounds: Rectangle,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_raster(image, bounds, transformation); layer.draw_raster(image, bounds, clip_bounds, transformation);
} }
} }
@ -789,9 +794,14 @@ impl core::svg::Renderer for Renderer {
self.image_cache.borrow_mut().measure_svg(handle) self.image_cache.borrow_mut().measure_svg(handle)
} }
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) { fn draw_svg(
&mut self,
svg: core::Svg,
bounds: Rectangle,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut(); let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(svg, bounds, transformation); layer.draw_svg(svg, bounds, clip_bounds, transformation);
} }
} }

View file

@ -337,7 +337,6 @@ pub fn draw<Renderer, Handle>(
renderer.draw_image( renderer.draw_image(
image::Image { image::Image {
handle: handle.clone(), handle: handle.clone(),
clip_bounds: bounds,
border_radius, border_radius,
filter_method, filter_method,
rotation: rotation.radians(), rotation: rotation.radians(),
@ -345,6 +344,7 @@ pub fn draw<Renderer, Handle>(
snap: true, snap: true,
}, },
drawing_bounds, drawing_bounds,
bounds,
); );
} }

View file

@ -314,7 +314,7 @@ where
_style: &renderer::Style, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor: mouse::Cursor, _cursor: mouse::Cursor,
_viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let state = tree.state.downcast_ref::<State>(); let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -348,7 +348,6 @@ where
renderer.draw_image( renderer.draw_image(
Image { Image {
handle: self.handle.clone(), handle: self.handle.clone(),
clip_bounds: Rectangle::INFINITE,
border_radius: border::Radius::default(), border_radius: border::Radius::default(),
filter_method: self.filter_method, filter_method: self.filter_method,
rotation: Radians(0.0), rotation: Radians(0.0),
@ -356,6 +355,7 @@ where
snap: true, snap: true,
}, },
drawing_bounds, drawing_bounds,
*viewport,
); );
}); });
}; };

View file

@ -240,25 +240,16 @@ where
let style = theme.style(&self.class, status); let style = theme.style(&self.class, status);
let render = |renderer: &mut Renderer| { renderer.draw_svg(
renderer.draw_svg( svg::Svg {
svg::Svg { handle: self.handle.clone(),
handle: self.handle.clone(), color: style.color,
color: style.color, rotation: self.rotation.radians(),
rotation: self.rotation.radians(), opacity: self.opacity,
opacity: self.opacity, },
}, drawing_bounds,
drawing_bounds, bounds,
); );
};
if adjusted_fit.width > bounds.width
|| adjusted_fit.height > bounds.height
{
renderer.with_layer(bounds, render);
} else {
render(renderer);
}
} }
} }