diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 62ce6305ea7303362a5b6fb8889e63e18ca3fd68..18f688f179e34c6a272351a7bcf7cae9e5dc7bd3 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -1138,6 +1138,10 @@ impl AppContext { pub fn has_active_drag(&self) -> bool { self.active_drag.is_some() } + + pub fn active_drag(&self) -> Option { + self.active_drag.as_ref().map(|drag| drag.view.clone()) + } } impl Context for AppContext { diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 226a477012a025474df2a58c70be56c10ef37fc3..e5ecd195baa14694bc65a15e9102d5cbd56be10a 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -482,48 +482,31 @@ impl IntoElement for AnyElement { } } -// impl Element for Option -// where -// V: 'static, -// E: Element, -// F: FnOnce(&mut V, &mut WindowContext<'_, V>) -> E + 'static, -// { -// type State = Option; - -// fn element_id(&self) -> Option { -// None -// } - -// fn layout( -// &mut self, -// _: Option, -// cx: &mut WindowContext, -// ) -> (LayoutId, Self::State) { -// let render = self.take().unwrap(); -// let mut element = (render)(view_state, cx).into_any(); -// let layout_id = element.layout(view_state, cx); -// (layout_id, Some(element)) -// } - -// fn paint( -// self, -// _bounds: Bounds, -// rendered_element: &mut Self::State, -// cx: &mut WindowContext, -// ) { -// rendered_element.take().unwrap().paint(view_state, cx); -// } -// } - -// impl RenderOnce for Option -// where -// V: 'static, -// E: Element, -// F: FnOnce(&mut V, &mut WindowContext) -> E + 'static, -// { -// type Element = Self; - -// fn render(self) -> Self::Element { -// self -// } -// } +/// The empty element, which renders nothing. +pub type Empty = (); + +impl IntoElement for () { + type Element = Self; + + fn element_id(&self) -> Option { + None + } + + fn into_element(self) -> Self::Element { + self + } +} + +impl Element for () { + type State = (); + + fn layout( + &mut self, + _state: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + (cx.request_layout(&crate::Style::default(), None), ()) + } + + fn paint(self, _bounds: Bounds, _state: &mut Self::State, _cx: &mut WindowContext) {} +} diff --git a/crates/gpui2/src/elements/canvas.rs b/crates/gpui2/src/elements/canvas.rs index 287a3b4b5a38fdc0c7c90c75763bb9a0921dfb7e..b3afd335d41d4544267a9453e8ffedea5c990b18 100644 --- a/crates/gpui2/src/elements/canvas.rs +++ b/crates/gpui2/src/elements/canvas.rs @@ -2,7 +2,7 @@ use refineable::Refineable as _; use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext}; -pub fn canvas(callback: impl 'static + FnOnce(Bounds, &mut WindowContext)) -> Canvas { +pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut WindowContext)) -> Canvas { Canvas { paint_callback: Box::new(callback), style: StyleRefinement::default(), @@ -10,7 +10,7 @@ pub fn canvas(callback: impl 'static + FnOnce(Bounds, &mut WindowContext } pub struct Canvas { - paint_callback: Box, &mut WindowContext)>, + paint_callback: Box, &mut WindowContext)>, style: StyleRefinement, } @@ -41,7 +41,7 @@ impl Element for Canvas { } fn paint(self, bounds: Bounds, _: &mut (), cx: &mut WindowContext) { - (self.paint_callback)(bounds, cx) + (self.paint_callback)(&bounds, cx) } } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index a102c71a6fbc1d8d0dbc0b2d984efcbdbc677276..7dfc9bae6a7648f74502387ac041e3c83d61c79b 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -29,6 +29,11 @@ pub struct GroupStyle { pub style: Box, } +pub struct DragMoveEvent { + pub event: MouseMoveEvent, + pub drag: View, +} + pub trait InteractiveElement: Sized { fn interactivity(&mut self) -> &mut Interactivity; @@ -192,6 +197,34 @@ pub trait InteractiveElement: Sized { self } + fn on_drag_move( + mut self, + listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, + ) -> Self + where + W: Render, + { + self.interactivity().mouse_move_listeners.push(Box::new( + move |event, bounds, phase, cx| { + if phase == DispatchPhase::Capture + && bounds.drag_target_contains(&event.position, cx) + { + if let Some(view) = cx.active_drag().and_then(|view| view.downcast::().ok()) + { + (listener)( + &DragMoveEvent { + event: event.clone(), + drag: view, + }, + cx, + ); + } + } + }, + )); + self + } + fn on_scroll_wheel( mut self, listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static, @@ -403,7 +436,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { self } - fn on_drag(mut self, listener: impl Fn(&mut WindowContext) -> View + 'static) -> Self + fn on_drag(mut self, constructor: impl Fn(&mut WindowContext) -> View + 'static) -> Self where Self: Sized, W: 'static + Render, @@ -413,7 +446,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { "calling on_drag more than once on the same element is not supported" ); self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag { - view: listener(cx).into(), + view: constructor(cx).into(), cursor_offset, })); self diff --git a/crates/gpui2_macros/src/derive_render.rs b/crates/gpui2_macros/src/derive_render.rs new file mode 100644 index 0000000000000000000000000000000000000000..3983a572f06ae66bbff7686851bb369840213309 --- /dev/null +++ b/crates/gpui2_macros/src/derive_render.rs @@ -0,0 +1,23 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +pub fn derive_render(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let type_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl(); + + let gen = quote! { + impl #impl_generics gpui::Render for #type_name #type_generics + #where_clause + { + type Element = (); + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + () + } + } + }; + + gen.into() +} diff --git a/crates/gpui2_macros/src/gpui2_macros.rs b/crates/gpui2_macros/src/gpui2_macros.rs index a56d80b86d0ec92c523eb9dcbcb185f4b2d11049..f0cd59908dc6967d3b00e1b0991d2390b61a59e1 100644 --- a/crates/gpui2_macros/src/gpui2_macros.rs +++ b/crates/gpui2_macros/src/gpui2_macros.rs @@ -1,4 +1,5 @@ mod derive_into_element; +mod derive_render; mod register_action; mod style_helpers; mod test; @@ -15,6 +16,11 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream { derive_into_element::derive_into_element(input) } +#[proc_macro_derive(Render)] +pub fn derive_render(input: TokenStream) -> TokenStream { + derive_render::derive_render(input) +} + #[proc_macro] pub fn style_helpers(input: TokenStream) -> TokenStream { style_helpers::style_helpers(input) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index b656a79a8d32860993b5b65287cb318e30659104..a71b8b4d6f9b652b29ef55f6d32a7ebf2897442a 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,5 +1,5 @@ +use crate::DraggedDock; use crate::{status_bar::StatusItemView, Workspace}; -use crate::{DockClickReset, DockDragState}; use gpui::{ div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Div, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render, @@ -493,27 +493,10 @@ impl Render for Dock { 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_drag(move |cx| cx.build_view(|_| DraggedDock(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(); - })); - } - }) + if e.down.button == MouseButton::Left && e.down.click_count == 2 { + v.resize_active_panel(None, cx) } })); diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a7dc76f41da49fe4bf9139ed2bf7b0ae8b6f4b23..fb465b2637d40ab45d9be7ebf2923611f5a8c996 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -30,11 +30,11 @@ use futures::{ }; use gpui::{ 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, MouseMoveEvent, ParentElement, PathPromptOptions, - Pixels, Point, PromptLevel, Render, Size, Styled, Subscription, Task, View, ViewContext, - VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, + AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, + DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, + InteractiveElement, KeyContext, ManagedView, Model, ModelContext, 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,9 +227,6 @@ 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); @@ -466,6 +463,7 @@ pub struct Workspace { _observe_current_user: Task>, _schedule_serialize: Option>, pane_history_timestamp: Arc, + bounds: Bounds, } impl EventEmitter for Workspace {} @@ -708,6 +706,8 @@ impl Workspace { subscriptions, pane_history_timestamp, workspace_actions: Default::default(), + // This data will be incorrect, but it will be overwritten by the time it needs to be used. + bounds: Default::default(), } } @@ -3580,13 +3580,8 @@ 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>); +#[derive(Render)] +struct DraggedDock(DockPosition); impl Render for Workspace { type Element = Div; @@ -3632,37 +3627,33 @@ 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 { + .child(canvas( + cx.listener(|workspace, bounds, cx| workspace.bounds = *bounds), + )) + .on_drag_move( + cx.listener(|workspace, e: &DragMoveEvent, cx| { + match e.drag.read(cx).0 { DockPosition::Left => { - let size = e.position.x; + let size = e.event.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; + let size = workspace.bounds.size.width - e.event.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; + let size = workspace.bounds.size.height - e.event.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()