wip: adding flex content drag handle

Eric Holk and Max Brunsfeld created

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/workspace/src/dock.rs      | 177 +++++++++++++-------------------
crates/workspace/src/workspace.rs | 173 ++++++++++++++++++++++++-------
2 files changed, 203 insertions(+), 147 deletions(-)

Detailed changes

crates/workspace/src/dock.rs 🔗

@@ -7,7 +7,7 @@ use client::proto;
 use gpui::{
     Action, AnyElement, AnyView, App, Axis, Context, Corner, Entity, EntityId, EventEmitter,
     FocusHandle, Focusable, IntoElement, KeyContext, MouseButton, MouseDownEvent, MouseUpEvent,
-    ParentElement, Render, SharedString, StyleRefinement, Styled, Subscription, WeakEntity, Window,
+    ParentElement, Render, SharedString, Styled, Subscription, WeakEntity, Window,
     deferred, div, px,
 };
 use settings::SettingsStore;
@@ -257,6 +257,12 @@ pub enum DockPosition {
     Right,
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum DockPart {
+    Fixed,
+    Flexible,
+}
+
 impl From<settings::DockPosition> for DockPosition {
     fn from(value: settings::DockPosition) -> Self {
         match value {
@@ -392,7 +398,7 @@ impl Dock {
         self.is_open
     }
 
-    fn resizable(&self, cx: &App) -> bool {
+    pub fn resizable(&self, cx: &App) -> bool {
         !(self.zoom_layer_open || self.modal_layer.read(cx).has_active_modal())
     }
 
@@ -797,10 +803,9 @@ impl Dock {
         }
     }
 
-    fn dispatch_context() -> KeyContext {
+    pub fn dispatch_context() -> KeyContext {
         let mut dispatch_context = KeyContext::new_with_defaults();
         dispatch_context.add("Dock");
-
         dispatch_context
     }
 
@@ -814,110 +819,68 @@ impl Dock {
     }
 }
 
-impl Render for Dock {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let dispatch_context = Self::dispatch_context();
-        if let Some(entry) = self
-            .visible_entry()
-            .filter(|entry| entry.panel.has_panel_content(window, cx))
-        {
-            let size = entry.panel.size(window, cx);
-
-            let panel_content = entry
-                .panel
-                .to_any()
-                .cached(StyleRefinement::default().v_flex().size_full());
-
-            let position = self.position;
-            let create_resize_handle = || {
-                let handle = div()
-                    .id("resize-handle")
-                    .on_drag(DraggedDock { position }, |dock, _, _, cx| {
-                        cx.stop_propagation();
-                        cx.new(|_| dock.clone())
-                    })
-                    .on_mouse_down(
-                        MouseButton::Left,
-                        cx.listener(|_, _: &MouseDownEvent, _, cx| {
-                            cx.stop_propagation();
-                        }),
-                    )
-                    .on_mouse_up(
-                        MouseButton::Left,
-                        cx.listener(|dock, e: &MouseUpEvent, window, cx| {
-                            if e.click_count == 2 {
-                                dock.resize_active_panel(None, window, cx);
-                                dock.workspace
-                                    .update(cx, |workspace, cx| {
-                                        workspace.serialize_workspace(window, cx);
-                                    })
-                                    .ok();
-                                cx.stop_propagation();
-                            }
-                        }),
-                    )
-                    .occlude();
-                match self.position() {
-                    DockPosition::Left => deferred(
-                        handle
-                            .absolute()
-                            .right(-RESIZE_HANDLE_SIZE / 2.)
-                            .top(px(0.))
-                            .h_full()
-                            .w(RESIZE_HANDLE_SIZE)
-                            .cursor_col_resize(),
-                    ),
-                    DockPosition::Bottom => deferred(
-                        handle
-                            .absolute()
-                            .top(-RESIZE_HANDLE_SIZE / 2.)
-                            .left(px(0.))
-                            .w_full()
-                            .h(RESIZE_HANDLE_SIZE)
-                            .cursor_row_resize(),
-                    ),
-                    DockPosition::Right => deferred(
-                        handle
-                            .absolute()
-                            .top(px(0.))
-                            .left(-RESIZE_HANDLE_SIZE / 2.)
-                            .h_full()
-                            .w(RESIZE_HANDLE_SIZE)
-                            .cursor_col_resize(),
-                    ),
+pub fn create_resize_handle(
+    position: DockPosition,
+    part: DockPart,
+    cx: &mut Context<Workspace>,
+) -> gpui::Deferred {
+    let handle = div()
+        .id(match part {
+            DockPart::Fixed => "resize-handle",
+            DockPart::Flexible => "flexible-resize-handle",
+        })
+        .on_drag(DraggedDock { position, part }, |dock, _, _, cx| {
+            cx.stop_propagation();
+            cx.new(|_| dock.clone())
+        })
+        .on_mouse_down(
+            MouseButton::Left,
+            cx.listener(|_, _: &MouseDownEvent, _, cx| {
+                cx.stop_propagation();
+            }),
+        )
+        .on_mouse_up(
+            MouseButton::Left,
+            cx.listener(move |workspace, e: &MouseUpEvent, window, cx| {
+                if e.click_count == 2 {
+                    let dock = workspace.dock_at_position(position);
+                    dock.update(cx, |dock, cx| {
+                        dock.resize_active_panel(None, window, cx);
+                    });
+                    workspace.serialize_workspace(window, cx);
+                    cx.stop_propagation();
                 }
-            };
-
-            div()
-                .key_context(dispatch_context)
-                .track_focus(&self.focus_handle(cx))
-                .flex()
-                .bg(cx.theme().colors().panel_background)
-                .border_color(cx.theme().colors().border)
-                .overflow_hidden()
-                .map(|this| match self.position().axis() {
-                    Axis::Horizontal => this
-                        .h_full()
-                        .flex_row()
-                        .child(div().w(size).h_full().child(panel_content)),
-                    Axis::Vertical => this
-                        .w_full()
-                        .flex_col()
-                        .child(div().h(size).w_full().child(panel_content)),
-                })
-                .map(|this| match self.position() {
-                    DockPosition::Left => this.border_r_1(),
-                    DockPosition::Right => this.border_l_1(),
-                    DockPosition::Bottom => this.border_t_1(),
-                })
-                .when(self.resizable(cx), |this| {
-                    this.child(create_resize_handle())
-                })
-        } else {
-            div()
-                .key_context(dispatch_context)
-                .track_focus(&self.focus_handle(cx))
-        }
+            }),
+        )
+        .occlude();
+    match position {
+        DockPosition::Left => deferred(
+            handle
+                .absolute()
+                .right(-RESIZE_HANDLE_SIZE / 2.)
+                .top(px(0.))
+                .h_full()
+                .w(RESIZE_HANDLE_SIZE)
+                .cursor_col_resize(),
+        ),
+        DockPosition::Bottom => deferred(
+            handle
+                .absolute()
+                .top(-RESIZE_HANDLE_SIZE / 2.)
+                .left(px(0.))
+                .w_full()
+                .h(RESIZE_HANDLE_SIZE)
+                .cursor_row_resize(),
+        ),
+        DockPosition::Right => deferred(
+            handle
+                .absolute()
+                .top(px(0.))
+                .left(-RESIZE_HANDLE_SIZE / 2.)
+                .h_full()
+                .w(RESIZE_HANDLE_SIZE)
+                .cursor_col_resize(),
+        ),
     }
 }
 

crates/workspace/src/workspace.rs 🔗

@@ -51,12 +51,13 @@ use futures::{
     future::{Shared, try_join_all},
 };
 use gpui::{
-    Action, AnyElement, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds,
-    Context, CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
-    Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
-    PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription,
-    SystemWindowTabController, Task, Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId,
-    WindowOptions, actions, canvas, point, relative, size, transparent_black,
+    Action, AnyElement, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Axis,
+    Bounds, Context, CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter,
+    FocusHandle, Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView,
+    MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful,
+    StyleRefinement, Subscription, SystemWindowTabController, Task, Tiling, WeakEntity,
+    WindowBounds, WindowHandle, WindowId, WindowOptions, actions, canvas, point, relative, size,
+    transparent_black,
 };
 pub use history_manager::*;
 pub use item::{
@@ -148,7 +149,7 @@ pub use workspace_settings::{
 };
 use zed_actions::{Spawn, feedback::FileBugReport};
 
-use crate::{item::ItemBufferKind, notifications::NotificationId};
+use crate::{dock::DockPart, item::ItemBufferKind, notifications::NotificationId};
 use crate::{
     persistence::{
         SerializedAxis,
@@ -6973,7 +6974,7 @@ impl Workspace {
         position: DockPosition,
         dock: &Entity<Dock>,
         window: &mut Window,
-        cx: &mut App,
+        cx: &mut Context<Self>,
     ) -> impl Iterator<Item = AnyElement> {
         let mut results = [None, None];
 
@@ -6987,12 +6988,57 @@ impl Workspace {
             leader_border_for_pane(follower_states, &pane, window, cx)
         });
 
+        let fixed_content = {
+            let dispatch_context = Dock::dispatch_context();
+            if let Some(panel) = dock
+                .read(cx)
+                .visible_panel()
+                .filter(|panel| panel.has_panel_content(window, cx))
+            {
+                let size = panel.size(window, cx);
+
+                let panel_content = panel
+                    .to_any()
+                    .cached(StyleRefinement::default().v_flex().size_full());
+
+                div()
+                    .key_context(dispatch_context)
+                    .track_focus(&self.focus_handle(cx))
+                    .flex()
+                    .bg(cx.theme().colors().panel_background)
+                    .border_color(cx.theme().colors().border)
+                    .overflow_hidden()
+                    .map(|this| match position.axis() {
+                        Axis::Horizontal => this
+                            .h_full()
+                            .flex_row()
+                            .child(div().w(size).h_full().child(panel_content)),
+                        Axis::Vertical => this
+                            .w_full()
+                            .flex_col()
+                            .child(div().h(size).w_full().child(panel_content)),
+                    })
+                    .map(|this| match position {
+                        DockPosition::Left => this.border_r_1(),
+                        DockPosition::Right => this.border_l_1(),
+                        DockPosition::Bottom => this.border_t_1(),
+                    })
+                    .when(dock.read(cx).resizable(cx), |this| {
+                        this.child(dock::create_resize_handle(position, DockPart::Fixed, cx))
+                    })
+            } else {
+                div()
+                    .key_context(dispatch_context)
+                    .track_focus(&self.focus_handle(cx))
+            }
+        };
+
         results[0] = Some(
             div()
                 .flex()
                 .flex_none()
                 .overflow_hidden()
-                .child(dock.clone())
+                .child(fixed_content)
                 .children(leader_border)
                 .into_any_element(),
         );
@@ -7008,7 +7054,13 @@ impl Workspace {
                     .flex_1()
                     .min_w(px(10.))
                     .flex_grow()
+                    .when(position == DockPosition::Right, |this| {
+                        this.child(dock::create_resize_handle(position, DockPart::Flexible, cx))
+                    })
                     .child(flex_content)
+                    .when(position == DockPosition::Left, |this| {
+                        this.child(dock::create_resize_handle(position, DockPart::Flexible, cx))
+                    })
                     .into_any_element()
             });
 
@@ -7246,6 +7298,11 @@ impl Workspace {
     }
 }
 
+fn render_resize_handle() -> impl IntoElement {
+    // FIXME: actually render the handle and wire it up.
+    div().debug_bg_magenta()
+}
+
 pub trait AnyActiveCall {
     fn entity(&self) -> AnyEntity;
     fn is_in_room(&self, _: &App) -> bool;
@@ -7590,6 +7647,7 @@ impl Focusable for Workspace {
 #[derive(Clone)]
 struct DraggedDock {
     position: DockPosition,
+    part: DockPart,
 }
 
 impl Render for DraggedDock {
@@ -7723,39 +7781,74 @@ impl Render for Workspace {
                             .when(self.zoomed.is_none(), |this| {
                                 this.on_drag_move(cx.listener(
                                     move |workspace, e: &DragMoveEvent<DraggedDock>, window, cx| {
-                                        if workspace.previous_dock_drag_coordinates
-                                            != Some(e.event.position)
-                                        {
-                                            workspace.previous_dock_drag_coordinates =
-                                                Some(e.event.position);
-
-                                            match e.drag(cx).position {
-                                                DockPosition::Left => {
-                                                    workspace.resize_left_dock(
-                                                        e.event.position.x
-                                                            - workspace.bounds.left(),
-                                                        window,
-                                                        cx,
-                                                    );
+                                        let drag = e.drag(cx);
+                                        match drag.part {
+                                            DockPart::Fixed => {
+                                                if workspace.previous_dock_drag_coordinates
+                                                    != Some(e.event.position)
+                                                {
+                                                    workspace.previous_dock_drag_coordinates =
+                                                        Some(e.event.position);
+
+                                                    match e.drag(cx).position {
+                                                        DockPosition::Left => {
+                                                            workspace.resize_left_dock(
+                                                                e.event.position.x
+                                                                    - workspace.bounds.left(),
+                                                                window,
+                                                                cx,
+                                                            );
+                                                        }
+                                                        DockPosition::Right => {
+                                                            workspace.resize_right_dock(
+                                                                workspace.bounds.right()
+                                                                    - e.event.position.x,
+                                                                window,
+                                                                cx,
+                                                            );
+                                                        }
+                                                        DockPosition::Bottom => {
+                                                            workspace.resize_bottom_dock(
+                                                                workspace.bounds.bottom()
+                                                                    - e.event.position.y,
+                                                                window,
+                                                                cx,
+                                                            );
+                                                        }
+                                                    };
+                                                    workspace.serialize_workspace(window, cx);
                                                 }
-                                                DockPosition::Right => {
-                                                    workspace.resize_right_dock(
-                                                        workspace.bounds.right()
-                                                            - e.event.position.x,
-                                                        window,
-                                                        cx,
-                                                    );
-                                                }
-                                                DockPosition::Bottom => {
-                                                    workspace.resize_bottom_dock(
-                                                        workspace.bounds.bottom()
-                                                            - e.event.position.y,
-                                                        window,
-                                                        cx,
-                                                    );
+                                            }
+                                            DockPart::Flexible => {
+                                                match drag.position {
+                                                    DockPosition::Left => {
+                                                        let fixed_width = workspace
+                                                            .left_dock
+                                                            .read(cx)
+                                                            .active_panel()
+                                                            .map_or(px(0.0), |p| {
+                                                                p.size(window, cx)
+                                                            });
+                                                        let start_x =
+                                                            workspace.bounds.left() + fixed_width;
+                                                        let new_width =
+                                                            e.event.position.x - start_x;
+
+                                                        dbg!(&e.event.position);
+                                                        dbg!(&workspace.bounds);
+                                                        dbg!(&fixed_width);
+                                                        dbg!(&start_x);
+                                                        dbg!(new_width);
+                                                        //
+                                                    }
+                                                    DockPosition::Right => {
+                                                        //
+                                                    }
+                                                    DockPosition::Bottom => unreachable!(
+                                                        "bottom dock cannot have flex content"
+                                                    ),
                                                 }
-                                            };
-                                            workspace.serialize_workspace(window, cx);
+                                            }
                                         }
                                     },
                                 ))