From 53add253b604cd75ab1574a891d6b31e66b2233d Mon Sep 17 00:00:00 2001 From: K Simmons Date: Thu, 21 Jul 2022 22:23:56 -0700 Subject: [PATCH 01/16] wip new mouse region events --- crates/gpui/src/scene/mouse_region.rs | 17 +++++++++++++++++ crates/workspace/src/pane.rs | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 704567450a..01bb9fde69 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -273,10 +273,12 @@ impl HandlerSet { pub enum MouseRegionEvent { Move(MouseMovedEvent), Drag(Vector2F, MouseMovedEvent), + DragOver(Vector2F, MouseMovedEvent), Hover(bool, MouseMovedEvent), Down(MouseButtonEvent), Up(MouseButtonEvent), Click(MouseButtonEvent), + UpOut(MouseButtonEvent), DownOut(MouseButtonEvent), ScrollWheel(ScrollWheelEvent), } @@ -291,6 +293,12 @@ impl MouseRegionEvent { Default::default(), )) } + pub fn drag_over_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::DragOver( + Default::default(), + Default::default(), + )) + } pub fn hover_disc() -> Discriminant { std::mem::discriminant(&MouseRegionEvent::Hover( Default::default(), @@ -303,6 +311,9 @@ impl MouseRegionEvent { pub fn up_disc() -> Discriminant { std::mem::discriminant(&MouseRegionEvent::Up(Default::default())) } + pub fn up_out_disc() -> Discriminant { + std::mem::discriminant(&MouseRegionEvent::UpOut(Default::default())) + } pub fn click_disc() -> Discriminant { std::mem::discriminant(&MouseRegionEvent::Click(Default::default())) } @@ -319,6 +330,9 @@ impl MouseRegionEvent { MouseRegionEvent::Drag(_, MouseMovedEvent { pressed_button, .. }) => { (Self::drag_disc(), *pressed_button) } + MouseRegionEvent::DragOver(_, MouseMovedEvent { pressed_button, .. }) => { + (Self::drag_over_disc(), *pressed_button) + } MouseRegionEvent::Hover(_, _) => (Self::hover_disc(), None), MouseRegionEvent::Down(MouseButtonEvent { button, .. }) => { (Self::down_disc(), Some(*button)) @@ -329,6 +343,9 @@ impl MouseRegionEvent { MouseRegionEvent::Click(MouseButtonEvent { button, .. }) => { (Self::click_disc(), Some(*button)) } + MouseRegionEvent::UpOut(MouseButtonEvent { button, .. }) => { + (Self::up_out_disc(), Some(*button)) + } MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => { (Self::down_out_disc(), Some(*button)) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 34108eef5e..eb21c2ac83 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1022,6 +1022,19 @@ impl Pane { cx.dispatch_action(ActivateItem(ix)); }) .on_click(MouseButton::Middle, close_tab_callback) + .on_drag(MouseButton::Left, |_, cx| { + cx.global::().dragging(some view handle) + }) + .on_mouse_up_out(MouseButton::Left, |_, cx| { + cx.global::().stopped_dragging(some view handle) + }) + .on_drag_over(MouseButton::Left, |started, _, cx| { + if started { + if let Some(tab) = cx.global::().current_dragged::() { + cx.dispatch_action(ReceivingTab) + } + } + }) .boxed() }) } From a650c146f14e17b9f151a89a6a222aba036e5f7e Mon Sep 17 00:00:00 2001 From: K Simmons Date: Fri, 22 Jul 2022 14:00:38 -0700 Subject: [PATCH 02/16] more event wip --- .../gpui/src/elements/mouse_event_handler.rs | 27 ++++ crates/gpui/src/presenter.rs | 147 ++++++++---------- crates/gpui/src/scene/mouse_region.rs | 92 +++++++++-- crates/workspace/src/pane.rs | 6 +- 4 files changed, 175 insertions(+), 97 deletions(-) diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 7ad43c47de..35e8d6407e 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -51,6 +51,15 @@ impl MouseEventHandler { self } + pub fn on_up( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_up(button, handler); + self + } + pub fn on_click( mut self, button: MouseButton, @@ -69,6 +78,15 @@ impl MouseEventHandler { self } + pub fn on_up_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_up(button, handler); + self + } + pub fn on_drag( mut self, button: MouseButton, @@ -78,6 +96,15 @@ impl MouseEventHandler { self } + pub fn on_drag_over( + mut self, + button: MouseButton, + handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_drag_over(button, handler); + self + } + pub fn on_hover( mut self, handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 023e013d13..deb6e5ed91 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -234,10 +234,7 @@ impl Presenter { pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool { if let Some(root_view_id) = cx.root_view_id(self.window_id) { let mut invalidated_views = Vec::new(); - let mut mouse_down_out_handlers = Vec::new(); - let mut mouse_down_region = None; - let mut clicked_region = None; - let mut dragged_region = None; + let mut events_to_send = Vec::new(); match &event { Event::MouseDown( @@ -248,32 +245,42 @@ impl Presenter { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(*position) { - if !hit { - hit = true; + if !std::mem::replace(&mut hit, true) { invalidated_views.push(region.view_id); - mouse_down_region = - Some((region.clone(), MouseRegionEvent::Down(e.clone()))); + events_to_send + .push((region.clone(), MouseRegionEvent::Down(e.clone()))); self.clicked_region = Some(region.clone()); self.prev_drag_position = Some(*position); } - } else if let Some(handler) = region - .handlers - .get(&(MouseRegionEvent::down_out_disc(), Some(*button))) - { - mouse_down_out_handlers.push(( - handler, - region.view_id, - MouseRegionEvent::DownOut(e.clone()), - )); + } else { + events_to_send + .push((region.clone(), MouseRegionEvent::DownOut(e.clone()))); } } } - Event::MouseUp(e @ MouseButtonEvent { position, .. }) => { + Event::MouseUp( + e @ MouseButtonEvent { + position, button, .. + }, + ) => { + let mut hit = false; + for (region, _) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(*position) { + if !std::mem::replace(&mut hit, true) { + invalidated_views.push(region.view_id); + events_to_send + .push((region.clone(), MouseRegionEvent::Up(e.clone()))); + } + } else { + events_to_send + .push((region.clone(), MouseRegionEvent::UpOut(e.clone()))); + } + } self.prev_drag_position.take(); if let Some(region) = self.clicked_region.take() { invalidated_views.push(region.view_id); if region.bounds.contains_point(*position) { - clicked_region = Some((region, MouseRegionEvent::Click(e.clone()))); + events_to_send.push((region, MouseRegionEvent::Click(e.clone()))); } } } @@ -283,7 +290,7 @@ impl Presenter { .as_ref() .zip(self.prev_drag_position.as_mut()) { - dragged_region = Some(( + events_to_send.push(( clicked_region.clone(), MouseRegionEvent::Drag(*prev_drag_position, *e), )); @@ -298,39 +305,11 @@ impl Presenter { let (mut handled, mut event_cx) = self.handle_hover_events(&event, &mut invalidated_views, cx); - for (handler, view_id, region_event) in mouse_down_out_handlers { - event_cx.with_current_view(view_id, |event_cx| handler(region_event, event_cx)) - } - - if let Some((mouse_down_region, region_event)) = mouse_down_region { + for (region, event) in events_to_send { handled = true; - if let Some(mouse_down_callback) = - mouse_down_region.handlers.get(®ion_event.handler_key()) - { - event_cx.with_current_view(mouse_down_region.view_id, |event_cx| { - mouse_down_callback(region_event, event_cx); - }) - } - } - - if let Some((clicked_region, region_event)) = clicked_region { - handled = true; - if let Some(click_callback) = - clicked_region.handlers.get(®ion_event.handler_key()) - { - event_cx.with_current_view(clicked_region.view_id, |event_cx| { - click_callback(region_event, event_cx); - }) - } - } - - if let Some((dragged_region, region_event)) = dragged_region { - handled = true; - if let Some(drag_callback) = - dragged_region.handlers.get(®ion_event.handler_key()) - { - event_cx.with_current_view(dragged_region.view_id, |event_cx| { - drag_callback(region_event, event_cx); + if let Some(callback) = region.handlers.get(&event.handler_key()) { + event_cx.with_current_view(region.view_id, |event_cx| { + callback(event, event_cx); }) } } @@ -366,38 +345,44 @@ impl Presenter { }, ) = event { - if pressed_button.is_none() { - let mut style_to_assign = CursorStyle::Arrow; - for region in self.cursor_regions.iter().rev() { - if region.bounds.contains_point(*position) { - style_to_assign = region.style; - break; - } + let mut style_to_assign = CursorStyle::Arrow; + for region in self.cursor_regions.iter().rev() { + if region.bounds.contains_point(*position) { + style_to_assign = region.style; + break; } - cx.platform().set_cursor_style(style_to_assign); + } + cx.platform().set_cursor_style(style_to_assign); - let mut hover_depth = None; - for (region, depth) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(*position) - && hover_depth.map_or(true, |hover_depth| hover_depth == *depth) - { - hover_depth = Some(*depth); - if let Some(region_id) = region.id() { - if !self.hovered_region_ids.contains(®ion_id) { - invalidated_views.push(region.view_id); - hover_regions - .push((region.clone(), MouseRegionEvent::Hover(true, *e))); - self.hovered_region_ids.insert(region_id); - } - } - } else if let Some(region_id) = region.id() { - if self.hovered_region_ids.contains(®ion_id) { + let mut hover_depth = None; + for (region, depth) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(*position) + && hover_depth.map_or(true, |hover_depth| hover_depth == *depth) + { + hover_depth = Some(*depth); + if let Some(region_id) = region.id() { + if !self.hovered_region_ids.contains(®ion_id) { invalidated_views.push(region.view_id); - hover_regions - .push((region.clone(), MouseRegionEvent::Hover(false, *e))); - self.hovered_region_ids.remove(®ion_id); + let region_event = if let Some(pressed_button) = pressed_button { + MouseRegionEvent::DragOver(true, e.clone()) + } else { + MouseRegionEvent::Hover(true, e.clone()) + }; + hover_regions.push((region.clone(), region_event)); + self.hovered_region_ids.insert(region_id); } } + } else if let Some(region_id) = region.id() { + if self.hovered_region_ids.contains(®ion_id) { + invalidated_views.push(region.view_id); + let region_event = if let Some(pressed_button) = pressed_button { + MouseRegionEvent::DragOver(false, e.clone()) + } else { + MouseRegionEvent::Hover(false, e.clone()) + }; + hover_regions.push((region.clone(), region_event)); + self.hovered_region_ids.remove(®ion_id); + } } } } @@ -407,9 +392,9 @@ impl Presenter { for (hover_region, region_event) in hover_regions { handled = true; - if let Some(hover_callback) = hover_region.handlers.get(®ion_event.handler_key()) { + if let Some(callback) = hover_region.handlers.get(®ion_event.handler_key()) { event_cx.with_current_view(hover_region.view_id, |event_cx| { - hover_callback(region_event, event_cx); + callback(region_event, event_cx); }) } } diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 01bb9fde69..33415bc990 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -1,4 +1,8 @@ -use std::{any::TypeId, mem::Discriminant, rc::Rc}; +use std::{ + any::TypeId, + mem::{discriminant, Discriminant}, + rc::Rc, +}; use collections::HashMap; use pathfinder_geometry::{rect::RectF, vector::Vector2F}; @@ -81,6 +85,15 @@ impl MouseRegion { self } + pub fn on_up_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_up_out(button, handler); + self + } + pub fn on_drag( mut self, button: MouseButton, @@ -90,6 +103,15 @@ impl MouseRegion { self } + pub fn on_drag_over( + mut self, + button: MouseButton, + handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_drag_over(button, handler); + self + } + pub fn on_hover( mut self, handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, @@ -129,6 +151,10 @@ impl HandlerSet { (MouseRegionEvent::drag_disc(), Some(button)), Rc::new(|_, _| {}), ); + set.insert( + (MouseRegionEvent::drag_over_disc(), Some(button)), + Rc::new(|_, _| {}), + ); set.insert( (MouseRegionEvent::down_disc(), Some(button)), Rc::new(|_, _| {}), @@ -145,6 +171,10 @@ impl HandlerSet { (MouseRegionEvent::down_out_disc(), Some(button)), Rc::new(|_, _| {}), ); + set.insert( + (MouseRegionEvent::up_out_disc(), Some(button)), + Rc::new(|_, _| {}), + ); } set.insert( (MouseRegionEvent::scroll_wheel_disc(), None), @@ -233,6 +263,24 @@ impl HandlerSet { self } + pub fn on_up_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::up_out_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::UpOut(mouse_button_event) = region_event { + handler(mouse_button_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}", + region_event); + } + })); + self + } + pub fn on_drag( mut self, button: MouseButton, @@ -251,14 +299,32 @@ impl HandlerSet { self } + pub fn on_drag_over( + mut self, + button: MouseButton, + handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::drag_over_disc(), Some(button)), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::DragOver(started, mouse_moved_event) = region_event { + handler(started, mouse_moved_event, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DragOver, found {:?}", + region_event); + } + })); + self + } + pub fn on_hover( mut self, handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::hover_disc(), None), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Hover(hover, mouse_moved_event) = region_event { - handler(hover, mouse_moved_event, cx); + if let MouseRegionEvent::Hover(started, mouse_moved_event) = region_event { + handler(started, mouse_moved_event, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}", @@ -273,7 +339,7 @@ impl HandlerSet { pub enum MouseRegionEvent { Move(MouseMovedEvent), Drag(Vector2F, MouseMovedEvent), - DragOver(Vector2F, MouseMovedEvent), + DragOver(bool, MouseMovedEvent), Hover(bool, MouseMovedEvent), Down(MouseButtonEvent), Up(MouseButtonEvent), @@ -285,40 +351,40 @@ pub enum MouseRegionEvent { impl MouseRegionEvent { pub fn move_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Move(Default::default())) + discriminant(&MouseRegionEvent::Move(Default::default())) } pub fn drag_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Drag( + discriminant(&MouseRegionEvent::Drag( Default::default(), Default::default(), )) } pub fn drag_over_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::DragOver( + discriminant(&MouseRegionEvent::DragOver( Default::default(), Default::default(), )) } pub fn hover_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Hover( + discriminant(&MouseRegionEvent::Hover( Default::default(), Default::default(), )) } pub fn down_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Down(Default::default())) + discriminant(&MouseRegionEvent::Down(Default::default())) } pub fn up_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Up(Default::default())) + discriminant(&MouseRegionEvent::Up(Default::default())) } pub fn up_out_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::UpOut(Default::default())) + discriminant(&MouseRegionEvent::UpOut(Default::default())) } pub fn click_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::Click(Default::default())) + discriminant(&MouseRegionEvent::Click(Default::default())) } pub fn down_out_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::DownOut(Default::default())) + discriminant(&MouseRegionEvent::DownOut(Default::default())) } pub fn scroll_wheel_disc() -> Discriminant { std::mem::discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index eb21c2ac83..2f58f7cba4 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1025,13 +1025,13 @@ impl Pane { .on_drag(MouseButton::Left, |_, cx| { cx.global::().dragging(some view handle) }) - .on_mouse_up_out(MouseButton::Left, |_, cx| { + .on_up_out(MouseButton::Left, |_, cx| { cx.global::().stopped_dragging(some view handle) }) - .on_drag_over(MouseButton::Left, |started, _, cx| { + .on_drag_over(MouseButton::Left, |started, _, _, cx| { if started { if let Some(tab) = cx.global::().current_dragged::() { - cx.dispatch_action(ReceivingTab) + cx.dispatch_action(ReceivingTab); } } }) From 86fdd55fd487f21602c3a47bb43770d9f743b267 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Fri, 22 Jul 2022 14:58:55 -0700 Subject: [PATCH 03/16] Combined presenter mouse region event dispatching and added support up_out, and drag_over --- crates/gpui/src/presenter.rs | 63 ++++++++++----------------- crates/gpui/src/scene/mouse_region.rs | 22 +++++++++- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index deb6e5ed91..899beacf47 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -65,23 +65,6 @@ impl Presenter { } } - // pub fn dispatch_path(&self, app: &AppContext) -> Vec { - // let mut path = Vec::new(); - // if let Some(view_id) = app.focused_view_id(self.window_id) { - // self.compute_dispatch_path_from(view_id, &mut path) - // } - // path - // } - - // pub(crate) fn compute_dispatch_path_from(&self, mut view_id: usize, path: &mut Vec) { - // path.push(view_id); - // while let Some(parent_id) = self.parents.get(&view_id).copied() { - // path.push(parent_id); - // view_id = parent_id; - // } - // path.reverse(); - // } - pub fn invalidate( &mut self, invalidation: &mut WindowInvalidation, @@ -237,15 +220,12 @@ impl Presenter { let mut events_to_send = Vec::new(); match &event { - Event::MouseDown( - e @ MouseButtonEvent { - position, button, .. - }, - ) => { + Event::MouseDown(e @ MouseButtonEvent { position, .. }) => { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(*position) { - if !std::mem::replace(&mut hit, true) { + if !hit { + hit = true; invalidated_views.push(region.view_id); events_to_send .push((region.clone(), MouseRegionEvent::Down(e.clone()))); @@ -258,15 +238,12 @@ impl Presenter { } } } - Event::MouseUp( - e @ MouseButtonEvent { - position, button, .. - }, - ) => { + Event::MouseUp(e @ MouseButtonEvent { position, .. }) => { let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(*position) { - if !std::mem::replace(&mut hit, true) { + if !hit { + hit = true; invalidated_views.push(region.view_id); events_to_send .push((region.clone(), MouseRegionEvent::Up(e.clone()))); @@ -306,7 +283,10 @@ impl Presenter { self.handle_hover_events(&event, &mut invalidated_views, cx); for (region, event) in events_to_send { - handled = true; + if event.is_local() { + handled = true; + } + if let Some(callback) = region.handlers.get(&event.handler_key()) { event_cx.with_current_view(region.view_id, |event_cx| { callback(event, event_cx); @@ -336,7 +316,8 @@ impl Presenter { invalidated_views: &mut Vec, cx: &'a mut MutableAppContext, ) -> (bool, EventContext<'a>) { - let mut hover_regions = Vec::new(); + let mut events_to_send = Vec::new(); + if let Event::MouseMoved( e @ MouseMovedEvent { position, @@ -363,24 +344,24 @@ impl Presenter { if let Some(region_id) = region.id() { if !self.hovered_region_ids.contains(®ion_id) { invalidated_views.push(region.view_id); - let region_event = if let Some(pressed_button) = pressed_button { + let region_event = if pressed_button.is_some() { MouseRegionEvent::DragOver(true, e.clone()) } else { MouseRegionEvent::Hover(true, e.clone()) }; - hover_regions.push((region.clone(), region_event)); + events_to_send.push((region.clone(), region_event)); self.hovered_region_ids.insert(region_id); } } } else if let Some(region_id) = region.id() { if self.hovered_region_ids.contains(®ion_id) { invalidated_views.push(region.view_id); - let region_event = if let Some(pressed_button) = pressed_button { + let region_event = if pressed_button.is_some() { MouseRegionEvent::DragOver(false, e.clone()) } else { MouseRegionEvent::Hover(false, e.clone()) }; - hover_regions.push((region.clone(), region_event)); + events_to_send.push((region.clone(), region_event)); self.hovered_region_ids.remove(®ion_id); } } @@ -390,11 +371,13 @@ impl Presenter { let mut event_cx = self.build_event_context(cx); let mut handled = false; - for (hover_region, region_event) in hover_regions { - handled = true; - if let Some(callback) = hover_region.handlers.get(®ion_event.handler_key()) { - event_cx.with_current_view(hover_region.view_id, |event_cx| { - callback(region_event, event_cx); + for (region, event) in events_to_send { + if event.is_local() { + handled = true; + } + if let Some(callback) = region.handlers.get(&event.handler_key()) { + event_cx.with_current_view(region.view_id, |event_cx| { + callback(event, event_cx); }) } } diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 33415bc990..6482310259 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -106,7 +106,7 @@ impl MouseRegion { pub fn on_drag_over( mut self, button: MouseButton, - handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_drag_over(button, handler); self @@ -353,41 +353,59 @@ impl MouseRegionEvent { pub fn move_disc() -> Discriminant { discriminant(&MouseRegionEvent::Move(Default::default())) } + pub fn drag_disc() -> Discriminant { discriminant(&MouseRegionEvent::Drag( Default::default(), Default::default(), )) } + pub fn drag_over_disc() -> Discriminant { discriminant(&MouseRegionEvent::DragOver( Default::default(), Default::default(), )) } + pub fn hover_disc() -> Discriminant { discriminant(&MouseRegionEvent::Hover( Default::default(), Default::default(), )) } + pub fn down_disc() -> Discriminant { discriminant(&MouseRegionEvent::Down(Default::default())) } + pub fn up_disc() -> Discriminant { discriminant(&MouseRegionEvent::Up(Default::default())) } + pub fn up_out_disc() -> Discriminant { discriminant(&MouseRegionEvent::UpOut(Default::default())) } + pub fn click_disc() -> Discriminant { discriminant(&MouseRegionEvent::Click(Default::default())) } + pub fn down_out_disc() -> Discriminant { discriminant(&MouseRegionEvent::DownOut(Default::default())) } + pub fn scroll_wheel_disc() -> Discriminant { - std::mem::discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) + discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) + } + + pub fn is_local(&self) -> bool { + match self { + MouseRegionEvent::DownOut(_) + | MouseRegionEvent::UpOut(_) + | MouseRegionEvent::DragOver(_, _) => false, + _ => true, + } } pub fn handler_key(&self) -> (Discriminant, Option) { From 133c194f4a6a660c842ee6e6f7ccdcae265f2c2a Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 26 Jul 2022 10:04:16 -0700 Subject: [PATCH 04/16] wip tab drag and drop --- Cargo.lock | 9 + crates/contacts_panel/src/contacts_panel.rs | 2 +- crates/drag_and_drop/Cargo.toml | 15 + crates/drag_and_drop/src/drag_and_drop.rs | 151 ++++ crates/gpui/src/elements/container.rs | 5 + .../gpui/src/elements/mouse_event_handler.rs | 35 +- crates/gpui/src/elements/tooltip.rs | 9 +- crates/gpui/src/presenter.rs | 207 +++-- crates/gpui/src/scene.rs | 2 + crates/gpui/src/scene/mouse_region.rs | 199 ++--- crates/gpui/src/scene/mouse_region_event.rs | 231 +++++ crates/project_panel/src/project_panel.rs | 58 +- crates/terminal/src/connected_el.rs | 4 +- crates/terminal/src/terminal_element.rs | 793 ++++++++++++++++++ crates/theme/src/theme.rs | 16 + crates/workspace/Cargo.toml | 1 + crates/workspace/src/drag_and_drop.rs | 0 crates/workspace/src/pane.rs | 281 ++++--- crates/workspace/src/sidebar.rs | 32 +- crates/workspace/src/workspace.rs | 5 + 20 files changed, 1642 insertions(+), 413 deletions(-) create mode 100644 crates/drag_and_drop/Cargo.toml create mode 100644 crates/drag_and_drop/src/drag_and_drop.rs create mode 100644 crates/gpui/src/scene/mouse_region_event.rs create mode 100644 crates/terminal/src/terminal_element.rs delete mode 100644 crates/workspace/src/drag_and_drop.rs diff --git a/Cargo.lock b/Cargo.lock index 4256d24c9d..eb89ff0392 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1577,6 +1577,14 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "drag_and_drop" +version = "0.1.0" +dependencies = [ + "collections", + "gpui", +] + [[package]] name = "dwrote" version = "0.11.0" @@ -6941,6 +6949,7 @@ dependencies = [ "clock", "collections", "context_menu", + "drag_and_drop", "futures", "gpui", "language", diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 9198efceba..4c9f846576 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -566,7 +566,7 @@ impl ContactsPanel { button .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, cx| { - let project = project_handle.upgrade(cx.deref_mut()); + let project = project_handle.upgrade(cx.app); cx.dispatch_action(ToggleProjectOnline { project }) }) .with_tooltip::( diff --git a/crates/drag_and_drop/Cargo.toml b/crates/drag_and_drop/Cargo.toml new file mode 100644 index 0000000000..2fd8ce27b8 --- /dev/null +++ b/crates/drag_and_drop/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "drag_and_drop" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/drag_and_drop.rs" +doctest = false + +[dependencies] +collections = { path = "../collections" } +gpui = { path = "../gpui" } + +[dev-dependencies] +gpui = { path = "../gpui", features = ["test-support"] } \ No newline at end of file diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs new file mode 100644 index 0000000000..37c331fbb1 --- /dev/null +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -0,0 +1,151 @@ +use std::{any::Any, sync::Arc}; + +use gpui::{ + elements::{Container, MouseEventHandler}, + geometry::{rect::RectF, vector::Vector2F}, + Element, ElementBox, EventContext, MouseButton, RenderContext, View, ViewContext, + WeakViewHandle, +}; + +struct State { + position: Vector2F, + region_offset: Vector2F, + payload: Arc, + render: Arc, &mut RenderContext) -> ElementBox>, +} + +impl Clone for State { + fn clone(&self) -> Self { + Self { + position: self.position.clone(), + region_offset: self.region_offset.clone(), + payload: self.payload.clone(), + render: self.render.clone(), + } + } +} + +pub struct DragAndDrop { + parent: WeakViewHandle, + currently_dragged: Option>, +} + +impl DragAndDrop { + pub fn new(parent: WeakViewHandle, cx: &mut ViewContext) -> Self { + // TODO: Figure out if detaching here would result in a memory leak + cx.observe_global::(|cx| { + if let Some(parent) = cx.global::().parent.upgrade(cx) { + parent.update(cx, |_, cx| cx.notify()) + } + }) + .detach(); + + Self { + parent, + currently_dragged: None, + } + } + + pub fn currently_dragged(&self) -> Option<(Vector2F, &T)> { + self.currently_dragged.as_ref().and_then( + |State { + position, payload, .. + }| { + payload + .downcast_ref::() + .map(|payload| (position.clone(), payload)) + }, + ) + } + + pub fn dragging( + relative_to: Option, + position: Vector2F, + payload: Arc, + cx: &mut EventContext, + render: Arc) -> ElementBox>, + ) { + cx.update_global::(|this, cx| { + let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() { + previous_state.region_offset + } else { + if let Some(relative_to) = relative_to { + relative_to.origin() - position + } else { + Vector2F::zero() + } + }; + + this.currently_dragged = Some(State { + region_offset, + position, + payload, + render: Arc::new(move |payload, cx| { + render(payload.downcast_ref::().unwrap(), cx) + }), + }); + + if let Some(parent) = this.parent.upgrade(cx) { + parent.update(cx, |_, cx| cx.notify()) + } + }); + } + + pub fn render(cx: &mut RenderContext) -> Option { + let currently_dragged = cx.global::().currently_dragged.clone(); + + currently_dragged.map( + |State { + region_offset, + position, + payload, + render, + }| { + let position = position + region_offset; + + MouseEventHandler::new::(0, cx, |_, cx| { + Container::new(render(payload, cx)) + .with_margin_left(position.x()) + .with_margin_top(position.y()) + .boxed() + }) + .on_up(MouseButton::Left, |_, cx| { + cx.defer(|cx| { + cx.update_global::(|this, _| this.currently_dragged.take()); + }); + cx.propogate_event(); + }) + .boxed() + }, + ) + } +} + +pub trait Draggable { + fn as_draggable( + self, + payload: P, + render: impl 'static + Fn(&P, &mut RenderContext) -> ElementBox, + ) -> Self + where + Self: Sized; +} + +impl Draggable for MouseEventHandler { + fn as_draggable( + self, + payload: P, + render: impl 'static + Fn(&P, &mut RenderContext) -> ElementBox, + ) -> Self + where + Self: Sized, + { + let payload = Arc::new(payload); + let render = Arc::new(render); + self.on_drag(MouseButton::Left, move |e, cx| { + let payload = payload.clone(); + let render = render.clone(); + DragAndDrop::::dragging(Some(e.region), e.position, payload, cx, render) + }) + } +} diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index a581e60b74..ad9d7fe0cb 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -104,6 +104,11 @@ impl Container { self } + pub fn with_padding_top(mut self, padding: f32) -> Self { + self.style.padding.top = padding; + self + } + pub fn with_padding_bottom(mut self, padding: f32) -> Self { self.style.padding.bottom = padding; self diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 35e8d6407e..570ec1b1a6 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -5,10 +5,13 @@ use crate::{ vector::{vec2f, Vector2F}, }, platform::CursorStyle, - scene::{CursorRegion, HandlerSet}, + scene::{ + ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent, + DragRegionEvent, HandlerSet, HoverRegionEvent, MoveRegionEvent, UpOutRegionEvent, + UpRegionEvent, + }, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext, - MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext, - RenderContext, SizeConstraint, View, + MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, }; use serde_json::json; use std::{any::TypeId, ops::Range}; @@ -42,10 +45,18 @@ impl MouseEventHandler { self } + pub fn on_move( + mut self, + handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_move(handler); + self + } + pub fn on_down( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down(button, handler); self @@ -54,7 +65,7 @@ impl MouseEventHandler { pub fn on_up( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_up(button, handler); self @@ -63,7 +74,7 @@ impl MouseEventHandler { pub fn on_click( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_click(button, handler); self @@ -72,7 +83,7 @@ impl MouseEventHandler { pub fn on_down_out( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down_out(button, handler); self @@ -81,16 +92,16 @@ impl MouseEventHandler { pub fn on_up_out( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static, ) -> Self { - self.handlers = self.handlers.on_up(button, handler); + self.handlers = self.handlers.on_up_out(button, handler); self } pub fn on_drag( mut self, button: MouseButton, - handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_drag(button, handler); self @@ -99,7 +110,7 @@ impl MouseEventHandler { pub fn on_drag_over( mut self, button: MouseButton, - handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_drag_over(button, handler); self @@ -107,7 +118,7 @@ impl MouseEventHandler { pub fn on_hover( mut self, - handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_hover(handler); self diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index 68605fb6b3..26f8a450db 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -7,8 +7,8 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::json, presenter::MeasurementContext, - Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext, - SizeConstraint, Task, View, + Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint, + Task, View, }; use serde::Deserialize; use std::{ @@ -93,10 +93,11 @@ impl Tooltip { }; let child = MouseEventHandler::new::, _, _>(id, cx, |_, _| child) - .on_hover(move |hover, MouseMovedEvent { position, .. }, cx| { + .on_hover(move |e, cx| { + let position = e.position; let window_id = cx.window_id(); if let Some(view_id) = cx.view_id() { - if hover { + if e.started { if !state.visible.get() { state.position.set(position); diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 899beacf47..87dc6377c7 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -6,7 +6,11 @@ use crate::{ json::{self, ToJson}, keymap::Keystroke, platform::{CursorStyle, Event}, - scene::{CursorRegion, MouseRegionEvent}, + scene::{ + ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent, + DragRegionEvent, HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, + UpRegionEvent, + }, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId, @@ -140,8 +144,7 @@ impl Presenter { if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { - let mut invalidated_views = Vec::new(); - self.handle_hover_events(&event, &mut invalidated_views, cx); + let invalidated_views = self.handle_hover_events(&event, cx).invalidated_views; for view_id in invalidated_views { cx.notify_view(self.window_id, view_id); @@ -216,48 +219,60 @@ impl Presenter { pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool { if let Some(root_view_id) = cx.root_view_id(self.window_id) { - let mut invalidated_views = Vec::new(); let mut events_to_send = Vec::new(); - match &event { Event::MouseDown(e @ MouseButtonEvent { position, .. }) => { - let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(*position) { - if !hit { - hit = true; - invalidated_views.push(region.view_id); - events_to_send - .push((region.clone(), MouseRegionEvent::Down(e.clone()))); - self.clicked_region = Some(region.clone()); - self.prev_drag_position = Some(*position); - } + events_to_send.push(( + region.clone(), + MouseRegionEvent::Down(DownRegionEvent { + region: region.bounds, + platform_event: e.clone(), + }), + )); } else { - events_to_send - .push((region.clone(), MouseRegionEvent::DownOut(e.clone()))); + events_to_send.push(( + region.clone(), + MouseRegionEvent::DownOut(DownOutRegionEvent { + region: region.bounds, + platform_event: e.clone(), + }), + )); } } } Event::MouseUp(e @ MouseButtonEvent { position, .. }) => { - let mut hit = false; for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(*position) { - if !hit { - hit = true; - invalidated_views.push(region.view_id); - events_to_send - .push((region.clone(), MouseRegionEvent::Up(e.clone()))); - } + events_to_send.push(( + region.clone(), + MouseRegionEvent::Up(UpRegionEvent { + region: region.bounds, + platform_event: e.clone(), + }), + )); } else { - events_to_send - .push((region.clone(), MouseRegionEvent::UpOut(e.clone()))); + events_to_send.push(( + region.clone(), + MouseRegionEvent::UpOut(UpOutRegionEvent { + region: region.bounds, + platform_event: e.clone(), + }), + )); } } self.prev_drag_position.take(); if let Some(region) = self.clicked_region.take() { - invalidated_views.push(region.view_id); if region.bounds.contains_point(*position) { - events_to_send.push((region, MouseRegionEvent::Click(e.clone()))); + let bounds = region.bounds.clone(); + events_to_send.push(( + region, + MouseRegionEvent::Click(ClickRegionEvent { + region: bounds, + platform_event: e.clone(), + }), + )); } } } @@ -269,9 +284,28 @@ impl Presenter { { events_to_send.push(( clicked_region.clone(), +<<<<<<< HEAD MouseRegionEvent::Drag(*prev_drag_position, *e), +======= + MouseRegionEvent::Drag(DragRegionEvent { + region: clicked_region.bounds, + prev_drag_position: *prev_drag_position, + platform_event: e.clone(), + }), +>>>>>>> 4bd8a4b0 (wip tab drag and drop) )); - *prev_drag_position = *position; + } + + for (region, _) in self.mouse_regions.iter().rev() { + if region.bounds.contains_point(*position) { + events_to_send.push(( + region.clone(), + MouseRegionEvent::Move(MoveRegionEvent { + region: region.bounds, + platform_event: e.clone(), + }), + )); + } } self.last_mouse_moved_event = Some(event.clone()); @@ -279,12 +313,12 @@ impl Presenter { _ => {} } - let (mut handled, mut event_cx) = - self.handle_hover_events(&event, &mut invalidated_views, cx); + let (invalidated_views, dispatch_directives, handled) = { + let mut event_cx = self.handle_hover_events(&event, cx); + event_cx.process_region_events(events_to_send); - for (region, event) in events_to_send { - if event.is_local() { - handled = true; + if !event_cx.handled { + event_cx.handled = event_cx.dispatch_event(root_view_id, &event); } if let Some(callback) = region.handlers.get(&event.handler_key()) { @@ -292,7 +326,13 @@ impl Presenter { callback(event, event_cx); }) } - } + + ( + event_cx.invalidated_views, + event_cx.dispatched_actions, + event_cx.handled, + ) + }; if !handled { handled = event_cx.dispatch_event(root_view_id, &event); @@ -313,9 +353,8 @@ impl Presenter { fn handle_hover_events<'a>( &'a mut self, event: &Event, - invalidated_views: &mut Vec, cx: &'a mut MutableAppContext, - ) -> (bool, EventContext<'a>) { + ) -> EventContext<'a> { let mut events_to_send = Vec::new(); if let Event::MouseMoved( @@ -343,46 +382,48 @@ impl Presenter { hover_depth = Some(*depth); if let Some(region_id) = region.id() { if !self.hovered_region_ids.contains(®ion_id) { - invalidated_views.push(region.view_id); let region_event = if pressed_button.is_some() { - MouseRegionEvent::DragOver(true, e.clone()) + MouseRegionEvent::DragOver(DragOverRegionEvent { + region: region.bounds, + started: true, + platform_event: e.clone(), + }) } else { - MouseRegionEvent::Hover(true, e.clone()) + MouseRegionEvent::Hover(HoverRegionEvent { + region: region.bounds, + started: true, + platform_event: e.clone(), + }) }; events_to_send.push((region.clone(), region_event)); self.hovered_region_ids.insert(region_id); } } } else if let Some(region_id) = region.id() { - if self.hovered_region_ids.contains(®ion_id) { - invalidated_views.push(region.view_id); - let region_event = if pressed_button.is_some() { - MouseRegionEvent::DragOver(false, e.clone()) - } else { - MouseRegionEvent::Hover(false, e.clone()) - }; - events_to_send.push((region.clone(), region_event)); - self.hovered_region_ids.remove(®ion_id); - } + if self.hovered_region_ids.contains(®ion_id) { + let region_event = if pressed_button.is_some() { + MouseRegionEvent::DragOver(DragOverRegionEvent { + region: region.bounds, + started: false, + platform_event: e.clone(), + }) + } else { + MouseRegionEvent::Hover(HoverRegionEvent { + region: region.bounds, + started: false, + platform_event: e.clone(), + }) + }; + events_to_send.push((region.clone(), region_event)); + self.hovered_region_ids.remove(®ion_id); + } } } } let mut event_cx = self.build_event_context(cx); - let mut handled = false; - - for (region, event) in events_to_send { - if event.is_local() { - handled = true; - } - if let Some(callback) = region.handlers.get(&event.handler_key()) { - event_cx.with_current_view(region.view_id, |event_cx| { - callback(event, event_cx); - }) - } - } - - (handled, event_cx) + event_cx.process_region_events(events_to_send); + event_cx } pub fn build_event_context<'a>( @@ -396,6 +437,9 @@ impl Presenter { view_stack: Default::default(), invalidated_views: Default::default(), notify_count: 0, + clicked_region: &mut self.clicked_region, + prev_drag_position: &mut self.prev_drag_position, + handled: false, window_id: self.window_id, app: cx, } @@ -615,6 +659,9 @@ pub struct EventContext<'a> { pub window_id: usize, pub notify_count: usize, view_stack: Vec, + clicked_region: &'a mut Option, + prev_drag_position: &'a mut Option, + handled: bool, invalidated_views: HashSet, } @@ -630,6 +677,36 @@ impl<'a> EventContext<'a> { } } + fn process_region_events(&mut self, events: Vec<(MouseRegion, MouseRegionEvent)>) { + for (region, event) in events { + if event.is_local() { + if self.handled { + continue; + } + + match &event { + MouseRegionEvent::Down(e) => { + *self.clicked_region = Some(region.clone()); + *self.prev_drag_position = Some(e.position); + } + MouseRegionEvent::Drag(e) => { + *self.prev_drag_position = Some(e.position); + } + _ => {} + } + + self.invalidated_views.insert(region.view_id); + } + + if let Some(callback) = region.handlers.get(&event.handler_key()) { + self.handled = true; + self.with_current_view(region.view_id, |event_cx| { + callback(event, event_cx); + }) + } + } + } + fn with_current_view(&mut self, view_id: usize, f: F) -> T where F: FnOnce(&mut Self) -> T, @@ -681,6 +758,10 @@ impl<'a> EventContext<'a> { pub fn notify_count(&self) -> usize { self.notify_count } + + pub fn propogate_event(&mut self) { + self.handled = false; + } } impl<'a> Deref for EventContext<'a> { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index a1f12b76d3..086af5f64d 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -1,4 +1,5 @@ mod mouse_region; +mod mouse_region_event; use serde::Deserialize; use serde_json::json; @@ -13,6 +14,7 @@ use crate::{ ImageData, }; pub use mouse_region::*; +pub use mouse_region_event::*; pub struct Scene { scale_factor: f32, diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 6482310259..308722fced 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -1,13 +1,14 @@ -use std::{ - any::TypeId, - mem::{discriminant, Discriminant}, - rc::Rc, -}; +use std::{any::TypeId, mem::Discriminant, rc::Rc}; use collections::HashMap; -use pathfinder_geometry::{rect::RectF, vector::Vector2F}; +use pathfinder_geometry::rect::RectF; -use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; +use crate::{EventContext, MouseButton}; + +use super::mouse_region_event::{ + ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent, DragRegionEvent, + HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, +}; #[derive(Clone, Default)] pub struct MouseRegion { @@ -52,7 +53,7 @@ impl MouseRegion { pub fn on_down( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down(button, handler); self @@ -61,7 +62,7 @@ impl MouseRegion { pub fn on_up( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_up(button, handler); self @@ -70,7 +71,7 @@ impl MouseRegion { pub fn on_click( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_click(button, handler); self @@ -79,7 +80,7 @@ impl MouseRegion { pub fn on_down_out( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_down_out(button, handler); self @@ -88,7 +89,7 @@ impl MouseRegion { pub fn on_up_out( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_up_out(button, handler); self @@ -97,7 +98,7 @@ impl MouseRegion { pub fn on_drag( mut self, button: MouseButton, - handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_drag(button, handler); self @@ -106,7 +107,7 @@ impl MouseRegion { pub fn on_drag_over( mut self, button: MouseButton, - handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_drag_over(button, handler); self @@ -114,7 +115,7 @@ impl MouseRegion { pub fn on_hover( mut self, - handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, ) -> Self { self.handlers = self.handlers.on_hover(handler); self @@ -191,15 +192,32 @@ impl HandlerSet { self.set.get(key).cloned() } + pub fn on_move( + mut self, + handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::move_disc(), None), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::Move(e) = region_event { + handler(e, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}", + region_event); + } + })); + self + } + pub fn on_down( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::down_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Down(mouse_button_event) = region_event { - handler(mouse_button_event, cx); + if let MouseRegionEvent::Down(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}", @@ -212,12 +230,12 @@ impl HandlerSet { pub fn on_up( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::up_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Up(mouse_button_event) = region_event { - handler(mouse_button_event, cx); + if let MouseRegionEvent::Up(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}", @@ -230,12 +248,12 @@ impl HandlerSet { pub fn on_click( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::click_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Click(mouse_button_event) = region_event { - handler(mouse_button_event, cx); + if let MouseRegionEvent::Click(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}", @@ -248,12 +266,12 @@ impl HandlerSet { pub fn on_down_out( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::DownOut(mouse_button_event) = region_event { - handler(mouse_button_event, cx); + if let MouseRegionEvent::DownOut(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}", @@ -266,12 +284,12 @@ impl HandlerSet { pub fn on_up_out( mut self, button: MouseButton, - handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static, + handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::up_out_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::UpOut(mouse_button_event) = region_event { - handler(mouse_button_event, cx); + if let MouseRegionEvent::UpOut(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}", @@ -284,12 +302,12 @@ impl HandlerSet { pub fn on_drag( mut self, button: MouseButton, - handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::drag_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event { - handler(prev_drag_position, mouse_moved_event, cx); + if let MouseRegionEvent::Drag(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}", @@ -302,12 +320,12 @@ impl HandlerSet { pub fn on_drag_over( mut self, button: MouseButton, - handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::drag_over_disc(), Some(button)), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::DragOver(started, mouse_moved_event) = region_event { - handler(started, mouse_moved_event, cx); + if let MouseRegionEvent::DragOver(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DragOver, found {:?}", @@ -319,12 +337,12 @@ impl HandlerSet { pub fn on_hover( mut self, - handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static, + handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, ) -> Self { self.set.insert((MouseRegionEvent::hover_disc(), None), Rc::new(move |region_event, cx| { - if let MouseRegionEvent::Hover(started, mouse_moved_event) = region_event { - handler(started, mouse_moved_event, cx); + if let MouseRegionEvent::Hover(e) = region_event { + handler(e, cx); } else { panic!( "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}", @@ -334,106 +352,3 @@ impl HandlerSet { self } } - -#[derive(Debug)] -pub enum MouseRegionEvent { - Move(MouseMovedEvent), - Drag(Vector2F, MouseMovedEvent), - DragOver(bool, MouseMovedEvent), - Hover(bool, MouseMovedEvent), - Down(MouseButtonEvent), - Up(MouseButtonEvent), - Click(MouseButtonEvent), - UpOut(MouseButtonEvent), - DownOut(MouseButtonEvent), - ScrollWheel(ScrollWheelEvent), -} - -impl MouseRegionEvent { - pub fn move_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Move(Default::default())) - } - - pub fn drag_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Drag( - Default::default(), - Default::default(), - )) - } - - pub fn drag_over_disc() -> Discriminant { - discriminant(&MouseRegionEvent::DragOver( - Default::default(), - Default::default(), - )) - } - - pub fn hover_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Hover( - Default::default(), - Default::default(), - )) - } - - pub fn down_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Down(Default::default())) - } - - pub fn up_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Up(Default::default())) - } - - pub fn up_out_disc() -> Discriminant { - discriminant(&MouseRegionEvent::UpOut(Default::default())) - } - - pub fn click_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Click(Default::default())) - } - - pub fn down_out_disc() -> Discriminant { - discriminant(&MouseRegionEvent::DownOut(Default::default())) - } - - pub fn scroll_wheel_disc() -> Discriminant { - discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) - } - - pub fn is_local(&self) -> bool { - match self { - MouseRegionEvent::DownOut(_) - | MouseRegionEvent::UpOut(_) - | MouseRegionEvent::DragOver(_, _) => false, - _ => true, - } - } - - pub fn handler_key(&self) -> (Discriminant, Option) { - match self { - MouseRegionEvent::Move(_) => (Self::move_disc(), None), - MouseRegionEvent::Drag(_, MouseMovedEvent { pressed_button, .. }) => { - (Self::drag_disc(), *pressed_button) - } - MouseRegionEvent::DragOver(_, MouseMovedEvent { pressed_button, .. }) => { - (Self::drag_over_disc(), *pressed_button) - } - MouseRegionEvent::Hover(_, _) => (Self::hover_disc(), None), - MouseRegionEvent::Down(MouseButtonEvent { button, .. }) => { - (Self::down_disc(), Some(*button)) - } - MouseRegionEvent::Up(MouseButtonEvent { button, .. }) => { - (Self::up_disc(), Some(*button)) - } - MouseRegionEvent::Click(MouseButtonEvent { button, .. }) => { - (Self::click_disc(), Some(*button)) - } - MouseRegionEvent::UpOut(MouseButtonEvent { button, .. }) => { - (Self::up_out_disc(), Some(*button)) - } - MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => { - (Self::down_out_disc(), Some(*button)) - } - MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), - } - } -} diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs new file mode 100644 index 0000000000..6e33240e4b --- /dev/null +++ b/crates/gpui/src/scene/mouse_region_event.rs @@ -0,0 +1,231 @@ +use std::{ + mem::{discriminant, Discriminant}, + ops::Deref, +}; + +use pathfinder_geometry::{rect::RectF, vector::Vector2F}; + +use crate::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; + +#[derive(Debug, Default)] +pub struct MoveRegionEvent { + pub region: RectF, + pub platform_event: MouseMovedEvent, +} + +impl Deref for MoveRegionEvent { + type Target = MouseMovedEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct DragRegionEvent { + pub region: RectF, + pub prev_drag_position: Vector2F, + pub platform_event: MouseMovedEvent, +} + +impl Deref for DragRegionEvent { + type Target = MouseMovedEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct DragOverRegionEvent { + pub region: RectF, + pub started: bool, + pub platform_event: MouseMovedEvent, +} + +impl Deref for DragOverRegionEvent { + type Target = MouseMovedEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct HoverRegionEvent { + pub region: RectF, + pub started: bool, + pub platform_event: MouseMovedEvent, +} + +impl Deref for HoverRegionEvent { + type Target = MouseMovedEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct DownRegionEvent { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for DownRegionEvent { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct UpRegionEvent { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for UpRegionEvent { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct ClickRegionEvent { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for ClickRegionEvent { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct DownOutRegionEvent { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for DownOutRegionEvent { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct UpOutRegionEvent { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for UpOutRegionEvent { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug, Default)] +pub struct ScrollWheelRegionEvent { + pub region: RectF, + pub platform_event: ScrollWheelEvent, +} + +impl Deref for ScrollWheelRegionEvent { + type Target = ScrollWheelEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + +#[derive(Debug)] +pub enum MouseRegionEvent { + Move(MoveRegionEvent), + Drag(DragRegionEvent), + DragOver(DragOverRegionEvent), + Hover(HoverRegionEvent), + Down(DownRegionEvent), + Up(UpRegionEvent), + Click(ClickRegionEvent), + DownOut(DownOutRegionEvent), + UpOut(UpOutRegionEvent), + ScrollWheel(ScrollWheelRegionEvent), +} + +impl MouseRegionEvent { + pub fn move_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Move(Default::default())) + } + + pub fn drag_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Drag(Default::default())) + } + + pub fn drag_over_disc() -> Discriminant { + discriminant(&MouseRegionEvent::DragOver(Default::default())) + } + + pub fn hover_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Hover(Default::default())) + } + + pub fn down_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Down(Default::default())) + } + + pub fn up_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Up(Default::default())) + } + + pub fn up_out_disc() -> Discriminant { + discriminant(&MouseRegionEvent::UpOut(Default::default())) + } + + pub fn click_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Click(Default::default())) + } + + pub fn down_out_disc() -> Discriminant { + discriminant(&MouseRegionEvent::DownOut(Default::default())) + } + + pub fn scroll_wheel_disc() -> Discriminant { + discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) + } + + pub fn is_local(&self) -> bool { + match self { + MouseRegionEvent::DownOut(_) + | MouseRegionEvent::UpOut(_) + | MouseRegionEvent::DragOver(_) => false, + _ => true, + } + } + + pub fn handler_key(&self) -> (Discriminant, Option) { + match self { + MouseRegionEvent::Move(_) => (Self::move_disc(), None), + MouseRegionEvent::Drag(e) => (Self::drag_disc(), e.pressed_button), + MouseRegionEvent::DragOver(e) => (Self::drag_over_disc(), e.pressed_button), + MouseRegionEvent::Hover(_) => (Self::hover_disc(), None), + MouseRegionEvent::Down(e) => (Self::down_disc(), Some(e.button)), + MouseRegionEvent::Up(e) => (Self::up_disc(), Some(e.button)), + MouseRegionEvent::Click(e) => (Self::click_disc(), Some(e.button)), + MouseRegionEvent::UpOut(e) => (Self::up_out_disc(), Some(e.button)), + MouseRegionEvent::DownOut(e) => (Self::down_out_disc(), Some(e.button)), + MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), + } + } +} diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 702c750ea2..8a071489ad 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,8 +12,7 @@ use gpui::{ impl_internal_actions, keymap, platform::CursorStyle, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, - MouseButtonEvent, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, - ViewHandle, + MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -1064,25 +1063,22 @@ impl ProjectPanel { .with_padding_left(padding) .boxed() }) - .on_click( - MouseButton::Left, - move |MouseButtonEvent { click_count, .. }, cx| { - if kind == EntryKind::Dir { - cx.dispatch_action(ToggleExpanded(entry_id)) - } else { - cx.dispatch_action(Open { - entry_id, - change_focus: click_count > 1, - }) - } - }, - ) - .on_down( - MouseButton::Right, - move |MouseButtonEvent { position, .. }, cx| { - cx.dispatch_action(DeployContextMenu { entry_id, position }) - }, - ) + .on_click(MouseButton::Left, move |e, cx| { + if kind == EntryKind::Dir { + cx.dispatch_action(ToggleExpanded(entry_id)) + } else { + cx.dispatch_action(Open { + entry_id, + change_focus: e.click_count > 1, + }) + } + }) + .on_down(MouseButton::Right, move |e, cx| { + cx.dispatch_action(DeployContextMenu { + entry_id, + position: e.position, + }) + }) .with_cursor_style(CursorStyle::PointingHand) .boxed() } @@ -1129,16 +1125,16 @@ impl View for ProjectPanel { .expanded() .boxed() }) - .on_down( - MouseButton::Right, - move |MouseButtonEvent { position, .. }, cx| { - // When deploying the context menu anywhere below the last project entry, - // act as if the user clicked the root of the last worktree. - if let Some(entry_id) = last_worktree_root_id { - cx.dispatch_action(DeployContextMenu { entry_id, position }) - } - }, - ) + .on_down(MouseButton::Right, move |e, cx| { + // When deploying the context menu anywhere below the last project entry, + // act as if the user clicked the root of the last worktree. + if let Some(entry_id) = last_worktree_root_id { + cx.dispatch_action(DeployContextMenu { + entry_id, + position: e.position, + }) + } + }) .boxed(), ) .with_child(ChildView::new(&self.context_menu).boxed()) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index ab08efc1b3..f138173a0b 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -16,8 +16,8 @@ use gpui::{ }, json::json, text_layout::{Line, RunStyle}, - Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, - PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle, + Event, FontCache, KeyDownEvent, MouseButton, MouseRegion, PaintContext, Quad, ScrollWheelEvent, + TextLayoutCache, WeakModelHandle, WeakViewHandle, }; use itertools::Itertools; use ordered_float::OrderedFloat; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs new file mode 100644 index 0000000000..183d833382 --- /dev/null +++ b/crates/terminal/src/terminal_element.rs @@ -0,0 +1,793 @@ +use alacritty_terminal::{ + grid::{Dimensions, GridIterator, Indexed, Scroll}, + index::{Column as GridCol, Line as GridLine, Point, Side}, + selection::{Selection, SelectionRange, SelectionType}, + sync::FairMutex, + term::{ + cell::{Cell, Flags}, + SizeInfo, + }, + Term, +}; +use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; +use gpui::{ + color::Color, + elements::*, + fonts::{TextStyle, Underline}, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::json, + text_layout::{Line, RunStyle}, + Event, FontCache, KeyDownEvent, MouseButton, MouseRegion, PaintContext, Quad, ScrollWheelEvent, + SizeConstraint, TextLayoutCache, WeakModelHandle, +}; +use itertools::Itertools; +use ordered_float::OrderedFloat; +use settings::Settings; +use theme::TerminalStyle; +use util::ResultExt; + +use std::{cmp::min, ops::Range, sync::Arc}; +use std::{fmt::Debug, ops::Sub}; + +use crate::{color_translation::convert_color, connection::TerminalConnection, ZedListener}; + +///Scrolling is unbearably sluggish by default. Alacritty supports a configurable +///Scroll multiplier that is set to 3 by default. This will be removed when I +///Implement scroll bars. +const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; + +///Used to display the grid as passed to Alacritty and the TTY. +///Useful for debugging inconsistencies between behavior and display +#[cfg(debug_assertions)] +const DEBUG_GRID: bool = false; + +///The GPUI element that paints the terminal. +///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection? +pub struct TerminalEl { + connection: WeakModelHandle, + view_id: usize, + modal: bool, +} + +///New type pattern so I don't mix these two up +struct CellWidth(f32); +struct LineHeight(f32); + +struct LayoutLine { + cells: Vec, + highlighted_range: Option>, +} + +///New type pattern to ensure that we use adjusted mouse positions throughout the code base, rather than +struct PaneRelativePos(Vector2F); + +///Functionally the constructor for the PaneRelativePos type, mutates the mouse_position +fn relative_pos(mouse_position: Vector2F, origin: Vector2F) -> PaneRelativePos { + PaneRelativePos(mouse_position.sub(origin)) //Avoid the extra allocation by mutating +} + +#[derive(Clone, Debug, Default)] +struct LayoutCell { + point: Point, + text: Line, //NOTE TO SELF THIS IS BAD PERFORMANCE RN! + background_color: Color, +} + +impl LayoutCell { + fn new(point: Point, text: Line, background_color: Color) -> LayoutCell { + LayoutCell { + point, + text, + background_color, + } + } +} + +///The information generated during layout that is nescessary for painting +pub struct LayoutState { + layout_lines: Vec, + line_height: LineHeight, + em_width: CellWidth, + cursor: Option, + background_color: Color, + cur_size: SizeInfo, + terminal: Arc>>, + selection_color: Color, +} + +impl TerminalEl { + pub fn new( + view_id: usize, + connection: WeakModelHandle, + modal: bool, + ) -> TerminalEl { + TerminalEl { + view_id, + connection, + modal, + } + } +} + +impl Element for TerminalEl { + type LayoutState = LayoutState; + type PaintState = (); + + fn layout( + &mut self, + constraint: gpui::SizeConstraint, + cx: &mut gpui::LayoutContext, + ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { + //Settings immutably borrows cx here for the settings and font cache + //and we need to modify the cx to resize the terminal. So instead of + //storing Settings or the font_cache(), we toss them ASAP and then reborrow later + let text_style = make_text_style(cx.font_cache(), cx.global::()); + let line_height = LineHeight(cx.font_cache().line_height(text_style.font_size)); + let cell_width = CellWidth( + cx.font_cache() + .em_advance(text_style.font_id, text_style.font_size), + ); + let connection_handle = self.connection.upgrade(cx).unwrap(); + + //Tell the view our new size. Requires a mutable borrow of cx and the view + let cur_size = make_new_size(constraint, &cell_width, &line_height); + //Note that set_size locks and mutates the terminal. + connection_handle.update(cx.app, |connection, _| connection.set_size(cur_size)); + + let (selection_color, terminal_theme) = { + let theme = &(cx.global::()).theme; + (theme.editor.selection.selection, &theme.terminal) + }; + + let terminal_mutex = connection_handle.read(cx).term.clone(); + let term = terminal_mutex.lock(); + let grid = term.grid(); + let cursor_point = grid.cursor.point; + let cursor_text = grid[cursor_point.line][cursor_point.column].c.to_string(); + + let content = term.renderable_content(); + + let layout_lines = layout_lines( + content.display_iter, + &text_style, + terminal_theme, + cx.text_layout_cache, + self.modal, + content.selection, + ); + + let block_text = cx.text_layout_cache.layout_str( + &cursor_text, + text_style.font_size, + &[( + cursor_text.len(), + RunStyle { + font_id: text_style.font_id, + color: terminal_theme.colors.background, + underline: Default::default(), + }, + )], + ); + + let cursor = get_cursor_shape( + content.cursor.point.line.0 as usize, + content.cursor.point.column.0 as usize, + content.display_offset, + &line_height, + &cell_width, + cur_size.total_lines(), + &block_text, + ) + .map(move |(cursor_position, block_width)| { + let block_width = if block_width != 0.0 { + block_width + } else { + cell_width.0 + }; + + Cursor::new( + cursor_position, + block_width, + line_height.0, + terminal_theme.colors.cursor, + CursorShape::Block, + Some(block_text.clone()), + ) + }); + drop(term); + + let background_color = if self.modal { + terminal_theme.colors.modal_background + } else { + terminal_theme.colors.background + }; + + ( + constraint.max, + LayoutState { + layout_lines, + line_height, + em_width: cell_width, + cursor, + cur_size, + background_color, + terminal: terminal_mutex, + selection_color, + }, + ) + } + + fn paint( + &mut self, + bounds: gpui::geometry::rect::RectF, + visible_bounds: gpui::geometry::rect::RectF, + layout: &mut Self::LayoutState, + cx: &mut gpui::PaintContext, + ) -> Self::PaintState { + //Setup element stuff + let clip_bounds = Some(visible_bounds); + + cx.paint_layer(clip_bounds, |cx| { + let cur_size = layout.cur_size.clone(); + let origin = bounds.origin() + vec2f(layout.em_width.0, 0.); + + //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse + attach_mouse_handlers( + origin, + cur_size, + self.view_id, + &layout.terminal, + visible_bounds, + cx, + ); + + cx.paint_layer(clip_bounds, |cx| { + //Start with a background color + cx.scene.push_quad(Quad { + bounds: RectF::new(bounds.origin(), bounds.size()), + background: Some(layout.background_color), + border: Default::default(), + corner_radius: 0., + }); + + //Draw cell backgrounds + for layout_line in &layout.layout_lines { + for layout_cell in &layout_line.cells { + let position = vec2f( + (origin.x() + layout_cell.point.column as f32 * layout.em_width.0) + .floor(), + origin.y() + layout_cell.point.line as f32 * layout.line_height.0, + ); + let size = vec2f(layout.em_width.0.ceil(), layout.line_height.0); + + cx.scene.push_quad(Quad { + bounds: RectF::new(position, size), + background: Some(layout_cell.background_color), + border: Default::default(), + corner_radius: 0., + }) + } + } + }); + + //Draw Selection + cx.paint_layer(clip_bounds, |cx| { + let mut highlight_y = None; + let highlight_lines = layout + .layout_lines + .iter() + .filter_map(|line| { + if let Some(range) = &line.highlighted_range { + if let None = highlight_y { + highlight_y = Some( + origin.y() + + line.cells[0].point.line as f32 * layout.line_height.0, + ); + } + let start_x = origin.x() + + line.cells[range.start].point.column as f32 * layout.em_width.0; + let end_x = origin.x() + + line.cells[range.end].point.column as f32 * layout.em_width.0 + + layout.em_width.0; + + return Some(HighlightedRangeLine { start_x, end_x }); + } else { + return None; + } + }) + .collect::>(); + + if let Some(y) = highlight_y { + let hr = HighlightedRange { + start_y: y, //Need to change this + line_height: layout.line_height.0, + lines: highlight_lines, + color: layout.selection_color, + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.line_height.0, + }; + hr.paint(bounds, cx.scene); + } + }); + + cx.paint_layer(clip_bounds, |cx| { + for layout_line in &layout.layout_lines { + for layout_cell in &layout_line.cells { + let point = layout_cell.point; + + //Don't actually know the start_x for a line, until here: + let cell_origin = vec2f( + (origin.x() + point.column as f32 * layout.em_width.0).floor(), + origin.y() + point.line as f32 * layout.line_height.0, + ); + + layout_cell.text.paint( + cell_origin, + visible_bounds, + layout.line_height.0, + cx, + ); + } + } + }); + + //Draw cursor + if let Some(cursor) = &layout.cursor { + cx.paint_layer(clip_bounds, |cx| { + cursor.paint(origin, cx); + }) + } + + #[cfg(debug_assertions)] + if DEBUG_GRID { + cx.paint_layer(clip_bounds, |cx| { + draw_debug_grid(bounds, layout, cx); + }) + } + }); + } + + fn dispatch_event( + &mut self, + event: &gpui::Event, + _bounds: gpui::geometry::rect::RectF, + visible_bounds: gpui::geometry::rect::RectF, + layout: &mut Self::LayoutState, + _paint: &mut Self::PaintState, + cx: &mut gpui::EventContext, + ) -> bool { + match event { + Event::ScrollWheel(ScrollWheelEvent { + delta, position, .. + }) => visible_bounds + .contains_point(*position) + .then(|| { + let vertical_scroll = + (delta.y() / layout.line_height.0) * ALACRITTY_SCROLL_MULTIPLIER; + + if let Some(connection) = self.connection.upgrade(cx.app) { + connection.update(cx.app, |connection, _| { + connection + .term + .lock() + .scroll_display(Scroll::Delta(vertical_scroll.round() as i32)); + }) + } + }) + .is_some(), + Event::KeyDown(KeyDownEvent { keystroke, .. }) => { + if !cx.is_parent_view_focused() { + return false; + } + + self.connection + .upgrade(cx.app) + .map(|connection| { + connection + .update(cx.app, |connection, _| connection.try_keystroke(keystroke)) + }) + .unwrap_or(false) + } + _ => false, + } + } + + fn debug( + &self, + _bounds: gpui::geometry::rect::RectF, + _layout: &Self::LayoutState, + _paint: &Self::PaintState, + _cx: &gpui::DebugContext, + ) -> gpui::serde_json::Value { + json!({ + "type": "TerminalElement", + }) + } +} + +pub fn mouse_to_cell_data( + pos: Vector2F, + origin: Vector2F, + cur_size: SizeInfo, + display_offset: usize, +) -> (Point, alacritty_terminal::index::Direction) { + let relative_pos = relative_pos(pos, origin); + let point = grid_cell(&relative_pos, cur_size, display_offset); + let side = cell_side(&relative_pos, cur_size); + (point, side) +} + +///Configures a text style from the current settings. +fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { + // Pull the font family from settings properly overriding + let family_id = settings + .terminal_overrides + .font_family + .as_ref() + .and_then(|family_name| font_cache.load_family(&[family_name]).log_err()) + .or_else(|| { + settings + .terminal_defaults + .font_family + .as_ref() + .and_then(|family_name| font_cache.load_family(&[family_name]).log_err()) + }) + .unwrap_or(settings.buffer_font_family); + + TextStyle { + color: settings.theme.editor.text_color, + font_family_id: family_id, + font_family_name: font_cache.family_name(family_id).unwrap(), + font_id: font_cache + .select_font(family_id, &Default::default()) + .unwrap(), + font_size: settings + .terminal_overrides + .font_size + .or(settings.terminal_defaults.font_size) + .unwrap_or(settings.buffer_font_size), + font_properties: Default::default(), + underline: Default::default(), + } +} + +///Configures a size info object from the given information. +fn make_new_size( + constraint: SizeConstraint, + cell_width: &CellWidth, + line_height: &LineHeight, +) -> SizeInfo { + SizeInfo::new( + constraint.max.x() - cell_width.0, + constraint.max.y(), + cell_width.0, + line_height.0, + 0., + 0., + false, + ) +} + +fn layout_lines( + grid: GridIterator, + text_style: &TextStyle, + terminal_theme: &TerminalStyle, + text_layout_cache: &TextLayoutCache, + modal: bool, + selection_range: Option, +) -> Vec { + let lines = grid.group_by(|i| i.point.line); + lines + .into_iter() + .enumerate() + .map(|(line_index, (_, line))| { + let mut highlighted_range = None; + let cells = line + .enumerate() + .map(|(x_index, indexed_cell)| { + if selection_range + .map(|range| range.contains(indexed_cell.point)) + .unwrap_or(false) + { + let mut range = highlighted_range.take().unwrap_or(x_index..x_index); + range.end = range.end.max(x_index); + highlighted_range = Some(range); + } + + let cell_text = &indexed_cell.c.to_string(); + + let cell_style = cell_style(&indexed_cell, terminal_theme, text_style, modal); + + //This is where we might be able to get better performance + let layout_cell = text_layout_cache.layout_str( + cell_text, + text_style.font_size, + &[(cell_text.len(), cell_style)], + ); + + LayoutCell::new( + Point::new(line_index as i32, indexed_cell.point.column.0 as i32), + layout_cell, + convert_color(&indexed_cell.bg, &terminal_theme.colors, modal), + ) + }) + .collect::>(); + + LayoutLine { + cells, + highlighted_range, + } + }) + .collect::>() +} + +// Compute the cursor position and expected block width, may return a zero width if x_for_index returns +// the same position for sequential indexes. Use em_width instead +//TODO: This function is messy, too many arguments and too many ifs. Simplify. +fn get_cursor_shape( + line: usize, + line_index: usize, + display_offset: usize, + line_height: &LineHeight, + cell_width: &CellWidth, + total_lines: usize, + text_fragment: &Line, +) -> Option<(Vector2F, f32)> { + let cursor_line = line + display_offset; + if cursor_line <= total_lines { + let cursor_width = if text_fragment.width() == 0. { + cell_width.0 + } else { + text_fragment.width() + }; + + Some(( + vec2f( + line_index as f32 * cell_width.0, + cursor_line as f32 * line_height.0, + ), + cursor_width, + )) + } else { + None + } +} + +///Convert the Alacritty cell styles to GPUI text styles and background color +fn cell_style( + indexed: &Indexed<&Cell>, + style: &TerminalStyle, + text_style: &TextStyle, + modal: bool, +) -> RunStyle { + let flags = indexed.cell.flags; + let fg = convert_color(&indexed.cell.fg, &style.colors, modal); + + let underline = flags + .contains(Flags::UNDERLINE) + .then(|| Underline { + color: Some(fg), + squiggly: false, + thickness: OrderedFloat(1.), + }) + .unwrap_or_default(); + + RunStyle { + color: fg, + font_id: text_style.font_id, + underline, + } +} + +fn attach_mouse_handlers( + origin: Vector2F, + cur_size: SizeInfo, + view_id: usize, + terminal_mutex: &Arc>>, + visible_bounds: RectF, + cx: &mut PaintContext, +) { + let click_mutex = terminal_mutex.clone(); + let drag_mutex = terminal_mutex.clone(); + let mouse_down_mutex = terminal_mutex.clone(); + + cx.scene.push_mouse_region( + MouseRegion::new(view_id, None, visible_bounds) + .on_down(MouseButton::Left, move |e, _| { + let mut term = mouse_down_mutex.lock(); + + let (point, side) = mouse_to_cell_data( + e.position, + origin, + cur_size, + term.renderable_content().display_offset, + ); + term.selection = Some(Selection::new(SelectionType::Simple, point, side)) + }) + .on_click(MouseButton::Left, move |e, cx| { + let mut term = click_mutex.lock(); + + let (point, side) = mouse_to_cell_data( + e.position, + origin, + cur_size, + term.renderable_content().display_offset, + ); + + let selection_type = match e.click_count { + 0 => return, //This is a release + 1 => Some(SelectionType::Simple), + 2 => Some(SelectionType::Semantic), + 3 => Some(SelectionType::Lines), + _ => None, + }; + + let selection = selection_type + .map(|selection_type| Selection::new(selection_type, point, side)); + + term.selection = selection; + cx.focus_parent_view(); + cx.notify(); + }) + .on_drag(MouseButton::Left, move |e, cx| { + let mut term = drag_mutex.lock(); + + let (point, side) = mouse_to_cell_data( + e.position, + origin, + cur_size, + term.renderable_content().display_offset, + ); + + if let Some(mut selection) = term.selection.take() { + selection.update(point, side); + term.selection = Some(selection); + } + + cx.notify(); + }), + ); +} + +///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side() +fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side { + let x = pos.0.x() as usize; + let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize; + let half_cell_width = (cur_size.cell_width() / 2.0) as usize; + + let additional_padding = + (cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width(); + let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding; + + if cell_x > half_cell_width + // Edge case when mouse leaves the window. + || x as f32 >= end_of_grid + { + Side::Right + } else { + Side::Left + } +} + +///Copied (with modifications) from alacritty/src/event.rs > Mouse::point() +///Position is a pane-relative position. That means the top left corner of the mouse +///Region should be (0,0) +fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point { + let pos = pos.0; + let col = pos.x() / cur_size.cell_width(); //TODO: underflow... + let col = min(GridCol(col as usize), cur_size.last_column()); + + let line = pos.y() / cur_size.cell_height(); + let line = min(line as i32, cur_size.bottommost_line().0); + + //when clicking, need to ADD to get to the top left cell + //e.g. total_lines - viewport_height, THEN subtract display offset + //0 -> total_lines - viewport_height - display_offset + mouse_line + + Point::new(GridLine(line - display_offset as i32), col) +} + +///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between +///Display and conceptual grid. +#[cfg(debug_assertions)] +fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { + let width = layout.cur_size.width(); + let height = layout.cur_size.height(); + //Alacritty uses 'as usize', so shall we. + for col in 0..(width / layout.em_width.0).round() as usize { + cx.scene.push_quad(Quad { + bounds: RectF::new( + bounds.origin() + vec2f((col + 1) as f32 * layout.em_width.0, 0.), + vec2f(1., height), + ), + background: Some(Color::green()), + border: Default::default(), + corner_radius: 0., + }); + } + for row in 0..((height / layout.line_height.0) + 1.0).round() as usize { + cx.scene.push_quad(Quad { + bounds: RectF::new( + bounds.origin() + vec2f(layout.em_width.0, row as f32 * layout.line_height.0), + vec2f(width, 1.), + ), + background: Some(Color::green()), + border: Default::default(), + corner_radius: 0., + }); + } +} + +mod test { + + #[test] + fn test_mouse_to_selection() { + let term_width = 100.; + let term_height = 200.; + let cell_width = 10.; + let line_height = 20.; + let mouse_pos_x = 100.; //Window relative + let mouse_pos_y = 100.; //Window relative + let origin_x = 10.; + let origin_y = 20.; + + let cur_size = alacritty_terminal::term::SizeInfo::new( + term_width, + term_height, + cell_width, + line_height, + 0., + 0., + false, + ); + + let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); + let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in + let (point, _) = + crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0); + assert_eq!( + point, + alacritty_terminal::index::Point::new( + alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), + alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), + ) + ); + } + + #[test] + fn test_mouse_to_selection_off_edge() { + let term_width = 100.; + let term_height = 200.; + let cell_width = 10.; + let line_height = 20.; + let mouse_pos_x = 100.; //Window relative + let mouse_pos_y = 100.; //Window relative + let origin_x = 10.; + let origin_y = 20.; + + let cur_size = alacritty_terminal::term::SizeInfo::new( + term_width, + term_height, + cell_width, + line_height, + 0., + 0., + false, + ); + + let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y); + let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in + let (point, _) = + crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0); + assert_eq!( + point, + alacritty_terminal::index::Point::new( + alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32), + alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize), + ) + ); + } +} diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1aaeed2d78..08fb65a001 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -78,6 +78,22 @@ pub struct TabBar { pub height: f32, } +impl TabBar { + pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab { + let tabs = if pane_active { + &self.active_pane + } else { + &self.inactive_pane + }; + + if tab_active { + &tabs.active_tab + } else { + &tabs.inactive_tab + } + } +} + #[derive(Clone, Deserialize, Default)] pub struct TabStyles { pub active_tab: Tab, diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 3534e293f8..c40ce56389 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -15,6 +15,7 @@ client = { path = "../client" } clock = { path = "../clock" } collections = { path = "../collections" } context_menu = { path = "../context_menu" } +drag_and_drop = { path = "../drag_and_drop" } gpui = { path = "../gpui" } language = { path = "../language" } menu = { path = "../menu" } diff --git a/crates/workspace/src/drag_and_drop.rs b/crates/workspace/src/drag_and_drop.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2f58f7cba4..68878dc6a4 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -876,7 +876,7 @@ impl Pane { }); } - fn render_tabs(&mut self, cx: &mut RenderContext) -> impl Element { + fn render_tab_bar(&mut self, cx: &mut RenderContext) -> impl Element { let theme = cx.global::().theme.clone(); enum Tabs {} @@ -889,131 +889,38 @@ impl Pane { None }; - let is_pane_active = self.is_active; - - let tab_styles = match is_pane_active { - true => theme.workspace.tab_bar.active_pane.clone(), - false => theme.workspace.tab_bar.inactive_pane.clone(), - }; - let filler_style = tab_styles.inactive_tab.clone(); + let pane_active = self.is_active; let mut row = Flex::row().scrollable::(1, autoscroll, cx); - for (ix, (item, detail)) in self.items.iter().zip(self.tab_details(cx)).enumerate() { - let item_id = item.id(); + for (ix, (item, detail)) in self + .items + .iter() + .cloned() + .zip(self.tab_details(cx)) + .enumerate() + { let detail = if detail == 0 { None } else { Some(detail) }; - let is_tab_active = ix == self.active_item_index; - - let close_tab_callback = { - let pane = pane.clone(); - move |_, cx: &mut EventContext| { - cx.dispatch_action(CloseItem { - item_id, - pane: pane.clone(), - }) - } - }; + let tab_active = ix == self.active_item_index; row.add_child({ - let mut tab_style = match is_tab_active { - true => tab_styles.active_tab.clone(), - false => tab_styles.inactive_tab.clone(), - }; + MouseEventHandler::new::(ix, cx, { + let item = item.clone(); + let pane = pane.clone(); + let hovered = mouse_state.hovered; - let title = item.tab_content(detail, &tab_style, cx); - - if ix == 0 { - tab_style.container.border.left = false; - } - - MouseEventHandler::new::(ix, cx, |_, cx| { - Container::new( - Flex::row() - .with_child( - Align::new({ - let diameter = 7.0; - let icon_color = if item.has_conflict(cx) { - Some(tab_style.icon_conflict) - } else if item.is_dirty(cx) { - Some(tab_style.icon_dirty) - } else { - None - }; - - ConstrainedBox::new( - Canvas::new(move |bounds, _, cx| { - if let Some(color) = icon_color { - let square = RectF::new( - bounds.origin(), - vec2f(diameter, diameter), - ); - cx.scene.push_quad(Quad { - bounds: square, - background: Some(color), - border: Default::default(), - corner_radius: diameter / 2., - }); - } - }) - .boxed(), - ) - .with_width(diameter) - .with_height(diameter) - .boxed() - }) - .boxed(), - ) - .with_child( - Container::new(Align::new(title).boxed()) - .with_style(ContainerStyle { - margin: Margin { - left: tab_style.spacing, - right: tab_style.spacing, - ..Default::default() - }, - ..Default::default() - }) - .boxed(), - ) - .with_child( - Align::new( - ConstrainedBox::new(if mouse_state.hovered { - enum TabCloseButton {} - let icon = Svg::new("icons/x_mark_thin_8.svg"); - MouseEventHandler::new::( - item_id, - cx, - |mouse_state, _| { - if mouse_state.hovered { - icon.with_color(tab_style.icon_close_active) - .boxed() - } else { - icon.with_color(tab_style.icon_close) - .boxed() - } - }, - ) - .with_padding(Padding::uniform(4.)) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, close_tab_callback.clone()) - .on_click( - MouseButton::Middle, - close_tab_callback.clone(), - ) - .named("close-tab-icon") - } else { - Empty::new().boxed() - }) - .with_width(tab_style.icon_width) - .boxed(), - ) - .boxed(), - ) - .boxed(), - ) - .with_style(tab_style.container) - .boxed() + move |_, cx| { + Self::render_tab( + &item, + pane, + detail, + hovered, + pane_active, + tab_active, + cx, + ) + } }) - .with_cursor_style(if is_tab_active && is_pane_active { + .with_cursor_style(if pane_active && tab_active { CursorStyle::Arrow } else { CursorStyle::PointingHand @@ -1021,24 +928,20 @@ impl Pane { .on_down(MouseButton::Left, move |_, cx| { cx.dispatch_action(ActivateItem(ix)); }) - .on_click(MouseButton::Middle, close_tab_callback) - .on_drag(MouseButton::Left, |_, cx| { - cx.global::().dragging(some view handle) - }) - .on_up_out(MouseButton::Left, |_, cx| { - cx.global::().stopped_dragging(some view handle) - }) - .on_drag_over(MouseButton::Left, |started, _, _, cx| { - if started { - if let Some(tab) = cx.global::().current_dragged::() { - cx.dispatch_action(ReceivingTab); - } + .on_click(MouseButton::Middle, { + let pane = pane.clone(); + move |_, cx: &mut EventContext| { + cx.dispatch_action(CloseItem { + item_id: item.id(), + pane: pane.clone(), + }) } }) .boxed() }) } + let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); row.add_child( Empty::new() .contained() @@ -1088,6 +991,109 @@ impl Pane { tab_details } + + fn render_tab( + item: &Box, + pane: WeakViewHandle, + detail: Option, + hovered: bool, + pane_active: bool, + tab_active: bool, + cx: &mut RenderContext, + ) -> ElementBox { + let theme = cx.global::().theme.clone(); + let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active); + let title = item.tab_content(detail, tab_style, cx); + + Container::new( + Flex::row() + .with_child( + Align::new({ + let diameter = 7.0; + let icon_color = if item.has_conflict(cx) { + Some(tab_style.icon_conflict) + } else if item.is_dirty(cx) { + Some(tab_style.icon_dirty) + } else { + None + }; + + ConstrainedBox::new( + Canvas::new(move |bounds, _, cx| { + if let Some(color) = icon_color { + let square = + RectF::new(bounds.origin(), vec2f(diameter, diameter)); + cx.scene.push_quad(Quad { + bounds: square, + background: Some(color), + border: Default::default(), + corner_radius: diameter / 2., + }); + } + }) + .boxed(), + ) + .with_width(diameter) + .with_height(diameter) + .boxed() + }) + .boxed(), + ) + .with_child( + Container::new(Align::new(title).boxed()) + .with_style(ContainerStyle { + margin: Margin { + left: tab_style.spacing, + right: tab_style.spacing, + ..Default::default() + }, + ..Default::default() + }) + .boxed(), + ) + .with_child( + Align::new( + ConstrainedBox::new(if hovered { + let item_id = item.id(); + enum TabCloseButton {} + let icon = Svg::new("icons/x_mark_thin_8.svg"); + MouseEventHandler::new::( + item_id, + cx, + |mouse_state, _| { + if mouse_state.hovered { + icon.with_color(tab_style.icon_close_active).boxed() + } else { + icon.with_color(tab_style.icon_close).boxed() + } + }, + ) + .with_padding(Padding::uniform(4.)) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let pane = pane.clone(); + move |_, cx| { + cx.dispatch_action(CloseItem { + item_id, + pane: pane.clone(), + }) + } + }) + .on_click(MouseButton::Middle, |_, cx| cx.propogate_event()) + .named("close-tab-icon") + } else { + Empty::new().boxed() + }) + .with_width(tab_style.icon_width) + .boxed(), + ) + .boxed(), + ) + .boxed(), + ) + .with_style(tab_style.container) + .boxed() + } } impl Entity for Pane { @@ -1110,7 +1116,7 @@ impl View for Pane { Flex::column() .with_child({ let mut tab_row = Flex::row() - .with_child(self.render_tabs(cx).flex(1., true).named("tabs")); + .with_child(self.render_tab_bar(cx).flex(1., true).named("tabs")); if self.is_active { tab_row.add_children([ @@ -1167,12 +1173,11 @@ impl View for Pane { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_down( - MouseButton::Left, - |MouseButtonEvent { position, .. }, cx| { - cx.dispatch_action(DeploySplitMenu { position }); - }, - ) + .on_down(MouseButton::Left, |e, cx| { + cx.dispatch_action(DeploySplitMenu { + position: e.position, + }); + }) .boxed(), ]) } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 6749de36a9..819959eb75 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -1,7 +1,7 @@ use crate::StatusItemView; use gpui::{ elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity, - MouseButton, MouseMovedEvent, RenderContext, Subscription, View, ViewContext, ViewHandle, + MouseButton, RenderContext, Subscription, View, ViewContext, ViewHandle, }; use serde::Deserialize; use settings::Settings; @@ -189,26 +189,18 @@ impl Sidebar { }) .with_cursor_style(CursorStyle::ResizeLeftRight) .on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere - .on_drag( - MouseButton::Left, - move |old_position, - MouseMovedEvent { - position: new_position, - .. - }, - cx| { - let delta = new_position.x() - old_position.x(); - let prev_width = *actual_width.borrow(); - *custom_width.borrow_mut() = 0f32 - .max(match side { - Side::Left => prev_width + delta, - Side::Right => prev_width - delta, - }) - .round(); + .on_drag(MouseButton::Left, move |e, cx| { + let delta = e.prev_drag_position.x() - e.position.x(); + let prev_width = *actual_width.borrow(); + *custom_width.borrow_mut() = 0f32 + .max(match side { + Side::Left => prev_width + delta, + Side::Right => prev_width - delta, + }) + .round(); - cx.notify(); - }, - ) + cx.notify(); + }) .boxed() } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 40934f4ed7..fa81cd9f11 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -16,6 +16,7 @@ use client::{ }; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; +use drag_and_drop::DragAndDrop; use futures::{channel::oneshot, FutureExt}; use gpui::{ actions, @@ -895,6 +896,9 @@ impl Workspace { status_bar }); + let drag_and_drop = DragAndDrop::new(cx.weak_handle(), cx); + cx.set_global(drag_and_drop); + let mut this = Workspace { modal: None, weak_self, @@ -2471,6 +2475,7 @@ impl View for Workspace { .with_background_color(theme.workspace.background) .boxed(), ) + .with_children(DragAndDrop::render(cx)) .with_children(self.render_disconnected_overlay(cx)) .named("workspace") } From ab9f07344399f70e4079644d9876536472178795 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Thu, 11 Aug 2022 12:24:48 -0700 Subject: [PATCH 05/16] fixed merge errors --- crates/gpui/src/presenter.rs | 58 +++++---------- crates/terminal/src/connected_el.rs | 111 ++++++++++++---------------- crates/workspace/src/pane.rs | 15 ++-- 3 files changed, 74 insertions(+), 110 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 87dc6377c7..92665dcaba 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -284,15 +284,11 @@ impl Presenter { { events_to_send.push(( clicked_region.clone(), -<<<<<<< HEAD - MouseRegionEvent::Drag(*prev_drag_position, *e), -======= MouseRegionEvent::Drag(DragRegionEvent { region: clicked_region.bounds, prev_drag_position: *prev_drag_position, platform_event: e.clone(), }), ->>>>>>> 4bd8a4b0 (wip tab drag and drop) )); } @@ -313,7 +309,7 @@ impl Presenter { _ => {} } - let (invalidated_views, dispatch_directives, handled) = { + let (invalidated_views, handled) = { let mut event_cx = self.handle_hover_events(&event, cx); event_cx.process_region_events(events_to_send); @@ -321,25 +317,9 @@ impl Presenter { event_cx.handled = event_cx.dispatch_event(root_view_id, &event); } - if let Some(callback) = region.handlers.get(&event.handler_key()) { - event_cx.with_current_view(region.view_id, |event_cx| { - callback(event, event_cx); - }) - } - - ( - event_cx.invalidated_views, - event_cx.dispatched_actions, - event_cx.handled, - ) + (event_cx.invalidated_views, event_cx.handled) }; - if !handled { - handled = event_cx.dispatch_event(root_view_id, &event); - } - - invalidated_views.extend(event_cx.invalidated_views); - for view_id in invalidated_views { cx.notify_view(self.window_id, view_id); } @@ -400,23 +380,23 @@ impl Presenter { } } } else if let Some(region_id) = region.id() { - if self.hovered_region_ids.contains(®ion_id) { - let region_event = if pressed_button.is_some() { - MouseRegionEvent::DragOver(DragOverRegionEvent { - region: region.bounds, - started: false, - platform_event: e.clone(), - }) - } else { - MouseRegionEvent::Hover(HoverRegionEvent { - region: region.bounds, - started: false, - platform_event: e.clone(), - }) - }; - events_to_send.push((region.clone(), region_event)); - self.hovered_region_ids.remove(®ion_id); - } + if self.hovered_region_ids.contains(®ion_id) { + let region_event = if pressed_button.is_some() { + MouseRegionEvent::DragOver(DragOverRegionEvent { + region: region.bounds, + started: false, + platform_event: e.clone(), + }) + } else { + MouseRegionEvent::Hover(HoverRegionEvent { + region: region.bounds, + started: false, + platform_event: e.clone(), + }) + }; + events_to_send.push((region.clone(), region_event)); + self.hovered_region_ids.remove(®ion_id); + } } } } diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index f138173a0b..b27d8795cb 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -421,75 +421,60 @@ impl TerminalEl { let drag_connection = self.terminal; cx.scene.push_mouse_region( MouseRegion::new(view_id, None, visible_bounds) - .on_down( - MouseButton::Left, - move |MouseButtonEvent { position, .. }, cx| { - if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); + .on_down(MouseButton::Left, move |e, cx| { + if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + let (point, side) = TerminalEl::mouse_to_cell_data( + e.position, + origin, + cur_size, + display_offset, + ); - terminal.mouse_down(point, side); + terminal.mouse_down(point, side); - cx.notify(); - }) - } - }, - ) - .on_click( - MouseButton::Left, - move |MouseButtonEvent { - position, - click_count, - .. - }, - cx| { - cx.focus_parent_view(); - if let Some(conn_handle) = click_connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); + cx.notify(); + }) + } + }) + .on_click(MouseButton::Left, move |e, cx| { + cx.focus_parent_view(); + if let Some(conn_handle) = click_connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + let (point, side) = TerminalEl::mouse_to_cell_data( + e.position, + origin, + cur_size, + display_offset, + ); - terminal.click(point, side, click_count); + terminal.click(point, side, e.click_count); - cx.notify(); - }); - } - }, - ) - .on_click( - MouseButton::Right, - move |MouseButtonEvent { position, .. }, cx| { - cx.dispatch_action(DeployContextMenu { position }); - }, - ) - .on_drag( - MouseButton::Left, - move |_, MouseMovedEvent { position, .. }, cx| { - if let Some(conn_handle) = drag_connection.upgrade(cx.app) { - conn_handle.update(cx.app, |terminal, cx| { - let (point, side) = TerminalEl::mouse_to_cell_data( - position, - origin, - cur_size, - display_offset, - ); + cx.notify(); + }); + } + }) + .on_click(MouseButton::Right, move |e, cx| { + cx.dispatch_action(DeployContextMenu { + position: e.position, + }); + }) + .on_drag(MouseButton::Left, move |e, cx| { + if let Some(conn_handle) = drag_connection.upgrade(cx.app) { + conn_handle.update(cx.app, |terminal, cx| { + let (point, side) = TerminalEl::mouse_to_cell_data( + e.position, + origin, + cur_size, + display_offset, + ); - terminal.drag(point, side); + terminal.drag(point, side); - cx.notify() - }); - } - }, - ), + cx.notify() + }); + } + }), ); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 68878dc6a4..145ba0f851 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -14,8 +14,8 @@ use gpui::{ impl_actions, impl_internal_actions, platform::{CursorStyle, NavigationDirection}, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext, - ModelHandle, MouseButton, MouseButtonEvent, MutableAppContext, PromptLevel, Quad, - RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, + ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, + ViewContext, ViewHandle, WeakViewHandle, }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; @@ -1143,12 +1143,11 @@ impl View for Pane { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_down( - MouseButton::Left, - |MouseButtonEvent { position, .. }, cx| { - cx.dispatch_action(DeployNewMenu { position }); - }, - ) + .on_down(MouseButton::Left, |e, cx| { + cx.dispatch_action(DeployNewMenu { + position: e.position, + }); + }) .boxed(), MouseEventHandler::new::( 1, From 03a344a272e287651fd1f16853796381beb8ef9b Mon Sep 17 00:00:00 2001 From: K Simmons Date: Fri, 12 Aug 2022 13:20:22 -0700 Subject: [PATCH 06/16] properly constrained tab --- crates/drag_and_drop/src/drag_and_drop.rs | 3 + crates/workspace/src/pane.rs | 173 ++++++++++++---------- 2 files changed, 95 insertions(+), 81 deletions(-) diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index 37c331fbb1..9fe239d2b5 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -107,6 +107,9 @@ impl DragAndDrop { Container::new(render(payload, cx)) .with_margin_left(position.x()) .with_margin_top(position.y()) + .aligned() + .top() + .left() .boxed() }) .on_up(MouseButton::Left, |_, cx| { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 145ba0f851..f28f42997c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -3,6 +3,7 @@ use crate::{toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHan use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use context_menu::{ContextMenu, ContextMenuItem}; +use drag_and_drop::Draggable; use futures::StreamExt; use gpui::{ actions, @@ -906,6 +907,7 @@ impl Pane { MouseEventHandler::new::(ix, cx, { let item = item.clone(); let pane = pane.clone(); + let detail = detail.clone(); let hovered = mouse_state.hovered; move |_, cx| { @@ -929,6 +931,7 @@ impl Pane { cx.dispatch_action(ActivateItem(ix)); }) .on_click(MouseButton::Middle, { + let item = item.clone(); let pane = pane.clone(); move |_, cx: &mut EventContext| { cx.dispatch_action(CloseItem { @@ -937,6 +940,15 @@ impl Pane { }) } }) + .as_draggable(item, { + let pane = pane.clone(); + let detail = detail.clone(); + + move |item, cx: &mut RenderContext| { + let pane = pane.clone(); + Pane::render_tab(item, pane, detail, false, pane_active, tab_active, cx) + } + }) .boxed() }) } @@ -1005,94 +1017,93 @@ impl Pane { let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active); let title = item.tab_content(detail, tab_style, cx); - Container::new( - Flex::row() - .with_child( - Align::new({ - let diameter = 7.0; - let icon_color = if item.has_conflict(cx) { - Some(tab_style.icon_conflict) - } else if item.is_dirty(cx) { - Some(tab_style.icon_dirty) - } else { - None - }; + Flex::row() + .with_child( + Align::new({ + let diameter = 7.0; + let icon_color = if item.has_conflict(cx) { + Some(tab_style.icon_conflict) + } else if item.is_dirty(cx) { + Some(tab_style.icon_dirty) + } else { + None + }; - ConstrainedBox::new( - Canvas::new(move |bounds, _, cx| { - if let Some(color) = icon_color { - let square = - RectF::new(bounds.origin(), vec2f(diameter, diameter)); - cx.scene.push_quad(Quad { - bounds: square, - background: Some(color), - border: Default::default(), - corner_radius: diameter / 2., - }); - } - }) - .boxed(), - ) - .with_width(diameter) - .with_height(diameter) - .boxed() - }) - .boxed(), - ) - .with_child( - Container::new(Align::new(title).boxed()) - .with_style(ContainerStyle { - margin: Margin { - left: tab_style.spacing, - right: tab_style.spacing, - ..Default::default() - }, - ..Default::default() + ConstrainedBox::new( + Canvas::new(move |bounds, _, cx| { + if let Some(color) = icon_color { + let square = RectF::new(bounds.origin(), vec2f(diameter, diameter)); + cx.scene.push_quad(Quad { + bounds: square, + background: Some(color), + border: Default::default(), + corner_radius: diameter / 2., + }); + } }) .boxed(), - ) - .with_child( - Align::new( - ConstrainedBox::new(if hovered { - let item_id = item.id(); - enum TabCloseButton {} - let icon = Svg::new("icons/x_mark_thin_8.svg"); - MouseEventHandler::new::( - item_id, - cx, - |mouse_state, _| { - if mouse_state.hovered { - icon.with_color(tab_style.icon_close_active).boxed() - } else { - icon.with_color(tab_style.icon_close).boxed() - } - }, - ) - .with_padding(Padding::uniform(4.)) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, { - let pane = pane.clone(); - move |_, cx| { - cx.dispatch_action(CloseItem { - item_id, - pane: pane.clone(), - }) - } - }) - .on_click(MouseButton::Middle, |_, cx| cx.propogate_event()) - .named("close-tab-icon") - } else { - Empty::new().boxed() - }) - .with_width(tab_style.icon_width) - .boxed(), ) + .with_width(diameter) + .with_height(diameter) + .boxed() + }) + .boxed(), + ) + .with_child( + Container::new(Align::new(title).boxed()) + .with_style(ContainerStyle { + margin: Margin { + left: tab_style.spacing, + right: tab_style.spacing, + ..Default::default() + }, + ..Default::default() + }) + .boxed(), + ) + .with_child( + Align::new( + ConstrainedBox::new(if hovered { + let item_id = item.id(); + enum TabCloseButton {} + let icon = Svg::new("icons/x_mark_thin_8.svg"); + MouseEventHandler::new::( + item_id, + cx, + |mouse_state, _| { + if mouse_state.hovered { + icon.with_color(tab_style.icon_close_active).boxed() + } else { + icon.with_color(tab_style.icon_close).boxed() + } + }, + ) + .with_padding(Padding::uniform(4.)) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, { + let pane = pane.clone(); + move |_, cx| { + cx.dispatch_action(CloseItem { + item_id, + pane: pane.clone(), + }) + } + }) + .on_click(MouseButton::Middle, |_, cx| cx.propogate_event()) + .named("close-tab-icon") + } else { + Empty::new().boxed() + }) + .with_width(tab_style.icon_width) .boxed(), ) .boxed(), - ) - .with_style(tab_style.container) - .boxed() + ) + .contained() + .with_style(tab_style.container) + .constrained() + .with_height(tab_style.height) + .boxed() } } From 0d6125889fdf05b8bbf19454d0fe85574a87811c Mon Sep 17 00:00:00 2001 From: K Simmons Date: Fri, 12 Aug 2022 16:11:15 -0700 Subject: [PATCH 07/16] WIP --- .cargo/config.toml | 7 - crates/drag_and_drop/src/drag_and_drop.rs | 22 ++- crates/gpui/src/presenter.rs | 43 ++--- crates/workspace/src/pane.rs | 224 ++++++++++++++++------ 4 files changed, 191 insertions(+), 105 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index cf5c88d091..0000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,7 +0,0 @@ -[target.'cfg(all())'] -rustflags = [ - "-Dwarnings", - "-Aclippy::reversed_empty_ranges", - "-Aclippy::missing_safety_doc", - "-Aclippy::let_unit_value", -] diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index 9fe239d2b5..1f77664cb4 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -1,4 +1,4 @@ -use std::{any::Any, sync::Arc}; +use std::{any::Any, rc::Rc}; use gpui::{ elements::{Container, MouseEventHandler}, @@ -10,8 +10,8 @@ use gpui::{ struct State { position: Vector2F, region_offset: Vector2F, - payload: Arc, - render: Arc, &mut RenderContext) -> ElementBox>, + payload: Rc, + render: Rc, &mut RenderContext) -> ElementBox>, } impl Clone for State { @@ -46,13 +46,15 @@ impl DragAndDrop { } } - pub fn currently_dragged(&self) -> Option<(Vector2F, &T)> { + pub fn currently_dragged(&self) -> Option<(Vector2F, Rc)> { self.currently_dragged.as_ref().and_then( |State { position, payload, .. }| { payload - .downcast_ref::() + .clone() + .downcast::() + .ok() .map(|payload| (position.clone(), payload)) }, ) @@ -61,9 +63,9 @@ impl DragAndDrop { pub fn dragging( relative_to: Option, position: Vector2F, - payload: Arc, + payload: Rc, cx: &mut EventContext, - render: Arc) -> ElementBox>, + render: Rc) -> ElementBox>, ) { cx.update_global::(|this, cx| { let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() { @@ -80,7 +82,7 @@ impl DragAndDrop { region_offset, position, payload, - render: Arc::new(move |payload, cx| { + render: Rc::new(move |payload, cx| { render(payload.downcast_ref::().unwrap(), cx) }), }); @@ -143,8 +145,8 @@ impl Draggable for MouseEventHandler { where Self: Sized, { - let payload = Arc::new(payload); - let render = Arc::new(render); + let payload = Rc::new(payload); + let render = Rc::new(render); self.on_drag(MouseButton::Left, move |e, cx| { let payload = payload.clone(); let render = render.clone(); diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 92665dcaba..f279b470da 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -224,6 +224,8 @@ impl Presenter { Event::MouseDown(e @ MouseButtonEvent { position, .. }) => { for (region, _) in self.mouse_regions.iter().rev() { if region.bounds.contains_point(*position) { + self.clicked_region = Some(region.clone()); + self.prev_drag_position = Some(*position); events_to_send.push(( region.clone(), MouseRegionEvent::Down(DownRegionEvent { @@ -277,19 +279,19 @@ impl Presenter { } } Event::MouseMoved(e @ MouseMovedEvent { position, .. }) => { - if let Some((clicked_region, prev_drag_position)) = self - .clicked_region - .as_ref() - .zip(self.prev_drag_position.as_mut()) - { - events_to_send.push(( - clicked_region.clone(), - MouseRegionEvent::Drag(DragRegionEvent { - region: clicked_region.bounds, - prev_drag_position: *prev_drag_position, - platform_event: e.clone(), - }), - )); + if let Some(clicked_region) = self.clicked_region.as_ref() { + if let Some(prev_drag_position) = self.prev_drag_position { + events_to_send.push(( + clicked_region.clone(), + MouseRegionEvent::Drag(DragRegionEvent { + region: clicked_region.bounds, + prev_drag_position, + platform_event: e.clone(), + }), + )); + } + + self.prev_drag_position = Some(*position) } for (region, _) in self.mouse_regions.iter().rev() { @@ -417,8 +419,6 @@ impl Presenter { view_stack: Default::default(), invalidated_views: Default::default(), notify_count: 0, - clicked_region: &mut self.clicked_region, - prev_drag_position: &mut self.prev_drag_position, handled: false, window_id: self.window_id, app: cx, @@ -639,8 +639,6 @@ pub struct EventContext<'a> { pub window_id: usize, pub notify_count: usize, view_stack: Vec, - clicked_region: &'a mut Option, - prev_drag_position: &'a mut Option, handled: bool, invalidated_views: HashSet, } @@ -664,17 +662,6 @@ impl<'a> EventContext<'a> { continue; } - match &event { - MouseRegionEvent::Down(e) => { - *self.clicked_region = Some(region.clone()); - *self.prev_drag_position = Some(e.position); - } - MouseRegionEvent::Drag(e) => { - *self.prev_drag_position = Some(e.position); - } - _ => {} - } - self.invalidated_views.insert(region.view_id); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index f28f42997c..3b97f084a9 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -3,7 +3,7 @@ use crate::{toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHan use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use context_menu::{ContextMenu, ContextMenuItem}; -use drag_and_drop::Draggable; +use drag_and_drop::{DragAndDrop, Draggable}; use futures::StreamExt; use gpui::{ actions, @@ -49,6 +49,14 @@ pub struct CloseItem { pub pane: WeakViewHandle, } +#[derive(Clone, PartialEq)] +pub struct MoveItem { + pub item_id: usize, + pub from: WeakViewHandle, + pub to: WeakViewHandle, + pub destination_index: usize, +} + #[derive(Clone, Deserialize, PartialEq)] pub struct GoBack { #[serde(skip_deserializing)] @@ -72,7 +80,7 @@ pub struct DeployNewMenu { } impl_actions!(pane, [GoBack, GoForward, ActivateItem]); -impl_internal_actions!(pane, [CloseItem, DeploySplitMenu, DeployNewMenu]); +impl_internal_actions!(pane, [CloseItem, DeploySplitMenu, DeployNewMenu, MoveItem]); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; @@ -99,6 +107,47 @@ pub fn init(cx: &mut MutableAppContext) { Ok(()) })) }); + cx.add_action(|workspace: &mut Workspace, action: &MoveItem, cx| { + // Get item handle to move + let from = if let Some(from) = action.from.upgrade(cx) { + from + } else { + return; + }; + + let (item_ix, item_handle) = from + .read(cx) + .items() + .enumerate() + .find(|(_, item_handle)| item_handle.id() == action.item_id) + .expect("Tried to move item handle which was not in from pane"); + + // Add item to new pane at given index + let to = if let Some(to) = action.to.upgrade(cx) { + to + } else { + return; + }; + + // This automatically removes duplicate items in the pane + Pane::add_item_at( + workspace, + to, + item_handle.clone(), + true, + true, + Some(action.destination_index), + cx, + ); + + if action.from != action.to { + // Close item from previous pane + from.update(cx, |from, cx| { + from.remove_item(item_ix, cx); + dbg!(from.items().collect::>()); + }); + } + }); cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)); cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)); cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)); @@ -408,12 +457,13 @@ impl Pane { } } - pub(crate) fn add_item( + pub fn add_item_at( workspace: &mut Workspace, pane: ViewHandle, item: Box, activate_pane: bool, focus_item: bool, + destination_index: Option, cx: &mut ViewContext, ) { // Prevent adding the same item to the pane more than once. @@ -428,16 +478,20 @@ impl Pane { item.added_to_pane(workspace, pane.clone(), cx); pane.update(cx, |pane, cx| { - // If there is already an active item, then insert the new item - // right after it. Otherwise, adjust the `active_item_index` field - // before activating the new item, so that in the `activate_item` - // method, we can detect that the active item is changing. - let item_ix; - if pane.active_item_index < pane.items.len() { - item_ix = pane.active_item_index + 1 + let item_ix = if let Some(destination_index) = destination_index { + destination_index } else { - item_ix = pane.items.len(); - pane.active_item_index = usize::MAX; + // If there is already an active item, then insert the new item + // right after it. Otherwise, adjust the `active_item_index` field + // before activating the new item, so that in the `activate_item` + // method, we can detect that the active item is changing. + if pane.active_item_index < pane.items.len() { + pane.active_item_index + 1 + } else { + let ix = pane.items.len(); + pane.active_item_index = usize::MAX; + ix + } }; cx.reparent(&item); @@ -447,6 +501,17 @@ impl Pane { }); } + pub(crate) fn add_item( + workspace: &mut Workspace, + pane: ViewHandle, + item: Box, + activate_pane: bool, + focus_item: bool, + cx: &mut ViewContext, + ) { + Self::add_item_at(workspace, pane, item, activate_pane, focus_item, None, cx) + } + pub fn items(&self) -> impl Iterator> { self.items.iter() } @@ -673,48 +738,7 @@ impl Pane { // Remove the item from the pane. pane.update(&mut cx, |pane, cx| { if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) { - if item_ix == pane.active_item_index { - // Activate the previous item if possible. - // This returns the user to the previously opened tab if they closed - // a ne item they just navigated to. - if item_ix > 0 { - pane.activate_prev_item(cx); - } else if item_ix + 1 < pane.items.len() { - pane.activate_next_item(cx); - } - } - - let item = pane.items.remove(item_ix); - cx.emit(Event::RemoveItem); - if pane.items.is_empty() { - item.deactivated(cx); - pane.update_toolbar(cx); - cx.emit(Event::Remove); - } - - if item_ix < pane.active_item_index { - pane.active_item_index -= 1; - } - - pane.nav_history - .borrow_mut() - .set_mode(NavigationMode::ClosingItem); - item.deactivated(cx); - pane.nav_history - .borrow_mut() - .set_mode(NavigationMode::Normal); - - if let Some(path) = item.project_path(cx) { - pane.nav_history - .borrow_mut() - .paths_by_item - .insert(item.id(), path); - } else { - pane.nav_history - .borrow_mut() - .paths_by_item - .remove(&item.id()); - } + pane.remove_item(item_ix, cx); } }); } @@ -724,6 +748,53 @@ impl Pane { }) } + fn remove_item(&mut self, item_ix: usize, cx: &mut ViewContext) { + if item_ix == self.active_item_index { + // Activate the previous item if possible. + // This returns the user to the previously opened tab if they closed + // a new item they just navigated to. + if item_ix > 0 { + self.activate_prev_item(cx); + } else if item_ix + 1 < self.items.len() { + self.activate_next_item(cx); + } + } + + let item = self.items.remove(item_ix); + cx.emit(Event::RemoveItem); + if self.items.is_empty() { + item.deactivated(cx); + self.update_toolbar(cx); + cx.emit(Event::Remove); + } + + if item_ix < self.active_item_index { + self.active_item_index -= 1; + } + + self.nav_history + .borrow_mut() + .set_mode(NavigationMode::ClosingItem); + item.deactivated(cx); + self.nav_history + .borrow_mut() + .set_mode(NavigationMode::Normal); + + if let Some(path) = item.project_path(cx) { + self.nav_history + .borrow_mut() + .paths_by_item + .insert(item.id(), path); + } else { + self.nav_history + .borrow_mut() + .paths_by_item + .remove(&item.id()); + } + + cx.notify(); + } + pub async fn save_item( project: ModelHandle, pane: &ViewHandle, @@ -880,6 +951,11 @@ impl Pane { fn render_tab_bar(&mut self, cx: &mut RenderContext) -> impl Element { let theme = cx.global::().theme.clone(); + struct DraggedItem { + item: Box, + pane: WeakViewHandle, + } + enum Tabs {} enum Tab {} let pane = cx.handle(); @@ -940,15 +1016,43 @@ impl Pane { }) } }) - .as_draggable(item, { + .on_up(MouseButton::Left, { let pane = pane.clone(); - let detail = detail.clone(); - - move |item, cx: &mut RenderContext| { - let pane = pane.clone(); - Pane::render_tab(item, pane, detail, false, pane_active, tab_active, cx) + move |_, cx: &mut EventContext| { + if let Some((_, dragged_item)) = cx + .global::>() + .currently_dragged::() + { + cx.dispatch_action(MoveItem { + item_id: dragged_item.item.id(), + from: dragged_item.pane.clone(), + to: pane.clone(), + destination_index: ix, + }) + } + cx.propogate_event(); } }) + .as_draggable( + DraggedItem { + item, + pane: pane.clone(), + }, + { + let detail = detail.clone(); + move |dragged_item, cx: &mut RenderContext| { + Pane::render_tab( + &dragged_item.item, + dragged_item.pane.clone(), + detail, + false, + pane_active, + tab_active, + cx, + ) + } + }, + ) .boxed() }) } From 3fb4e6356c49796ac285a1a2b3b39fed78d2f025 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Fri, 12 Aug 2022 19:00:48 -0700 Subject: [PATCH 08/16] Rework presenter dispatch_event to allow isolated propogation of individual MouseRegionEvent types Co-Authored-By: mikayla@zed.dev --- crates/gpui/src/app.rs | 33 +- .../gpui/src/elements/mouse_event_handler.rs | 14 +- crates/gpui/src/presenter.rs | 458 +++++++++--------- crates/gpui/src/scene/mouse_region.rs | 35 +- crates/gpui/src/scene/mouse_region_event.rs | 82 ++-- crates/workspace/src/sidebar.rs | 2 +- 6 files changed, 303 insertions(+), 321 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9071f98dfe..c9748e08b4 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -9,7 +9,7 @@ use crate::{ platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, presenter::Presenter, util::post_inc, - AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseRegionId, + AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton, MouseRegionId, PathPromptOptions, TextLayoutCache, }; pub use action::*; @@ -483,6 +483,7 @@ impl TestAppContext { keystroke: keystroke.clone(), is_held, }), + false, cx, ) { return true; @@ -569,8 +570,7 @@ impl TestAppContext { view_type: PhantomData, titlebar_height: 0., hovered_region_ids: Default::default(), - clicked_region_id: None, - right_clicked_region_id: None, + clicked_region_ids: None, refreshing: false, }; f(view, &mut render_cx) @@ -1278,8 +1278,7 @@ impl MutableAppContext { view_id, titlebar_height, hovered_region_ids: Default::default(), - clicked_region_id: None, - right_clicked_region_id: None, + clicked_region_ids: None, refreshing: false, }) .unwrap(), @@ -1958,7 +1957,7 @@ impl MutableAppContext { } } - presenter.borrow_mut().dispatch_event(event, cx) + presenter.borrow_mut().dispatch_event(event, false, cx) } else { false } @@ -4013,8 +4012,7 @@ pub struct RenderParams { pub view_id: usize, pub titlebar_height: f32, pub hovered_region_ids: HashSet, - pub clicked_region_id: Option, - pub right_clicked_region_id: Option, + pub clicked_region_ids: Option<(Vec, MouseButton)>, pub refreshing: bool, } @@ -4023,8 +4021,7 @@ pub struct RenderContext<'a, T: View> { pub(crate) view_id: usize, pub(crate) view_type: PhantomData, pub(crate) hovered_region_ids: HashSet, - pub(crate) clicked_region_id: Option, - pub(crate) right_clicked_region_id: Option, + pub(crate) clicked_region_ids: Option<(Vec, MouseButton)>, pub app: &'a mut MutableAppContext, pub titlebar_height: f32, pub refreshing: bool, @@ -4033,8 +4030,7 @@ pub struct RenderContext<'a, T: View> { #[derive(Clone, Copy, Default)] pub struct MouseState { pub hovered: bool, - pub clicked: bool, - pub right_clicked: bool, + pub clicked: Option, } impl<'a, V: View> RenderContext<'a, V> { @@ -4046,8 +4042,7 @@ impl<'a, V: View> RenderContext<'a, V> { view_type: PhantomData, titlebar_height: params.titlebar_height, hovered_region_ids: params.hovered_region_ids.clone(), - clicked_region_id: params.clicked_region_id, - right_clicked_region_id: params.right_clicked_region_id, + clicked_region_ids: params.clicked_region_ids.clone(), refreshing: params.refreshing, } } @@ -4071,8 +4066,13 @@ impl<'a, V: View> RenderContext<'a, V> { }; MouseState { hovered: self.hovered_region_ids.contains(®ion_id), - clicked: self.clicked_region_id == Some(region_id), - right_clicked: self.right_clicked_region_id == Some(region_id), + clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| { + if ids.contains(®ion_id) { + Some(*button) + } else { + None + } + }), } } @@ -6025,6 +6025,7 @@ mod tests { cmd: false, click_count: 1, }), + false, cx, ); assert_eq!(mouse_down_count.load(SeqCst), 1); diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 570ec1b1a6..52e701b051 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -6,9 +6,8 @@ use crate::{ }, platform::CursorStyle, scene::{ - ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent, - DragRegionEvent, HandlerSet, HoverRegionEvent, MoveRegionEvent, UpOutRegionEvent, - UpRegionEvent, + ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, + HandlerSet, HoverRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, }, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext, MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View, @@ -107,15 +106,6 @@ impl MouseEventHandler { self } - pub fn on_drag_over( - mut self, - button: MouseButton, - handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers = self.handlers.on_drag_over(button, handler); - self - } - pub fn on_hover( mut self, handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index f279b470da..d5ac91bed2 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -7,15 +7,14 @@ use crate::{ keymap::Keystroke, platform::{CursorStyle, Event}, scene::{ - ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent, - DragRegionEvent, HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, - UpRegionEvent, + ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, + HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, }, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, - FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId, - ParentId, ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, - UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle, + FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId, ParentId, + ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, + View, ViewHandle, WeakModelHandle, WeakViewHandle, }; use collections::{HashMap, HashSet}; use pathfinder_geometry::vector::{vec2f, Vector2F}; @@ -37,9 +36,9 @@ pub struct Presenter { asset_cache: Arc, last_mouse_moved_event: Option, hovered_region_ids: HashSet, - clicked_region: Option, - right_clicked_region: Option, - prev_drag_position: Option, + clicked_regions: Vec, + clicked_button: Option, + mouse_position: Vector2F, titlebar_height: f32, } @@ -62,9 +61,9 @@ impl Presenter { asset_cache, last_mouse_moved_event: None, hovered_region_ids: Default::default(), - clicked_region: None, - right_clicked_region: None, - prev_drag_position: None, + clicked_regions: Vec::new(), + clicked_button: None, + mouse_position: vec2f(0., 0.), titlebar_height, } } @@ -87,11 +86,15 @@ impl Presenter { view_id: *view_id, titlebar_height: self.titlebar_height, hovered_region_ids: self.hovered_region_ids.clone(), - clicked_region_id: self.clicked_region.as_ref().and_then(MouseRegion::id), - right_clicked_region_id: self - .right_clicked_region - .as_ref() - .and_then(MouseRegion::id), + clicked_region_ids: self.clicked_button.map(|button| { + ( + self.clicked_regions + .iter() + .filter_map(MouseRegion::id) + .collect(), + button, + ) + }), refreshing: false, }) .unwrap(), @@ -109,11 +112,15 @@ impl Presenter { view_id: *view_id, titlebar_height: self.titlebar_height, hovered_region_ids: self.hovered_region_ids.clone(), - clicked_region_id: self.clicked_region.as_ref().and_then(MouseRegion::id), - right_clicked_region_id: self - .right_clicked_region - .as_ref() - .and_then(MouseRegion::id), + clicked_region_ids: self.clicked_button.map(|button| { + ( + self.clicked_regions + .iter() + .filter_map(MouseRegion::id) + .collect(), + button, + ) + }), refreshing: true, }) .unwrap(); @@ -144,11 +151,7 @@ impl Presenter { if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { - let invalidated_views = self.handle_hover_events(&event, cx).invalidated_views; - - for view_id in invalidated_views { - cx.notify_view(self.window_id, view_id); - } + self.dispatch_event(event, true, cx); } } } else { @@ -181,8 +184,15 @@ impl Presenter { view_stack: Vec::new(), refreshing, hovered_region_ids: self.hovered_region_ids.clone(), - clicked_region_id: self.clicked_region.as_ref().and_then(MouseRegion::id), - right_clicked_region_id: self.right_clicked_region.as_ref().and_then(MouseRegion::id), + clicked_region_ids: self.clicked_button.map(|button| { + ( + self.clicked_regions + .iter() + .filter_map(MouseRegion::id) + .collect(), + button, + ) + }), titlebar_height: self.titlebar_height, window_size, app: cx, @@ -217,197 +227,230 @@ impl Presenter { }) } - pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool { + pub fn dispatch_event( + &mut self, + event: Event, + event_reused: bool, + cx: &mut MutableAppContext, + ) -> bool { if let Some(root_view_id) = cx.root_view_id(self.window_id) { let mut events_to_send = Vec::new(); + + //1. Allocate the correct set of GPUI events generated from the platform events + // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] + // -> Also moves around mouse related state match &event { - Event::MouseDown(e @ MouseButtonEvent { position, .. }) => { - for (region, _) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(*position) { - self.clicked_region = Some(region.clone()); - self.prev_drag_position = Some(*position); - events_to_send.push(( - region.clone(), - MouseRegionEvent::Down(DownRegionEvent { - region: region.bounds, - platform_event: e.clone(), - }), - )); - } else { - events_to_send.push(( - region.clone(), - MouseRegionEvent::DownOut(DownOutRegionEvent { - region: region.bounds, - platform_event: e.clone(), - }), - )); - } - } - } - Event::MouseUp(e @ MouseButtonEvent { position, .. }) => { - for (region, _) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(*position) { - events_to_send.push(( - region.clone(), - MouseRegionEvent::Up(UpRegionEvent { - region: region.bounds, - platform_event: e.clone(), - }), - )); - } else { - events_to_send.push(( - region.clone(), - MouseRegionEvent::UpOut(UpOutRegionEvent { - region: region.bounds, - platform_event: e.clone(), - }), - )); - } - } - self.prev_drag_position.take(); - if let Some(region) = self.clicked_region.take() { - if region.bounds.contains_point(*position) { - let bounds = region.bounds.clone(); - events_to_send.push(( - region, - MouseRegionEvent::Click(ClickRegionEvent { - region: bounds, - platform_event: e.clone(), - }), - )); - } - } - } - Event::MouseMoved(e @ MouseMovedEvent { position, .. }) => { - if let Some(clicked_region) = self.clicked_region.as_ref() { - if let Some(prev_drag_position) = self.prev_drag_position { - events_to_send.push(( - clicked_region.clone(), - MouseRegionEvent::Drag(DragRegionEvent { - region: clicked_region.bounds, - prev_drag_position, - platform_event: e.clone(), - }), - )); - } + Event::MouseDown(e) => { + //Click events are weird because they can be fired after a drag event. + //MDN says that browsers handle this by starting from 'the most + //specific ancestor element that contained both [positions]' + //So we need to store the overlapping regions on mouse down. + self.clicked_regions = self + .mouse_regions + .iter() + .filter_map(|(region, _)| { + region + .bounds + .contains_point(e.position) + .then(|| region.clone()) + }) + .collect(); + self.clicked_button = Some(e.button); - self.prev_drag_position = Some(*position) + events_to_send.push(MouseRegionEvent::Down(DownRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::DownOut(DownOutRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseUp(e) => { + //NOTE: The order of event pushes is important! MouseUp events MUST be fired + //before click events, and so the UpRegionEvent events need to be pushed before + //ClickRegionEvents + events_to_send.push(MouseRegionEvent::Up(UpRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::UpOut(UpOutRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::Click(ClickRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseMoved( + e @ MouseMovedEvent { + position, + pressed_button, + .. + }, + ) => { + let mut style_to_assign = CursorStyle::Arrow; + for region in self.cursor_regions.iter().rev() { + if region.bounds.contains_point(*position) { + style_to_assign = region.style; + break; + } + } + cx.platform().set_cursor_style(style_to_assign); + + if pressed_button.is_some() { + events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent { + region: Default::default(), + prev_mouse_position: self.mouse_position, + platform_event: e.clone(), + })); } - for (region, _) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(*position) { - events_to_send.push(( - region.clone(), - MouseRegionEvent::Move(MoveRegionEvent { - region: region.bounds, - platform_event: e.clone(), - }), - )); - } - } + events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + + events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent { + region: Default::default(), + platform_event: e.clone(), + started: true, + })); + + events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent { + region: Default::default(), + platform_event: e.clone(), + started: false, + })); self.last_mouse_moved_event = Some(event.clone()); } _ => {} } - let (invalidated_views, handled) = { - let mut event_cx = self.handle_hover_events(&event, cx); - event_cx.process_region_events(events_to_send); + if let Some(position) = event.position() { + self.mouse_position = position; + } - if !event_cx.handled { - event_cx.handled = event_cx.dispatch_event(root_view_id, &event); + let mut invalidated_views: HashSet = Default::default(); + let mut any_event_handled = false; + //2. Process the raw mouse events into region events + for mut region_event in events_to_send { + let mut valid_regions = Vec::new(); + + //GPUI elements are arranged by depth but sibling elements can register overlapping + //mouse regions. As such, hover events are only fired on overlapping elements which + //are at the same depth as the deepest element which overlaps with the mouse. + if let MouseRegionEvent::Hover(_) = region_event { + let mut top_most_depth = None; + let mouse_position = self.mouse_position.clone(); + for (region, depth) in self.mouse_regions.iter().rev() { + let contains_mouse = region.bounds.contains_point(mouse_position); + + if contains_mouse && top_most_depth.is_none() { + top_most_depth = Some(depth); + } + + if let Some(region_id) = region.id() { + //This unwrap relies on short circuiting boolean expressions + //The right side of the && is only executed when contains_mouse + //is true, and we know above that when contains_mouse is true + //top_most_depth is set + if contains_mouse && depth == top_most_depth.unwrap() { + //Ensure that hover entrance events aren't sent twice + if self.hovered_region_ids.insert(region_id) { + valid_regions.push(region.clone()); + } + } else { + //Ensure that hover exit events aren't sent twice + if self.hovered_region_ids.remove(®ion_id) { + valid_regions.push(region.clone()); + } + } + } + } + } else if let MouseRegionEvent::Click(e) = ®ion_event { + //Clear presenter state + let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new()); + self.clicked_button = None; + + //Find regions which still overlap with the mouse since the last MouseDown happened + for clicked_region in clicked_regions.into_iter().rev() { + if clicked_region.bounds.contains_point(e.position) { + valid_regions.push(clicked_region); + } + } + } else if region_event.is_local() { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + //Contains + if mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } + } + } else { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + //NOT contains + if !mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } + } } - (event_cx.invalidated_views, event_cx.handled) - }; + //3. Fire region events + let hovered_region_ids = self.hovered_region_ids.clone(); + let mut event_cx = self.build_event_context(cx); + for valid_region in valid_regions.into_iter() { + region_event.set_region(valid_region.bounds); + if let MouseRegionEvent::Hover(e) = &mut region_event { + e.started = valid_region + .id() + .map(|region_id| hovered_region_ids.contains(®ion_id)) + .unwrap_or(false) + } + + if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { + if event_reused { + invalidated_views.insert(valid_region.view_id); + } + + event_cx.handled = true; + let local = region_event.is_local(); + event_cx.with_current_view(valid_region.view_id, { + let region_event = region_event.clone(); + |cx| { + callback(region_event, cx); + } + }); + + // For bubbling events, if the event was handled, don't continue dispatching + // This only makes sense for local events. 'Out*' events are already + if event_cx.handled && local { + break; + } + } + } + + invalidated_views.extend(event_cx.invalidated_views); + any_event_handled = any_event_handled && event_cx.handled; + } + + if !any_event_handled { + let mut event_cx = self.build_event_context(cx); + any_event_handled = event_cx.dispatch_event(root_view_id, &event); + invalidated_views.extend(event_cx.invalidated_views); + } for view_id in invalidated_views { cx.notify_view(self.window_id, view_id); } - handled + any_event_handled } else { false } } - fn handle_hover_events<'a>( - &'a mut self, - event: &Event, - cx: &'a mut MutableAppContext, - ) -> EventContext<'a> { - let mut events_to_send = Vec::new(); - - if let Event::MouseMoved( - e @ MouseMovedEvent { - position, - pressed_button, - .. - }, - ) = event - { - let mut style_to_assign = CursorStyle::Arrow; - for region in self.cursor_regions.iter().rev() { - if region.bounds.contains_point(*position) { - style_to_assign = region.style; - break; - } - } - cx.platform().set_cursor_style(style_to_assign); - - let mut hover_depth = None; - for (region, depth) in self.mouse_regions.iter().rev() { - if region.bounds.contains_point(*position) - && hover_depth.map_or(true, |hover_depth| hover_depth == *depth) - { - hover_depth = Some(*depth); - if let Some(region_id) = region.id() { - if !self.hovered_region_ids.contains(®ion_id) { - let region_event = if pressed_button.is_some() { - MouseRegionEvent::DragOver(DragOverRegionEvent { - region: region.bounds, - started: true, - platform_event: e.clone(), - }) - } else { - MouseRegionEvent::Hover(HoverRegionEvent { - region: region.bounds, - started: true, - platform_event: e.clone(), - }) - }; - events_to_send.push((region.clone(), region_event)); - self.hovered_region_ids.insert(region_id); - } - } - } else if let Some(region_id) = region.id() { - if self.hovered_region_ids.contains(®ion_id) { - let region_event = if pressed_button.is_some() { - MouseRegionEvent::DragOver(DragOverRegionEvent { - region: region.bounds, - started: false, - platform_event: e.clone(), - }) - } else { - MouseRegionEvent::Hover(HoverRegionEvent { - region: region.bounds, - started: false, - platform_event: e.clone(), - }) - }; - events_to_send.push((region.clone(), region_event)); - self.hovered_region_ids.remove(®ion_id); - } - } - } - } - - let mut event_cx = self.build_event_context(cx); - event_cx.process_region_events(events_to_send); - event_cx - } - pub fn build_event_context<'a>( &'a mut self, cx: &'a mut MutableAppContext, @@ -454,8 +497,7 @@ pub struct LayoutContext<'a> { pub window_size: Vector2F, titlebar_height: f32, hovered_region_ids: HashSet, - clicked_region_id: Option, - right_clicked_region_id: Option, + clicked_region_ids: Option<(Vec, MouseButton)>, } impl<'a> LayoutContext<'a> { @@ -526,8 +568,7 @@ impl<'a> LayoutContext<'a> { view_type: PhantomData, titlebar_height: self.titlebar_height, hovered_region_ids: self.hovered_region_ids.clone(), - clicked_region_id: self.clicked_region_id, - right_clicked_region_id: self.right_clicked_region_id, + clicked_region_ids: self.clicked_region_ids.clone(), refreshing: self.refreshing, }; f(view, &mut render_cx) @@ -655,25 +696,6 @@ impl<'a> EventContext<'a> { } } - fn process_region_events(&mut self, events: Vec<(MouseRegion, MouseRegionEvent)>) { - for (region, event) in events { - if event.is_local() { - if self.handled { - continue; - } - - self.invalidated_views.insert(region.view_id); - } - - if let Some(callback) = region.handlers.get(&event.handler_key()) { - self.handled = true; - self.with_current_view(region.view_id, |event_cx| { - callback(event, event_cx); - }) - } - } - } - fn with_current_view(&mut self, view_id: usize, f: F) -> T where F: FnOnce(&mut Self) -> T, diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 308722fced..639f52208b 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -6,8 +6,8 @@ use pathfinder_geometry::rect::RectF; use crate::{EventContext, MouseButton}; use super::mouse_region_event::{ - ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent, DragRegionEvent, - HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, + ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent, + MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, }; #[derive(Clone, Default)] @@ -104,15 +104,6 @@ impl MouseRegion { self } - pub fn on_drag_over( - mut self, - button: MouseButton, - handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static, - ) -> Self { - self.handlers = self.handlers.on_drag_over(button, handler); - self - } - pub fn on_hover( mut self, handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, @@ -152,10 +143,6 @@ impl HandlerSet { (MouseRegionEvent::drag_disc(), Some(button)), Rc::new(|_, _| {}), ); - set.insert( - (MouseRegionEvent::drag_over_disc(), Some(button)), - Rc::new(|_, _| {}), - ); set.insert( (MouseRegionEvent::down_disc(), Some(button)), Rc::new(|_, _| {}), @@ -317,24 +304,6 @@ impl HandlerSet { self } - pub fn on_drag_over( - mut self, - button: MouseButton, - handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static, - ) -> Self { - self.set.insert((MouseRegionEvent::drag_over_disc(), Some(button)), - Rc::new(move |region_event, cx| { - if let MouseRegionEvent::DragOver(e) = region_event { - handler(e, cx); - } else { - panic!( - "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DragOver, found {:?}", - region_event); - } - })); - self - } - pub fn on_hover( mut self, handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static, diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs index 6e33240e4b..6556f57fea 100644 --- a/crates/gpui/src/scene/mouse_region_event.rs +++ b/crates/gpui/src/scene/mouse_region_event.rs @@ -7,7 +7,7 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F}; use crate::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct MoveRegionEvent { pub region: RectF, pub platform_event: MouseMovedEvent, @@ -21,10 +21,10 @@ impl Deref for MoveRegionEvent { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct DragRegionEvent { pub region: RectF, - pub prev_drag_position: Vector2F, + pub prev_mouse_position: Vector2F, pub platform_event: MouseMovedEvent, } @@ -36,22 +36,7 @@ impl Deref for DragRegionEvent { } } -#[derive(Debug, Default)] -pub struct DragOverRegionEvent { - pub region: RectF, - pub started: bool, - pub platform_event: MouseMovedEvent, -} - -impl Deref for DragOverRegionEvent { - type Target = MouseMovedEvent; - - fn deref(&self) -> &Self::Target { - &self.platform_event - } -} - -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct HoverRegionEvent { pub region: RectF, pub started: bool, @@ -66,7 +51,7 @@ impl Deref for HoverRegionEvent { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct DownRegionEvent { pub region: RectF, pub platform_event: MouseButtonEvent, @@ -80,7 +65,7 @@ impl Deref for DownRegionEvent { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct UpRegionEvent { pub region: RectF, pub platform_event: MouseButtonEvent, @@ -94,7 +79,7 @@ impl Deref for UpRegionEvent { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct ClickRegionEvent { pub region: RectF, pub platform_event: MouseButtonEvent, @@ -108,7 +93,7 @@ impl Deref for ClickRegionEvent { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct DownOutRegionEvent { pub region: RectF, pub platform_event: MouseButtonEvent, @@ -122,7 +107,7 @@ impl Deref for DownOutRegionEvent { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct UpOutRegionEvent { pub region: RectF, pub platform_event: MouseButtonEvent, @@ -136,7 +121,7 @@ impl Deref for UpOutRegionEvent { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct ScrollWheelRegionEvent { pub region: RectF, pub platform_event: ScrollWheelEvent, @@ -150,11 +135,10 @@ impl Deref for ScrollWheelRegionEvent { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum MouseRegionEvent { Move(MoveRegionEvent), Drag(DragRegionEvent), - DragOver(DragOverRegionEvent), Hover(HoverRegionEvent), Down(DownRegionEvent), Up(UpRegionEvent), @@ -164,6 +148,36 @@ pub enum MouseRegionEvent { ScrollWheel(ScrollWheelRegionEvent), } +impl MouseRegionEvent { + pub fn set_region(&mut self, region: RectF) { + match self { + MouseRegionEvent::Move(r) => r.region = region, + MouseRegionEvent::Drag(r) => r.region = region, + MouseRegionEvent::Hover(r) => r.region = region, + MouseRegionEvent::Down(r) => r.region = region, + MouseRegionEvent::Up(r) => r.region = region, + MouseRegionEvent::Click(r) => r.region = region, + MouseRegionEvent::DownOut(r) => r.region = region, + MouseRegionEvent::UpOut(r) => r.region = region, + MouseRegionEvent::ScrollWheel(r) => r.region = region, + } + } + + pub fn is_local(&self) -> bool { + match self { + MouseRegionEvent::Move(_) => true, + MouseRegionEvent::Drag(_) => true, + MouseRegionEvent::Hover(_) => true, + MouseRegionEvent::Down(_) => true, + MouseRegionEvent::Up(_) => true, + MouseRegionEvent::Click(_) => true, + MouseRegionEvent::DownOut(_) => false, + MouseRegionEvent::UpOut(_) => false, + MouseRegionEvent::ScrollWheel(_) => true, + } + } +} + impl MouseRegionEvent { pub fn move_disc() -> Discriminant { discriminant(&MouseRegionEvent::Move(Default::default())) @@ -173,10 +187,6 @@ impl MouseRegionEvent { discriminant(&MouseRegionEvent::Drag(Default::default())) } - pub fn drag_over_disc() -> Discriminant { - discriminant(&MouseRegionEvent::DragOver(Default::default())) - } - pub fn hover_disc() -> Discriminant { discriminant(&MouseRegionEvent::Hover(Default::default())) } @@ -205,20 +215,10 @@ impl MouseRegionEvent { discriminant(&MouseRegionEvent::ScrollWheel(Default::default())) } - pub fn is_local(&self) -> bool { - match self { - MouseRegionEvent::DownOut(_) - | MouseRegionEvent::UpOut(_) - | MouseRegionEvent::DragOver(_) => false, - _ => true, - } - } - pub fn handler_key(&self) -> (Discriminant, Option) { match self { MouseRegionEvent::Move(_) => (Self::move_disc(), None), MouseRegionEvent::Drag(e) => (Self::drag_disc(), e.pressed_button), - MouseRegionEvent::DragOver(e) => (Self::drag_over_disc(), e.pressed_button), MouseRegionEvent::Hover(_) => (Self::hover_disc(), None), MouseRegionEvent::Down(e) => (Self::down_disc(), Some(e.button)), MouseRegionEvent::Up(e) => (Self::up_disc(), Some(e.button)), diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 819959eb75..c89057cb11 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -190,7 +190,7 @@ impl Sidebar { .with_cursor_style(CursorStyle::ResizeLeftRight) .on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere .on_drag(MouseButton::Left, move |e, cx| { - let delta = e.prev_drag_position.x() - e.position.x(); + let delta = e.prev_mouse_position.x() - e.position.x(); let prev_width = *actual_width.borrow(); *custom_width.borrow_mut() = 0f32 .max(match side { From b97940be9b78520b21234c759c7853b952688249 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 15 Aug 2022 10:05:58 -0700 Subject: [PATCH 09/16] more wip but currently beach balling --- crates/gpui/src/presenter.rs | 38 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index d5ac91bed2..918bf31322 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -299,25 +299,20 @@ impl Presenter { } cx.platform().set_cursor_style(style_to_assign); - if pressed_button.is_some() { - events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent { + if !event_reused { + if pressed_button.is_some() { + events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent { + region: Default::default(), + prev_mouse_position: self.mouse_position, + platform_event: e.clone(), + })); + } + events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent { region: Default::default(), - prev_mouse_position: self.mouse_position, platform_event: e.clone(), })); } - events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent { - region: Default::default(), - platform_event: e.clone(), - })); - - events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent { - region: Default::default(), - platform_event: e.clone(), - started: true, - })); - events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent { region: Default::default(), platform_event: e.clone(), @@ -399,7 +394,7 @@ impl Presenter { //3. Fire region events let hovered_region_ids = self.hovered_region_ids.clone(); - let mut event_cx = self.build_event_context(cx); + let mut event_cx = self.build_event_context(&mut invalidated_views, cx); for valid_region in valid_regions.into_iter() { region_event.set_region(valid_region.bounds); if let MouseRegionEvent::Hover(e) = &mut region_event { @@ -410,7 +405,7 @@ impl Presenter { } if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { - if event_reused { + if !event_reused { invalidated_views.insert(valid_region.view_id); } @@ -424,21 +419,19 @@ impl Presenter { }); // For bubbling events, if the event was handled, don't continue dispatching - // This only makes sense for local events. 'Out*' events are already + // This only makes sense for local events. if event_cx.handled && local { break; } } } - invalidated_views.extend(event_cx.invalidated_views); any_event_handled = any_event_handled && event_cx.handled; } if !any_event_handled { - let mut event_cx = self.build_event_context(cx); + let mut event_cx = self.build_event_context(&mut invalidated_views, cx); any_event_handled = event_cx.dispatch_event(root_view_id, &event); - invalidated_views.extend(event_cx.invalidated_views); } for view_id in invalidated_views { @@ -453,6 +446,7 @@ impl Presenter { pub fn build_event_context<'a>( &'a mut self, + invalidated_views: &'a mut HashSet, cx: &'a mut MutableAppContext, ) -> EventContext<'a> { EventContext { @@ -460,7 +454,7 @@ impl Presenter { font_cache: &self.font_cache, text_layout_cache: &self.text_layout_cache, view_stack: Default::default(), - invalidated_views: Default::default(), + invalidated_views, notify_count: 0, handled: false, window_id: self.window_id, @@ -681,7 +675,7 @@ pub struct EventContext<'a> { pub notify_count: usize, view_stack: Vec, handled: bool, - invalidated_views: HashSet, + invalidated_views: &'a mut HashSet, } impl<'a> EventContext<'a> { From 0f43ef933148c4388806539b55bec18c51c09172 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 22 Aug 2022 16:16:27 -0700 Subject: [PATCH 10/16] Dispatch Event refactoring. Appears to be workingCo-Authored-By: nathan@zed.dev --- crates/gpui/src/elements/list.rs | 4 +- crates/gpui/src/presenter.rs | 139 ++++---- crates/gpui/src/presenter/event_context.rs | 100 ++++++ crates/gpui/src/presenter/event_dispatcher.rs | 308 ++++++++++++++++++ crates/gpui/src/scene/mouse_region.rs | 2 +- crates/gpui/src/scene/mouse_region_event.rs | 6 +- crates/workspace/src/sidebar.rs | 2 +- 7 files changed, 492 insertions(+), 69 deletions(-) create mode 100644 crates/gpui/src/presenter/event_context.rs create mode 100644 crates/gpui/src/presenter/event_dispatcher.rs diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e0ca47b598..e53672501c 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -698,7 +698,7 @@ mod tests { 40., vec2f(0., -54.), true, - &mut presenter.build_event_context(cx), + &mut presenter.build_event_context(&mut Default::default(), cx), ); let (_, logical_scroll_top) = list.layout( constraint, @@ -807,7 +807,7 @@ mod tests { height, delta, true, - &mut presenter.build_event_context(cx), + &mut presenter.build_event_context(&mut Default::default(), cx), ); } 30..=34 => { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 918bf31322..42dac6d207 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -79,6 +79,7 @@ impl Presenter { self.rendered_views.remove(view_id); } for view_id in &invalidation.updated { + dbg!(view_id); self.rendered_views.insert( *view_id, cx.render_view(RenderParams { @@ -151,6 +152,7 @@ impl Presenter { if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { + println!("Redispatching mouse moved"); self.dispatch_event(event, true, cx); } } @@ -236,15 +238,15 @@ impl Presenter { if let Some(root_view_id) = cx.root_view_id(self.window_id) { let mut events_to_send = Vec::new(); - //1. Allocate the correct set of GPUI events generated from the platform events - // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] - // -> Also moves around mouse related state + // 1. Allocate the correct set of GPUI events generated from the platform events + // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] + // -> Also moves around mouse related state match &event { Event::MouseDown(e) => { - //Click events are weird because they can be fired after a drag event. - //MDN says that browsers handle this by starting from 'the most - //specific ancestor element that contained both [positions]' - //So we need to store the overlapping regions on mouse down. + // Click events are weird because they can be fired after a drag event. + // MDN says that browsers handle this by starting from 'the most + // specific ancestor element that contained both [positions]' + // So we need to store the overlapping regions on mouse down. self.clicked_regions = self .mouse_regions .iter() @@ -267,9 +269,9 @@ impl Presenter { })); } Event::MouseUp(e) => { - //NOTE: The order of event pushes is important! MouseUp events MUST be fired - //before click events, and so the UpRegionEvent events need to be pushed before - //ClickRegionEvents + // NOTE: The order of event pushes is important! MouseUp events MUST be fired + // before click events, and so the UpRegionEvent events need to be pushed before + // ClickRegionEvents events_to_send.push(MouseRegionEvent::Up(UpRegionEvent { region: Default::default(), platform_event: e.clone(), @@ -330,74 +332,87 @@ impl Presenter { let mut invalidated_views: HashSet = Default::default(); let mut any_event_handled = false; - //2. Process the raw mouse events into region events + // 2. Process the raw mouse events into region events for mut region_event in events_to_send { let mut valid_regions = Vec::new(); - //GPUI elements are arranged by depth but sibling elements can register overlapping - //mouse regions. As such, hover events are only fired on overlapping elements which - //are at the same depth as the deepest element which overlaps with the mouse. - if let MouseRegionEvent::Hover(_) = region_event { - let mut top_most_depth = None; - let mouse_position = self.mouse_position.clone(); - for (region, depth) in self.mouse_regions.iter().rev() { - let contains_mouse = region.bounds.contains_point(mouse_position); + // GPUI elements are arranged by depth but sibling elements can register overlapping + // mouse regions. As such, hover events are only fired on overlapping elements which + // are at the same depth as the deepest element which overlaps with the mouse. - if contains_mouse && top_most_depth.is_none() { - top_most_depth = Some(depth); - } + match ®ion_event { + MouseRegionEvent::Hover(_) => { + let mut top_most_depth = None; + let mouse_position = self.mouse_position.clone(); + for (region, depth) in self.mouse_regions.iter().rev() { + let contains_mouse = region.bounds.contains_point(mouse_position); - if let Some(region_id) = region.id() { - //This unwrap relies on short circuiting boolean expressions - //The right side of the && is only executed when contains_mouse - //is true, and we know above that when contains_mouse is true - //top_most_depth is set - if contains_mouse && depth == top_most_depth.unwrap() { - //Ensure that hover entrance events aren't sent twice - if self.hovered_region_ids.insert(region_id) { - valid_regions.push(region.clone()); - } - } else { - //Ensure that hover exit events aren't sent twice - if self.hovered_region_ids.remove(®ion_id) { - valid_regions.push(region.clone()); + if contains_mouse && top_most_depth.is_none() { + top_most_depth = Some(depth); + } + + if let Some(region_id) = region.id() { + // This unwrap relies on short circuiting boolean expressions + // The right side of the && is only executed when contains_mouse + // is true, and we know above that when contains_mouse is true + // top_most_depth is set + if contains_mouse && depth == top_most_depth.unwrap() { + //Ensure that hover entrance events aren't sent twice + if self.hovered_region_ids.insert(region_id) { + valid_regions.push(region.clone()); + } + } else { + // Ensure that hover exit events aren't sent twice + if self.hovered_region_ids.remove(®ion_id) { + valid_regions.push(region.clone()); + } } } } } - } else if let MouseRegionEvent::Click(e) = ®ion_event { - //Clear presenter state - let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new()); - self.clicked_button = None; + MouseRegionEvent::Click(e) => { + // Clear presenter state + let clicked_regions = + std::mem::replace(&mut self.clicked_regions, Vec::new()); + self.clicked_button = None; - //Find regions which still overlap with the mouse since the last MouseDown happened - for clicked_region in clicked_regions.into_iter().rev() { - if clicked_region.bounds.contains_point(e.position) { - valid_regions.push(clicked_region); + // Find regions which still overlap with the mouse since the last MouseDown happened + for clicked_region in clicked_regions.into_iter().rev() { + if clicked_region.bounds.contains_point(e.position) { + valid_regions.push(clicked_region); + } } } - } else if region_event.is_local() { - for (mouse_region, _) in self.mouse_regions.iter().rev() { - //Contains - if mouse_region.bounds.contains_point(self.mouse_position) { - valid_regions.push(mouse_region.clone()); + MouseRegionEvent::Drag(_) => { + for clicked_region in self.clicked_regions.iter().rev() { + valid_regions.push(clicked_region.clone()); } } - } else { - for (mouse_region, _) in self.mouse_regions.iter().rev() { - //NOT contains - if !mouse_region.bounds.contains_point(self.mouse_position) { - valid_regions.push(mouse_region.clone()); + + MouseRegionEvent::UpOut(_) | MouseRegionEvent::DownOut(_) => { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + // NOT contains + if !mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } + } + } + _ => { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + // Contains + if mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } } } } //3. Fire region events let hovered_region_ids = self.hovered_region_ids.clone(); - let mut event_cx = self.build_event_context(&mut invalidated_views, cx); for valid_region in valid_regions.into_iter() { region_event.set_region(valid_region.bounds); if let MouseRegionEvent::Hover(e) = &mut region_event { + println!("Hover event selected"); e.started = valid_region .id() .map(|region_id| hovered_region_ids.contains(®ion_id)) @@ -405,12 +420,11 @@ impl Presenter { } if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { - if !event_reused { - invalidated_views.insert(valid_region.view_id); - } + dbg!(valid_region.view_id); + invalidated_views.insert(valid_region.view_id); + let mut event_cx = self.build_event_context(&mut invalidated_views, cx); event_cx.handled = true; - let local = region_event.is_local(); event_cx.with_current_view(valid_region.view_id, { let region_event = region_event.clone(); |cx| { @@ -418,18 +432,17 @@ impl Presenter { } }); + any_event_handled = any_event_handled || event_cx.handled; // For bubbling events, if the event was handled, don't continue dispatching // This only makes sense for local events. - if event_cx.handled && local { + if event_cx.handled && region_event.is_capturable() { break; } } } - - any_event_handled = any_event_handled && event_cx.handled; } - if !any_event_handled { + if !any_event_handled && !event_reused { let mut event_cx = self.build_event_context(&mut invalidated_views, cx); any_event_handled = event_cx.dispatch_event(root_view_id, &event); } diff --git a/crates/gpui/src/presenter/event_context.rs b/crates/gpui/src/presenter/event_context.rs new file mode 100644 index 0000000000..d4258a6d1e --- /dev/null +++ b/crates/gpui/src/presenter/event_context.rs @@ -0,0 +1,100 @@ +use std::ops::{Deref, DerefMut}; + +use collections::{HashMap, HashSet}; + +use crate::{Action, ElementBox, Event, FontCache, MutableAppContext, TextLayoutCache}; + +pub struct EventContext<'a> { + rendered_views: &'a mut HashMap, + pub font_cache: &'a FontCache, + pub text_layout_cache: &'a TextLayoutCache, + pub app: &'a mut MutableAppContext, + pub window_id: usize, + pub notify_count: usize, + view_stack: Vec, + pub(crate) handled: bool, + pub(crate) invalidated_views: HashSet, +} + +impl<'a> EventContext<'a> { + pub(crate) fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool { + if let Some(mut element) = self.rendered_views.remove(&view_id) { + let result = + self.with_current_view(view_id, |this| element.dispatch_event(event, this)); + self.rendered_views.insert(view_id, element); + result + } else { + false + } + } + + pub(crate) fn with_current_view(&mut self, view_id: usize, f: F) -> T + where + F: FnOnce(&mut Self) -> T, + { + self.view_stack.push(view_id); + let result = f(self); + self.view_stack.pop(); + result + } + + pub fn window_id(&self) -> usize { + self.window_id + } + + pub fn view_id(&self) -> Option { + self.view_stack.last().copied() + } + + pub fn is_parent_view_focused(&self) -> bool { + if let Some(parent_view_id) = self.view_stack.last() { + self.app.focused_view_id(self.window_id) == Some(*parent_view_id) + } else { + false + } + } + + pub fn focus_parent_view(&mut self) { + if let Some(parent_view_id) = self.view_stack.last() { + self.app.focus(self.window_id, Some(*parent_view_id)) + } + } + + pub fn dispatch_any_action(&mut self, action: Box) { + self.app + .dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action) + } + + pub fn dispatch_action(&mut self, action: A) { + self.dispatch_any_action(Box::new(action)); + } + + pub fn notify(&mut self) { + self.notify_count += 1; + if let Some(view_id) = self.view_stack.last() { + self.invalidated_views.insert(*view_id); + } + } + + pub fn notify_count(&self) -> usize { + self.notify_count + } + + pub fn propogate_event(&mut self) { + self.handled = false; + } +} + +impl<'a> Deref for EventContext<'a> { + type Target = MutableAppContext; + + fn deref(&self) -> &Self::Target { + self.app + } +} + +impl<'a> DerefMut for EventContext<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.app + } +} diff --git a/crates/gpui/src/presenter/event_dispatcher.rs b/crates/gpui/src/presenter/event_dispatcher.rs new file mode 100644 index 0000000000..4c72334910 --- /dev/null +++ b/crates/gpui/src/presenter/event_dispatcher.rs @@ -0,0 +1,308 @@ +use std::sync::Arc; + +use collections::{HashMap, HashSet}; +use pathfinder_geometry::vector::Vector2F; + +use crate::{ + scene::{ + ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent, + MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, + }, + CursorRegion, CursorStyle, ElementBox, Event, EventContext, FontCache, MouseButton, + MouseMovedEvent, MouseRegion, MouseRegionId, MutableAppContext, Scene, TextLayoutCache, +}; + +pub struct EventDispatcher { + window_id: usize, + font_cache: Arc, + + last_mouse_moved_event: Option, + cursor_regions: Vec, + mouse_regions: Vec<(MouseRegion, usize)>, + clicked_regions: Vec, + clicked_button: Option, + mouse_position: Vector2F, + hovered_region_ids: HashSet, +} + +impl EventDispatcher { + pub fn new(window_id: usize, font_cache: Arc) -> Self { + Self { + window_id, + font_cache, + + last_mouse_moved_event: Default::default(), + cursor_regions: Default::default(), + mouse_regions: Default::default(), + clicked_regions: Default::default(), + clicked_button: Default::default(), + mouse_position: Default::default(), + hovered_region_ids: Default::default(), + } + } + + pub fn clicked_region_ids(&self) -> Option<(Vec, MouseButton)> { + self.clicked_button.map(|button| { + ( + self.clicked_regions + .iter() + .filter_map(MouseRegion::id) + .collect(), + button, + ) + }) + } + + pub fn hovered_region_ids(&self) -> HashSet { + self.hovered_region_ids.clone() + } + + pub fn update_mouse_regions(&mut self, scene: &Scene) { + self.cursor_regions = scene.cursor_regions(); + self.mouse_regions = scene.mouse_regions(); + } + + pub fn redispatch_mouse_moved_event<'a>(&'a mut self, cx: &mut EventContext<'a>) { + if let Some(event) = self.last_mouse_moved_event.clone() { + self.dispatch_event(event, true, cx); + } + } + + pub fn dispatch_event<'a>( + &'a mut self, + event: Event, + event_reused: bool, + cx: &mut EventContext<'a>, + ) -> bool { + let root_view_id = cx.root_view_id(self.window_id); + if root_view_id.is_none() { + return false; + } + + let root_view_id = root_view_id.unwrap(); + //1. Allocate the correct set of GPUI events generated from the platform events + // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] + // -> Also moves around mouse related state + let events_to_send = self.select_region_events(&event, cx, event_reused); + + // For a given platform event, potentially multiple mouse region events can be created. For a given + // region event, dispatch continues until a mouse region callback fails to propogate (handled is set to true) + // If no region handles any of the produced platform events, we fallback to the old dispatch event style. + let mut invalidated_views: HashSet = Default::default(); + let mut any_event_handled = false; + for mut region_event in events_to_send { + //2. Find mouse regions relevant to each region_event. For example, if the event is click, select + // the clicked_regions that overlap with the mouse position + let valid_regions = self.select_relevant_mouse_regions(®ion_event); + let hovered_region_ids = self.hovered_region_ids.clone(); + + //3. Dispatch region event ot each valid mouse region + for valid_region in valid_regions.into_iter() { + region_event.set_region(valid_region.bounds); + if let MouseRegionEvent::Hover(e) = &mut region_event { + e.started = valid_region + .id() + .map(|region_id| hovered_region_ids.contains(®ion_id)) + .unwrap_or(false) + } + + if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { + if !event_reused { + invalidated_views.insert(valid_region.view_id); + } + + cx.handled = true; + cx.with_current_view(valid_region.view_id, { + let region_event = region_event.clone(); + |cx| { + callback(region_event, cx); + } + }); + + // For bubbling events, if the event was handled, don't continue dispatching + // This only makes sense for local events. + if cx.handled && region_event.is_local() { + break; + } + } + } + + // Keep track if any platform event was handled + any_event_handled = any_event_handled && cx.handled; + } + + if !any_event_handled { + // No platform event was handled, so fall back to old mouse event dispatch style + any_event_handled = cx.dispatch_event(root_view_id, &event); + } + + // Notify any views which have been validated from event callbacks + for view_id in invalidated_views { + cx.notify_view(self.window_id, view_id); + } + + any_event_handled + } + + fn select_region_events( + &mut self, + event: &Event, + cx: &mut MutableAppContext, + event_reused: bool, + ) -> Vec { + let mut events_to_send = Vec::new(); + match event { + Event::MouseDown(e) => { + //Click events are weird because they can be fired after a drag event. + //MDN says that browsers handle this by starting from 'the most + //specific ancestor element that contained both [positions]' + //So we need to store the overlapping regions on mouse down. + self.clicked_regions = self + .mouse_regions + .iter() + .filter_map(|(region, _)| { + region + .bounds + .contains_point(e.position) + .then(|| region.clone()) + }) + .collect(); + self.clicked_button = Some(e.button); + + events_to_send.push(MouseRegionEvent::Down(DownRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::DownOut(DownOutRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseUp(e) => { + //NOTE: The order of event pushes is important! MouseUp events MUST be fired + //before click events, and so the UpRegionEvent events need to be pushed before + //ClickRegionEvents + events_to_send.push(MouseRegionEvent::Up(UpRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::UpOut(UpOutRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::Click(ClickRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseMoved( + e @ MouseMovedEvent { + position, + pressed_button, + .. + }, + ) => { + let mut style_to_assign = CursorStyle::Arrow; + for region in self.cursor_regions.iter().rev() { + if region.bounds.contains_point(*position) { + style_to_assign = region.style; + break; + } + } + cx.platform().set_cursor_style(style_to_assign); + + if !event_reused { + if pressed_button.is_some() { + events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent { + region: Default::default(), + prev_mouse_position: self.mouse_position, + platform_event: e.clone(), + })); + } + events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + + events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent { + region: Default::default(), + platform_event: e.clone(), + started: false, + })); + + self.last_mouse_moved_event = Some(event.clone()); + } + _ => {} + } + if let Some(position) = event.position() { + self.mouse_position = position; + } + events_to_send + } + + fn select_relevant_mouse_regions( + &mut self, + region_event: &MouseRegionEvent, + ) -> Vec { + let mut valid_regions = Vec::new(); + //GPUI elements are arranged by depth but sibling elements can register overlapping + //mouse regions. As such, hover events are only fired on overlapping elements which + //are at the same depth as the deepest element which overlaps with the mouse. + if let MouseRegionEvent::Hover(_) = *region_event { + let mut top_most_depth = None; + let mouse_position = self.mouse_position.clone(); + for (region, depth) in self.mouse_regions.iter().rev() { + let contains_mouse = region.bounds.contains_point(mouse_position); + + if contains_mouse && top_most_depth.is_none() { + top_most_depth = Some(depth); + } + + if let Some(region_id) = region.id() { + //This unwrap relies on short circuiting boolean expressions + //The right side of the && is only executed when contains_mouse + //is true, and we know above that when contains_mouse is true + //top_most_depth is set + if contains_mouse && depth == top_most_depth.unwrap() { + //Ensure that hover entrance events aren't sent twice + if self.hovered_region_ids.insert(region_id) { + valid_regions.push(region.clone()); + } + } else { + //Ensure that hover exit events aren't sent twice + if self.hovered_region_ids.remove(®ion_id) { + valid_regions.push(region.clone()); + } + } + } + } + } else if let MouseRegionEvent::Click(e) = region_event { + //Clear stored clicked_regions + let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new()); + self.clicked_button = None; + + //Find regions which still overlap with the mouse since the last MouseDown happened + for clicked_region in clicked_regions.into_iter().rev() { + if clicked_region.bounds.contains_point(e.position) { + valid_regions.push(clicked_region); + } + } + } else if region_event.is_local() { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + //Contains + if mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } + } + } else { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + //NOT contains + if !mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } + } + } + valid_regions + } +} diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 639f52208b..5919d09180 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -113,7 +113,7 @@ impl MouseRegion { } } -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct MouseRegionId { pub view_id: usize, pub discriminant: (TypeId, usize), diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs index 6556f57fea..a375651c7a 100644 --- a/crates/gpui/src/scene/mouse_region_event.rs +++ b/crates/gpui/src/scene/mouse_region_event.rs @@ -163,10 +163,12 @@ impl MouseRegionEvent { } } - pub fn is_local(&self) -> bool { + /// When true, mouse event handlers must call cx.propagate_event() to bubble + /// the event to handlers they are painted on top of. + pub fn is_capturable(&self) -> bool { match self { MouseRegionEvent::Move(_) => true, - MouseRegionEvent::Drag(_) => true, + MouseRegionEvent::Drag(_) => false, MouseRegionEvent::Hover(_) => true, MouseRegionEvent::Down(_) => true, MouseRegionEvent::Up(_) => true, diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index c89057cb11..2071d3802b 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -190,7 +190,7 @@ impl Sidebar { .with_cursor_style(CursorStyle::ResizeLeftRight) .on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere .on_drag(MouseButton::Left, move |e, cx| { - let delta = e.prev_mouse_position.x() - e.position.x(); + let delta = e.position.x() - e.prev_mouse_position.x(); let prev_width = *actual_width.borrow(); *custom_width.borrow_mut() = 0f32 .max(match side { From e44536344ad4bde224e4745e42964b4326d8ba94 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 22 Aug 2022 16:49:14 -0700 Subject: [PATCH 11/16] Removed EventHandler from workspace in favor of mouse event handler --- crates/gpui/src/elements/mouse_event_handler.rs | 5 +++++ crates/gpui/src/presenter.rs | 2 -- crates/gpui/src/scene/mouse_region.rs | 4 ++-- crates/workspace/src/workspace.rs | 12 ++++++------ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 52e701b051..78dbfd65b7 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -44,6 +44,11 @@ impl MouseEventHandler { self } + pub fn capture_all(mut self) -> Self { + self.handlers = HandlerSet::capture_all(); + self + } + pub fn on_move( mut self, handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 42dac6d207..d3acaf72e5 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -79,7 +79,6 @@ impl Presenter { self.rendered_views.remove(view_id); } for view_id in &invalidation.updated { - dbg!(view_id); self.rendered_views.insert( *view_id, cx.render_view(RenderParams { @@ -152,7 +151,6 @@ impl Presenter { if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { - println!("Redispatching mouse moved"); self.dispatch_event(event, true, cx); } } diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 5919d09180..9a73c499f6 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -46,7 +46,7 @@ impl MouseRegion { view_id, discriminant, bounds, - handlers: HandlerSet::handle_all(), + handlers: HandlerSet::capture_all(), } } @@ -129,7 +129,7 @@ pub struct HandlerSet { } impl HandlerSet { - pub fn handle_all() -> Self { + pub fn capture_all() -> Self { #[allow(clippy::type_complexity)] let mut set: HashMap< (Discriminant, Option), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index fa81cd9f11..c71f21ccdc 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2071,11 +2071,11 @@ impl Workspace { } } - fn render_disconnected_overlay(&self, cx: &AppContext) -> Option { + fn render_disconnected_overlay(&self, cx: &mut RenderContext) -> Option { if self.project.read(cx).is_read_only() { - let theme = &cx.global::().theme; Some( - EventHandler::new( + MouseEventHandler::new::(0, cx, |_, cx| { + let theme = &cx.global::().theme; Label::new( "Your connection to the remote project has been lost.".to_string(), theme.workspace.disconnected_overlay.text.clone(), @@ -2083,9 +2083,9 @@ impl Workspace { .aligned() .contained() .with_style(theme.workspace.disconnected_overlay.container) - .boxed(), - ) - .capture_all::(0) + .boxed() + }) + .capture_all() .boxed(), ) } else { From de3acbd75b45002c409715474e4714181f41ea45 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 23 Aug 2022 15:41:59 -0700 Subject: [PATCH 12/16] Introduce ReorderBehavior to pane, and address drag and drop offset error --- crates/drag_and_drop/src/drag_and_drop.rs | 16 +- crates/gpui/src/presenter.rs | 2 - crates/workspace/src/pane.rs | 208 ++++++++++++++-------- crates/workspace/src/workspace.rs | 8 +- 4 files changed, 141 insertions(+), 93 deletions(-) diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index 1f77664cb4..0ccb785a2b 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -2,7 +2,8 @@ use std::{any::Any, rc::Rc}; use gpui::{ elements::{Container, MouseEventHandler}, - geometry::{rect::RectF, vector::Vector2F}, + geometry::vector::Vector2F, + scene::DragRegionEvent, Element, ElementBox, EventContext, MouseButton, RenderContext, View, ViewContext, WeakViewHandle, }; @@ -61,8 +62,7 @@ impl DragAndDrop { } pub fn dragging( - relative_to: Option, - position: Vector2F, + event: DragRegionEvent, payload: Rc, cx: &mut EventContext, render: Rc) -> ElementBox>, @@ -71,16 +71,12 @@ impl DragAndDrop { let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() { previous_state.region_offset } else { - if let Some(relative_to) = relative_to { - relative_to.origin() - position - } else { - Vector2F::zero() - } + event.region.origin() - event.prev_mouse_position }; this.currently_dragged = Some(State { region_offset, - position, + position: event.position, payload, render: Rc::new(move |payload, cx| { render(payload.downcast_ref::().unwrap(), cx) @@ -150,7 +146,7 @@ impl Draggable for MouseEventHandler { self.on_drag(MouseButton::Left, move |e, cx| { let payload = payload.clone(); let render = render.clone(); - DragAndDrop::::dragging(Some(e.region), e.position, payload, cx, render) + DragAndDrop::::dragging(e, payload, cx, render) }) } } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index a9cd57871e..ab5217740e 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -411,7 +411,6 @@ impl Presenter { for valid_region in valid_regions.into_iter() { region_event.set_region(valid_region.bounds); if let MouseRegionEvent::Hover(e) = &mut region_event { - println!("Hover event selected"); e.started = valid_region .id() .map(|region_id| hovered_region_ids.contains(®ion_id)) @@ -419,7 +418,6 @@ impl Presenter { } if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { - dbg!(valid_region.view_id); invalidated_views.insert(valid_region.view_id); let mut event_cx = self.build_event_context(&mut invalidated_views, cx); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 3b97f084a9..1d90f508db 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -86,10 +86,10 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { - pane.activate_item(action.0, true, true, false, cx); + pane.activate_item(action.0, true, true, ReorderBehavior::None, cx); }); cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { - pane.activate_item(pane.items.len() - 1, true, true, false, cx); + pane.activate_item(pane.items.len() - 1, true, true, ReorderBehavior::None, cx); }); cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { pane.activate_prev_item(cx); @@ -107,47 +107,32 @@ pub fn init(cx: &mut MutableAppContext) { Ok(()) })) }); - cx.add_action(|workspace: &mut Workspace, action: &MoveItem, cx| { - // Get item handle to move - let from = if let Some(from) = action.from.upgrade(cx) { - from - } else { - return; - }; + cx.add_action( + |workspace, + MoveItem { + from, + to, + item_id, + destination_index, + }, + cx| { + // Get item handle to move + let from = if let Some(from) = from.upgrade(cx) { + from + } else { + return; + }; - let (item_ix, item_handle) = from - .read(cx) - .items() - .enumerate() - .find(|(_, item_handle)| item_handle.id() == action.item_id) - .expect("Tried to move item handle which was not in from pane"); + // Add item to new pane at given index + let to = if let Some(to) = to.upgrade(cx) { + to + } else { + return; + }; - // Add item to new pane at given index - let to = if let Some(to) = action.to.upgrade(cx) { - to - } else { - return; - }; - - // This automatically removes duplicate items in the pane - Pane::add_item_at( - workspace, - to, - item_handle.clone(), - true, - true, - Some(action.destination_index), - cx, - ); - - if action.from != action.to { - // Close item from previous pane - from.update(cx, |from, cx| { - from.remove_item(item_ix, cx); - dbg!(from.items().collect::>()); - }); - } - }); + Pane::move_item(workspace, from, to, *item_id, *destination_index, cx) + }, + ); cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)); cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)); cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)); @@ -236,6 +221,17 @@ pub struct NavigationEntry { pub data: Option>, } +struct DraggedItem { + item: Box, + pane: WeakViewHandle, +} + +pub enum ReorderBehavior { + None, + MoveAfterActive, + MoveToIndex(usize), +} + impl Pane { pub fn new(cx: &mut ViewContext) -> Self { let handle = cx.weak_handle(); @@ -350,7 +346,7 @@ impl Pane { { let prev_active_item_index = pane.active_item_index; pane.nav_history.borrow_mut().set_mode(mode); - pane.activate_item(index, true, true, false, cx); + pane.activate_item(index, true, true, ReorderBehavior::None, cx); pane.nav_history .borrow_mut() .set_mode(NavigationMode::Normal); @@ -442,7 +438,7 @@ impl Pane { && item.project_entry_ids(cx).as_slice() == [project_entry_id] { let item = item.boxed_clone(); - pane.activate_item(ix, true, focus_item, true, cx); + pane.activate_item(ix, true, focus_item, ReorderBehavior::MoveAfterActive, cx); return Some(item); } } @@ -468,10 +464,18 @@ impl Pane { ) { // Prevent adding the same item to the pane more than once. // If there is already an active item, reorder the desired item to be after it - // and activate it. - if let Some(item_ix) = pane.read(cx).items.iter().position(|i| i.id() == item.id()) { + // (or at the desired destination_index) and activate it. + if let Some(item_ix) = pane.read(cx).items.iter().position(|i| { + i.id() == item.id() || i.project_entry_ids(cx) == item.project_entry_ids(cx) + }) { pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, activate_pane, focus_item, true, cx) + pane.activate_item( + item_ix, + activate_pane, + focus_item, + ReorderBehavior::MoveAfterActive, + cx, + ) }); return; } @@ -496,7 +500,13 @@ impl Pane { cx.reparent(&item); pane.items.insert(item_ix, item); - pane.activate_item(item_ix, activate_pane, focus_item, false, cx); + pane.activate_item( + item_ix, + activate_pane, + focus_item, + ReorderBehavior::None, + cx, + ); cx.notify(); }); } @@ -549,20 +559,24 @@ impl Pane { mut index: usize, activate_pane: bool, focus_item: bool, - move_after_current_active: bool, + reorder: ReorderBehavior, cx: &mut ViewContext, ) { use NavigationMode::{GoingBack, GoingForward}; if index < self.items.len() { - if move_after_current_active { + if let Some(destination_index) = match reorder { + ReorderBehavior::MoveAfterActive => Some(self.active_item_index + 1), + ReorderBehavior::MoveToIndex(destination_index) => Some(destination_index), + ReorderBehavior::None => None, + } { // If there is already an active item, reorder the desired item to be after it // and activate it. - if self.active_item_index != index && self.active_item_index < self.items.len() { + if destination_index - 1 != index && destination_index <= self.items.len() { let pane_to_activate = self.items.remove(index); - if self.active_item_index < index { - index = self.active_item_index + 1; - } else if self.active_item_index < self.items.len() + 1 { - index = self.active_item_index; + if destination_index - 1 < index { + index = destination_index; + } else if destination_index <= self.items.len() + 1 { + index = destination_index - 1; // Index is less than active_item_index. Reordering will decrement the // active_item_index, so adjust it accordingly self.active_item_index = index - 1; @@ -601,7 +615,7 @@ impl Pane { } else if !self.items.is_empty() { index = self.items.len() - 1; } - self.activate_item(index, true, true, false, cx); + self.activate_item(index, true, true, ReorderBehavior::None, cx); } pub fn activate_next_item(&mut self, cx: &mut ViewContext) { @@ -611,7 +625,7 @@ impl Pane { } else { index = 0; } - self.activate_item(index, true, true, false, cx); + self.activate_item(index, true, true, ReorderBehavior::None, cx); } pub fn close_active_item( @@ -818,7 +832,7 @@ impl Pane { if has_conflict && can_save { let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, false, cx); + pane.activate_item(item_ix, true, true, ReorderBehavior::None, cx); cx.prompt( PromptLevel::Warning, CONFLICT_MESSAGE, @@ -839,7 +853,7 @@ impl Pane { }); let should_save = if should_prompt_for_save && !will_autosave { let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, false, cx); + pane.activate_item(item_ix, true, true, ReorderBehavior::None, cx); cx.prompt( PromptLevel::Warning, DIRTY_MESSAGE, @@ -901,6 +915,42 @@ impl Pane { } } + fn move_item( + workspace: &mut Workspace, + from: ViewHandle, + to: ViewHandle, + item_to_move: usize, + destination_index: usize, + cx: &mut ViewContext, + ) { + let (item_ix, item_handle) = from + .read(cx) + .items() + .enumerate() + .find(|(_, item_handle)| item_handle.id() == item_to_move) + .expect("Tried to move item handle which was not in from pane"); + + // This automatically removes duplicate items in the pane + Pane::add_item_at( + workspace, + to.clone(), + item_handle.clone(), + true, + true, + Some(destination_index), + cx, + ); + + if from != to { + // Close item from previous pane + from.update(cx, |from, cx| { + from.remove_item(item_ix, cx); + }); + } + + cx.focus(to); + } + pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext) { cx.emit(Event::Split(direction)); } @@ -950,11 +1000,7 @@ impl Pane { fn render_tab_bar(&mut self, cx: &mut RenderContext) -> impl Element { let theme = cx.global::().theme.clone(); - - struct DraggedItem { - item: Box, - pane: WeakViewHandle, - } + let filler_index = self.items.len(); enum Tabs {} enum Tab {} @@ -1018,20 +1064,7 @@ impl Pane { }) .on_up(MouseButton::Left, { let pane = pane.clone(); - move |_, cx: &mut EventContext| { - if let Some((_, dragged_item)) = cx - .global::>() - .currently_dragged::() - { - cx.dispatch_action(MoveItem { - item_id: dragged_item.item.id(), - from: dragged_item.pane.clone(), - to: pane.clone(), - destination_index: ix, - }) - } - cx.propogate_event(); - } + move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, ix, cx) }) .as_draggable( DraggedItem { @@ -1063,12 +1096,15 @@ impl Pane { .contained() .with_style(filler_style.container) .with_border(filler_style.container.border) - .flex(0., true) + .flex(1., true) .named("filler"), ); row.boxed() }) + .on_up(MouseButton::Left, move |_, cx| { + Pane::handle_dropped_item(&pane, filler_index, cx) + }) } fn tab_details(&self, cx: &AppContext) -> Vec { @@ -1209,6 +1245,22 @@ impl Pane { .with_height(tab_style.height) .boxed() } + + fn handle_dropped_item(pane: &WeakViewHandle, index: usize, cx: &mut EventContext) { + if let Some((_, dragged_item)) = cx + .global::>() + .currently_dragged::() + { + cx.dispatch_action(MoveItem { + item_id: dragged_item.item.id(), + from: dragged_item.pane.clone(), + to: pane.clone(), + destination_index: index, + }) + } else { + cx.propogate_event(); + } + } } impl Entity for Pane { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ff7336f461..cfc18c1684 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1535,7 +1535,9 @@ impl Workspace { .map(|ix| (pane.clone(), ix)) }); if let Some((pane, ix)) = result { - pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, false, cx)); + pane.update(cx, |pane, cx| { + pane.activate_item(ix, true, true, ReorderBehavior::None, cx) + }); true } else { false @@ -3004,7 +3006,7 @@ mod tests { let close_items = workspace.update(cx, |workspace, cx| { pane.update(cx, |pane, cx| { - pane.activate_item(1, true, true, false, cx); + pane.activate_item(1, true, true, ReorderBehavior::None, cx); assert_eq!(pane.active_item().unwrap().id(), item2.id()); }); @@ -3106,7 +3108,7 @@ mod tests { workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx); } left_pane.update(cx, |pane, cx| { - pane.activate_item(2, true, true, false, cx); + pane.activate_item(2, true, true, ReorderBehavior::None, cx); }); workspace From 5c38021a4d0b4d99056979a7aa7c095f9b61a788 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 23 Aug 2022 17:31:55 -0700 Subject: [PATCH 13/16] Move pane item reordering from activate_tab to add_item_at. Co-authored-by: nathan@zed.dev --- crates/workspace/src/pane.rs | 143 +++++++++++++----------------- crates/workspace/src/workspace.rs | 8 +- 2 files changed, 65 insertions(+), 86 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1d90f508db..3191bd6ddc 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -21,7 +21,7 @@ use gpui::{ use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; use settings::{Autosave, Settings}; -use std::{any::Any, cell::RefCell, mem, path::Path, rc::Rc}; +use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use util::ResultExt; #[derive(Clone, Deserialize, PartialEq)] @@ -86,10 +86,10 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| { - pane.activate_item(action.0, true, true, ReorderBehavior::None, cx); + pane.activate_item(action.0, true, true, cx); }); cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| { - pane.activate_item(pane.items.len() - 1, true, true, ReorderBehavior::None, cx); + pane.activate_item(pane.items.len() - 1, true, true, cx); }); cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| { pane.activate_prev_item(cx); @@ -346,7 +346,7 @@ impl Pane { { let prev_active_item_index = pane.active_item_index; pane.nav_history.borrow_mut().set_mode(mode); - pane.activate_item(index, true, true, ReorderBehavior::None, cx); + pane.activate_item(index, true, true, cx); pane.nav_history .borrow_mut() .set_mode(NavigationMode::Normal); @@ -433,24 +433,22 @@ impl Pane { build_item: impl FnOnce(&mut ViewContext) -> Box, ) -> Box { let existing_item = pane.update(cx, |pane, cx| { - for (ix, item) in pane.items.iter().enumerate() { + for item in pane.items.iter() { if item.project_path(cx).is_some() && item.project_entry_ids(cx).as_slice() == [project_entry_id] { let item = item.boxed_clone(); - pane.activate_item(ix, true, focus_item, ReorderBehavior::MoveAfterActive, cx); return Some(item); } } None }); - if let Some(existing_item) = existing_item { - existing_item - } else { - let item = pane.update(cx, |_, cx| build_item(cx)); - Self::add_item(workspace, pane, item.boxed_clone(), true, focus_item, cx); - item - } + + // Even if the item exists, we re-add it to reorder it after the active item. + // We may revisit this behavior after adding an "activation history" for pane items. + let item = existing_item.unwrap_or_else(|| pane.update(cx, |_, cx| build_item(cx))); + Pane::add_item(workspace, pane, item.clone(), true, focus_item, cx); + item } pub fn add_item_at( @@ -462,53 +460,58 @@ impl Pane { destination_index: Option, cx: &mut ViewContext, ) { - // Prevent adding the same item to the pane more than once. - // If there is already an active item, reorder the desired item to be after it - // (or at the desired destination_index) and activate it. - if let Some(item_ix) = pane.read(cx).items.iter().position(|i| { + // If no destination index is specified, add or move the item after the active item. + let mut destination_index = if let Some(destination_index) = destination_index { + destination_index + } else { + let pane = pane.read(cx); + cmp::min(pane.active_item_index + 1, pane.items.len()) + }; + + // Does the item already exist? + if let Some(existing_item_index) = pane.read(cx).items.iter().position(|i| { i.id() == item.id() || i.project_entry_ids(cx) == item.project_entry_ids(cx) }) { + // If the item already exists, move it to the desired destination and activate it pane.update(cx, |pane, cx| { - pane.activate_item( - item_ix, - activate_pane, - focus_item, - ReorderBehavior::MoveAfterActive, - cx, - ) - }); - return; - } + if existing_item_index != destination_index { + let existing_item_is_active = existing_item_index == pane.active_item_index; - item.added_to_pane(workspace, pane.clone(), cx); - pane.update(cx, |pane, cx| { - let item_ix = if let Some(destination_index) = destination_index { - destination_index - } else { - // If there is already an active item, then insert the new item - // right after it. Otherwise, adjust the `active_item_index` field - // before activating the new item, so that in the `activate_item` - // method, we can detect that the active item is changing. - if pane.active_item_index < pane.items.len() { - pane.active_item_index + 1 - } else { - let ix = pane.items.len(); - pane.active_item_index = usize::MAX; - ix + pane.items.remove(existing_item_index); + if existing_item_index < destination_index { + destination_index -= 1; + } + if existing_item_index < pane.active_item_index { + pane.active_item_index -= 1; + } + + pane.items.insert(destination_index, item.clone()); + + if existing_item_is_active { + pane.active_item_index = destination_index; + } else if destination_index <= pane.active_item_index { + pane.active_item_index += 1; + } + + cx.notify(); } - }; - cx.reparent(&item); - pane.items.insert(item_ix, item); - pane.activate_item( - item_ix, - activate_pane, - focus_item, - ReorderBehavior::None, - cx, - ); - cx.notify(); - }); + pane.activate_item(destination_index, activate_pane, focus_item, cx); + }); + } else { + // If the item doesn't already exist, add it and activate it + item.added_to_pane(workspace, pane.clone(), cx); + pane.update(cx, |pane, cx| { + cx.reparent(&item); + pane.items.insert(destination_index, item); + if destination_index <= pane.active_item_index { + pane.active_item_index += 1; + } + + pane.activate_item(destination_index, activate_pane, focus_item, cx); + cx.notify(); + }); + } } pub(crate) fn add_item( @@ -556,35 +559,13 @@ impl Pane { pub fn activate_item( &mut self, - mut index: usize, + index: usize, activate_pane: bool, focus_item: bool, - reorder: ReorderBehavior, cx: &mut ViewContext, ) { use NavigationMode::{GoingBack, GoingForward}; if index < self.items.len() { - if let Some(destination_index) = match reorder { - ReorderBehavior::MoveAfterActive => Some(self.active_item_index + 1), - ReorderBehavior::MoveToIndex(destination_index) => Some(destination_index), - ReorderBehavior::None => None, - } { - // If there is already an active item, reorder the desired item to be after it - // and activate it. - if destination_index - 1 != index && destination_index <= self.items.len() { - let pane_to_activate = self.items.remove(index); - if destination_index - 1 < index { - index = destination_index; - } else if destination_index <= self.items.len() + 1 { - index = destination_index - 1; - // Index is less than active_item_index. Reordering will decrement the - // active_item_index, so adjust it accordingly - self.active_item_index = index - 1; - } - self.items.insert(index, pane_to_activate); - } - } - let prev_active_item_ix = mem::replace(&mut self.active_item_index, index); if prev_active_item_ix != self.active_item_index || matches!(self.nav_history.borrow().mode, GoingBack | GoingForward) @@ -615,7 +596,7 @@ impl Pane { } else if !self.items.is_empty() { index = self.items.len() - 1; } - self.activate_item(index, true, true, ReorderBehavior::None, cx); + self.activate_item(index, true, true, cx); } pub fn activate_next_item(&mut self, cx: &mut ViewContext) { @@ -625,7 +606,7 @@ impl Pane { } else { index = 0; } - self.activate_item(index, true, true, ReorderBehavior::None, cx); + self.activate_item(index, true, true, cx); } pub fn close_active_item( @@ -832,7 +813,7 @@ impl Pane { if has_conflict && can_save { let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, ReorderBehavior::None, cx); + pane.activate_item(item_ix, true, true, cx); cx.prompt( PromptLevel::Warning, CONFLICT_MESSAGE, @@ -853,7 +834,7 @@ impl Pane { }); let should_save = if should_prompt_for_save && !will_autosave { let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, ReorderBehavior::None, cx); + pane.activate_item(item_ix, true, true, cx); cx.prompt( PromptLevel::Warning, DIRTY_MESSAGE, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cfc18c1684..2d2782eaa4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1535,9 +1535,7 @@ impl Workspace { .map(|ix| (pane.clone(), ix)) }); if let Some((pane, ix)) = result { - pane.update(cx, |pane, cx| { - pane.activate_item(ix, true, true, ReorderBehavior::None, cx) - }); + pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); true } else { false @@ -3006,7 +3004,7 @@ mod tests { let close_items = workspace.update(cx, |workspace, cx| { pane.update(cx, |pane, cx| { - pane.activate_item(1, true, true, ReorderBehavior::None, cx); + pane.activate_item(1, true, true, cx); assert_eq!(pane.active_item().unwrap().id(), item2.id()); }); @@ -3108,7 +3106,7 @@ mod tests { workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx); } left_pane.update(cx, |pane, cx| { - pane.activate_item(2, true, true, ReorderBehavior::None, cx); + pane.activate_item(2, true, true, cx); }); workspace From 0a97a9c0fd116abafa538c03ea51fa10fdfc6b3d Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 23 Aug 2022 18:02:01 -0700 Subject: [PATCH 14/16] Change dragged tab styling --- crates/theme/src/theme.rs | 1 + crates/workspace/src/pane.rs | 27 +++++++++++---------------- styles/src/styleTree/components.ts | 8 ++++++++ styles/src/styleTree/tabBar.ts | 17 ++++++++++++++++- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 08fb65a001..d9a6c631c6 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -75,6 +75,7 @@ pub struct TabBar { pub pane_button: Interactive, pub active_pane: TabStyles, pub inactive_pane: TabStyles, + pub dragged_tab: Tab, pub height: f32, } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 3191bd6ddc..0a8f68744f 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1013,16 +1013,12 @@ impl Pane { let detail = detail.clone(); let hovered = mouse_state.hovered; + let theme = cx.global::().theme.clone(); + move |_, cx| { - Self::render_tab( - &item, - pane, - detail, - hovered, - pane_active, - tab_active, - cx, - ) + let tab_style = + theme.workspace.tab_bar.tab_style(pane_active, tab_active); + Self::render_tab(&item, pane, detail, hovered, tab_style, cx) } }) .with_cursor_style(if pane_active && tab_active { @@ -1053,15 +1049,17 @@ impl Pane { pane: pane.clone(), }, { + let theme = cx.global::().theme.clone(); + let detail = detail.clone(); move |dragged_item, cx: &mut RenderContext| { + let tab_style = &theme.workspace.tab_bar.dragged_tab; Pane::render_tab( &dragged_item.item, dragged_item.pane.clone(), detail, false, - pane_active, - tab_active, + &tab_style, cx, ) } @@ -1130,13 +1128,10 @@ impl Pane { pane: WeakViewHandle, detail: Option, hovered: bool, - pane_active: bool, - tab_active: bool, + tab_style: &theme::Tab, cx: &mut RenderContext, ) -> ElementBox { - let theme = cx.global::().theme.clone(); - let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active); - let title = item.tab_content(detail, tab_style, cx); + let title = item.tab_content(detail, &tab_style, cx); Flex::row() .with_child( diff --git a/styles/src/styleTree/components.ts b/styles/src/styleTree/components.ts index 10e70ba3ef..f0d1dd8412 100644 --- a/styles/src/styleTree/components.ts +++ b/styles/src/styleTree/components.ts @@ -94,3 +94,11 @@ export function popoverShadow(theme: Theme) { offset: [1, 2], }; } + +export function draggedShadow(theme: Theme) { + return { + blur: 6, + color: theme.shadow, + offset: [1, 2], + }; +} diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/styleTree/tabBar.ts index 66da26d7ed..039b8d89f4 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/styleTree/tabBar.ts @@ -1,5 +1,6 @@ import Theme from "../themes/common/theme"; -import { iconColor, text, border, backgroundColor } from "./components"; +import { withOpacity } from "../utils/color"; +import { iconColor, text, border, backgroundColor, draggedShadow } from "./components"; export default function tabBar(theme: Theme) { const height = 32; @@ -55,6 +56,19 @@ export default function tabBar(theme: Theme) { }, } + const draggedTab = { + ...activePaneActiveTab, + background: withOpacity(tab.background, 0.8), + border: { + ...tab.border, + top: false, + left: false, + right: false, + bottom: false, + }, + shadow: draggedShadow(theme), + } + return { height, background: backgroundColor(theme, 300), @@ -71,6 +85,7 @@ export default function tabBar(theme: Theme) { activeTab: inactivePaneActiveTab, inactiveTab: inactivePaneInactiveTab, }, + draggedTab, paneButton: { color: iconColor(theme, "secondary"), border: { From 042ece00b18df686641c9650ead601c16002d7b1 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Thu, 25 Aug 2022 11:14:24 -0700 Subject: [PATCH 15/16] Drag and drop tabs working. all known bugs fixed --- crates/drag_and_drop/src/drag_and_drop.rs | 2 + crates/gpui/src/elements/container.rs | 20 ++++- .../gpui/src/elements/mouse_event_handler.rs | 22 +++-- crates/gpui/src/presenter.rs | 53 +++++++----- crates/gpui/src/scene/mouse_region.rs | 8 ++ crates/theme/src/theme.rs | 1 + crates/workspace/src/pane.rs | 81 ++++++++++++++----- styles/src/styleTree/tabBar.ts | 1 + 8 files changed, 142 insertions(+), 46 deletions(-) diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index 0ccb785a2b..28e30f97a7 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -116,6 +116,8 @@ impl DragAndDrop { }); cx.propogate_event(); }) + // Don't block hover events or invalidations + .with_hoverable(false) .boxed() }, ) diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index ad9d7fe0cb..326efdfd19 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -24,6 +24,8 @@ pub struct ContainerStyle { pub padding: Padding, #[serde(rename = "background")] pub background_color: Option, + #[serde(rename = "overlay")] + pub overlay_color: Option, #[serde(default)] pub border: Border, #[serde(default)] @@ -119,6 +121,11 @@ impl Container { self } + pub fn with_overlay_color(mut self, color: Color) -> Self { + self.style.overlay_color = Some(color); + self + } + pub fn with_border(mut self, border: Border) -> Self { self.style.border = border; self @@ -245,7 +252,7 @@ impl Element for Container { cx.scene.push_layer(None); cx.scene.push_quad(Quad { bounds: quad_bounds, - background: Default::default(), + background: self.style.overlay_color, border: self.style.border, corner_radius: self.style.corner_radius, }); @@ -264,6 +271,17 @@ impl Element for Container { self.style.border.top_width(), ); self.child.paint(child_origin, visible_bounds, cx); + + if self.style.overlay_color.is_some() { + cx.scene.push_layer(None); + cx.scene.push_quad(Quad { + bounds: quad_bounds, + background: self.style.overlay_color, + border: Default::default(), + corner_radius: 0., + }); + cx.scene.pop_layer(); + } } } diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 78dbfd65b7..9e5465e91a 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -20,6 +20,7 @@ pub struct MouseEventHandler { discriminant: (TypeId, usize), cursor_style: Option, handlers: HandlerSet, + hoverable: bool, padding: Padding, } @@ -35,6 +36,7 @@ impl MouseEventHandler { cursor_style: None, discriminant: (TypeId::of::(), id), handlers: Default::default(), + hoverable: true, padding: Default::default(), } } @@ -119,6 +121,11 @@ impl MouseEventHandler { self } + pub fn with_hoverable(mut self, is_hoverable: bool) -> Self { + self.hoverable = is_hoverable; + self + } + pub fn with_padding(mut self, padding: Padding) -> Self { self.padding = padding; self @@ -160,12 +167,15 @@ impl Element for MouseEventHandler { }); } - cx.scene.push_mouse_region(MouseRegion::from_handlers( - cx.current_view_id(), - Some(self.discriminant), - hit_bounds, - self.handlers.clone(), - )); + cx.scene.push_mouse_region( + MouseRegion::from_handlers( + cx.current_view_id(), + Some(self.discriminant), + hit_bounds, + self.handlers.clone(), + ) + .with_hoverable(self.hoverable), + ); self.child.paint(bounds.origin(), visible_bounds, cx); } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index ab5217740e..4b96a3bb47 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -245,17 +245,21 @@ impl Presenter { // MDN says that browsers handle this by starting from 'the most // specific ancestor element that contained both [positions]' // So we need to store the overlapping regions on mouse down. - self.clicked_regions = self - .mouse_regions - .iter() - .filter_map(|(region, _)| { - region - .bounds - .contains_point(e.position) - .then(|| region.clone()) - }) - .collect(); - self.clicked_button = Some(e.button); + + // If there is already clicked_button stored, don't replace it. + if self.clicked_button.is_none() { + self.clicked_regions = self + .mouse_regions + .iter() + .filter_map(|(region, _)| { + region + .bounds + .contains_point(e.position) + .then(|| region.clone()) + }) + .collect(); + self.clicked_button = Some(e.button); + } events_to_send.push(MouseRegionEvent::Down(DownRegionEvent { region: Default::default(), @@ -337,13 +341,18 @@ impl Presenter { // GPUI elements are arranged by depth but sibling elements can register overlapping // mouse regions. As such, hover events are only fired on overlapping elements which - // are at the same depth as the deepest element which overlaps with the mouse. + // are at the same depth as the topmost element which overlaps with the mouse. match ®ion_event { MouseRegionEvent::Hover(_) => { let mut top_most_depth = None; let mouse_position = self.mouse_position.clone(); for (region, depth) in self.mouse_regions.iter().rev() { + // Allow mouse regions to appear transparent to hovers + if !region.hoverable { + continue; + } + let contains_mouse = region.bounds.contains_point(mouse_position); if contains_mouse && top_most_depth.is_none() { @@ -359,26 +368,30 @@ impl Presenter { //Ensure that hover entrance events aren't sent twice if self.hovered_region_ids.insert(region_id) { valid_regions.push(region.clone()); + invalidated_views.insert(region.view_id); } } else { // Ensure that hover exit events aren't sent twice if self.hovered_region_ids.remove(®ion_id) { valid_regions.push(region.clone()); + invalidated_views.insert(region.view_id); } } } } } MouseRegionEvent::Click(e) => { - // Clear presenter state - let clicked_regions = - std::mem::replace(&mut self.clicked_regions, Vec::new()); - self.clicked_button = None; + if e.button == self.clicked_button.unwrap() { + // Clear clicked regions and clicked button + let clicked_regions = + std::mem::replace(&mut self.clicked_regions, Vec::new()); + self.clicked_button = None; - // Find regions which still overlap with the mouse since the last MouseDown happened - for clicked_region in clicked_regions.into_iter().rev() { - if clicked_region.bounds.contains_point(e.position) { - valid_regions.push(clicked_region); + // Find regions which still overlap with the mouse since the last MouseDown happened + for clicked_region in clicked_regions.into_iter().rev() { + if clicked_region.bounds.contains_point(e.position) { + valid_regions.push(clicked_region); + } } } } diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index b17d2d8c53..d64551e306 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -17,6 +17,7 @@ pub struct MouseRegion { pub discriminant: Option<(TypeId, usize)>, pub bounds: RectF, pub handlers: HandlerSet, + pub hoverable: bool, } impl MouseRegion { @@ -35,6 +36,7 @@ impl MouseRegion { discriminant, bounds, handlers, + hoverable: true, } } @@ -48,6 +50,7 @@ impl MouseRegion { discriminant, bounds, handlers: HandlerSet::capture_all(), + hoverable: true, } } @@ -120,6 +123,11 @@ impl MouseRegion { self.handlers = self.handlers.on_move(handler); self } + + pub fn with_hoverable(mut self, is_hoverable: bool) -> Self { + self.hoverable = is_hoverable; + self + } } #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index d9a6c631c6..be89148985 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -77,6 +77,7 @@ pub struct TabBar { pub inactive_pane: TabStyles, pub dragged_tab: Tab, pub height: f32, + pub drop_target_overlay_color: Color, } impl TabBar { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 0a8f68744f..7852ad6868 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -7,6 +7,7 @@ use drag_and_drop::{DragAndDrop, Draggable}; use futures::StreamExt; use gpui::{ actions, + color::Color, elements::*, geometry::{ rect::RectF, @@ -22,6 +23,7 @@ use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; use settings::{Autosave, Settings}; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; +use theme::Theme; use util::ResultExt; #[derive(Clone, Deserialize, PartialEq)] @@ -475,15 +477,14 @@ impl Pane { // If the item already exists, move it to the desired destination and activate it pane.update(cx, |pane, cx| { if existing_item_index != destination_index { + cx.reparent(&item); let existing_item_is_active = existing_item_index == pane.active_item_index; pane.items.remove(existing_item_index); - if existing_item_index < destination_index { - destination_index -= 1; - } if existing_item_index < pane.active_item_index { pane.active_item_index -= 1; } + destination_index = destination_index.min(pane.items.len()); pane.items.insert(destination_index, item.clone()); @@ -985,8 +986,9 @@ impl Pane { enum Tabs {} enum Tab {} + enum Filler {} let pane = cx.handle(); - MouseEventHandler::new::(0, cx, |mouse_state, cx| { + MouseEventHandler::new::(0, cx, |_, cx| { let autoscroll = if mem::take(&mut self.autoscroll) { Some(self.active_item_index) } else { @@ -1011,14 +1013,22 @@ impl Pane { let item = item.clone(); let pane = pane.clone(); let detail = detail.clone(); - let hovered = mouse_state.hovered; let theme = cx.global::().theme.clone(); - move |_, cx| { + move |mouse_state, cx| { let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active); - Self::render_tab(&item, pane, detail, hovered, tab_style, cx) + let hovered = mouse_state.hovered; + Self::render_tab( + &item, + pane, + detail, + hovered, + Self::tab_overlay_color(hovered, theme.as_ref(), cx), + tab_style, + cx, + ) } }) .with_cursor_style(if pane_active && tab_active { @@ -1059,6 +1069,7 @@ impl Pane { dragged_item.pane.clone(), detail, false, + None, &tab_style, cx, ) @@ -1069,14 +1080,25 @@ impl Pane { }) } + // Use the inactive tab style along with the current pane's active status to decide how to render + // the filler let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); row.add_child( - Empty::new() - .contained() - .with_style(filler_style.container) - .with_border(filler_style.container.border) - .flex(1., true) - .named("filler"), + MouseEventHandler::new::(0, cx, |mouse_state, cx| { + let mut filler = Empty::new() + .contained() + .with_style(filler_style.container) + .with_border(filler_style.container.border); + + if let Some(overlay) = Self::tab_overlay_color(mouse_state.hovered, &theme, cx) + { + filler = filler.with_overlay_color(overlay); + } + + filler.boxed() + }) + .flex(1., true) + .named("filler"), ); row.boxed() @@ -1128,12 +1150,13 @@ impl Pane { pane: WeakViewHandle, detail: Option, hovered: bool, + overlay: Option, tab_style: &theme::Tab, cx: &mut RenderContext, ) -> ElementBox { let title = item.tab_content(detail, &tab_style, cx); - Flex::row() + let mut tab = Flex::row() .with_child( Align::new({ let diameter = 7.0; @@ -1216,10 +1239,13 @@ impl Pane { .boxed(), ) .contained() - .with_style(tab_style.container) - .constrained() - .with_height(tab_style.height) - .boxed() + .with_style(tab_style.container); + + if let Some(overlay) = overlay { + tab = tab.with_overlay_color(overlay); + } + + tab.constrained().with_height(tab_style.height).boxed() } fn handle_dropped_item(pane: &WeakViewHandle, index: usize, cx: &mut EventContext) { @@ -1237,6 +1263,23 @@ impl Pane { cx.propogate_event(); } } + + fn tab_overlay_color( + hovered: bool, + theme: &Theme, + cx: &mut RenderContext, + ) -> Option { + if hovered + && cx + .global::>() + .currently_dragged::() + .is_some() + { + Some(theme.workspace.tab_bar.drop_target_overlay_color) + } else { + None + } + } } impl Entity for Pane { @@ -1327,7 +1370,7 @@ impl View for Pane { tab_row .constrained() .with_height(cx.global::().theme.workspace.tab_bar.height) - .boxed() + .named("tab bar") }) .with_child(ChildView::new(&self.toolbar).boxed()) .with_child(ChildView::new(active_item).flex(1., true).boxed()) diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/styleTree/tabBar.ts index 039b8d89f4..1e50d7fff4 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/styleTree/tabBar.ts @@ -72,6 +72,7 @@ export default function tabBar(theme: Theme) { return { height, background: backgroundColor(theme, 300), + dropTargetOverlayColor: withOpacity(theme.textColor.muted, 0.8), border: border(theme, "primary", { left: true, bottom: true, From 22f62ee137027b10b5164a7ef3ea33d52259d399 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Thu, 25 Aug 2022 16:21:44 -0700 Subject: [PATCH 16/16] Add tests for Pane::add_item --- crates/workspace/src/pane.rs | 328 ++++++++++++++++++++++++++---- crates/workspace/src/workspace.rs | 22 +- 2 files changed, 306 insertions(+), 44 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 7852ad6868..d8e1ea8e2f 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -449,13 +449,13 @@ impl Pane { // Even if the item exists, we re-add it to reorder it after the active item. // We may revisit this behavior after adding an "activation history" for pane items. let item = existing_item.unwrap_or_else(|| pane.update(cx, |_, cx| build_item(cx))); - Pane::add_item(workspace, pane, item.clone(), true, focus_item, cx); + Pane::add_item(workspace, &pane, item.clone(), true, focus_item, None, cx); item } - pub fn add_item_at( + pub fn add_item( workspace: &mut Workspace, - pane: ViewHandle, + pane: &ViewHandle, item: Box, activate_pane: bool, focus_item: bool, @@ -463,69 +463,74 @@ impl Pane { cx: &mut ViewContext, ) { // If no destination index is specified, add or move the item after the active item. - let mut destination_index = if let Some(destination_index) = destination_index { - destination_index - } else { + let mut insertion_index = { let pane = pane.read(cx); - cmp::min(pane.active_item_index + 1, pane.items.len()) + cmp::min( + if let Some(destination_index) = destination_index { + destination_index + } else { + pane.active_item_index + 1 + }, + pane.items.len(), + ) }; // Does the item already exist? - if let Some(existing_item_index) = pane.read(cx).items.iter().position(|i| { - i.id() == item.id() || i.project_entry_ids(cx) == item.project_entry_ids(cx) + if let Some(existing_item_index) = pane.read(cx).items.iter().position(|existing_item| { + let existing_item_entry_ids = existing_item.project_entry_ids(cx); + let added_item_entry_ids = item.project_entry_ids(cx); + let entries_match = !existing_item_entry_ids.is_empty() + && existing_item_entry_ids == added_item_entry_ids; + + existing_item.id() == item.id() || entries_match }) { // If the item already exists, move it to the desired destination and activate it pane.update(cx, |pane, cx| { - if existing_item_index != destination_index { + if existing_item_index != insertion_index { cx.reparent(&item); let existing_item_is_active = existing_item_index == pane.active_item_index; - pane.items.remove(existing_item_index); - if existing_item_index < pane.active_item_index { - pane.active_item_index -= 1; - } - destination_index = destination_index.min(pane.items.len()); + // If the caller didn't specify a destination and the added item is already + // the active one, don't move it + if existing_item_is_active && destination_index.is_none() { + insertion_index = existing_item_index; + } else { + pane.items.remove(existing_item_index); + if existing_item_index < pane.active_item_index { + pane.active_item_index -= 1; + } + insertion_index = insertion_index.min(pane.items.len()); - pane.items.insert(destination_index, item.clone()); + pane.items.insert(insertion_index, item.clone()); - if existing_item_is_active { - pane.active_item_index = destination_index; - } else if destination_index <= pane.active_item_index { - pane.active_item_index += 1; + if existing_item_is_active { + pane.active_item_index = insertion_index; + } else if insertion_index <= pane.active_item_index { + pane.active_item_index += 1; + } } cx.notify(); } - pane.activate_item(destination_index, activate_pane, focus_item, cx); + pane.activate_item(insertion_index, activate_pane, focus_item, cx); }); } else { // If the item doesn't already exist, add it and activate it item.added_to_pane(workspace, pane.clone(), cx); pane.update(cx, |pane, cx| { cx.reparent(&item); - pane.items.insert(destination_index, item); - if destination_index <= pane.active_item_index { + pane.items.insert(insertion_index, item); + if insertion_index <= pane.active_item_index { pane.active_item_index += 1; } - pane.activate_item(destination_index, activate_pane, focus_item, cx); + pane.activate_item(insertion_index, activate_pane, focus_item, cx); cx.notify(); }); } } - pub(crate) fn add_item( - workspace: &mut Workspace, - pane: ViewHandle, - item: Box, - activate_pane: bool, - focus_item: bool, - cx: &mut ViewContext, - ) { - Self::add_item_at(workspace, pane, item, activate_pane, focus_item, None, cx) - } - pub fn items(&self) -> impl Iterator> { self.items.iter() } @@ -913,9 +918,9 @@ impl Pane { .expect("Tried to move item handle which was not in from pane"); // This automatically removes duplicate items in the pane - Pane::add_item_at( + Pane::add_item( workspace, - to.clone(), + &to, item_handle.clone(), true, true, @@ -1527,3 +1532,252 @@ impl NavHistory { } } } + +#[cfg(test)] +mod tests { + use gpui::TestAppContext; + use project::FakeFs; + + use crate::tests::TestItem; + + use super::*; + + #[gpui::test] + async fn test_add_item_with_new_item(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + Settings::test_async(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + // 1. Add with a destination index + // a. Add before the active item + set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item( + workspace, + &pane, + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + Some(0), + cx, + ); + }); + assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + + // b. Add after the active item + set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item( + workspace, + &pane, + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + Some(2), + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + + // c. Add at the end of the item list (including off the length) + set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item( + workspace, + &pane, + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + Some(5), + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + + // 2. Add without a destination index + // a. Add with active item at the start of the item list + set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item( + workspace, + &pane, + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + None, + cx, + ); + }); + set_labeled_items(&workspace, &pane, ["A", "D*", "B", "C"], cx); + + // b. Add with active item at the end of the item list + set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item( + workspace, + &pane, + Box::new(cx.add_view(|_| TestItem::new().with_label("D"))), + false, + false, + None, + cx, + ); + }); + assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); + } + + #[gpui::test] + async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + Settings::test_async(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, None, cx).await; + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + // 1. Add with a destination index + // 1a. Add before the active item + let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, d, false, false, Some(0), cx); + }); + assert_item_labels(&pane, ["D*", "A", "B", "C"], cx); + + // 1b. Add after the active item + let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, d, false, false, Some(2), cx); + }); + assert_item_labels(&pane, ["A", "B", "D*", "C"], cx); + + // 1c. Add at the end of the item list (including off the length) + let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C", "D"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, a, false, false, Some(5), cx); + }); + assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + + // 1d. Add same item to active index + let [_, b, _] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, b, false, false, Some(1), cx); + }); + assert_item_labels(&pane, ["A", "B*", "C"], cx); + + // 1e. Add item to index after same item in last position + let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B*", "C"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, c, false, false, Some(2), cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + // 2. Add without a destination index + // 2a. Add with active item at the start of the item list + let [_, _, _, d] = set_labeled_items(&workspace, &pane, ["A*", "B", "C", "D"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, d, false, false, None, cx); + }); + assert_item_labels(&pane, ["A", "D*", "B", "C"], cx); + + // 2b. Add with active item at the end of the item list + let [a, _, _, _] = set_labeled_items(&workspace, &pane, ["A", "B", "C", "D*"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, a, false, false, None, cx); + }); + assert_item_labels(&pane, ["B", "C", "D", "A*"], cx); + + // 2c. Add active item to active item at end of list + let [_, _, c] = set_labeled_items(&workspace, &pane, ["A", "B", "C*"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, c, false, false, None, cx); + }); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + // 2d. Add active item to active item at start of list + let [a, _, _] = set_labeled_items(&workspace, &pane, ["A*", "B", "C"], cx); + workspace.update(cx, |workspace, cx| { + Pane::add_item(workspace, &pane, a, false, false, None, cx); + }); + assert_item_labels(&pane, ["A*", "B", "C"], cx); + } + + fn set_labeled_items( + workspace: &ViewHandle, + pane: &ViewHandle, + labels: [&str; COUNT], + cx: &mut TestAppContext, + ) -> [Box>; COUNT] { + pane.update(cx, |pane, _| { + pane.items.clear(); + }); + + workspace.update(cx, |workspace, cx| { + let mut active_item_index = 0; + + let mut index = 0; + let items = labels.map(|mut label| { + if label.ends_with("*") { + label = label.trim_end_matches("*"); + active_item_index = index; + } + + let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label))); + Pane::add_item( + workspace, + pane, + labeled_item.clone(), + false, + false, + None, + cx, + ); + index += 1; + labeled_item + }); + + pane.update(cx, |pane, cx| { + pane.activate_item(active_item_index, false, false, cx) + }); + + items + }) + } + + // Assert the item label, with the active item label suffixed with a '*' + fn assert_item_labels( + pane: &ViewHandle, + expected_states: [&str; COUNT], + cx: &mut TestAppContext, + ) { + pane.read_with(cx, |pane, cx| { + let actual_states = pane + .items + .iter() + .enumerate() + .map(|(ix, item)| { + let mut state = item + .to_any() + .downcast::() + .unwrap() + .read(cx) + .label + .clone(); + if ix == pane.active_item_index { + state.push('*'); + } + state + }) + .collect::>(); + + assert_eq!( + actual_states, expected_states, + "pane items do not match expectation" + ); + }) + } +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2d2782eaa4..1261a89f95 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1448,8 +1448,8 @@ impl Workspace { } pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { - let pane = self.active_pane().clone(); - Pane::add_item(self, pane, item, true, true, cx); + let active_pane = self.active_pane().clone(); + Pane::add_item(self, &active_pane, item, true, true, None, cx); } pub fn open_path( @@ -1649,7 +1649,7 @@ impl Workspace { pane.read(cx).active_item().map(|item| { let new_pane = self.add_pane(cx); if let Some(clone) = item.clone_on_split(cx.as_mut()) { - Pane::add_item(self, new_pane.clone(), clone, true, true, cx); + Pane::add_item(self, &new_pane, clone, true, true, None, cx); } self.center.split(&pane, &new_pane, direction).unwrap(); cx.notify(); @@ -2392,7 +2392,7 @@ impl Workspace { } for (pane, item) in items_to_add { - Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx); + Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx); if pane == self.active_pane { pane.update(cx, |pane, cx| pane.focus_active_item(cx)); } @@ -3330,8 +3330,9 @@ mod tests { }); } - struct TestItem { + pub struct TestItem { state: String, + pub label: String, save_count: usize, save_as_count: usize, reload_count: usize, @@ -3345,7 +3346,7 @@ mod tests { tab_detail: Cell>, } - enum TestItemEvent { + pub enum TestItemEvent { Edit, } @@ -3353,6 +3354,7 @@ mod tests { fn clone(&self) -> Self { Self { state: self.state.clone(), + label: self.label.clone(), save_count: self.save_count, save_as_count: self.save_as_count, reload_count: self.reload_count, @@ -3369,9 +3371,10 @@ mod tests { } impl TestItem { - fn new() -> Self { + pub fn new() -> Self { Self { state: String::new(), + label: String::new(), save_count: 0, save_as_count: 0, reload_count: 0, @@ -3386,6 +3389,11 @@ mod tests { } } + pub fn with_label(mut self, state: &str) -> Self { + self.label = state.to_string(); + self + } + fn set_state(&mut self, state: String, cx: &mut ViewContext) { self.push_to_nav_history(cx); self.state = state;