Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 17 additions & 30 deletions crates/gpui/src/elements/img.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,18 @@ impl Element for Img {
if self.transform.swaps_axes() {
std::mem::swap(&mut image_size.width, &mut image_size.height);
}
style.aspect_ratio = Some(image_size.width / image_size.height);
// The decoded image ratio is intrinsic sizing data, not
// an override for a caller-specified box. When both axes
// are definite (including percentages such as
// `size_full()`), preserve that box and let ObjectFit
// place the pixels inside it. Taffy otherwise uses this
// ratio to recompute the height from the width, which can
// push the image's bottom edge beyond its parent's clip.
let width_is_auto = matches!(style.size.width, Length::Auto);
let height_is_auto = matches!(style.size.height, Length::Auto);
if style.aspect_ratio.is_none() && (width_is_auto || height_is_auto) {
style.aspect_ratio = Some(image_size.width / image_size.height);
}

if let Length::Auto = style.size.width {
style.size.width = match style.size.height {
Expand Down Expand Up @@ -582,42 +593,18 @@ impl Element for Img {
.max(1.0) as i32,
);
}
let new_bounds = self.style.object_fit.get_bounds(bounds, tex_size);
let mut crop4 = crop.unwrap_or([0.0, 0.0, 1.0, 1.0]);
// Overflowing fits (Cover / None) would paint an
// oversized quad whose corner radii round OFFSCREEN
// corners — the element clip is rectangular, so the
// visible corners come out square. Fold the overflow
// into the uv crop instead: the quad stays exactly the
// visible bounds and the radii round what the user sees.
let mut paint_bounds = new_bounds;
let visible = bounds.intersect(&new_bounds);
if visible != new_bounds
&& f32::from(new_bounds.size.width) > 0.0
&& f32::from(new_bounds.size.height) > 0.0
{
let fx = f32::from(visible.origin.x - new_bounds.origin.x)
/ f32::from(new_bounds.size.width);
let fy = f32::from(visible.origin.y - new_bounds.origin.y)
/ f32::from(new_bounds.size.height);
let fw = f32::from(visible.size.width) / f32::from(new_bounds.size.width);
let fh = f32::from(visible.size.height) / f32::from(new_bounds.size.height);
crop4 = [
crop4[0] + fx * crop4[2],
crop4[1] + fy * crop4[3],
crop4[2] * fw,
crop4[3] * fh,
];
paint_bounds = visible;
}
let paint_bounds = self.style.object_fit.get_bounds(bounds, tex_size);
let clip_bounds = paint_bounds.intersect(&bounds);
let crop4 = crop.unwrap_or([0.0, 0.0, 1.0, 1.0]);
let corner_radii = style
.corner_radii
.to_pixels(window.rem_size())
.clamp_radii_for_quad_size(paint_bounds.size);
.clamp_radii_for_quad_size(clip_bounds.size);
let uv_transform = self.transform.encode();
window
.paint_image(
paint_bounds,
clip_bounds,
corner_radii,
data,
layout_state.frame_index,
Expand Down
30 changes: 5 additions & 25 deletions crates/gpui/src/elements/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,31 +123,11 @@ impl Element for Surface {
((i32::from(size.height) as f32) * c[3]).round().max(1.0) as i32,
);
}
let new_bounds = self.object_fit.get_bounds(bounds, size);
let mut crop4 = crop.unwrap_or([0.0, 0.0, 1.0, 1.0]);
// Same as `img`: fold Cover-style overflow into the uv crop
// so corner radii round the VISIBLE corners, not the
// offscreen corners of an oversized quad.
let mut paint_bounds = new_bounds;
let visible = bounds.intersect(&new_bounds);
if visible != new_bounds
&& f32::from(new_bounds.size.width) > 0.0
&& f32::from(new_bounds.size.height) > 0.0
{
let fx = f32::from(visible.origin.x - new_bounds.origin.x)
/ f32::from(new_bounds.size.width);
let fy = f32::from(visible.origin.y - new_bounds.origin.y)
/ f32::from(new_bounds.size.height);
let fw = f32::from(visible.size.width) / f32::from(new_bounds.size.width);
let fh = f32::from(visible.size.height) / f32::from(new_bounds.size.height);
crop4 = [
crop4[0] + fx * crop4[2],
crop4[1] + fy * crop4[3],
crop4[2] * fw,
crop4[3] * fh,
];
paint_bounds = visible;
}
let (paint_bounds, crop4) = self.object_fit.get_bounds_and_crop(
bounds,
size,
crop.unwrap_or([0.0, 0.0, 1.0, 1.0]),
);
let corner_radii = style_corner_radii(&self.style, window);
window.paint_surface(paint_bounds, corner_radii, surface.clone(), crop4);
}
Expand Down
3 changes: 3 additions & 0 deletions crates/gpui/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ pub struct PolychromeSprite {
pub grayscale: PaddedBool32,
pub opacity: f32,
pub bounds: Bounds<ScaledPixels>,
/// Visible viewport used by the rounded-corner SDF. Cover may paint an
/// oversized sampling quad while rounding this fixed intersection.
pub clip_bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile,
Expand Down
87 changes: 87 additions & 0 deletions crates/gpui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,67 @@ impl ObjectFit {
},
}
}

/// Resolve the painted quad and normalized source crop together. Cover
/// keeps the quad at the element bounds and crops the sampled texture;
/// its rounded corners therefore belong to the visible rectangle instead
/// of an oversized offscreen quad.
pub(crate) fn get_bounds_and_crop(
&self,
bounds: Bounds<Pixels>,
image_size: Size<DevicePixels>,
crop: [f32; 4],
) -> (Bounds<Pixels>, [f32; 4]) {
if matches!(self, ObjectFit::Cover) {
let image_width = u32::from(image_size.width).max(1) as f32;
let image_height = u32::from(image_size.height).max(1) as f32;
let bounds_width = f32::from(bounds.size.width).max(1.0);
let bounds_height = f32::from(bounds.size.height).max(1.0);
let image_ratio = image_width / image_height;
let bounds_ratio = bounds_width / bounds_height;
let (local_x, local_y, local_width, local_height) = if image_ratio > bounds_ratio {
let width = bounds_ratio / image_ratio;
((1.0 - width) / 2.0, 0.0, width, 1.0)
} else {
let height = image_ratio / bounds_ratio;
(0.0, (1.0 - height) / 2.0, 1.0, height)
};
return (
bounds,
[
crop[0] + local_x * crop[2],
crop[1] + local_y * crop[3],
crop[2] * local_width,
crop[3] * local_height,
],
);
}

let fitted_bounds = self.get_bounds(bounds, image_size);
let visible = bounds.intersect(&fitted_bounds);
if visible == fitted_bounds
|| f32::from(fitted_bounds.size.width) <= 0.0
|| f32::from(fitted_bounds.size.height) <= 0.0
{
return (fitted_bounds, crop);
}

let local_x = f32::from(visible.origin.x - fitted_bounds.origin.x)
/ f32::from(fitted_bounds.size.width);
let local_y = f32::from(visible.origin.y - fitted_bounds.origin.y)
/ f32::from(fitted_bounds.size.height);
let local_width = f32::from(visible.size.width) / f32::from(fitted_bounds.size.width);
let local_height = f32::from(visible.size.height) / f32::from(fitted_bounds.size.height);
(
visible,
[
crop[0] + local_x * crop[2],
crop[1] + local_y * crop[3],
crop[2] * local_width,
crop[3] * local_height,
],
)
}
}

/// The minimum size of a column or row in a grid layout
Expand Down Expand Up @@ -1333,6 +1394,32 @@ mod tests {

use util_macros::perf;

#[test]
fn cover_paints_the_visible_bounds_and_crops_the_texture() {
let bounds = Bounds::from_corners(point(px(0.), px(0.)), point(px(1000.), px(500.)));
let (paint_bounds, crop) = ObjectFit::Cover.get_bounds_and_crop(
bounds,
size(DevicePixels(1000), DevicePixels(1000)),
[0.0, 0.0, 1.0, 1.0],
);

assert_eq!(paint_bounds, bounds);
assert_eq!(crop, [0.0, 0.25, 1.0, 0.5]);
}

#[test]
fn cover_composes_with_an_existing_crop() {
let bounds = Bounds::from_corners(point(px(0.), px(0.)), point(px(500.), px(1000.)));
let (paint_bounds, crop) = ObjectFit::Cover.get_bounds_and_crop(
bounds,
size(DevicePixels(1000), DevicePixels(500)),
[0.1, 0.2, 0.8, 0.6],
);

assert_eq!(paint_bounds, bounds);
assert_eq!(crop, [0.4, 0.2, 0.2, 0.6]);
}

#[perf]
fn test_basic_highlight_style_combination() {
let style_a = HighlightStyle::default();
Expand Down
4 changes: 4 additions & 0 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4069,6 +4069,7 @@ impl Window {
pad: 0,
grayscale: false.into(),
bounds,
clip_bounds: bounds,
corner_radii: Default::default(),
content_mask,
tile,
Expand Down Expand Up @@ -4152,6 +4153,7 @@ impl Window {
pub fn paint_image(
&mut self,
bounds: Bounds<Pixels>,
clip_bounds: Bounds<Pixels>,
corner_radii: Corners<Pixels>,
data: Arc<RenderImage>,
frame_index: usize,
Expand All @@ -4162,6 +4164,7 @@ impl Window {
self.invalidator.debug_assert_paint();

let bounds = self.snap_bounds(bounds);
let clip_bounds = self.snap_bounds(clip_bounds);
let params = RenderImageParams {
image_id: data.id,
frame_index,
Expand All @@ -4188,6 +4191,7 @@ impl Window {
pad: 0,
grayscale: grayscale.into(),
bounds,
clip_bounds,
content_mask,
corner_radii,
tile,
Expand Down
2 changes: 1 addition & 1 deletion crates/gpui_macos/src/shaders.metal
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,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.clip_bounds, sprite.corner_radii);

float4 color = sample;
if (sprite.grayscale) {
Expand Down
3 changes: 2 additions & 1 deletion crates/gpui_wgpu/src/shaders.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,7 @@ struct PolychromeSprite {
grayscale: u32,
opacity: f32,
bounds: Bounds,
clip_bounds: Bounds,
content_mask: Bounds,
corner_radii: Corners,
tile: AtlasTile,
Expand Down Expand Up @@ -1330,7 +1331,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.clip_bounds, sprite.corner_radii);

var color = sample;
if (sprite.grayscale != 0u) {
Expand Down
3 changes: 2 additions & 1 deletion crates/gpui_windows/src/shaders.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,7 @@ struct PolychromeSprite {
uint grayscale;
float opacity;
Bounds bounds;
Bounds clip_bounds;
Bounds content_mask;
Corners corner_radii;
AtlasTile tile;
Expand Down Expand Up @@ -1267,7 +1268,7 @@ PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexI
float4 polychrome_sprite_fragment(PolychromeSpriteFragmentInput input): SV_Target {
PolychromeSprite sprite = poly_sprites[input.sprite_id];
float4 sample = t_sprite.Sample(s_sprite, input.tile_position);
float distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
float distance = quad_sdf(input.position.xy, sprite.clip_bounds, sprite.corner_radii);

float4 color = sample;
if (sprite.grayscale != 0u) {
Expand Down