diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index fec2df7092..67c7c0a4ee 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -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 } } diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index a866e272b6..1b30c128cb 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -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) -> impl IntoElement { + let checkered_background = |bounds: Bounds, _, 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(), + ), ) } }