@@ -99,6 +99,8 @@ pub enum ObjectFit {
Contain,
/// The image will be scaled to cover the bounds of the element.
Cover,
+ /// The image will be scaled down to fit within the bounds of the element.
+ ScaleDown,
/// The image will maintain its original size.
None,
}
@@ -114,7 +116,7 @@ impl ObjectFit {
let image_ratio = image_size.width / image_size.height;
let bounds_ratio = bounds.size.width / bounds.size.height;
- match self {
+ let result_bounds = match self {
ObjectFit::Fill => bounds,
ObjectFit::Contain => {
let new_size = if bounds_ratio > image_ratio {
@@ -137,6 +139,42 @@ impl ObjectFit {
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 => {
let new_size = if bounds_ratio > image_ratio {
size(
@@ -162,7 +200,9 @@ impl ObjectFit {
origin: bounds.origin,
size: image_size,
},
- }
+ };
+
+ result_bounds
}
}
@@ -1,10 +1,11 @@
use gpui::{
canvas, div, fill, img, opaque_grey, point, size, AnyElement, AppContext, Bounds, Context,
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 ui::{h_flex, prelude::*};
+use ui::prelude::*;
use project::{Project, ProjectEntryId, ProjectPath};
use std::{ffi::OsStr, path::PathBuf};
@@ -155,64 +156,67 @@ impl FocusableView for ImageView {
impl Render for ImageView {
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()
.track_focus(&self.focus_handle)
.size_full()
+ .child(checkered_background)
.child(
- // Checkered background behind the image
- canvas(
- |_, _| (),
- |bounds, _, cx| {
- 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;
- }
- },
- )
- .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())),
- ),
+ div()
+ .flex()
+ .justify_center()
+ .items_center()
+ .w_full()
+ // TODO: In browser based Tailwind & Flex this would be h-screen and we'd use w-full
+ .h_full()
+ .child(
+ img(self.path.clone())
+ .object_fit(ObjectFit::ScaleDown)
+ .max_w_full()
+ .max_h_full(),
+ ),
)
}
}