503 lines · 16.0 KB · Rust
RawDownload
//! This holds the resize logic for picker windows.//!//! # Resizing basics://! We render a resize handle (`render_resize`) for each side and corner that//! can be dragged. The `Side` trait implementations (`Left`, `Right`,//! `Bottom`, `LeftCorner`, `RightCorner`, ...) determine where each handle is//! placed and how a drag changes the picker's shape.//!//! If there is a preview to the right or below there is an additional resize//! handle on the divider between the results and the preview.//!//! The resize's are div's aka rectangles that are "placed" by specifying the//! position of their sides. When hovering over these the cursor is changed//! so it is clear you can resize.//!//! We set up two callbacks in each//!  - on_drag: fires when the user starts dragging//!  - on_drag_move: runs every frame while the user is dragging//!//! The actual shape of the resize is updated in `on_drag_move`. When a preview//! is active dragging the outside edge modifies the//!//! # Resizing persistence//! Each picker has a 'fixed' size and tracks it's last resize. When manually//! resized the window size is stored as a percentage of the viewport//! width/height. The size is serialized as soon as the use lets go of the drag.//!//! # Diagrams & Details//! ```txt//! ================ CHANGING WIDTH ======================================//! The picker position stays constant during the drag but it is centered//! directly after. (when the user lets go)//! ================ DRAGGING RIGHT ======================================//!//! ------------//! |    |     |  <- dragging this edge right//! |    |     |//! ------------//! leads to://! --------------------//! |    |             |  <- dragged on this edge//! |    |  preview    |//! --------------------//!//! self.w_preview = w_preview + drag//! ================ DRAGGING LEFT =======================================//!//!                       ------------//! dragging this left -> |   |      |//!                       |   |      |//!                       ------------//! leads to://! -------------------//! | list     |      |//! |          |      |//! -------------------//! ``` use std::{any::type_name, marker::PhantomData}; use gpui::{ClickEvent, Context, CursorStyle, DragMoveEvent, MouseButton, Point, Styled, Window};use ui::prelude::*; use crate::shape::{Centered, PositionAndShape, Shape, SizeBounds};use crate::{Picker, PickerDelegate, preview::Layout}; pub struct DragPreview; impl Render for DragPreview {    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {        div()    }} #[derive(Clone, Copy)]struct ResizeDrag<S> {    shape_before: PositionAndShape,    phantom_data: PhantomData<S>,    mouse_pos_before: Point<Pixels>,} pub(crate) trait Side: Copy + 'static {    fn id() -> &'static str {        type_name::<Self>()    }    /// The thickness of the grab strip along the picker's edge.    ///    /// Expressed in rems so it scales with the user's UI font size.    fn handle_width(window: &Window) -> Pixels {        rems(0.375).to_pixels(window.rem_size())    }    fn handle_offset(window: &Window) -> Pixels {        Self::handle_width(window) / 2.0    }    /// How far the grab strip is inset from the top and bottom corners so it doesn't overlap the    /// corner resize handles.    fn corner_clearance(window: &Window) -> Pixels {        rems(1.125).to_pixels(window.rem_size())    }    /// The resize cursor for this side's handle.    fn cursor(&self) -> CursorStyle;    /// Places and sizes the grab strip along this side's edge.    fn position(        &self,        div: gpui::Stateful<Div>,        shape: PositionAndShape,        window: &Window,    ) -> gpui::Stateful<Div>;    fn current_position_and_shape(        &self,        shape_before: PositionAndShape,        mouse_movement: Point<Pixels>,    ) -> PositionAndShape;    fn clamp(        &self,        working: &mut PositionAndShape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &Window,    );    fn revert_to_default_size(&self, shape: &mut Shape, default: &Centered, window: &Window);} #[derive(Clone, Copy)]pub(crate) struct Left;impl Side for Left {    fn cursor(&self) -> CursorStyle {        CursorStyle::ResizeColumn    }    fn position(        &self,        div: gpui::Stateful<Div>,        _: PositionAndShape,        window: &Window,    ) -> gpui::Stateful<Div> {        div.top_0()            .bottom(Self::corner_clearance(window))            .w(Self::handle_width(window))            .left(-Self::handle_offset(window))    }    fn current_position_and_shape(        &self,        mut shape_before: PositionAndShape,        mouse_movement: Point<Pixels>,    ) -> PositionAndShape {        shape_before.left += mouse_movement.x;        shape_before    }    fn clamp(        &self,        working: &mut PositionAndShape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &Window,    ) {        bounds.clamp_left_edge(working, layout, window);        bounds.clamp_divider(working, layout, window);    }    fn revert_to_default_size(&self, shape: &mut Shape, default: &Centered, _window: &Window) {        shape.reset_width(default);    }}#[derive(Clone, Copy)]pub(crate) struct Right(pub(crate) Layout);impl Side for Right {    fn cursor(&self) -> CursorStyle {        CursorStyle::ResizeColumn    }    fn position(        &self,        div: gpui::Stateful<Div>,        _: PositionAndShape,        window: &Window,    ) -> gpui::Stateful<Div> {        div.top_0()            .bottom(Self::corner_clearance(window))            .w(Self::handle_width(window))            .right(-Self::handle_offset(window))    }    fn current_position_and_shape(        &self,        mut shape_before: PositionAndShape,        mouse_movement: Point<Pixels>,    ) -> PositionAndShape {        if let Layout::Right = self.0 {            shape_before.preview += mouse_movement.x;        }        shape_before.right += mouse_movement.x;        shape_before    }    fn clamp(        &self,        working: &mut PositionAndShape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &Window,    ) {        bounds.clamp_right_edge(working, layout, window);        bounds.clamp_divider(working, layout, window);    }    fn revert_to_default_size(&self, shape: &mut Shape, default: &Centered, _window: &Window) {        shape.reset_width(default);    }} #[derive(Clone, Copy)]pub(crate) struct Middle(pub(crate) Layout);impl Side for Middle {    fn cursor(&self) -> CursorStyle {        match self.0 {            Layout::Hidden => {                unreachable!("This resize handle is not drawn when the preview is hidden")            }            Layout::Below => CursorStyle::ResizeRow,            Layout::Right => CursorStyle::ResizeColumn,        }    }     fn position(        &self,        div: gpui::Stateful<Div>,        shape: PositionAndShape,        window: &Window,    ) -> gpui::Stateful<Div> {        match self.0 {            Layout::Hidden => {                unreachable!("This resize handle is not drawn when the preview is hidden")            }            Layout::Below => div                .left(Self::corner_clearance(window))                .right(Self::corner_clearance(window))                .h(Self::handle_width(window))                .bottom(shape.preview - Self::handle_offset(window)),            Layout::Right => div                .top_0()                .bottom(Self::corner_clearance(window))                .w(Self::handle_width(window))                .right(shape.preview - Self::handle_offset(window)),        }    }     fn current_position_and_shape(        &self,        mut shape_before: PositionAndShape,        mouse_movement: Point<Pixels>,    ) -> PositionAndShape {        match self.0 {            Layout::Hidden => {                unreachable!("This resize handle is not drawn when the preview is hidden")            }            Layout::Below => shape_before.preview -= mouse_movement.y,            Layout::Right => shape_before.preview -= mouse_movement.x,        }        shape_before    }    fn clamp(        &self,        working: &mut PositionAndShape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &Window,    ) {        // The divider only moves the preview; the outer edges are unchanged.        bounds.clamp_divider(working, layout, window);    }    fn revert_to_default_size(&self, shape: &mut Shape, _default: &Centered, window: &Window) {        shape.center_divider(self.0, window);    }} #[derive(Clone, Copy)]pub(crate) struct Bottom(pub(crate) Layout);impl Side for Bottom {    fn cursor(&self) -> CursorStyle {        CursorStyle::ResizeRow    }    fn position(        &self,        div: gpui::Stateful<Div>,        _: PositionAndShape,        window: &Window,    ) -> gpui::Stateful<Div> {        div.left(Self::corner_clearance(window))            .right(Self::corner_clearance(window))            .h(Self::handle_width(window))            .bottom(-Self::handle_offset(window))    }    fn current_position_and_shape(        &self,        mut shape_before: PositionAndShape,        mouse_movement: Point<Pixels>,    ) -> PositionAndShape {        if let Layout::Below = self.0 {            shape_before.preview += mouse_movement.y;        }        shape_before.bottom += mouse_movement.y;        shape_before    }    fn clamp(        &self,        working: &mut PositionAndShape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &Window,    ) {        bounds.clamp_bottom_edge(working, layout, window);        bounds.clamp_divider(working, layout, window);    }    fn revert_to_default_size(&self, shape: &mut Shape, default: &Centered, _window: &Window) {        shape.reset_height(default);    }}#[derive(Clone, Copy)]pub(crate) struct LeftCorner(pub(crate) Layout);impl Side for LeftCorner {    fn cursor(&self) -> CursorStyle {        CursorStyle::ResizeUpRightDownLeft    }    fn position(        &self,        div: gpui::Stateful<Div>,        _: PositionAndShape,        window: &Window,    ) -> gpui::Stateful<Div> {        div.w(Self::handle_width(window))            .h(Self::handle_width(window))            .left(-Self::handle_offset(window))            .bottom(-Self::handle_offset(window))    }    fn current_position_and_shape(        &self,        mut shape_before: PositionAndShape,        mouse_movement: Point<Pixels>,    ) -> PositionAndShape {        match self.0 {            Layout::Hidden => (),            Layout::Below => shape_before.preview += mouse_movement.y,            Layout::Right => shape_before.preview += mouse_movement.x,        }        shape_before.left += mouse_movement.x;        shape_before.bottom += mouse_movement.y;        shape_before    }    fn clamp(        &self,        working: &mut PositionAndShape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &Window,    ) {        bounds.clamp_left_edge(working, layout, window);        bounds.clamp_bottom_edge(working, layout, window);        bounds.clamp_divider(working, layout, window);    }    fn revert_to_default_size(&self, shape: &mut Shape, default: &Centered, _window: &Window) {        shape.reset_width(default);        shape.reset_height(default);    }}#[derive(Clone, Copy)]pub(crate) struct RightCorner(pub(crate) Layout);impl Side for RightCorner {    fn cursor(&self) -> CursorStyle {        CursorStyle::ResizeUpLeftDownRight    }    fn position(        &self,        div: gpui::Stateful<Div>,        _: PositionAndShape,        window: &Window,    ) -> gpui::Stateful<Div> {        div.w(Self::handle_width(window))            .h(Self::handle_width(window))            .right(-Self::handle_offset(window))            .bottom(-Self::handle_offset(window))    }    fn current_position_and_shape(        &self,        mut shape_before: PositionAndShape,        mouse_movement: Point<Pixels>,    ) -> PositionAndShape {        match self.0 {            Layout::Hidden => (),            Layout::Below => shape_before.preview += mouse_movement.y,            Layout::Right => shape_before.preview += mouse_movement.x,        }        shape_before.right += mouse_movement.x;        shape_before.bottom += mouse_movement.y;        shape_before    }    fn clamp(        &self,        working: &mut PositionAndShape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &Window,    ) {        bounds.clamp_right_edge(working, layout, window);        bounds.clamp_bottom_edge(working, layout, window);        bounds.clamp_divider(working, layout, window);    }    fn revert_to_default_size(&self, shape: &mut Shape, default: &Centered, _window: &Window) {        shape.reset_width(default);        shape.reset_height(default);    }} impl<S: Side> ResizeDrag<S> {    fn start_new(        shape: Shape,        bounds: &SizeBounds,        layout: Option<Layout>,        window: &mut Window,    ) -> Self {        Self {            mouse_pos_before: window.mouse_position(),            // Before rendering we always clamp so the current shape may not be            // within SizeBounds so use a clamped one            shape_before: shape.clamped_position_and_size(layout, bounds, window),            phantom_data: PhantomData,        }    }} impl<D: PickerDelegate> Picker<D> {    /// Resizes the picker modal by dragging the handle on the given side or corner    pub(crate) fn render_resize<S: Side>(        &self,        side: S,        window: &mut Window,        cx: &mut Context<Self>,    ) -> impl IntoElement {        div()            .id(S::id())            .absolute()            .cursor(side.cursor())            .map(|this| {                side.position(                    this,                    self.shape.clamped_position_and_size(                        self.preview_layout_rendered(window),                        &self.size_bounds,                        window,                    ),                    window,                )            })            .block_mouse_except_scroll()            .on_mouse_down(MouseButton::Left, do_nothing)            .on_drag(                ResizeDrag::<S>::start_new(                    self.shape,                    &self.size_bounds,                    self.preview_layout_rendered(window),                    window,                ),                |_, _, _, cx| cx.new(|_| DragPreview),            )            .on_drag_move::<ResizeDrag<S>>(cx.listener(                move |this, event: &DragMoveEvent<ResizeDrag<S>>, window, cx| {                    let drag = event.drag(cx);                    let delta = event.event.position - drag.mouse_pos_before;                    let mut working = side.current_position_and_shape(drag.shape_before, delta);                    side.clamp(                        &mut working,                        &this.size_bounds,                        this.preview_layout_rendered(window),                        window,                    );                    this.shape = Shape::Resizing(working);                    cx.notify();                },            ))            .on_click(cx.listener(move |this, event: &ClickEvent, window, cx| {                this.reset_size_to_default_on_double_click(side, event, window, cx)            }))    }     fn reset_size_to_default_on_double_click<S: Side>(        &mut self,        side: S,        event: &ClickEvent,        window: &mut Window,        cx: &mut Context<Picker<D>>,    ) {        if event.click_count() < 2 {            return;        }        side.revert_to_default_size(&mut self.shape, &self.default_shape, window);        let pos = self.shape.clamped_position_and_size(            self.preview_layout_rendered(window),            &self.size_bounds,            window,        );        self.shape = Shape::Resizing(pos);        cx.notify();    }} fn do_nothing(_: &gpui::MouseDownEvent, window: &mut Window, cx: &mut App) {

Lines 1–500 of 503.