diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index d881e8ff4c..1d19c0d02c 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -391,9 +391,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; } if !cx.was_top_layer(&event.position, stacking_order) { @@ -439,7 +439,7 @@ impl EditorElement { text_bounds: Bounds, cx: &mut ViewContext, ) { - if !text_bounds.contains_point(&event.position) { + if !text_bounds.contains(&event.position) { return; } let point_for_position = position_map.point_for_position(text_bounds, event.position); @@ -469,7 +469,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); @@ -531,8 +531,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); @@ -900,7 +900,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) @@ -966,7 +966,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() @@ -1377,7 +1377,7 @@ impl EditorElement { } let mouse_position = cx.mouse_position(); - if track_bounds.contains_point(&mouse_position) { + if track_bounds.contains(&mouse_position) { cx.set_cursor_style(CursorStyle::Arrow); } @@ -1405,7 +1405,7 @@ impl EditorElement { cx.stop_propagation(); } else { editor.scroll_manager.set_is_dragging_scrollbar(false, cx); - if track_bounds.contains_point(&event.position) { + if track_bounds.contains(&event.position) { editor.scroll_manager.show_scrollbar(cx); } } @@ -1428,7 +1428,7 @@ impl EditorElement { let editor = self.editor.clone(); move |event: &MouseDownEvent, phase, cx| { editor.update(cx, |editor, cx| { - if track_bounds.contains_point(&event.position) { + if track_bounds.contains(&event.position) { editor.scroll_manager.set_is_dragging_scrollbar(true, cx); let y = event.position.y; @@ -2502,10 +2502,10 @@ impl EditorElement { gutter_bounds, &stacking_order, cx, - ) + ); }), MouseButton::Right => editor.update(cx, |editor, cx| { - Self::mouse_right_down(editor, event, &position_map, text_bounds, cx) + Self::mouse_right_down(editor, event, &position_map, text_bounds, cx); }), _ => {} }; diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 8d2ac9c83d..c926ab3670 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -513,6 +513,10 @@ impl AppContext { self.platform.path_for_auxiliary_executable(name) } + pub fn double_click_interval(&self) -> Duration { + self.platform.double_click_interval() + } + pub fn prompt_for_paths( &self, options: PathPromptOptions, diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index c27ac52cde..25566e7483 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(); } } @@ -1068,8 +1068,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 }; @@ -1183,7 +1183,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); @@ -1193,7 +1193,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); @@ -1204,7 +1204,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); } @@ -1215,7 +1215,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/executor.rs b/crates/gpui2/src/executor.rs index e01846c404..81fa6e64ca 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -57,8 +57,12 @@ where T: 'static, E: 'static + Debug, { + #[track_caller] pub fn detach_and_log_err(self, cx: &mut AppContext) { - cx.foreground_executor().spawn(self.log_err()).detach(); + let location = core::panic::Location::caller(); + cx.foreground_executor() + .spawn(self.log_tracked_err(*location)) + .detach(); } } diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index 50f680f493..ee2f42d2a2 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -8,6 +8,62 @@ use std::{ ops::{Add, Div, Mul, MulAssign, Sub}, }; +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Axis { + Vertical, + Horizontal, +} + +impl Axis { + pub fn invert(&self) -> Self { + match self { + Axis::Vertical => Axis::Horizontal, + Axis::Horizontal => Axis::Vertical, + } + } +} + +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( + &self, + statement: &sqlez::statement::Statement, + start_index: i32, + ) -> anyhow::Result { + match self { + Axis::Horizontal => "Horizontal", + Axis::Vertical => "Vertical", + } + .bind(statement, start_index) + } +} + +impl sqlez::bindable::Column for Axis { + fn column( + statement: &mut sqlez::statement::Statement, + start_index: i32, + ) -> anyhow::Result<(Self, i32)> { + String::column(statement, start_index).and_then(|(axis_text, next_index)| { + Ok(( + match axis_text.as_str() { + "Horizontal" => Axis::Horizontal, + "Vertical" => Axis::Vertical, + _ => anyhow::bail!("Stored serialized item kind is incorrect"), + }, + next_index, + )) + }) + } +} + /// Describes a location in a 2D cartesian coordinate space. /// /// It holds two public fields, `x` and `y`, which represent the coordinates in the space. @@ -96,6 +152,30 @@ impl 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()), + y: self.y.clone(), + }, + Axis::Vertical => Point { + x: self.x.clone(), + y: f(self.y.clone()), + }, + } + } +} + impl Point { /// Scales the point by a given factor, which is typically derived from the resolution /// of a target display to ensure proper sizing of UI elements. @@ -373,6 +453,34 @@ impl Size { } } +impl Along for Size +where + T: Clone + Default + Debug, +{ + type Unit = T; + + fn along(&self, axis: Axis) -> T { + match axis { + Axis::Horizontal => self.width.clone(), + Axis::Vertical => self.height.clone(), + } + } + + /// Returns the value of this size along the given axis. + fn apply_along(&self, axis: Axis, f: impl FnOnce(T) -> T) -> Self { + match axis { + Axis::Horizontal => Size { + width: f(self.width.clone()), + height: self.height.clone(), + }, + Axis::Vertical => Size { + width: self.width.clone(), + height: f(self.height.clone()), + }, + } + } +} + impl Size where T: PartialOrd + Clone + Default + Debug, @@ -992,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 fee4403a02..1c6955111c 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/platform.rs b/crates/gpui2/src/platform.rs index 4c14efd632..b9d5a9f222 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -105,6 +105,7 @@ pub(crate) trait Platform: 'static { fn app_version(&self) -> Result; fn app_path(&self) -> Result; fn local_timezone(&self) -> UtcOffset; + fn double_click_interval(&self) -> Duration; fn path_for_auxiliary_executable(&self, name: &str) -> Result; fn set_cursor_style(&self, style: CursorStyle); diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 8b83ee5d1f..f3c3b8ea5d 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -49,6 +49,7 @@ use std::{ rc::Rc, slice, str, sync::Arc, + time::Duration, }; use time::UtcOffset; @@ -657,6 +658,13 @@ impl Platform for MacPlatform { "macOS" } + fn double_click_interval(&self) -> Duration { + unsafe { + let double_click_interval: f64 = msg_send![class!(NSEvent), doubleClickInterval]; + Duration::from_secs_f64(double_click_interval) + } + } + fn os_version(&self) -> Result { unsafe { let process_info = NSProcessInfo::processInfo(nil); diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 8997eb4902..dc1136c0f5 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -12,6 +12,7 @@ use std::{ path::PathBuf, rc::{Rc, Weak}, sync::Arc, + time::Duration, }; pub struct TestPlatform { @@ -274,4 +275,8 @@ impl Platform for TestPlatform { fn delete_credentials(&self, _url: &str) -> Result<()> { Ok(()) } + + fn double_click_interval(&self) -> std::time::Duration { + Duration::from_millis(500) + } } diff --git a/crates/gpui2/src/taffy.rs b/crates/gpui2/src/taffy.rs index 2bceb1bc13..b4fc6c3abe 100644 --- a/crates/gpui2/src/taffy.rs +++ b/crates/gpui2/src/taffy.rs @@ -477,3 +477,12 @@ impl From for AvailableSpace { AvailableSpace::Definite(pixels) } } + +impl From> for Size { + fn from(size: Size) -> Self { + Size { + width: AvailableSpace::Definite(size.width), + height: AvailableSpace::Definite(size.height), + } + } +} diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index c86f232544..ce860b1b65 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; @@ -866,7 +876,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/util/src/util.rs b/crates/util/src/util.rs index aacec422fe..3f2371121c 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -184,6 +184,11 @@ pub trait TryFutureExt { fn log_err(self) -> LogErrorFuture where Self: Sized; + + fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture + where + Self: Sized; + fn warn_on_err(self) -> LogErrorFuture where Self: Sized; @@ -197,18 +202,29 @@ where F: Future>, E: std::fmt::Debug, { + #[track_caller] fn log_err(self) -> LogErrorFuture where Self: Sized, { - LogErrorFuture(self, log::Level::Error) + let location = Location::caller(); + LogErrorFuture(self, log::Level::Error, *location) } + fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture + where + Self: Sized, + { + LogErrorFuture(self, log::Level::Error, location) + } + + #[track_caller] fn warn_on_err(self) -> LogErrorFuture where Self: Sized, { - LogErrorFuture(self, log::Level::Warn) + let location = Location::caller(); + LogErrorFuture(self, log::Level::Warn, *location) } fn unwrap(self) -> UnwrapFuture @@ -219,7 +235,7 @@ where } } -pub struct LogErrorFuture(F, log::Level); +pub struct LogErrorFuture(F, log::Level, core::panic::Location<'static>); impl Future for LogErrorFuture where @@ -230,12 +246,19 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let level = self.1; + let location = self.2; let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) }; match inner.poll(cx) { Poll::Ready(output) => Poll::Ready(match output { Ok(output) => Some(output), Err(error) => { - log::log!(level, "{:?}", error); + log::log!( + level, + "{}:{}: {:?}", + location.file(), + location.line(), + error + ); None } }), diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 5a7097b86f..e8840fb172 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,8 +1,9 @@ -use crate::{status_bar::StatusItemView, Axis, Workspace}; +use crate::{status_bar::StatusItemView, Workspace}; +use crate::{DockClickReset, DockDragState}; use gpui::{ - div, px, Action, AnchorCorner, AnyView, AppContext, Div, Entity, EntityId, EventEmitter, - FocusHandle, FocusableView, IntoElement, ParentElement, Render, SharedString, Styled, - Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, + div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Div, Entity, EntityId, + EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render, + SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -364,7 +365,7 @@ impl Dock { this.set_open(false, cx); } } - PanelEvent::Focus => todo!(), + PanelEvent::Focus => {} }), ]; @@ -485,6 +486,48 @@ impl Render for Dock { if let Some(entry) = self.visible_entry() { let size = entry.panel.size(cx); + let mut pre_resize_handle = None; + let mut post_resize_handle = None; + let position = self.position; + let handler = div() + .id("resize-handle") + .bg(cx.theme().colors().border) + .on_mouse_down(gpui::MouseButton::Left, move |_, cx| { + cx.update_global(|drag: &mut DockDragState, cx| drag.0 = Some(position)) + }) + .on_click(cx.listener(|v, e: &ClickEvent, cx| { + if e.down.button == MouseButton::Left { + cx.update_global(|state: &mut DockClickReset, cx| { + if state.0.is_some() { + state.0 = None; + v.resize_active_panel(None, cx) + } else { + let double_click = cx.double_click_interval(); + let timer = cx.background_executor().timer(double_click); + state.0 = Some(cx.spawn(|_, mut cx| async move { + timer.await; + cx.update_global(|state: &mut DockClickReset, cx| { + state.0 = None; + }) + .ok(); + })); + } + }) + } + })); + + match self.position() { + DockPosition::Left => { + post_resize_handle = Some(handler.w_1().h_full().cursor_col_resize()) + } + DockPosition::Bottom => { + pre_resize_handle = Some(handler.w_full().h_1().cursor_row_resize()) + } + DockPosition::Right => { + pre_resize_handle = Some(handler.w_full().h_1().cursor_col_resize()) + } + } + div() .border_color(cx.theme().colors().border) .map(|this| match self.position().axis() { @@ -496,7 +539,9 @@ impl Render for Dock { DockPosition::Right => this.border_l(), DockPosition::Bottom => this.border_t(), }) + .children(pre_resize_handle) .child(entry.panel.to_any()) + .children(post_resize_handle) } else { div() } diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index f17f90d8f4..5c5801960e 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -1046,10 +1046,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 66465a4982..5f14df833d 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,13 +1,9 @@ -use crate::{AppState, FollowerState, Pane, Workspace}; -use anyhow::{anyhow, bail, Result}; +use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace}; +use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use collections::HashMap; -use db::sqlez::{ - bindable::{Bind, Column, StaticColumnCount}, - statement::Statement, -}; use gpui::{ - point, size, AnyWeakView, Bounds, Div, Entity as _, IntoElement, Model, Pixels, Point, View, + point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View, ViewContext, }; use parking_lot::Mutex; @@ -16,42 +12,10 @@ 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.; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Axis { - Vertical, - Horizontal, -} - -impl StaticColumnCount for Axis {} -impl Bind for Axis { - fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { - match self { - Axis::Horizontal => "Horizontal", - Axis::Vertical => "Vertical", - } - .bind(statement, start_index) - } -} - -impl Column for Axis { - fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { - String::column(statement, start_index).and_then(|(axis_text, next_index)| { - Ok(( - match axis_text.as_str() { - "Horizontal" => Axis::Horizontal, - "Vertical" => Axis::Vertical, - _ => bail!("Stored serialized item kind is incorrect"), - }, - next_index, - )) - }) - } -} - #[derive(Clone, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, @@ -612,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), @@ -632,80 +596,42 @@ impl PaneAxis { zoomed: Option<&AnyWeakView>, app_state: &Arc, cx: &mut ViewContext, - ) -> Div { + ) -> gpui::AnyElement { debug_assert!(self.members.len() == self.flexes.lock().len()); + let mut active_pane_ix = None; - div() - .flex() - .flex_auto() - .map(|s| match self.axis { - Axis::Vertical => s.flex_col(), - Axis::Horizontal => s.flex_row(), - }) - .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(), - } - })) + 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() + .child(pane.clone()) + .into_any_element(), + } + })) + .with_active_pane(active_pane_ix) + .into_any_element() } } @@ -767,403 +693,677 @@ impl SplitDirection { } } -// mod element { -// // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; +mod element { -// // use gpui::{ -// // geometry::{ -// // rect::Bounds, -// // vector::{vec2f, Vector2F}, -// // }, -// // json::{self, ToJson}, -// // platform::{CursorStyle, MouseButton}, -// // scene::MouseDrag, -// // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, BoundsExt, -// // SizeConstraint, Vector2FExt, ViewContext, -// // }; + use std::{cell::RefCell, iter, rc::Rc, sync::Arc}; -// use crate::{ -// pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, -// Workspace, WorkspaceSettings, -// }; + use gpui::{ + px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Style, WindowContext, + }; + use parking_lot::Mutex; + use smallvec::SmallVec; -// pub struct PaneAxisElement { -// axis: Axis, -// basis: usize, -// active_pane_ix: Option, -// flexes: Rc>>, -// children: Vec>, -// bounding_boxes: Rc>>>>, -// } + use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}; -// impl PaneAxisElement { -// pub fn new( -// axis: Axis, -// basis: usize, -// flexes: Rc>>, -// bounding_boxes: Rc>>>>, -// ) -> Self { -// Self { -// axis, -// basis, -// flexes, -// bounding_boxes, -// active_pane_ix: None, -// children: Default::default(), -// } -// } + 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, + } + } -// pub fn set_active_pane(&mut self, active_pane_ix: Option) { -// self.active_pane_ix = active_pane_ix; -// } + pub struct PaneAxisElement { + axis: Axis, + basis: usize, + flexes: Arc>>, + bounding_boxes: Arc>>>>, + children: SmallVec<[AnyElement; 2]>, + active_pane_ix: Option, + } -// fn layout_children( -// &mut self, -// active_pane_magnification: f32, -// constraint: SizeConstraint, -// remaining_space: &mut f32, -// remaining_flex: &mut f32, -// cross_axis_max: &mut f32, -// view: &mut Workspace, -// cx: &mut ViewContext, -// ) { -// let flexes = self.flexes.borrow(); -// let cross_axis = self.axis.invert(); -// for (ix, child) in self.children.iter_mut().enumerate() { -// let flex = if active_pane_magnification != 1. { -// if let Some(active_pane_ix) = self.active_pane_ix { -// if ix == active_pane_ix { -// active_pane_magnification -// } else { -// 1. -// } -// } else { -// 1. -// } -// } else { -// flexes[ix] -// }; + impl PaneAxisElement { + pub fn with_active_pane(mut self, active_pane_ix: Option) -> Self { + self.active_pane_ix = active_pane_ix; + self + } -// let child_size = if *remaining_flex == 0.0 { -// *remaining_space -// } else { -// let space_per_flex = *remaining_space / *remaining_flex; -// space_per_flex * flex -// }; + 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 child_constraint = match self.axis { -// Axis::Horizontal => SizeConstraint::new( -// vec2f(child_size, constraint.min.y()), -// vec2f(child_size, constraint.max.y()), -// ), -// Axis::Vertical => SizeConstraint::new( -// vec2f(constraint.min.x(), child_size), -// vec2f(constraint.max.x(), child_size), -// ), -// }; -// let child_size = child.layout(child_constraint, view, cx); -// *remaining_space -= child_size.along(self.axis); -// *remaining_flex -= flex; -// *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); -// } -// } + let size = move |ix, flexes: &[f32]| { + axis_bounds.size.along(axis) * (flexes[ix] / flexes.len() as f32) + }; -// fn handle_resize( -// flexes: Rc>>, -// axis: Axis, -// preceding_ix: usize, -// child_start: Vector2F, -// drag_bounds: Bounds, -// ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { -// let size = move |ix, flexes: &[f32]| { -// drag_bounds.length_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; + } -// 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(); + let mut proposed_current_pixel_change = + (e.position - axis_bounds.origin).along(axis) - size(ix, flexes.as_slice()); -// // 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 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 proposed_current_pixel_change = (drag.position - child_start).along(axis) -// - size(preceding_ix, flexes.as_slice()); + 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) + }; -// 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) -// }; + ix_offset += 1; -// 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) -// }; + result + } + }); -// ix_offset += 1; + while proposed_current_pixel_change.abs() > px(0.) { + let Some(current_ix) = successors.next() else { + break; + }; -// result -// } -// }); + let next_target_size = Pixels::max( + size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, + min_size, + ); -// while proposed_current_pixel_change.abs() > 0. { -// let Some(current_ix) = successors.next() else { -// break; -// }; + 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 next_target_size = f32::max( -// size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, -// min_size, -// ); + let current_pixel_change = + current_target_size - size(current_ix, flexes.as_slice()); -// 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_target_flex, next_target_flex) = + flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); -// let current_pixel_change = -// current_target_size - size(current_ix, flexes.as_slice()); + flexes[current_ix] = current_target_flex; + flexes[current_ix + 1] = next_target_flex; -// let (current_target_flex, next_target_flex) = -// flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); + proposed_current_pixel_change -= current_pixel_change; + } -// flexes[current_ix] = current_target_flex; -// flexes[current_ix + 1] = next_target_flex; + // todo!(reserialize workspace) + // workspace.schedule_serialize(cx); + cx.notify(); + } -// proposed_current_pixel_change -= current_pixel_change; -// } + fn push_handle( + flexes: Arc>>, + dragged_handle: Rc>>, + 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)), + }; -// workspace.schedule_serialize(cx); -// cx.notify(); -// } -// } -// } + 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, + }) + } -// impl Extend> for PaneAxisElement { -// fn extend>>(&mut self, children: T) { -// self.children.extend(children); -// } -// } + cx.add_opaque_layer(handle_bounds); -// impl Element for PaneAxisElement { -// type LayoutState = f32; -// type PaintState = (); + cx.on_mouse_event({ + let dragged_handle = dragged_handle.clone(); + move |e: &MouseDownEvent, phase, cx| { + if phase.bubble() && handle_bounds.contains(&e.position) { + dragged_handle.replace(Some(ix)); + } + } + }); + cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| { + let dragged_handle = dragged_handle.borrow(); + if *dragged_handle == Some(ix) { + Self::compute_resize(&flexes, e, ix, axis, axis_bounds, cx) + } + }); + }); + } + } -// fn layout( -// &mut self, -// constraint: SizeConstraint, -// view: &mut Workspace, -// cx: &mut ViewContext, -// ) -> (Vector2F, Self::LayoutState) { -// debug_assert!(self.children.len() == self.flexes.borrow().len()); + impl IntoElement for PaneAxisElement { + type Element = Self; -// let active_pane_magnification = -// settings::get::(cx).active_pane_magnification; + fn element_id(&self) -> Option { + Some(self.basis.into()) + } -// let mut remaining_flex = 0.; + fn into_element(self) -> Self::Element { + self + } + } -// if active_pane_magnification != 1. { -// let active_pane_flex = self -// .active_pane_ix -// .map(|_| active_pane_magnification) -// .unwrap_or(1.); -// remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; -// } else { -// for flex in self.flexes.borrow().iter() { -// remaining_flex += flex; -// } -// } + impl Element for PaneAxisElement { + type State = Rc>>; -// let mut cross_axis_max: f32 = 0.0; -// let mut remaining_space = constraint.max_along(self.axis); + fn layout( + &mut self, + state: Option, + cx: &mut ui::prelude::WindowContext, + ) -> (gpui::LayoutId, Self::State) { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + let layout_id = cx.request_layout(&style, None); + let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None))); + (layout_id, dragged_pane) + } -// if remaining_space.is_infinite() { -// panic!("flex contains flexible children but has an infinite constraint along the flex axis"); -// } + fn paint( + self, + bounds: gpui::Bounds, + state: &mut Self::State, + cx: &mut ui::prelude::WindowContext, + ) { + let flexes = self.flexes.lock().clone(); + let len = self.children.len(); + debug_assert!(flexes.len() == len); + debug_assert!(flex_values_in_bounds(flexes.as_slice())); -// self.layout_children( -// active_pane_magnification, -// constraint, -// &mut remaining_space, -// &mut remaining_flex, -// &mut cross_axis_max, -// view, -// cx, -// ); + let mut origin = bounds.origin; + let space_per_flex = bounds.size.along(self.axis) / len as f32; -// let mut size = match self.axis { -// Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), -// Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), -// }; + let mut bounding_boxes = self.bounding_boxes.lock(); + bounding_boxes.clear(); -// if constraint.min.x().is_finite() { -// size.set_x(size.x().max(constraint.min.x())); -// } -// if constraint.min.y().is_finite() { -// size.set_y(size.y().max(constraint.min.y())); -// } + for (ix, child) in self.children.into_iter().enumerate() { + //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]); -// if size.x() > constraint.max.x() { -// size.set_x(constraint.max.x()); -// } -// if size.y() > constraint.max.y() { -// size.set_y(constraint.max.y()); -// } + 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(), + state.clone(), + self.axis, + ix, + child_bounds, + bounds, + cx, + ); + } + }); -// (size, remaining_space) -// } + origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis)); + } -// fn paint( -// &mut self, -// bounds: Bounds, -// visible_bounds: Bounds, -// remaining_space: &mut Self::LayoutState, -// view: &mut Workspace, -// cx: &mut ViewContext, -// ) -> Self::PaintState { -// let can_resize = settings::get::(cx).active_pane_magnification == 1.; -// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + cx.with_z_index(1, |cx| { + cx.on_mouse_event({ + let state = state.clone(); + move |e: &MouseUpEvent, phase, cx| { + if phase.bubble() { + state.replace(None); + } + } + }); + }) + } + } -// let overflowing = *remaining_space < 0.; -// if overflowing { -// cx.scene().push_layer(Some(visible_bounds)); -// } + impl ParentElement for PaneAxisElement { + fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> { + &mut self.children + } + } -// let mut child_origin = bounds.origin(); + 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}; -// let mut bounding_boxes = self.bounding_boxes.borrow_mut(); -// bounding_boxes.clear(); + // // use gpui::{ + // // geometry::{ + // // rect::Bounds, + // // vector::{vec2f, Vector2F}, + // // }, + // // json::{self, ToJson}, + // // platform::{CursorStyle, MouseButton}, + // // scene::MouseDrag, + // // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, BoundsExt, + // // SizeConstraint, Vector2FExt, ViewContext, + // // }; -// let mut children_iter = self.children.iter_mut().enumerate().peekable(); -// while let Some((ix, child)) = children_iter.next() { -// let child_start = child_origin.clone(); -// child.paint(child_origin, visible_bounds, view, cx); + // use crate::{ + // pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, + // Workspace, WorkspaceSettings, + // }; -// bounding_boxes.push(Some(Bounds::new(child_origin, child.size()))); + // pub struct PaneAxisElement { + // axis: Axis, + // basis: usize, + // active_pane_ix: Option, + // flexes: Rc>>, + // children: Vec>, + // bounding_boxes: Rc>>>>, + // } -// match self.axis { -// Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), -// Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), -// } + // impl PaneAxisElement { + // pub fn new( + // axis: Axis, + // basis: usize, + // flexes: Rc>>, + // bounding_boxes: Rc>>>>, + // ) -> Self { + // Self { + // axis, + // basis, + // flexes, + // bounding_boxes, + // active_pane_ix: None, + // children: Default::default(), + // } + // } -// if can_resize && children_iter.peek().is_some() { -// cx.scene().push_stacking_context(None, None); + // pub fn set_active_pane(&mut self, active_pane_ix: Option) { + // self.active_pane_ix = active_pane_ix; + // } -// let handle_origin = match self.axis { -// Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), -// Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), -// }; + // fn layout_children( + // &mut self, + // active_pane_magnification: f32, + // constraint: SizeConstraint, + // remaining_space: &mut f32, + // remaining_flex: &mut f32, + // cross_axis_max: &mut f32, + // view: &mut Workspace, + // cx: &mut ViewContext, + // ) { + // let flexes = self.flexes.borrow(); + // let cross_axis = self.axis.invert(); + // for (ix, child) in self.children.iter_mut().enumerate() { + // let flex = if active_pane_magnification != 1. { + // if let Some(active_pane_ix) = self.active_pane_ix { + // if ix == active_pane_ix { + // active_pane_magnification + // } else { + // 1. + // } + // } else { + // 1. + // } + // } else { + // flexes[ix] + // }; -// let handle_bounds = match self.axis { -// Axis::Horizontal => Bounds::new( -// handle_origin, -// vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), -// ), -// Axis::Vertical => Bounds::new( -// handle_origin, -// vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), -// ), -// }; + // let child_size = if *remaining_flex == 0.0 { + // *remaining_space + // } else { + // let space_per_flex = *remaining_space / *remaining_flex; + // space_per_flex * flex + // }; -// let style = match self.axis { -// Axis::Horizontal => CursorStyle::ResizeLeftRight, -// Axis::Vertical => CursorStyle::ResizeUpDown, -// }; + // let child_constraint = match self.axis { + // Axis::Horizontal => SizeConstraint::new( + // vec2f(child_size, constraint.min.y()), + // vec2f(child_size, constraint.max.y()), + // ), + // Axis::Vertical => SizeConstraint::new( + // vec2f(constraint.min.x(), child_size), + // vec2f(constraint.max.x(), child_size), + // ), + // }; + // let child_size = child.layout(child_constraint, view, cx); + // *remaining_space -= child_size.along(self.axis); + // *remaining_flex -= flex; + // *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); + // } + // } -// cx.scene().push_cursor_region(CursorRegion { -// bounds: handle_bounds, -// style, -// }); + // fn handle_resize( + // flexes: Rc>>, + // axis: Axis, + // preceding_ix: usize, + // child_start: Vector2F, + // drag_bounds: Bounds, + // ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { + // let size = move |ix, flexes: &[f32]| { + // drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) + // }; -// enum ResizeHandle {} -// let mut mouse_region = MouseRegion::new::( -// cx.view_id(), -// self.basis + ix, -// handle_bounds, -// ); -// mouse_region = mouse_region -// .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| { -// if e.click_count >= 2 { -// let mut borrow = flexes.borrow_mut(); -// *borrow = vec![1.; borrow.len()]; -// v.schedule_serialize(cx); -// cx.notify(); -// } -// } -// }); -// cx.scene().push_mouse_region(mouse_region); + // 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(); -// cx.scene().pop_stacking_context(); -// } -// } + // // 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; + // } -// if overflowing { -// cx.scene().pop_layer(); -// } -// } + // let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) + // - size(preceding_ix, flexes.as_slice()); -// fn rect_for_text_range( -// &self, -// range_utf16: Range, -// _: Bounds, -// _: Bounds, -// _: &Self::LayoutState, -// _: &Self::PaintState, -// view: &Workspace, -// cx: &ViewContext, -// ) -> Option> { -// self.children -// .iter() -// .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) -// } + // 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) + // }; -// fn debug( -// &self, -// bounds: Bounds, -// _: &Self::LayoutState, -// _: &Self::PaintState, -// view: &Workspace, -// cx: &ViewContext, -// ) -> json::Value { -// serde_json::json!({ -// "type": "PaneAxis", -// "bounds": bounds.to_json(), -// "axis": self.axis.to_json(), -// "flexes": *self.flexes.borrow(), -// "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() -// }) -// } -// } -// } + // 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 { + // fn extend>>(&mut self, children: T) { + // self.children.extend(children); + // } + // } + + // impl Element for PaneAxisElement { + // type LayoutState = f32; + // type PaintState = (); + + // fn layout( + // &mut self, + // constraint: SizeConstraint, + // view: &mut Workspace, + // cx: &mut ViewContext, + // ) -> (Vector2F, Self::LayoutState) { + // debug_assert!(self.children.len() == self.flexes.borrow().len()); + + // let active_pane_magnification = + // settings::get::(cx).active_pane_magnification; + + // let mut remaining_flex = 0.; + + // if active_pane_magnification != 1. { + // let active_pane_flex = self + // .active_pane_ix + // .map(|_| active_pane_magnification) + // .unwrap_or(1.); + // remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; + // } else { + // for flex in self.flexes.borrow().iter() { + // remaining_flex += flex; + // } + // } + + // let mut cross_axis_max: f32 = 0.0; + // let mut remaining_space = constraint.max_along(self.axis); + + // if remaining_space.is_infinite() { + // panic!("flex contains flexible children but has an infinite constraint along the flex axis"); + // } + + // self.layout_children( + // active_pane_magnification, + // constraint, + // &mut remaining_space, + // &mut remaining_flex, + // &mut cross_axis_max, + // view, + // cx, + // ); + + // let mut size = match self.axis { + // Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), + // Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), + // }; + + // if constraint.min.x().is_finite() { + // size.set_x(size.x().max(constraint.min.x())); + // } + // if constraint.min.y().is_finite() { + // size.set_y(size.y().max(constraint.min.y())); + // } + + // if size.x() > constraint.max.x() { + // size.set_x(constraint.max.x()); + // } + // if size.y() > constraint.max.y() { + // size.set_y(constraint.max.y()); + // } + + // (size, remaining_space) + // } + + // fn paint( + // &mut self, + // bounds: Bounds, + // visible_bounds: Bounds, + // remaining_space: &mut Self::LayoutState, + // view: &mut Workspace, + // cx: &mut ViewContext, + // ) -> Self::PaintState { + // let can_resize = settings::get::(cx).active_pane_magnification == 1.; + // let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + // let overflowing = *remaining_space < 0.; + // if overflowing { + // cx.scene().push_layer(Some(visible_bounds)); + // } + + // let mut child_origin = bounds.origin(); + + // let mut bounding_boxes = self.bounding_boxes.borrow_mut(); + // bounding_boxes.clear(); + + // let mut children_iter = self.children.iter_mut().enumerate().peekable(); + // while let Some((ix, child)) = children_iter.next() { + // let child_start = child_origin.clone(); + // child.paint(child_origin, visible_bounds, view, cx); + + // bounding_boxes.push(Some(Bounds::new(child_origin, child.size()))); + + // match self.axis { + // Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), + // Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), + // } + + // if can_resize && children_iter.peek().is_some() { + // cx.scene().push_stacking_context(None, None); + + // let handle_origin = match self.axis { + // Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), + // Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), + // }; + + // let handle_bounds = match self.axis { + // Axis::Horizontal => Bounds::new( + // handle_origin, + // vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), + // ), + // Axis::Vertical => Bounds::new( + // handle_origin, + // vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), + // ), + // }; + + // let style = match self.axis { + // Axis::Horizontal => CursorStyle::ResizeLeftRight, + // Axis::Vertical => CursorStyle::ResizeUpDown, + // }; + + // cx.scene().push_cursor_region(CursorRegion { + // bounds: handle_bounds, + // style, + // }); + + // enum ResizeHandle {} + // let mut mouse_region = MouseRegion::new::( + // cx.view_id(), + // self.basis + ix, + // handle_bounds, + // ); + // mouse_region = mouse_region + // .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| { + // if e.click_count >= 2 { + // let mut borrow = flexes.borrow_mut(); + // *borrow = vec![1.; borrow.len()]; + // v.schedule_serialize(cx); + // cx.notify(); + // } + // } + // }); + // cx.scene().push_mouse_region(mouse_region); + + // cx.scene().pop_stacking_context(); + // } + // } + + // if overflowing { + // cx.scene().pop_layer(); + // } + // } + + // fn rect_for_text_range( + // &self, + // range_utf16: Range, + // _: Bounds, + // _: Bounds, + // _: &Self::LayoutState, + // _: &Self::PaintState, + // view: &Workspace, + // cx: &ViewContext, + // ) -> Option> { + // self.children + // .iter() + // .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) + // } + + // fn debug( + // &self, + // bounds: Bounds, + // _: &Self::LayoutState, + // _: &Self::PaintState, + // view: &Workspace, + // cx: &ViewContext, + // ) -> json::Value { + // serde_json::json!({ + // "type": "PaneAxis", + // "bounds": bounds.to_json(), + // "axis": self.axis.to_json(), + // "flexes": *self.flexes.borrow(), + // "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() + // }) + // } + // } +} diff --git a/crates/workspace2/src/persistence.rs b/crates/workspace2/src/persistence.rs index b842355991..1abb06dccf 100644 --- a/crates/workspace2/src/persistence.rs +++ b/crates/workspace2/src/persistence.rs @@ -6,12 +6,12 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::WindowBounds; +use gpui::{Axis, WindowBounds}; use util::{unzip_option, ResultExt}; use uuid::Uuid; -use crate::{Axis, WorkspaceId}; +use crate::WorkspaceId; use model::{ GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, @@ -403,7 +403,7 @@ impl WorkspaceDb { .map(|(group_id, axis, pane_id, active, flexes)| { if let Some((group_id, axis)) = group_id.zip(axis) { let flexes = flexes - .map(|flexes| serde_json::from_str::>(&flexes)) + .map(|flexes: String| serde_json::from_str::>(&flexes)) .transpose()?; Ok(SerializedPaneGroup::Group { diff --git a/crates/workspace2/src/persistence/model.rs b/crates/workspace2/src/persistence/model.rs index 74304d3c8e..f204e5152c 100644 --- a/crates/workspace2/src/persistence/model.rs +++ b/crates/workspace2/src/persistence/model.rs @@ -1,13 +1,11 @@ -use crate::{ - item::ItemHandle, Axis, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, -}; +use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId}; use anyhow::{Context, Result}; use async_recursion::async_recursion; use db::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds}; +use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView, WindowBounds}; use project::Project; use std::{ path::{Path, PathBuf}, diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index eb846fbea8..43aa13847d 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -29,12 +29,12 @@ use futures::{ Future, FutureExt, StreamExt, }; use gpui::{ - actions, div, impl_actions, point, size, Action, AnyModel, AnyView, AnyWeakView, + actions, canvas, div, impl_actions, point, size, Action, AnyModel, AnyView, AnyWeakView, AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, InteractiveElement, - KeyContext, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Point, - PromptLevel, Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, - WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, + KeyContext, ManagedView, Model, ModelContext, MouseMoveEvent, ParentElement, PathPromptOptions, + Pixels, Point, PromptLevel, Render, Size, Styled, Subscription, Task, View, ViewContext, + VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -227,6 +227,9 @@ pub fn init_settings(cx: &mut AppContext) { } pub fn init(app_state: Arc, cx: &mut AppContext) { + cx.default_global::(); + cx.default_global::(); + init_settings(cx); notifications::init(cx); @@ -480,8 +483,6 @@ struct FollowerState { items_by_leader_view_id: HashMap>, } -enum WorkspaceBounds {} - impl Workspace { pub fn new( workspace_id: WorkspaceId, @@ -2032,7 +2033,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(), }; @@ -3571,6 +3572,16 @@ impl FocusableView for Workspace { } } +struct WorkspaceBounds(Bounds); + +//todo!("remove this when better drag APIs are in GPUI2") +#[derive(Default)] +struct DockDragState(Option); + +//todo!("remove this when better double APIs are in GPUI2") +#[derive(Default)] +struct DockClickReset(Option>); + impl Render for Workspace { type Element = Div; @@ -3614,6 +3625,37 @@ impl Render for Workspace { .border_t() .border_b() .border_color(cx.theme().colors().border) + .on_mouse_up(gpui::MouseButton::Left, |_, cx| { + cx.update_global(|drag: &mut DockDragState, cx| { + drag.0 = None; + }) + }) + .on_mouse_move(cx.listener(|workspace, e: &MouseMoveEvent, cx| { + if let Some(types) = &cx.global::().0 { + let workspace_bounds = cx.global::().0; + match types { + DockPosition::Left => { + let size = e.position.x; + workspace.left_dock.update(cx, |left_dock, cx| { + left_dock.resize_active_panel(Some(size.0), cx); + }); + } + DockPosition::Right => { + let size = workspace_bounds.size.width - e.position.x; + workspace.right_dock.update(cx, |right_dock, cx| { + right_dock.resize_active_panel(Some(size.0), cx); + }); + } + DockPosition::Bottom => { + let size = workspace_bounds.size.height - e.position.y; + workspace.bottom_dock.update(cx, |bottom_dock, cx| { + bottom_dock.resize_active_panel(Some(size.0), cx); + }); + } + } + } + })) + .child(canvas(|bounds, cx| cx.set_global(WorkspaceBounds(bounds)))) .child(self.modal_layer.clone()) .child( div()