Implement ObjectFit::ScaleDown for images (#10063)
While working towards fixes for the image viewer, @mikayla-maki and I discovered that we didn't have `object-fit: scale-down` implemented. This doesn't _fully_ solve the image issues as there is some issue where only the bounds width is updating on layout change that I haven't fully chased down. Co-Authored-By: @mikayla-maki Release Notes: - N/A --------- Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
parent
c64c2758c0
commit
c7961b9054
2 changed files with 102 additions and 58 deletions
|
@ -99,6 +99,8 @@ pub enum ObjectFit {
|
||||||
Contain,
|
Contain,
|
||||||
/// The image will be scaled to cover the bounds of the element.
|
/// The image will be scaled to cover the bounds of the element.
|
||||||
Cover,
|
Cover,
|
||||||
|
/// The image will be scaled down to fit within the bounds of the element.
|
||||||
|
ScaleDown,
|
||||||
/// The image will maintain its original size.
|
/// The image will maintain its original size.
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
@ -114,7 +116,7 @@ impl ObjectFit {
|
||||||
let image_ratio = image_size.width / image_size.height;
|
let image_ratio = image_size.width / image_size.height;
|
||||||
let bounds_ratio = bounds.size.width / bounds.size.height;
|
let bounds_ratio = bounds.size.width / bounds.size.height;
|
||||||
|
|
||||||
match self {
|
let result_bounds = match self {
|
||||||
ObjectFit::Fill => bounds,
|
ObjectFit::Fill => bounds,
|
||||||
ObjectFit::Contain => {
|
ObjectFit::Contain => {
|
||||||
let new_size = if bounds_ratio > image_ratio {
|
let new_size = if bounds_ratio > image_ratio {
|
||||||
|
@ -137,6 +139,42 @@ impl ObjectFit {
|
||||||
size: new_size,
|
size: new_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ObjectFit::ScaleDown => {
|
||||||
|
// Check if the image is larger than the bounds in either dimension.
|
||||||
|
if image_size.width > bounds.size.width || image_size.height > bounds.size.height {
|
||||||
|
// If the image is larger, use the same logic as Contain to scale it down.
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the image is smaller than or equal to the container, display it at its original size,
|
||||||
|
// centered within the container.
|
||||||
|
let original_size = size(image_size.width, image_size.height);
|
||||||
|
Bounds {
|
||||||
|
origin: point(
|
||||||
|
bounds.origin.x + (bounds.size.width - original_size.width) / 2.0,
|
||||||
|
bounds.origin.y + (bounds.size.height - original_size.height) / 2.0,
|
||||||
|
),
|
||||||
|
size: original_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ObjectFit::Cover => {
|
ObjectFit::Cover => {
|
||||||
let new_size = if bounds_ratio > image_ratio {
|
let new_size = if bounds_ratio > image_ratio {
|
||||||
size(
|
size(
|
||||||
|
@ -162,7 +200,9 @@ impl ObjectFit {
|
||||||
origin: bounds.origin,
|
origin: bounds.origin,
|
||||||
size: image_size,
|
size: image_size,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
result_bounds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, fill, img, opaque_grey, point, size, AnyElement, AppContext, Bounds, Context,
|
canvas, div, fill, img, opaque_grey, point, size, AnyElement, AppContext, Bounds, Context,
|
||||||
EventEmitter, FocusHandle, FocusableView, Img, InteractiveElement, IntoElement, Model,
|
EventEmitter, FocusHandle, FocusableView, Img, InteractiveElement, IntoElement, Model,
|
||||||
ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
|
ObjectFit, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
use persistence::IMAGE_VIEWER;
|
use persistence::IMAGE_VIEWER;
|
||||||
use ui::{h_flex, prelude::*};
|
use ui::prelude::*;
|
||||||
|
|
||||||
use project::{Project, ProjectEntryId, ProjectPath};
|
use project::{Project, ProjectEntryId, ProjectPath};
|
||||||
use std::{ffi::OsStr, path::PathBuf};
|
use std::{ffi::OsStr, path::PathBuf};
|
||||||
|
@ -155,64 +156,67 @@ impl FocusableView for ImageView {
|
||||||
|
|
||||||
impl Render for ImageView {
|
impl Render for ImageView {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut ElementContext| {
|
||||||
|
let square_size = 32.0;
|
||||||
|
|
||||||
|
let start_y = bounds.origin.y.0;
|
||||||
|
let height = bounds.size.height.0;
|
||||||
|
let start_x = bounds.origin.x.0;
|
||||||
|
let width = bounds.size.width.0;
|
||||||
|
|
||||||
|
let mut y = start_y;
|
||||||
|
let mut x = start_x;
|
||||||
|
let mut color_swapper = true;
|
||||||
|
// draw checkerboard pattern
|
||||||
|
while y <= start_y + height {
|
||||||
|
// Keeping track of the grid in order to be resilient to resizing
|
||||||
|
let start_swap = color_swapper;
|
||||||
|
while x <= start_x + width {
|
||||||
|
let rect =
|
||||||
|
Bounds::new(point(px(x), px(y)), size(px(square_size), px(square_size)));
|
||||||
|
|
||||||
|
let color = if color_swapper {
|
||||||
|
opaque_grey(0.6, 0.4)
|
||||||
|
} else {
|
||||||
|
opaque_grey(0.7, 0.4)
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.paint_quad(fill(rect, color));
|
||||||
|
color_swapper = !color_swapper;
|
||||||
|
x += square_size;
|
||||||
|
}
|
||||||
|
x = start_x;
|
||||||
|
color_swapper = !start_swap;
|
||||||
|
y += square_size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let checkered_background = canvas(|_, _| (), checkered_background)
|
||||||
|
.border_2()
|
||||||
|
.border_color(cx.theme().styles.colors.border)
|
||||||
|
.size_full()
|
||||||
|
.absolute()
|
||||||
|
.top_0()
|
||||||
|
.left_0();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.size_full()
|
.size_full()
|
||||||
|
.child(checkered_background)
|
||||||
.child(
|
.child(
|
||||||
// Checkered background behind the image
|
div()
|
||||||
canvas(
|
.flex()
|
||||||
|_, _| (),
|
.justify_center()
|
||||||
|bounds, _, cx| {
|
.items_center()
|
||||||
let square_size = 32.0;
|
.w_full()
|
||||||
|
// TODO: In browser based Tailwind & Flex this would be h-screen and we'd use w-full
|
||||||
let start_y = bounds.origin.y.0;
|
.h_full()
|
||||||
let height = bounds.size.height.0;
|
.child(
|
||||||
let start_x = bounds.origin.x.0;
|
img(self.path.clone())
|
||||||
let width = bounds.size.width.0;
|
.object_fit(ObjectFit::ScaleDown)
|
||||||
|
.max_w_full()
|
||||||
let mut y = start_y;
|
.max_h_full(),
|
||||||
let mut x = start_x;
|
),
|
||||||
let mut color_swapper = true;
|
|
||||||
// draw checkerboard pattern
|
|
||||||
while y <= start_y + height {
|
|
||||||
// Keeping track of the grid in order to be resilient to resizing
|
|
||||||
let start_swap = color_swapper;
|
|
||||||
while x <= start_x + width {
|
|
||||||
let rect = Bounds::new(
|
|
||||||
point(px(x), px(y)),
|
|
||||||
size(px(square_size), px(square_size)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let color = if color_swapper {
|
|
||||||
opaque_grey(0.6, 0.4)
|
|
||||||
} else {
|
|
||||||
opaque_grey(0.7, 0.4)
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.paint_quad(fill(rect, color));
|
|
||||||
color_swapper = !color_swapper;
|
|
||||||
x += square_size;
|
|
||||||
}
|
|
||||||
x = start_x;
|
|
||||||
color_swapper = !start_swap;
|
|
||||||
y += square_size;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.border_2()
|
|
||||||
.border_color(cx.theme().styles.colors.border)
|
|
||||||
.size_full()
|
|
||||||
.absolute()
|
|
||||||
.top_0()
|
|
||||||
.left_0(),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
v_flex().h_full().justify_around().child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.justify_around()
|
|
||||||
.child(img(self.path.clone())),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue