From 66f833eccf7531c44b3c483ac4df8bd303469a85 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 19 Dec 2023 18:31:27 -0700 Subject: [PATCH] Use MouseState instead of ActiveDrag to make room for clicks Co-Authored-By: Max --- crates/gpui2/src/app.rs | 48 +++++++++++++++++++++++------- crates/gpui2/src/elements/div.rs | 50 ++++++++++++++------------------ crates/gpui2/src/window.rs | 22 +++++++------- 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 8748a60249..ab32286cc1 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -17,10 +17,11 @@ use time::UtcOffset; use crate::{ current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, - DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap, - Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, - TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, + DispatchPhase, DisplayId, ElementId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, + Keymap, Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, + Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, + TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, + WindowId, }; use anyhow::{anyhow, Result}; use collections::{FxHashMap, FxHashSet, VecDeque}; @@ -188,7 +189,7 @@ pub struct AppContext { flushing_effects: bool, pending_updates: usize, pub(crate) actions: Rc, - pub(crate) active_drag: Option, + pub(crate) mouse_state: MouseState, pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: FxHashMap>, pub(crate) frame_consumers: FxHashMap>, @@ -250,7 +251,7 @@ impl AppContext { actions: Rc::new(ActionRegistry::default()), flushing_effects: false, pending_updates: 0, - active_drag: None, + mouse_state: MouseState::None, active_tooltip: None, next_frame_callbacks: FxHashMap::default(), frame_consumers: FxHashMap::default(), @@ -1089,13 +1090,15 @@ impl AppContext { } pub fn has_active_drag(&self) -> bool { - self.active_drag.is_some() + self.mouse_state.is_dragging() } pub fn active_drag(&self) -> Option<&T> { - self.active_drag - .as_ref() - .and_then(|drag| drag.value.downcast_ref()) + if let MouseState::Dragging(drag) = &self.mouse_state { + drag.value.downcast_ref() + } else { + None + } } } @@ -1243,6 +1246,31 @@ impl DerefMut for GlobalLease { } } +#[derive(Default)] +pub enum MouseState { + #[default] + None, + Clicked(ElementId), + Dragging(AnyDrag), +} + +impl MouseState { + pub fn take_drag(&mut self) -> Option { + match mem::take(self) { + MouseState::None => None, + MouseState::Clicked(element_id) => { + *self = MouseState::Clicked(element_id); + None + } + MouseState::Dragging(drag) => Some(drag), + } + } + + pub fn is_dragging(&self) -> bool { + matches!(self, Self::Dragging(_)) + } +} + /// Contains state associated with an active drag operation, started by dragging an element /// within the window or by dragging into the app from the underlying platform. pub struct AnyDrag { diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 0d477fc62d..5d64e3d4f4 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -2,8 +2,8 @@ use crate::{ point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, - StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, + MouseState, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, + Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, }; use collections::HashMap; @@ -38,9 +38,7 @@ pub struct DragMoveEvent { impl DragMoveEvent { pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T { - cx.active_drag - .as_ref() - .and_then(|drag| drag.value.downcast_ref::()) + cx.active_drag() .expect("DragMoveEvent is only valid when the stored active drag is of the same type.") } } @@ -152,19 +150,17 @@ impl Interactivity { self.mouse_move_listeners .push(Box::new(move |event, bounds, phase, cx| { if phase == DispatchPhase::Capture { - if cx - .active_drag - .as_ref() - .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::()) - { - (listener)( - &DragMoveEvent { - event: event.clone(), - bounds: bounds.bounds, - drag: PhantomData, - }, - cx, - ); + if let MouseState::Dragging(drag) = &cx.mouse_state { + if drag.value.as_ref().downcast_ref::().is_some() { + (listener)( + &DragMoveEvent { + event: event.clone(), + bounds: bounds.bounds, + drag: PhantomData, + }, + cx, + ); + } } } })); @@ -1114,7 +1110,7 @@ impl Interactivity { if self.hover_style.is_some() || self.base_style.mouse_cursor.is_some() - || cx.active_drag.is_some() && !self.drag_over_styles.is_empty() + || cx.mouse_state.is_dragging() && !self.drag_over_styles.is_empty() { let bounds = bounds.intersect(&cx.content_mask().bounds); let hovered = bounds.contains(&cx.mouse_position()); @@ -1135,18 +1131,14 @@ impl Interactivity { cx.on_mouse_event({ let interactive_bounds = interactive_bounds.clone(); move |event: &MouseUpEvent, phase, cx| { - if let Some(drag) = &cx.active_drag { + if let MouseState::Dragging(drag) = &cx.mouse_state { if phase == DispatchPhase::Bubble && interactive_bounds.drag_target_contains(&event.position, cx) { let drag_state_type = drag.value.as_ref().type_id(); for (drop_state_type, listener) in &drop_listeners { if *drop_state_type == drag_state_type { - let drag = cx - .active_drag - .take() - .expect("checked for type drag state type above"); - + let drag = cx.mouse_state.take_drag().unwrap(); listener(drag.value.as_ref(), cx); cx.notify(); cx.stop_propagation(); @@ -1189,7 +1181,7 @@ impl Interactivity { let mut pending_mouse_down = pending_mouse_down.borrow_mut(); if let Some(mouse_down) = pending_mouse_down.clone() { - if cx.active_drag.is_some() { + if cx.mouse_state.is_dragging() { if phase == DispatchPhase::Capture { cx.notify(); } @@ -1201,7 +1193,7 @@ impl Interactivity { *active_state.borrow_mut() = ElementClickedState::default(); let cursor_offset = event.position - bounds.origin; let drag = (drag_listener)(drag_value.as_ref(), cx); - cx.active_drag = Some(AnyDrag { + cx.mouse_state = MouseState::Dragging(AnyDrag { view: drag, value: drag_value, cursor_offset, @@ -1502,7 +1494,7 @@ impl Interactivity { } } - if let Some(drag) = cx.active_drag.take() { + if let Some(drag) = cx.mouse_state.take_drag() { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { if *state_type == drag.value.as_ref().type_id() @@ -1527,7 +1519,7 @@ impl Interactivity { } } - cx.active_drag = Some(drag); + cx.mouse_state = MouseState::Dragging(drag); } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index f7ebddd0fe..54d0b9a1bf 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -6,12 +6,12 @@ use crate::{ DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, - MouseButton, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, - PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, - RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SceneBuilder, - Shadow, SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, - Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, - SUBPIXEL_VARIANTS, + MouseButton, MouseMoveEvent, MouseState, MouseUpEvent, Path, Pixels, PlatformAtlas, + PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, + Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, + SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, Surface, + TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashMap; @@ -1324,13 +1324,13 @@ impl<'a> WindowContext<'a> { }) }); - if let Some(active_drag) = self.app.active_drag.take() { + if let Some(active_drag) = self.app.mouse_state.take_drag() { self.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| { let offset = cx.mouse_position() - active_drag.cursor_offset; let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); active_drag.view.draw(offset, available_space, cx); }); - self.active_drag = Some(active_drag); + self.app.mouse_state = MouseState::Dragging(active_drag); } else if let Some(active_tooltip) = self.app.active_tooltip.take() { self.with_z_index(1, |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); @@ -1435,8 +1435,8 @@ impl<'a> WindowContext<'a> { InputEvent::FileDrop(file_drop) => match file_drop { FileDropEvent::Entered { position, files } => { self.window.mouse_position = position; - if self.active_drag.is_none() { - self.active_drag = Some(AnyDrag { + if !self.mouse_state.is_dragging() { + self.mouse_state = MouseState::Dragging(AnyDrag { value: Box::new(files.clone()), view: self.build_view(|_| files).into(), cursor_offset: position, @@ -1515,7 +1515,7 @@ impl<'a> WindowContext<'a> { } if self.app.propagate_event && event.downcast_ref::().is_some() { - self.active_drag = None; + self.mouse_state = MouseState::None; } self.window