diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 8b2cfe6fa2..8f9e9e4d90 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -12,12 +12,12 @@ use editor::{ }; use file_icons::FileIcons; use gpui::{ - AnyElement, App, Bounds, Context, DismissEvent, DispatchPhase, Element, ElementId, Entity, - EventEmitter, FocusHandle, Focusable, Font, GlobalElementId, InspectorElementId, - InteractiveElement, IntoElement, KeyDownEvent, LayoutId, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, PinchEvent, Pixels, Point, Render, RenderImage, - ScrollDelta, ScrollWheelEvent, Style, Styled, Subscription, Task, WeakEntity, Window, actions, - anchored, canvas, checkerboard, deferred, div, img, point, px, size, + AnyElement, App, Bounds, Context, DismissEvent, DispatchPhase, DragMoveEvent, Element, + ElementId, Entity, EventEmitter, FocusHandle, Focusable, Font, GlobalElementId, + InspectorElementId, InteractiveElement, IntoElement, KeyDownEvent, LayoutId, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, PinchEvent, Pixels, Point, Render, + RenderImage, ScrollDelta, ScrollWheelEvent, Style, Styled, Subscription, Task, WeakEntity, + Window, actions, anchored, canvas, checkerboard, deferred, div, img, point, px, size, }; use language::File as _; use pdf_viewer::{ @@ -123,6 +123,9 @@ const FIND_BAR_WIDTH: f32 = 280.0; const THUMBNAIL_DPI: f32 = 20.0; // Renders letter pages about 170px wide. const THUMBNAIL_PANEL_WIDTH: f32 = 150.0; const OUTLINE_PANEL_WIDTH: f32 = 220.0; +const OUTLINE_PANEL_MIN_WIDTH: f32 = 120.0; +const OUTLINE_PANEL_MAX_WIDTH: f32 = 480.0; +const OUTLINE_RESIZE_HANDLE_WIDTH: f32 = 6.0; const OUTLINE_INDENT: f32 = 12.0; const PROPERTIES_PANEL_WIDTH: f32 = 220.0; const HIGHLIGHT_COLORS: [pdf_viewer::PdfAnnotationColor; 4] = [ @@ -202,6 +205,15 @@ struct NormRect { height: f32, } +/// Drag payload identifying a resize drag of the outline panel's right edge. +struct OutlinePanelResize; + +impl Render for OutlinePanelResize { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + div() + } +} + /// Which annotation tool the viewer is armed with. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum PdfAnnotationTool { @@ -267,6 +279,10 @@ pub struct ImageView { pdf_find: Option, show_thumbnail_panel: bool, show_outline_panel: bool, + /// Current outline panel width, adjusted by dragging its resize handle. + outline_panel_width: Pixels, + /// Mouse x and panel width when the outline resize drag started. + outline_resize_origin: Option<(Pixels, Pixels)>, show_properties_panel: bool, presentation_mode: bool, page_rotation: u16, @@ -425,6 +441,8 @@ impl ImageView { pdf_find: None, show_thumbnail_panel: false, show_outline_panel: false, + outline_panel_width: px(OUTLINE_PANEL_WIDTH), + outline_resize_origin: None, show_properties_panel: false, presentation_mode: false, page_rotation: 0, @@ -1911,6 +1929,7 @@ impl ImageView { /// Renders the outline sidebar, indenting nested entries like a document tree. fn render_outline_panel( outline: Option<&[pdf_viewer::PdfOutlineItem]>, + width: Pixels, cx: &mut Context, ) -> AnyElement { let mut next_id = 0_usize; @@ -1921,7 +1940,8 @@ impl ImageView { div() .id("pdf-outline") - .w(px(OUTLINE_PANEL_WIDTH)) + .debug_selector(|| "pdf-outline".into()) + .w(width) .h_full() .p_2() .overflow_y_scroll() @@ -1937,6 +1957,37 @@ impl ImageView { .into_any_element() } + /// Renders the drag handle on the outline panel's right edge. The actual + /// resize tracking happens in the `on_drag_move` handler on the surrounding + /// row so the pointer can roam while dragging. + fn render_outline_resize_handle(&mut self, cx: &mut Context) -> AnyElement { + div() + .id("outline-resize-handle") + .debug_selector(|| "outline-resize-handle".into()) + .occlude() + .w(px(OUTLINE_RESIZE_HANDLE_WIDTH)) + .h_full() + .cursor_col_resize() + .hover(|style| style.bg(cx.theme().colors().element_hover)) + .on_drag(OutlinePanelResize, |_, _, _, cx| { + cx.stop_propagation(); + cx.new(|_| OutlinePanelResize) + }) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, event: &MouseDownEvent, _, _| { + this.outline_resize_origin = Some((event.position.x, this.outline_panel_width)); + }), + ) + .on_mouse_up( + MouseButton::Left, + cx.listener(|this, _: &MouseUpEvent, _, _| { + this.outline_resize_origin = None; + }), + ) + .into_any_element() + } + fn render_outline_items( items: &[pdf_viewer::PdfOutlineItem], depth: usize, @@ -3147,6 +3198,8 @@ impl Item for ImageView { pdf_find: None, show_thumbnail_panel: false, show_outline_panel: false, + outline_panel_width: self.outline_panel_width, + outline_resize_origin: None, show_properties_panel: false, presentation_mode: false, page_rotation: 0, @@ -3934,16 +3987,46 @@ impl Render for ImageView { cx, )) } else if self.show_outline_panel { - Some(Self::render_outline_panel(self.pdf_outline.as_deref(), cx)) + Some(Self::render_outline_panel( + self.pdf_outline.as_deref(), + self.outline_panel_width, + cx, + )) + } else { + None + }; + + let has_sidebar = sidebar.is_some(); + let resize_handle = if self.show_outline_panel { + Some(self.render_outline_resize_handle(cx)) } else { None }; h_flex() .size_full() - .when_some(sidebar, |this, sidebar| { - this.child(sidebar).child(Divider::vertical()) - }) + .when_some(sidebar, |this, sidebar| this.child(sidebar)) + .when_some(resize_handle, |this, handle| this.child(handle)) + .when(has_sidebar, |this| this.child(Divider::vertical())) + .on_drag_move(cx.listener( + |this, event: &DragMoveEvent, _, cx| { + if let Some((origin_x, origin_width)) = + this.outline_resize_origin + { + let width = + origin_width + (event.event.position.x - origin_x); + this.outline_panel_width = if width < px(OUTLINE_PANEL_MIN_WIDTH) { + px(OUTLINE_PANEL_MIN_WIDTH) + } else if width > px(OUTLINE_PANEL_MAX_WIDTH) { + px(OUTLINE_PANEL_MAX_WIDTH) + } else { + width + }; + cx.stop_propagation(); + cx.notify(); + } + }, + )) .child(div().flex_1().h_full().child(main_pdf_view)) .when(self.show_text_panel, |this| { this.child(Divider::vertical()).child( @@ -5301,7 +5384,6 @@ mod tests { assert_eq!(cache.current_memory_bytes(), 0); assert!(cache.is_empty()); } - } mod persistence {