gpui: img element object-fit (#9393)

Release Notes:

- Added [object-fit
API](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) to the
`img` element. This allows the user to decide how the image is scaled
within the element bounds.
- Fixes corner radius not working as expected on overflowing elements.
This commit is contained in:
Matthias Grandl 2024-03-15 18:06:07 +01:00 committed by GitHub
parent 55f4c8e51b
commit b38e3f16ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 31 deletions

View file

@ -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<ImageSource>) -> 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<Pixels>,
image_size: Size<DevicePixels>,
) -> Bounds<Pixels> {
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<Pixels>, image_size: Size<DevicePixels>) -> Bounds<Pixels> {
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,
}
}

View file

@ -550,7 +550,7 @@ fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4<f32> {
}
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) {

View file

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