diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index a5c781a9a4..2ed8c09360 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -68,6 +68,7 @@ pub struct Img { interactivity: Interactivity, source: ImageSource, grayscale: bool, + object_fit: ObjectFit, } /// Create a new image element. @@ -76,6 +77,82 @@ pub fn img(source: impl Into) -> Img { interactivity: Interactivity::default(), source: source.into(), grayscale: false, + object_fit: ObjectFit::Contain, + } +} + +/// How to fit the image into the bounds of the element. +pub enum ObjectFit { + /// The image will be stretched to fill the bounds of the element. + Fill, + /// The image will be scaled to fit within the bounds of the element. + Contain, + /// The image will be scaled to cover the bounds of the element. + Cover, + /// The image will maintain its original size. + None, +} + +impl ObjectFit { + /// Get the bounds of the image within the given bounds. + pub fn get_bounds( + &self, + bounds: Bounds, + image_size: Size, + ) -> Bounds { + let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension))); + let image_ratio = image_size.width / image_size.height; + let bounds_ratio = bounds.size.width / bounds.size.height; + + match self { + ObjectFit::Fill => bounds, + ObjectFit::Contain => { + let new_size = if bounds_ratio > image_ratio { + size( + image_size.width * (bounds.size.height / image_size.height), + bounds.size.height, + ) + } else { + size( + bounds.size.width, + image_size.height * (bounds.size.width / image_size.width), + ) + }; + + Bounds { + origin: point( + bounds.origin.x + (bounds.size.width - new_size.width) / 2.0, + bounds.origin.y + (bounds.size.height - new_size.height) / 2.0, + ), + size: new_size, + } + } + ObjectFit::Cover => { + let new_size = if bounds_ratio > image_ratio { + size( + bounds.size.width, + image_size.height * (bounds.size.width / image_size.width), + ) + } else { + size( + image_size.width * (bounds.size.height / image_size.height), + bounds.size.height, + ) + }; + + Bounds { + origin: point( + bounds.origin.x + (bounds.size.width - new_size.width) / 2.0, + bounds.origin.y + (bounds.size.height - new_size.height) / 2.0, + ), + size: new_size, + } + } + ObjectFit::None => Bounds { + origin: bounds.origin, + size: image_size, + }, + } } } @@ -85,6 +162,11 @@ impl Img { self.grayscale = grayscale; self } + /// Set the object fit for the image. + pub fn object_fit(mut self, object_fit: ObjectFit) -> Self { + self.object_fit = object_fit; + self + } } impl Element for Img { @@ -133,7 +215,7 @@ impl Element for Img { .now_or_never() .and_then(|result| result.ok()) { - let new_bounds = preserve_aspect_ratio(bounds, data.size()); + let new_bounds = self.object_fit.get_bounds(bounds, data.size()); cx.paint_image(new_bounds, corner_radii, data, self.grayscale) .log_err(); } else { @@ -147,7 +229,7 @@ impl Element for Img { } ImageSource::Data(data) => { - let new_bounds = preserve_aspect_ratio(bounds, data.size()); + let new_bounds = self.object_fit.get_bounds(bounds, data.size()); cx.paint_image(new_bounds, corner_radii, data, self.grayscale) .log_err(); } @@ -155,7 +237,7 @@ impl Element for Img { #[cfg(target_os = "macos")] ImageSource::Surface(surface) => { let size = size(surface.width().into(), surface.height().into()); - let new_bounds = preserve_aspect_ratio(bounds, size); + let new_bounds = self.object_fit.get_bounds(bounds, size); // TODO: Add support for corner_radii and grayscale. cx.paint_surface(new_bounds, surface); } @@ -183,29 +265,3 @@ impl InteractiveElement for Img { &mut self.interactivity } } - -fn preserve_aspect_ratio(bounds: Bounds, image_size: Size) -> Bounds { - let image_size = image_size.map(|dimension| Pixels::from(u32::from(dimension))); - let image_ratio = image_size.width / image_size.height; - let bounds_ratio = bounds.size.width / bounds.size.height; - - let new_size = if bounds_ratio > image_ratio { - size( - image_size.width * (bounds.size.height / image_size.height), - bounds.size.height, - ) - } else { - size( - bounds.size.width, - image_size.height * (bounds.size.width / image_size.width), - ) - }; - - Bounds { - origin: point( - bounds.origin.x + (bounds.size.width - new_size.width) / 2.0, - bounds.origin.y + (bounds.size.height - new_size.height) / 2.0, - ), - size: new_size, - } -} diff --git a/crates/gpui/src/platform/blade/shaders.wgsl b/crates/gpui/src/platform/blade/shaders.wgsl index 756d96197a..0ebda2d796 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/blade/shaders.wgsl @@ -550,7 +550,7 @@ fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4 { } let sprite = b_poly_sprites[input.sprite_id]; - let distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); + let distance = quad_sdf(input.position.xy, sprite.content_mask, sprite.corner_radii); var color = sample; if ((sprite.grayscale & 0xFFu) != 0u) { diff --git a/crates/gpui/src/platform/mac/shaders.metal b/crates/gpui/src/platform/mac/shaders.metal index beadd83021..efe494de07 100644 --- a/crates/gpui/src/platform/mac/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders.metal @@ -372,7 +372,7 @@ fragment float4 polychrome_sprite_fragment( float4 sample = atlas_texture.sample(atlas_texture_sampler, input.tile_position); float distance = - quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); + quad_sdf(input.position.xy, sprite.content_mask.bounds, sprite.corner_radii); float4 color = sample; if (sprite.grayscale) {