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,
|
interactivity: Interactivity,
|
||||||
source: ImageSource,
|
source: ImageSource,
|
||||||
grayscale: bool,
|
grayscale: bool,
|
||||||
|
object_fit: ObjectFit,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new image element.
|
/// Create a new image element.
|
||||||
|
@ -76,6 +77,82 @@ pub fn img(source: impl Into<ImageSource>) -> Img {
|
||||||
interactivity: Interactivity::default(),
|
interactivity: Interactivity::default(),
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
grayscale: false,
|
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.grayscale = grayscale;
|
||||||
self
|
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 {
|
impl Element for Img {
|
||||||
|
@ -133,7 +215,7 @@ impl Element for Img {
|
||||||
.now_or_never()
|
.now_or_never()
|
||||||
.and_then(|result| result.ok())
|
.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)
|
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
|
||||||
.log_err();
|
.log_err();
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,7 +229,7 @@ impl Element for Img {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageSource::Data(data) => {
|
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)
|
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
@ -155,7 +237,7 @@ impl Element for Img {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
ImageSource::Surface(surface) => {
|
ImageSource::Surface(surface) => {
|
||||||
let size = size(surface.width().into(), surface.height().into());
|
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.
|
// TODO: Add support for corner_radii and grayscale.
|
||||||
cx.paint_surface(new_bounds, surface);
|
cx.paint_surface(new_bounds, surface);
|
||||||
}
|
}
|
||||||
|
@ -183,29 +265,3 @@ impl InteractiveElement for Img {
|
||||||
&mut self.interactivity
|
&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 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;
|
var color = sample;
|
||||||
if ((sprite.grayscale & 0xFFu) != 0u) {
|
if ((sprite.grayscale & 0xFFu) != 0u) {
|
||||||
|
|
|
@ -372,7 +372,7 @@ fragment float4 polychrome_sprite_fragment(
|
||||||
float4 sample =
|
float4 sample =
|
||||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
float distance =
|
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;
|
float4 color = sample;
|
||||||
if (sprite.grayscale) {
|
if (sprite.grayscale) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue