Simple cascading split (#2790)

This PR cascades the split resizing to adjacent splits, if the current
split has already hit the minimum size. This PR also adds support for
detecting the end of a drag event to GPUI, via a bool on the dispatched
drag.

Release Notes:

- Made split resizing more flexible
This commit is contained in:
Mikayla Maki 2023-07-26 09:49:27 -07:00 committed by GitHub
commit 711073cf3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 64 deletions

View file

@ -518,6 +518,18 @@ impl<'a> WindowContext<'a> {
// NOTE: The order of event pushes is important! MouseUp events MUST be fired // 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 // before click events, and so the MouseUp events need to be pushed before
// MouseClick events. // 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 { mouse_events.push(MouseEvent::Up(MouseUp {
region: Default::default(), region: Default::default(),
platform_event: e.clone(), platform_event: e.clone(),
@ -565,8 +577,16 @@ impl<'a> WindowContext<'a> {
region: Default::default(), region: Default::default(),
prev_mouse_position: self.window.mouse_position, prev_mouse_position: self.window.mouse_position,
platform_event: e.clone(), platform_event: e.clone(),
end: false,
})); }));
} else if let Some((_, clicked_button)) = self.window.clicked_region { } 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 // Mouse up event happened outside the current window. Simulate mouse up button event
let button_event = e.to_button_event(clicked_button); let button_event = e.to_button_event(clicked_button);
mouse_events.push(MouseEvent::Up(MouseUp { mouse_events.push(MouseEvent::Up(MouseUp {

View file

@ -32,6 +32,7 @@ pub struct MouseDrag {
pub region: RectF, pub region: RectF,
pub prev_mouse_position: Vector2F, pub prev_mouse_position: Vector2F,
pub platform_event: MouseMovedEvent, pub platform_event: MouseMovedEvent,
pub end: bool,
} }
impl Deref for MouseDrag { impl Deref for MouseDrag {

View file

@ -584,7 +584,7 @@ impl SplitDirection {
} }
mod element { mod element {
use std::{cell::RefCell, ops::Range, rc::Rc}; use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
use gpui::{ use gpui::{
geometry::{ geometry::{
@ -593,8 +593,9 @@ mod element {
}, },
json::{self, ToJson}, json::{self, ToJson},
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt, scene::MouseDrag,
SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
}; };
use crate::{ use crate::{
@ -682,6 +683,96 @@ mod element {
*cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
} }
} }
fn handle_resize(
flexes: Rc<RefCell<Vec<f32>>>,
axis: Axis,
preceding_ix: usize,
child_start: Vector2F,
drag_bounds: RectF,
) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
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<AnyElement<Workspace>> for PaneAxisElement { impl Extend<AnyElement<Workspace>> for PaneAxisElement {
@ -792,8 +883,7 @@ mod element {
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), 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); scene.push_stacking_context(None, None);
let handle_origin = match self.axis { let handle_origin = match self.axis {
@ -822,15 +912,6 @@ mod element {
style, 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 {} enum ResizeHandle {}
let mut mouse_region = MouseRegion::new::<ResizeHandle>( let mut mouse_region = MouseRegion::new::<ResizeHandle>(
cx.view_id(), cx.view_id(),
@ -838,56 +919,16 @@ mod element {
handle_bounds, handle_bounds,
); );
mouse_region = mouse_region mouse_region = mouse_region
.on_drag(MouseButton::Left, { .on_drag(
let flexes = self.flexes.clone(); MouseButton::Left,
move |drag, workspace: &mut Workspace, cx| { Self::handle_resize(
let min_size = match axis { self.flexes.clone(),
Axis::Horizontal => HORIZONTAL_MIN_SIZE, self.axis,
Axis::Vertical => VERTICAL_MIN_SIZE, ix,
}; child_start,
// Don't allow resizing to less than the minimum size, if elements are already too small visible_bounds.clone(),
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_click(MouseButton::Left, { .on_click(MouseButton::Left, {
let flexes = self.flexes.clone(); let flexes = self.flexes.clone();
move |e, v: &mut Workspace, cx| { move |e, v: &mut Workspace, cx| {