From fd34d1da31c94becafb87fcc3eebe18b82f6c011 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 8 Dec 2023 16:29:42 -0800 Subject: [PATCH] Add back semi-funcitonal pane resizing code --- crates/editor2/src/element.rs | 16 +- crates/gpui2/src/elements/div.rs | 22 +- crates/gpui2/src/elements/text.rs | 2 +- crates/gpui2/src/geometry.rs | 31 +- crates/gpui2/src/interactive.rs | 6 + crates/gpui2/src/window.rs | 12 +- crates/ui2/src/components/right_click_menu.rs | 2 +- crates/workspace2/src/pane.rs | 5 +- crates/workspace2/src/pane_group.rs | 309 +++++++++++++----- crates/workspace2/src/workspace2.rs | 2 +- 10 files changed, 299 insertions(+), 108 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index ad66ed8090..7219e90665 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -389,9 +389,9 @@ impl EditorElement { let mut click_count = event.click_count; let modifiers = event.modifiers; - if gutter_bounds.contains_point(&event.position) { + if gutter_bounds.contains(&event.position) { click_count = 3; // Simulate triple-click when clicking the gutter to select lines - } else if !text_bounds.contains_point(&event.position) { + } else if !text_bounds.contains(&event.position) { return false; } if !cx.was_top_layer(&event.position, stacking_order) { @@ -437,7 +437,7 @@ impl EditorElement { text_bounds: Bounds, cx: &mut ViewContext, ) -> bool { - if !text_bounds.contains_point(&event.position) { + if !text_bounds.contains(&event.position) { return false; } let point_for_position = position_map.point_for_position(text_bounds, event.position); @@ -467,7 +467,7 @@ impl EditorElement { if !pending_nonempty_selections && event.modifiers.command - && text_bounds.contains_point(&event.position) + && text_bounds.contains(&event.position) && cx.was_top_layer(&event.position, stacking_order) { let point = position_map.point_for_position(text_bounds, event.position); @@ -529,8 +529,8 @@ impl EditorElement { ); } - let text_hovered = text_bounds.contains_point(&event.position); - let gutter_hovered = gutter_bounds.contains_point(&event.position); + let text_hovered = text_bounds.contains(&event.position); + let gutter_hovered = gutter_bounds.contains(&event.position); let was_top = cx.was_top_layer(&event.position, stacking_order); editor.set_gutter_hovered(gutter_hovered, cx); @@ -894,7 +894,7 @@ impl EditorElement { bounds: text_bounds, }), |cx| { - if text_bounds.contains_point(&cx.mouse_position()) { + if text_bounds.contains(&cx.mouse_position()) { if self .editor .read(cx) @@ -960,7 +960,7 @@ impl EditorElement { |fold_element_state, cx| { if fold_element_state.is_active() { gpui::blue() - } else if fold_bounds.contains_point(&cx.mouse_position()) { + } else if fold_bounds.contains(&cx.mouse_position()) { gpui::black() } else { gpui::red() diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 10fd7dda0a..479ef262d3 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -761,7 +761,7 @@ pub struct InteractiveBounds { impl InteractiveBounds { pub fn visibly_contains(&self, point: &Point, cx: &WindowContext) -> bool { - self.bounds.contains_point(point) && cx.was_top_layer(&point, &self.stacking_order) + self.bounds.contains(point) && cx.was_top_layer(&point, &self.stacking_order) } } @@ -860,10 +860,10 @@ impl Interactivity { .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); if let Some(group_bounds) = hover_group_bounds { - let hovered = group_bounds.contains_point(&cx.mouse_position()); + let hovered = group_bounds.contains(&cx.mouse_position()); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { if phase == DispatchPhase::Capture { - if group_bounds.contains_point(&event.position) != hovered { + if group_bounds.contains(&event.position) != hovered { cx.notify(); } } @@ -875,10 +875,10 @@ impl Interactivity { || cx.active_drag.is_some() && !self.drag_over_styles.is_empty() { let bounds = bounds.intersect(&cx.content_mask().bounds); - let hovered = bounds.contains_point(&cx.mouse_position()); + let hovered = bounds.contains(&cx.mouse_position()); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { if phase == DispatchPhase::Capture { - if bounds.contains_point(&event.position) != hovered { + if bounds.contains(&event.position) != hovered { cx.notify(); } } @@ -1067,8 +1067,8 @@ impl Interactivity { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| { if phase == DispatchPhase::Bubble { - let group = active_group_bounds - .map_or(false, |bounds| bounds.contains_point(&down.position)); + let group = + active_group_bounds.map_or(false, |bounds| bounds.contains(&down.position)); let element = interactive_bounds.visibly_contains(&down.position, cx); if group || element { *active_state.borrow_mut() = ElementClickedState { group, element }; @@ -1182,7 +1182,7 @@ impl Interactivity { let mouse_position = cx.mouse_position(); if let Some(group_hover) = self.group_hover_style.as_ref() { if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) { - if group_bounds.contains_point(&mouse_position) + if group_bounds.contains(&mouse_position) && cx.was_top_layer(&mouse_position, cx.stacking_order()) { style.refine(&group_hover.style); @@ -1192,7 +1192,7 @@ impl Interactivity { if self.hover_style.is_some() { if bounds .intersect(&cx.content_mask().bounds) - .contains_point(&mouse_position) + .contains(&mouse_position) && cx.was_top_layer(&mouse_position, cx.stacking_order()) { style.refine(&self.hover_style); @@ -1203,7 +1203,7 @@ impl Interactivity { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { if *state_type == drag.view.entity_type() - && group_bounds.contains_point(&mouse_position) + && group_bounds.contains(&mouse_position) { style.refine(&group_drag_style.style); } @@ -1214,7 +1214,7 @@ impl Interactivity { if *state_type == drag.view.entity_type() && bounds .intersect(&cx.content_mask().bounds) - .contains_point(&mouse_position) + .contains(&mouse_position) { style.refine(drag_over_style); } diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index d398b1f8fe..b8fe5e6866 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -253,7 +253,7 @@ impl TextState { } fn index_for_position(&self, bounds: Bounds, position: Point) -> Option { - if !bounds.contains_point(&position) { + if !bounds.contains(&position) { return None; } diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index 4a13fa83f6..ee2f42d2a2 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -23,6 +23,14 @@ impl Axis { } } +pub trait Along { + type Unit; + + fn along(&self, axis: Axis) -> Self::Unit; + + fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self; +} + impl sqlez::bindable::StaticColumnCount for Axis {} impl sqlez::bindable::Bind for Axis { fn bind( @@ -142,8 +150,19 @@ impl Point { y: f(self.y.clone()), } } +} - pub fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Point { +impl Along for Point { + type Unit = T; + + fn along(&self, axis: Axis) -> T { + match axis { + Axis::Horizontal => self.x.clone(), + Axis::Vertical => self.y.clone(), + } + } + + fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Point { match axis { Axis::Horizontal => Point { x: f(self.x.clone()), @@ -434,11 +453,13 @@ impl Size { } } -impl Size +impl Along for Size where T: Clone + Default + Debug, { - pub fn along(&self, axis: Axis) -> T { + type Unit = T; + + fn along(&self, axis: Axis) -> T { match axis { Axis::Horizontal => self.width.clone(), Axis::Vertical => self.height.clone(), @@ -446,7 +467,7 @@ where } /// Returns the value of this size along the given axis. - pub fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self { + fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self { match axis { Axis::Horizontal => Size { width: f(self.width.clone()), @@ -1079,7 +1100,7 @@ where /// assert!(bounds.contains_point(&inside_point)); /// assert!(!bounds.contains_point(&outside_point)); /// ``` - pub fn contains_point(&self, point: &Point) -> bool { + pub fn contains(&self, point: &Point) -> bool { point.x >= self.origin.x && point.x <= self.origin.x.clone() + self.size.width.clone() && point.y >= self.origin.y diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 84636630f3..3aff57ed34 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -131,6 +131,12 @@ pub struct MouseMoveEvent { pub modifiers: Modifiers, } +impl MouseMoveEvent { + pub fn dragging(&self) -> bool { + self.pressed_button == Some(MouseButton::Left) + } +} + #[derive(Clone, Debug)] pub struct ScrollWheelEvent { pub position: Point, diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index f98d9820c2..33d6a111c1 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -60,6 +60,16 @@ pub enum DispatchPhase { Capture, } +impl DispatchPhase { + pub fn bubble(self) -> bool { + self == DispatchPhase::Bubble + } + + pub fn capture(self) -> bool { + self == DispatchPhase::Capture + } +} + type AnyObserver = Box bool + 'static>; type AnyMouseListener = Box; type AnyFocusListener = Box; @@ -859,7 +869,7 @@ impl<'a> WindowContext<'a> { /// same layer as the given stacking order. pub fn was_top_layer(&self, point: &Point, level: &StackingOrder) -> bool { for (stack, bounds) in self.window.rendered_frame.depth_map.iter() { - if bounds.contains_point(point) { + if bounds.contains(point) { return level.starts_with(stack) || stack.starts_with(level); } } diff --git a/crates/ui2/src/components/right_click_menu.rs b/crates/ui2/src/components/right_click_menu.rs index 27c4fdab96..19031b2be7 100644 --- a/crates/ui2/src/components/right_click_menu.rs +++ b/crates/ui2/src/components/right_click_menu.rs @@ -137,7 +137,7 @@ impl Element for RightClickMenu { cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { if phase == DispatchPhase::Bubble && event.button == MouseButton::Right - && bounds.contains_point(&event.position) + && bounds.contains(&event.position) { cx.stop_propagation(); cx.prevent_default(); diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 3b1c00994f..f1e9649e10 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1040,10 +1040,11 @@ impl Pane { { pane.remove_item(item_ix, false, cx); } - })?; + }) + .ok(); } - pane.update(&mut cx, |_, cx| cx.notify())?; + pane.update(&mut cx, |_, cx| cx.notify()).ok(); Ok(()) }) } diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 398f57bf6f..4865a40a57 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::sync::Arc; use ui::{prelude::*, Button}; -const HANDLE_HITBOX_SIZE: f32 = 4.0; +const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4) const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; @@ -576,7 +576,7 @@ impl PaneAxis { for (idx, member) in self.members.iter().enumerate() { if let Some(coordinates) = bounding_boxes[idx] { - if coordinates.contains_point(&coordinate) { + if coordinates.contains(&coordinate) { return match member { Member::Pane(found) => Some(found), Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), @@ -598,73 +598,41 @@ impl PaneAxis { cx: &mut ViewContext, ) -> gpui::AnyElement { debug_assert!(self.members.len() == self.flexes.lock().len()); + let mut active_pane_ix = None; - pane_axis(self.axis, basis, self.flexes.clone()) - .children(self.members.iter().enumerate().map(|(ix, member)| { - match member { - Member::Axis(axis) => axis - .render( - project, - basis, - follower_states, - active_pane, - zoomed, - app_state, - cx, - ) - .into_any_element(), - Member::Pane(pane) => pane.clone().into_any_element(), - } - })) - .into_any_element() + pane_axis( + self.axis, + basis, + self.flexes.clone(), + self.bounding_boxes.clone(), + ) + .children(self.members.iter().enumerate().map(|(ix, member)| { + if member.contains(active_pane) { + active_pane_ix = Some(ix); + } - // let mut pane_axis = PaneAxisElement::new( - // self.axis, - // basis, - // self.flexes.clone(), - // self.bounding_boxes.clone(), - // ); - // let mut active_pane_ix = None; - - // let mut members = self.members.iter().enumerate().peekable(); - // while let Some((ix, member)) = members.next() { - // let last = members.peek().is_none(); - - // if member.contains(active_pane) { - // active_pane_ix = Some(ix); - // } - - // let mut member = member.render( - // project, - // (basis + ix) * 10, - // theme, - // follower_states, - // active_call, - // active_pane, - // zoomed, - // app_state, - // cx, - // ); - - // if !last { - // let mut border = theme.workspace.pane_divider; - // border.left = false; - // border.right = false; - // border.top = false; - // border.bottom = false; - - // match self.axis { - // Axis::Vertical => border.bottom = true, - // Axis::Horizontal => border.right = true, - // } - - // member = member.contained().with_border(border).into_any(); - // } - - // pane_axis = pane_axis.with_child(member.into_any()); - // } - // pane_axis.set_active_pane(active_pane_ix); - // pane_axis.into_any() + match member { + Member::Axis(axis) => axis + .render( + project, + (basis + ix) * 10, + follower_states, + active_pane, + zoomed, + app_state, + cx, + ) + .into_any_element(), + Member::Pane(pane) => div() + .size_full() + .border() + .border_color(gpui::green()) + .child(pane.clone()) + .into_any_element(), + } + })) + .with_active_pane(active_pane_ix) + .into_any_element() } } @@ -727,18 +695,31 @@ impl SplitDirection { } mod element { - use std::sync::Arc; - use gpui::{relative, AnyElement, Axis, Element, IntoElement, ParentElement, Style}; + use std::{iter, sync::Arc}; + + use gpui::{ + px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement, + MouseMoveEvent, ParentElement, Pixels, Style, WindowContext, + }; use parking_lot::Mutex; use smallvec::SmallVec; - pub fn pane_axis(axis: Axis, basis: usize, flexes: Arc>>) -> PaneAxisElement { + use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}; + + pub fn pane_axis( + axis: Axis, + basis: usize, + flexes: Arc>>, + bounding_boxes: Arc>>>>, + ) -> PaneAxisElement { PaneAxisElement { axis, basis, flexes, + bounding_boxes, children: SmallVec::new(), + active_pane_ix: None, } } @@ -746,7 +727,145 @@ mod element { axis: Axis, basis: usize, flexes: Arc>>, + bounding_boxes: Arc>>>>, children: SmallVec<[AnyElement; 2]>, + active_pane_ix: Option, + } + + impl PaneAxisElement { + pub fn with_active_pane(mut self, active_pane_ix: Option) -> Self { + self.active_pane_ix = active_pane_ix; + self + } + + fn compute_resize( + flexes: &Arc>>, + e: &MouseMoveEvent, + ix: usize, + axis: Axis, + axis_bounds: Bounds, + cx: &mut WindowContext, + ) { + let min_size = match axis { + Axis::Horizontal => px(HORIZONTAL_MIN_SIZE), + Axis::Vertical => px(VERTICAL_MIN_SIZE), + }; + let mut flexes = flexes.lock(); + debug_assert!(flex_values_in_bounds(flexes.as_slice())); + + let size = move |ix, flexes: &[f32]| { + axis_bounds.size.along(axis) * (flexes[ix] / flexes.len() as f32) + }; + + // Don't allow resizing to less than the minimum size, if elements are already too small + if min_size - px(1.) > size(ix, flexes.as_slice()) { + return; + } + + let mut proposed_current_pixel_change = + (e.position - axis_bounds.origin).along(axis) - size(ix, flexes.as_slice()); + + let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { + let flex_change = pixel_dx / axis_bounds.size.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 = iter::from_fn({ + let forward = proposed_current_pixel_change > px(0.); + let mut ix_offset = 0; + let len = flexes.len(); + move || { + let result = if forward { + (ix + 1 + ix_offset < len).then(|| ix + ix_offset) + } else { + (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset) + }; + + ix_offset += 1; + + result + } + }); + + while dbg!(proposed_current_pixel_change).abs() > px(0.) { + let Some(current_ix) = successors.next() else { + break; + }; + + dbg!(current_ix); + + let next_target_size = Pixels::max( + size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, + min_size, + ); + + let current_target_size = Pixels::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; + } + + // todo!(reserialize workspace) + // workspace.schedule_serialize(cx); + cx.notify(); + } + + fn push_handle( + flexes: Arc>>, + axis: Axis, + ix: usize, + pane_bounds: Bounds, + axis_bounds: Bounds, + cx: &mut WindowContext, + ) { + let handle_bounds = Bounds { + origin: pane_bounds.origin.apply_along(axis, |o| { + o + pane_bounds.size.along(axis) - Pixels(HANDLE_HITBOX_SIZE / 2.) + }), + size: pane_bounds + .size + .apply_along(axis, |_| Pixels(HANDLE_HITBOX_SIZE)), + }; + + cx.with_z_index(3, |cx| { + if handle_bounds.contains(&cx.mouse_position()) { + cx.set_cursor_style(match axis { + Axis::Vertical => CursorStyle::ResizeUpDown, + Axis::Horizontal => CursorStyle::ResizeLeftRight, + }) + } + + cx.add_opaque_layer(handle_bounds); + + cx.paint_quad( + handle_bounds, + Default::default(), + gpui::red(), + Default::default(), + gpui::red(), + ); + + cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| { + if phase.bubble() && e.dragging() && handle_bounds.contains(&e.position) { + Self::compute_resize(&flexes, e, ix, axis, axis_bounds, cx) + } + }); + }); + } } impl IntoElement for PaneAxisElement { @@ -784,16 +903,47 @@ mod element { cx: &mut ui::prelude::WindowContext, ) { let flexes = self.flexes.lock().clone(); - debug_assert!(flexes.len() == self.children.len()); - - let origin = bounds.origin; - let size = bounds.size; let len = self.children.len(); - let child_size = size.apply_along(self.axis, |val| val / len as f32); + debug_assert!(flexes.len() == len); + debug_assert!(flex_values_in_bounds(flexes.as_slice())); + + let mut origin = bounds.origin; + let space_per_flex = bounds.size.along(self.axis) / len as f32; + + let mut bounding_boxes = self.bounding_boxes.lock(); + bounding_boxes.clear(); + for (ix, child) in self.children.into_iter().enumerate() { - let origin = - origin.apply_along(self.axis, |val| val + child_size.along(self.axis) * ix); - child.draw(origin, child_size.into(), cx); + //todo!(active_pane_magnification) + // If usign active pane magnification, need to switch to using + // 1 for all non-active panes, and then the magnification for the + // active pane. + let child_size = bounds + .size + .apply_along(self.axis, |_| space_per_flex * flexes[ix]); + + let child_bounds = Bounds { + origin, + size: child_size, + }; + bounding_boxes.push(Some(child_bounds)); + cx.with_z_index(0, |cx| { + child.draw(origin, child_size.into(), cx); + }); + cx.with_z_index(1, |cx| { + if ix < len - 1 { + Self::push_handle( + self.flexes.clone(), + self.axis, + ix, + child_bounds, + bounds, + cx, + ); + } + }); + + origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis)); } } } @@ -804,6 +954,9 @@ mod element { } } + fn flex_values_in_bounds(flexes: &[f32]) -> bool { + (flexes.iter().copied().sum::() - flexes.len() as f32).abs() < 0.001 + } // // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; // // use gpui::{ diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 251f0685b0..72c9fb8aa2 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -2015,7 +2015,7 @@ impl Workspace { }; let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx); let center = match cursor { - Some(cursor) if bounding_box.contains_point(&cursor) => cursor, + Some(cursor) if bounding_box.contains(&cursor) => cursor, _ => bounding_box.center(), };