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:
parent
55f4c8e51b
commit
b38e3f16ad
3 changed files with 87 additions and 31 deletions
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue