diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 1dc88d2e71..e4beb58873 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -518,6 +518,18 @@ impl<'a> WindowContext<'a> { // NOTE: The order of event pushes is important! MouseUp events MUST be fired // before click events, and so the MouseUp events need to be pushed before // MouseClick events. + + // Synthesize one last drag event to end the drag + mouse_events.push(MouseEvent::Drag(MouseDrag { + region: Default::default(), + prev_mouse_position: self.window.mouse_position, + platform_event: MouseMovedEvent { + position: e.position, + pressed_button: Some(e.button), + modifiers: e.modifiers, + }, + end: true, + })); mouse_events.push(MouseEvent::Up(MouseUp { region: Default::default(), platform_event: e.clone(), @@ -565,8 +577,16 @@ impl<'a> WindowContext<'a> { region: Default::default(), prev_mouse_position: self.window.mouse_position, platform_event: e.clone(), + end: false, })); } else if let Some((_, clicked_button)) = self.window.clicked_region { + mouse_events.push(MouseEvent::Drag(MouseDrag { + region: Default::default(), + prev_mouse_position: self.window.mouse_position, + platform_event: e.clone(), + end: true, + })); + // Mouse up event happened outside the current window. Simulate mouse up button event let button_event = e.to_button_event(clicked_button); mouse_events.push(MouseEvent::Up(MouseUp { diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index a492da771b..89bf874583 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -32,6 +32,7 @@ pub struct MouseDrag { pub region: RectF, pub prev_mouse_position: Vector2F, pub platform_event: MouseMovedEvent, + pub end: bool, } impl Deref for MouseDrag { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index e60f6deb2f..3d7032c148 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -584,7 +584,7 @@ impl SplitDirection { } mod element { - use std::{cell::RefCell, ops::Range, rc::Rc}; + use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; use gpui::{ geometry::{ @@ -593,8 +593,9 @@ mod element { }, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt, - SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, + scene::MouseDrag, + AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion, + RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, }; use crate::{ @@ -682,6 +683,96 @@ mod element { *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); } } + + fn handle_resize( + flexes: Rc>>, + axis: Axis, + preceding_ix: usize, + child_start: Vector2F, + drag_bounds: RectF, + ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { + let size = move |ix, flexes: &[f32]| { + drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) + }; + + move |drag, workspace: &mut Workspace, cx| { + if drag.end { + // TODO: Clear cascading resize state + return; + } + let min_size = match axis { + Axis::Horizontal => HORIZONTAL_MIN_SIZE, + Axis::Vertical => VERTICAL_MIN_SIZE, + }; + let mut flexes = flexes.borrow_mut(); + + // Don't allow resizing to less than the minimum size, if elements are already too small + if min_size - 1. > size(preceding_ix, flexes.as_slice()) { + return; + } + + let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) + - size(preceding_ix, flexes.as_slice()); + + let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { + let flex_change = pixel_dx / drag_bounds.length_along(axis); + let current_target_flex = flexes[target_ix] + flex_change; + let next_target_flex = + flexes[(target_ix as isize + next) as usize] - flex_change; + (current_target_flex, next_target_flex) + }; + + let mut successors = from_fn({ + let forward = proposed_current_pixel_change > 0.; + let mut ix_offset = 0; + let len = flexes.len(); + move || { + let result = if forward { + (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset) + } else { + (preceding_ix as isize - ix_offset as isize >= 0) + .then(|| preceding_ix - ix_offset) + }; + + ix_offset += 1; + + result + } + }); + + while proposed_current_pixel_change.abs() > 0. { + let Some(current_ix) = successors.next() else { + break; + }; + + let next_target_size = f32::max( + size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, + min_size, + ); + + let current_target_size = f32::max( + size(current_ix, flexes.as_slice()) + + size(current_ix + 1, flexes.as_slice()) + - next_target_size, + min_size, + ); + + let current_pixel_change = + current_target_size - size(current_ix, flexes.as_slice()); + + let (current_target_flex, next_target_flex) = + flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); + + flexes[current_ix] = current_target_flex; + flexes[current_ix + 1] = next_target_flex; + + proposed_current_pixel_change -= current_pixel_change; + } + + workspace.schedule_serialize(cx); + cx.notify(); + } + } } impl Extend> for PaneAxisElement { @@ -792,8 +883,7 @@ mod element { Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), } - if let Some(Some((next_ix, next_child))) = can_resize.then(|| children_iter.peek()) - { + if can_resize && children_iter.peek().is_some() { scene.push_stacking_context(None, None); let handle_origin = match self.axis { @@ -822,15 +912,6 @@ mod element { style, }); - let axis = self.axis; - let child_size = child.size(); - let next_child_size = next_child.size(); - let drag_bounds = visible_bounds.clone(); - let flexes = self.flexes.borrow(); - let current_flex = flexes[ix]; - let next_ix = *next_ix; - let next_flex = flexes[next_ix]; - drop(flexes); enum ResizeHandle {} let mut mouse_region = MouseRegion::new::( cx.view_id(), @@ -838,56 +919,16 @@ mod element { handle_bounds, ); mouse_region = mouse_region - .on_drag(MouseButton::Left, { - let flexes = self.flexes.clone(); - move |drag, workspace: &mut Workspace, cx| { - let min_size = match axis { - Axis::Horizontal => HORIZONTAL_MIN_SIZE, - Axis::Vertical => VERTICAL_MIN_SIZE, - }; - // Don't allow resizing to less than the minimum size, if elements are already too small - if min_size - 1. > child_size.along(axis) - || min_size - 1. > next_child_size.along(axis) - { - return; - } - - let mut current_target_size = - (drag.position - child_start).along(axis); - - let proposed_current_pixel_change = - current_target_size - child_size.along(axis); - - if proposed_current_pixel_change < 0. { - current_target_size = f32::max(current_target_size, min_size); - } else if proposed_current_pixel_change > 0. { - // TODO: cascade this change to other children if current item is at min size - let next_target_size = f32::max( - next_child_size.along(axis) - proposed_current_pixel_change, - min_size, - ); - current_target_size = f32::min( - current_target_size, - child_size.along(axis) + next_child_size.along(axis) - - next_target_size, - ); - } - - let current_pixel_change = - current_target_size - child_size.along(axis); - let flex_change = - current_pixel_change / drag_bounds.length_along(axis); - let current_target_flex = current_flex + flex_change; - let next_target_flex = next_flex - flex_change; - - let mut borrow = flexes.borrow_mut(); - *borrow.get_mut(ix).unwrap() = current_target_flex; - *borrow.get_mut(next_ix).unwrap() = next_target_flex; - - workspace.schedule_serialize(cx); - cx.notify(); - } - }) + .on_drag( + MouseButton::Left, + Self::handle_resize( + self.flexes.clone(), + self.axis, + ix, + child_start, + visible_bounds.clone(), + ), + ) .on_click(MouseButton::Left, { let flexes = self.flexes.clone(); move |e, v: &mut Workspace, cx| {