diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index 28e30f97a7..6365b423d5 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -4,7 +4,7 @@ use gpui::{ elements::{Container, MouseEventHandler}, geometry::vector::Vector2F, scene::DragRegionEvent, - Element, ElementBox, EventContext, MouseButton, RenderContext, View, ViewContext, + CursorStyle, Element, ElementBox, EventContext, MouseButton, RenderContext, View, ViewContext, WeakViewHandle, }; @@ -33,7 +33,6 @@ pub struct DragAndDrop { 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()) @@ -110,6 +109,7 @@ impl DragAndDrop { .left() .boxed() }) + .with_cursor_style(CursorStyle::Arrow) .on_up(MouseButton::Left, |_, cx| { cx.defer(|cx| { cx.update_global::(|this, _| this.currently_dragged.take()); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index f16dfd18ad..4c8bed0491 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -5103,6 +5103,7 @@ impl From> for AnyWeakModelHandle { } } +#[derive(Debug)] pub struct WeakViewHandle { window_id: usize, view_id: usize, diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 4b96a3bb47..c1de513aa3 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -381,7 +381,12 @@ impl Presenter { } } MouseRegionEvent::Click(e) => { - if e.button == self.clicked_button.unwrap() { + // Only raise click events if the released button is the same as the one stored + if self + .clicked_button + .map(|clicked_button| clicked_button == e.button) + .unwrap_or(false) + { // Clear clicked regions and clicked button let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new()); diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs index a375651c7a..4aff9f1ab9 100644 --- a/crates/gpui/src/scene/mouse_region_event.rs +++ b/crates/gpui/src/scene/mouse_region_event.rs @@ -169,7 +169,7 @@ impl MouseRegionEvent { match self { MouseRegionEvent::Move(_) => true, MouseRegionEvent::Drag(_) => false, - MouseRegionEvent::Hover(_) => true, + MouseRegionEvent::Hover(_) => false, MouseRegionEvent::Down(_) => true, MouseRegionEvent::Up(_) => true, MouseRegionEvent::Click(_) => true, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index d8e1ea8e2f..f88b723bdf 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -172,7 +172,7 @@ pub enum Event { Focused, ActivateItem { local: bool }, Remove, - RemoveItem, + RemoveItem { item_id: usize }, Split(SplitDirection), ChangeItemTitle, } @@ -453,7 +453,7 @@ impl Pane { item } - pub fn add_item( + pub(crate) fn add_item( workspace: &mut Workspace, pane: &ViewHandle, item: Box, @@ -475,6 +475,8 @@ impl Pane { ) }; + item.added_to_pane(workspace, pane.clone(), cx); + // Does the item already exist? 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); @@ -516,8 +518,6 @@ impl Pane { 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(insertion_index, item); @@ -762,7 +762,7 @@ impl Pane { } let item = self.items.remove(item_ix); - cx.emit(Event::RemoveItem); + cx.emit(Event::RemoveItem { item_id: item.id() }); if self.items.is_empty() { item.deactivated(cx); self.update_toolbar(cx); @@ -1069,7 +1069,7 @@ impl Pane { let detail = detail.clone(); move |dragged_item, cx: &mut RenderContext| { let tab_style = &theme.workspace.tab_bar.dragged_tab; - Pane::render_tab( + Self::render_tab( &dragged_item.item, dragged_item.pane.clone(), detail, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1261a89f95..f258589ba7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -535,97 +535,123 @@ impl ItemHandle for ViewHandle { } } - let mut pending_autosave = None; - let mut cancel_pending_autosave = oneshot::channel::<()>().0; - let pending_update = Rc::new(RefCell::new(None)); - let pending_update_scheduled = Rc::new(AtomicBool::new(false)); - let pane = pane.downgrade(); - cx.subscribe(self, move |workspace, item, event, cx| { - let pane = if let Some(pane) = pane.upgrade(cx) { - pane - } else { - log::error!("unexpected item event after pane was dropped"); - return; - }; + if workspace + .panes_by_item + .insert(self.id(), pane.downgrade()) + .is_none() + { + let mut pending_autosave = None; + let mut cancel_pending_autosave = oneshot::channel::<()>().0; + let pending_update = Rc::new(RefCell::new(None)); + let pending_update_scheduled = Rc::new(AtomicBool::new(false)); - if let Some(item) = item.to_followable_item_handle(cx) { - let leader_id = workspace.leader_for_pane(&pane); + let mut event_subscription = + Some(cx.subscribe(self, move |workspace, item, event, cx| { + let pane = if let Some(pane) = workspace + .panes_by_item + .get(&item.id()) + .and_then(|pane| pane.upgrade(cx)) + { + pane + } else { + log::error!("unexpected item event after pane was dropped"); + return; + }; - if leader_id.is_some() && item.should_unfollow_on_event(event, cx) { - workspace.unfollow(&pane, cx); - } + if let Some(item) = item.to_followable_item_handle(cx) { + let leader_id = workspace.leader_for_pane(&pane); - if item.add_event_to_update_proto(event, &mut *pending_update.borrow_mut(), cx) - && !pending_update_scheduled.load(SeqCst) - { - pending_update_scheduled.store(true, SeqCst); - cx.after_window_update({ - let pending_update = pending_update.clone(); - let pending_update_scheduled = pending_update_scheduled.clone(); - move |this, cx| { - pending_update_scheduled.store(false, SeqCst); - this.update_followers( - proto::update_followers::Variant::UpdateView(proto::UpdateView { - id: item.id() as u64, - variant: pending_update.borrow_mut().take(), - leader_id: leader_id.map(|id| id.0), - }), - cx, - ); - } - }); - } - } - - if T::should_close_item_on_event(event) { - Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx); - return; - } - - if T::should_update_tab_on_event(event) { - pane.update(cx, |_, cx| { - cx.emit(pane::Event::ChangeItemTitle); - cx.notify(); - }); - } - - if T::is_edit_event(event) { - if let Autosave::AfterDelay { milliseconds } = cx.global::().autosave { - let prev_autosave = pending_autosave - .take() - .unwrap_or_else(|| Task::ready(Some(()))); - let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>(); - let prev_cancel_tx = mem::replace(&mut cancel_pending_autosave, cancel_tx); - let project = workspace.project.downgrade(); - let _ = prev_cancel_tx.send(()); - pending_autosave = Some(cx.spawn_weak(|_, mut cx| async move { - let mut timer = cx - .background() - .timer(Duration::from_millis(milliseconds)) - .fuse(); - prev_autosave.await; - futures::select_biased! { - _ = cancel_rx => return None, - _ = timer => {} + if leader_id.is_some() && item.should_unfollow_on_event(event, cx) { + workspace.unfollow(&pane, cx); } - let project = project.upgrade(&cx)?; - cx.update(|cx| Pane::autosave_item(&item, project, cx)) - .await - .log_err(); - None - })); - } - } - }) - .detach(); + if item.add_event_to_update_proto( + event, + &mut *pending_update.borrow_mut(), + cx, + ) && !pending_update_scheduled.load(SeqCst) + { + pending_update_scheduled.store(true, SeqCst); + cx.after_window_update({ + let pending_update = pending_update.clone(); + let pending_update_scheduled = pending_update_scheduled.clone(); + move |this, cx| { + pending_update_scheduled.store(false, SeqCst); + this.update_followers( + proto::update_followers::Variant::UpdateView( + proto::UpdateView { + id: item.id() as u64, + variant: pending_update.borrow_mut().take(), + leader_id: leader_id.map(|id| id.0), + }, + ), + cx, + ); + } + }); + } + } - cx.observe_focus(self, move |workspace, item, focused, cx| { - if !focused && cx.global::().autosave == Autosave::OnFocusChange { - Pane::autosave_item(&item, workspace.project.clone(), cx).detach_and_log_err(cx); - } - }) - .detach(); + if T::should_close_item_on_event(event) { + Pane::close_item(workspace, pane, item.id(), cx).detach_and_log_err(cx); + return; + } + + if T::should_update_tab_on_event(event) { + pane.update(cx, |_, cx| { + cx.emit(pane::Event::ChangeItemTitle); + cx.notify(); + }); + } + + if T::is_edit_event(event) { + if let Autosave::AfterDelay { milliseconds } = + cx.global::().autosave + { + let prev_autosave = pending_autosave + .take() + .unwrap_or_else(|| Task::ready(Some(()))); + let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>(); + let prev_cancel_tx = + mem::replace(&mut cancel_pending_autosave, cancel_tx); + let project = workspace.project.downgrade(); + let _ = prev_cancel_tx.send(()); + pending_autosave = Some(cx.spawn_weak(|_, mut cx| async move { + let mut timer = cx + .background() + .timer(Duration::from_millis(milliseconds)) + .fuse(); + prev_autosave.await; + futures::select_biased! { + _ = cancel_rx => return None, + _ = timer => {} + } + + let project = project.upgrade(&cx)?; + cx.update(|cx| Pane::autosave_item(&item, project, cx)) + .await + .log_err(); + None + })); + } + } + })); + + cx.observe_focus(self, move |workspace, item, focused, cx| { + if !focused && cx.global::().autosave == Autosave::OnFocusChange { + Pane::autosave_item(&item, workspace.project.clone(), cx) + .detach_and_log_err(cx); + } + }) + .detach(); + + let item_id = self.id(); + cx.observe_release(self, move |workspace, _, _| { + workspace.panes_by_item.remove(&item_id); + event_subscription.take(); + }) + .detach(); + } } fn deactivated(&self, cx: &mut MutableAppContext) { @@ -799,6 +825,7 @@ pub struct Workspace { left_sidebar: ViewHandle, right_sidebar: ViewHandle, panes: Vec>, + panes_by_item: HashMap>, active_pane: ViewHandle, status_bar: ViewHandle, notifications: Vec<(TypeId, usize, Box)>, @@ -910,6 +937,7 @@ impl Workspace { weak_self, center: PaneGroup::new(pane.clone()), panes: vec![pane.clone()], + panes_by_item: Default::default(), active_pane: pane.clone(), status_bar, notifications: Default::default(), @@ -1631,8 +1659,13 @@ impl Workspace { } self.update_window_edited(cx); } - pane::Event::RemoveItem => { + pane::Event::RemoveItem { item_id } => { self.update_window_edited(cx); + if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) { + if entry.get().id() == pane.id() { + entry.remove(); + } + } } } } else { @@ -1663,6 +1696,9 @@ impl Workspace { cx.focus(self.panes.last().unwrap().clone()); self.unfollow(&pane, cx); self.last_leaders_by_pane.remove(&pane.downgrade()); + for removed_item in pane.read(cx).items() { + self.panes_by_item.remove(&removed_item.id()); + } cx.notify(); } else { self.active_item_path_changed(cx); diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/styleTree/tabBar.ts index 1e50d7fff4..a3406e567c 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/styleTree/tabBar.ts @@ -72,7 +72,7 @@ export default function tabBar(theme: Theme) { return { height, background: backgroundColor(theme, 300), - dropTargetOverlayColor: withOpacity(theme.textColor.muted, 0.8), + dropTargetOverlayColor: withOpacity(theme.textColor.muted, 0.6), border: border(theme, "primary", { left: true, bottom: true,