gpui: Track in-app drags outside of every window
Platforms that can query the global cursor position (X11 via QueryPointer, tests via an injected state) now report it through Platform::global_cursor_state. While a drag is active, App samples that state to keep drag_hover_window current: the drag ghost paints only in the window under the cursor and in no window while the cursor is outside all of them, fixing stale ghosts left behind in the source window. When the button is released outside every window, no window receives the mouse-up, so the drag used to stick until the next click. The poll now detects that release and hands the drag to a handler registered with App::set_drag_dropped_outside_handler, letting the workspace layer decide what a tear-off means.
This commit is contained in:
1 parent
935e2b0bd0
commit
a7b8190c24
10 files changed
+284
-36
No files matched your search
+120
-6
@@ -44,14 +44,14 @@ use crate::{
|
||||
Action, ActionBuildError, ActionRegistry, ActivityGuard, Any, AnyView, AnyWindowHandle,
|
||||
AppContext, Arena, ArenaBox, Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem,
|
||||
ClipboardReadError, CursorStyle, DispatchPhase, DisplayId, EventEmitter, ExternalDragPayload,
|
||||
FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext, Keymap, Keystroke,
|
||||
LayoutId, Menu, MenuItem, MissingGlyph, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, Priority,
|
||||
PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage,
|
||||
FocusHandle, FocusMap, ForegroundExecutor, Global, GlobalCursorState, KeyBinding, KeyContext,
|
||||
Keymap, Keystroke, LayoutId, Menu, MenuItem, MissingGlyph, OwnedMenu, PathPromptOptions,
|
||||
Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point,
|
||||
Priority, PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage,
|
||||
RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
|
||||
Subscription, SvgRenderer, SystemNotification, SystemNotificationResponse, Task,
|
||||
TextRenderingMode, TextSystem, ThermalState, Window, WindowAppearance, WindowButtonLayout,
|
||||
WindowHandle, WindowId, WindowInvalidator,
|
||||
TextRenderingMode, TextSystem, ThermalState, Window, WindowAppearance, WindowBounds,
|
||||
WindowButtonLayout, WindowHandle, WindowId, WindowInvalidator,
|
||||
colors::{Colors, GlobalColors},
|
||||
hash, init_app_menus,
|
||||
};
|
||||
@@ -738,6 +738,10 @@ enum PlatformOwnedDragState {
|
||||
RestoredInSourceWindow,
|
||||
}
|
||||
|
||||
/// How often the global cursor state is sampled while an in-app drag is
|
||||
/// active, to notice releases that happen outside of every window.
|
||||
const DRAG_POLL_INTERVAL: Duration = Duration::from_millis(30);
|
||||
|
||||
/// Contains the state of the full application, and passed as a reference to a variety of callbacks.
|
||||
/// Other [Context] derefs to this type.
|
||||
/// You need a reference to an `App` to access the state of a [Entity].
|
||||
@@ -749,6 +753,10 @@ pub struct App {
|
||||
pub(crate) actions: Rc<ActionRegistry>,
|
||||
pub(crate) active_drag: Option<AnyDrag>,
|
||||
platform_owned_drag: Option<PlatformOwnedDrag>,
|
||||
pub(crate) drag_hover_window: Option<Option<WindowId>>,
|
||||
drag_poll_running: bool,
|
||||
drag_poll_task: Option<Task<()>>,
|
||||
drag_dropped_outside_handler: Option<Box<dyn FnMut(&AnyDrag, Point<Pixels>, &mut App)>>,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
#[cfg(feature = "profiler")]
|
||||
@@ -875,6 +883,10 @@ impl App {
|
||||
pending_updates: 0,
|
||||
active_drag: None,
|
||||
platform_owned_drag: None,
|
||||
drag_hover_window: None,
|
||||
drag_poll_running: false,
|
||||
drag_poll_task: None,
|
||||
drag_dropped_outside_handler: None,
|
||||
background_executor,
|
||||
foreground_executor,
|
||||
#[cfg(feature = "profiler")]
|
||||
@@ -2636,6 +2648,7 @@ impl App {
|
||||
pub fn stop_active_drag(&mut self, window: &mut Window) -> bool {
|
||||
if self.active_drag.is_some() {
|
||||
self.active_drag = None;
|
||||
self.drag_hover_window = None;
|
||||
if self.platform_owned_drag.as_ref().is_some_and(|drag| {
|
||||
drag.source_window == window.window_handle().window_id()
|
||||
&& matches!(&drag.state, PlatformOwnedDragState::RestoredInSourceWindow)
|
||||
@@ -2649,6 +2662,107 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a handler invoked when an in-app drag is released outside of
|
||||
/// every application window, on platforms that report the global cursor
|
||||
/// state.
|
||||
pub fn set_drag_dropped_outside_handler(
|
||||
&mut self,
|
||||
handler: impl FnMut(&AnyDrag, Point<Pixels>, &mut App) + 'static,
|
||||
) {
|
||||
self.drag_dropped_outside_handler = Some(Box::new(handler));
|
||||
}
|
||||
|
||||
pub(crate) fn should_paint_drag_in(&self, window_id: WindowId) -> bool {
|
||||
match self.drag_hover_window {
|
||||
None => true,
|
||||
Some(hovered_window) => hovered_window == Some(window_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_drag_hover_window(&mut self, hovered: Option<Option<WindowId>>) {
|
||||
if self.drag_hover_window == hovered {
|
||||
return;
|
||||
}
|
||||
let previous = mem::replace(&mut self.drag_hover_window, hovered).flatten();
|
||||
let next = hovered.flatten();
|
||||
for window_id in [previous, next].into_iter().flatten() {
|
||||
if let Some(handle) = self.window_handles.get(&window_id).copied() {
|
||||
handle
|
||||
.update(self, |_, window, _| window.refresh())
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_drag_poll_running(&mut self) {
|
||||
if self.drag_poll_running || self.active_drag.is_none() {
|
||||
return;
|
||||
}
|
||||
if self.platform.global_cursor_state().is_none() {
|
||||
return;
|
||||
}
|
||||
self.drag_poll_running = true;
|
||||
self.drag_poll_task = Some(self.spawn(async move |cx| {
|
||||
loop {
|
||||
cx.background_executor.timer(DRAG_POLL_INTERVAL).await;
|
||||
let Some(state) = cx.update(|cx: &mut App| cx.platform.global_cursor_state())
|
||||
else {
|
||||
break;
|
||||
};
|
||||
let finished = cx.update(|cx: &mut App| cx.poll_drag_state(state));
|
||||
if finished {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cx.update(|cx: &mut App| cx.drag_poll_running = false);
|
||||
}));
|
||||
}
|
||||
|
||||
fn poll_drag_state(&mut self, state: GlobalCursorState) -> bool {
|
||||
if self.active_drag.is_none() {
|
||||
self.set_drag_hover_window(None);
|
||||
return true;
|
||||
}
|
||||
let hovered_window = self.window_under_point(state.position);
|
||||
self.set_drag_hover_window(Some(hovered_window));
|
||||
if state.left_button_pressed {
|
||||
return false;
|
||||
}
|
||||
if hovered_window.is_none()
|
||||
&& let Some(drag) = self.active_drag.take()
|
||||
{
|
||||
self.set_drag_hover_window(None);
|
||||
let mut handler = self.drag_dropped_outside_handler.take();
|
||||
if let Some(handler) = handler.as_mut() {
|
||||
handler(&drag, state.position, self);
|
||||
}
|
||||
self.drag_dropped_outside_handler = handler;
|
||||
self.refresh_windows();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn window_under_point(&mut self, position: Point<Pixels>) -> Option<WindowId> {
|
||||
let ordered_handles = self
|
||||
.platform
|
||||
.window_stack()
|
||||
.unwrap_or_else(|| self.window_handles.values().copied().collect());
|
||||
for handle in ordered_handles {
|
||||
let Ok(bounds) = handle.update(self, |_, window, _| window.window_bounds()) else {
|
||||
continue;
|
||||
};
|
||||
let bounds = match bounds {
|
||||
WindowBounds::Windowed(bounds)
|
||||
| WindowBounds::Maximized(bounds)
|
||||
| WindowBounds::Fullscreen(bounds) => bounds,
|
||||
};
|
||||
if bounds.contains(&position) {
|
||||
return Some(handle.window_id());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn hand_active_drag_to_platform(&mut self, source_window: WindowId) -> bool {
|
||||
let Some(drag) = self.active_drag.take() else {
|
||||
return false;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::{
|
||||
Action, AnyView, AnyWindowHandle, App, AppCell, AppContext, AsyncApp, AvailableSpace,
|
||||
BackgroundExecutor, BorrowAppContext, Bounds, Capslock, ClipboardItem, DrawPhase, Drawable,
|
||||
Element, Empty, EntityId, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, Platform, PlatformTextSystem, Point, Render, Result, SharedString, Size,
|
||||
SystemNotification, SystemNotificationResponse, Task, TestDispatcher, TestPlatform,
|
||||
TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window, WindowBounds,
|
||||
WindowHandle, WindowOptions, WindowVisibility, app::GpuiMode, window::ElementArenaScope,
|
||||
Element, Empty, EntityId, EventEmitter, ForegroundExecutor, Global, GlobalCursorState,
|
||||
InputEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, Pixels, Platform, PlatformTextSystem, Point, Render, Result,
|
||||
SharedString, Size, SystemNotification, SystemNotificationResponse, Task, TestDispatcher,
|
||||
TestPlatform, TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window,
|
||||
WindowBounds, WindowHandle, WindowOptions, WindowVisibility, app::GpuiMode,
|
||||
window::ElementArenaScope,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt, channel::oneshot};
|
||||
@@ -500,6 +501,12 @@ impl TestAppContext {
|
||||
self.test_platform.set_screen_capture_sources(sources);
|
||||
}
|
||||
|
||||
/// Sets the global cursor state reported by the test platform, for
|
||||
/// exercising drags that leave every window.
|
||||
pub fn set_global_cursor_state(&self, state: Option<GlobalCursorState>) {
|
||||
self.test_platform.set_global_cursor_state(state);
|
||||
}
|
||||
|
||||
/// Returns all windows open in the test.
|
||||
pub fn windows(&self) -> Vec<AnyWindowHandle> {
|
||||
self.app.borrow().windows()
|
||||
|
||||
@@ -182,6 +182,16 @@ pub fn guess_compositor() -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// The global cursor position and mouse button state, used to follow
|
||||
/// in-app drags once the cursor leaves every application window.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct GlobalCursorState {
|
||||
/// The cursor position in global display coordinates.
|
||||
pub position: Point<Pixels>,
|
||||
/// Whether the left mouse button is currently pressed.
|
||||
pub left_button_pressed: bool,
|
||||
}
|
||||
|
||||
#[expect(missing_docs)]
|
||||
pub trait Platform: 'static {
|
||||
fn background_executor(&self) -> BackgroundExecutor;
|
||||
@@ -203,6 +213,13 @@ pub trait Platform: 'static {
|
||||
None
|
||||
}
|
||||
|
||||
/// The global cursor position and button state, for platforms that can
|
||||
/// query it without an active pointer grab. Returning `None` disables
|
||||
/// following drags outside of the application's windows.
|
||||
fn global_cursor_state(&self) -> Option<GlobalCursorState> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_screen_capture_supported(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ use crate::NoopTextSystem;
|
||||
use crate::PathPromptOptions;
|
||||
use crate::{
|
||||
ActivityGuard, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
|
||||
DummyKeyboardMapper, ForegroundExecutor, Keymap, OwnedMenu, Platform, PlatformDisplay,
|
||||
PlatformHeadlessRenderer, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
|
||||
PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SharedString,
|
||||
SourceMetadata, SystemNotification, SystemNotificationResponse, Task, TestDisplay, TestWindow,
|
||||
ThermalState, WindowAppearance, WindowParams, size,
|
||||
DummyKeyboardMapper, ForegroundExecutor, GlobalCursorState, Keymap, OwnedMenu, Platform,
|
||||
PlatformDisplay, PlatformHeadlessRenderer, PlatformKeyboardLayout, PlatformKeyboardMapper,
|
||||
PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
|
||||
SharedString, SourceMetadata, SystemNotification, SystemNotificationResponse, Task,
|
||||
TestDisplay, TestWindow, ThermalState, WindowAppearance, WindowParams, size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -42,6 +42,7 @@ pub(crate) struct TestPlatform {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) prompts: RefCell<TestPrompts>,
|
||||
screen_capture_sources: RefCell<Vec<TestScreenCaptureSource>>,
|
||||
pub(crate) global_cursor_state: RefCell<Option<GlobalCursorState>>,
|
||||
pub opened_url: RefCell<Option<String>>,
|
||||
pub(crate) system_notifications: RefCell<TestSystemNotifications>,
|
||||
pub text_system: Arc<dyn PlatformTextSystem>,
|
||||
@@ -153,6 +154,7 @@ impl TestPlatform {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
prompts: Default::default(),
|
||||
screen_capture_sources: Default::default(),
|
||||
global_cursor_state: Default::default(),
|
||||
active_cursor: Default::default(),
|
||||
active_display: Rc::new(TestDisplay::new()),
|
||||
active_window: Default::default(),
|
||||
@@ -174,6 +176,11 @@ impl TestPlatform {
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the global cursor state reported by [`Platform::global_cursor_state`].
|
||||
pub fn set_global_cursor_state(&self, state: Option<GlobalCursorState>) {
|
||||
*self.global_cursor_state.borrow_mut() = state;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn simulate_new_path_selection(
|
||||
&self,
|
||||
@@ -489,6 +496,10 @@ impl Platform for TestPlatform {
|
||||
.map(|window| window.0.lock().handle)
|
||||
}
|
||||
|
||||
fn global_cursor_state(&self) -> Option<GlobalCursorState> {
|
||||
*self.global_cursor_state.borrow()
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
|
||||
@@ -1442,7 +1442,9 @@ pub fn font_name_with_fallbacks_shared<'a>(
|
||||
// IBM Plex/Lilex, and so retained here for backward compatibility.
|
||||
match name.as_str() {
|
||||
".SystemUIFont" => system,
|
||||
".GwenSans" | ".ZedSans" | "Zed Plex Sans" => const { &SharedString::new_static("IBM Plex Sans") },
|
||||
".GwenSans" | ".ZedSans" | "Zed Plex Sans" => {
|
||||
const { &SharedString::new_static("IBM Plex Sans") }
|
||||
}
|
||||
".GwenMono" | ".ZedMono" | "Zed Plex Mono" => const { &SharedString::new_static("Lilex") },
|
||||
_ => name,
|
||||
}
|
||||
|
||||
@@ -3563,7 +3563,9 @@ impl Window {
|
||||
element.prepaint_as_root(Point::default(), root_size.into(), self, cx);
|
||||
prompt_element = Some(element);
|
||||
self.prompt = Some(prompt);
|
||||
} else if let Some(active_drag) = cx.active_drag.take() {
|
||||
} else if cx.should_paint_drag_in(self.handle.window_id())
|
||||
&& let Some(active_drag) = cx.active_drag.take()
|
||||
{
|
||||
let mut element = active_drag.view.clone().into_any_element();
|
||||
let offset = self.mouse_position() - active_drag.cursor_offset;
|
||||
element.prepaint_as_root(offset, AvailableSpace::min_size(), self, cx);
|
||||
@@ -5789,14 +5791,17 @@ impl Window {
|
||||
self.rendered_frame.mouse_listeners = mouse_listeners;
|
||||
|
||||
if cx.has_active_drag() {
|
||||
cx.ensure_drag_poll_running();
|
||||
if event.is::<MouseMoveEvent>() {
|
||||
// If this was a mouse move event, redraw the window so that the
|
||||
// active drag can follow the mouse cursor.
|
||||
cx.set_drag_hover_window(Some(Some(self.handle.window_id())));
|
||||
self.refresh();
|
||||
} else if event.is::<MouseUpEvent>() {
|
||||
// If this was a mouse up event, cancel the active drag and redraw
|
||||
// the window.
|
||||
cx.active_drag = None;
|
||||
cx.set_drag_hover_window(None);
|
||||
self.refresh();
|
||||
}
|
||||
}
|
||||
@@ -7640,15 +7645,16 @@ mod tests {
|
||||
cell::{Cell, RefCell},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
AnyWindowHandle, AppContext as _, Bounds, ContentMask, Context, DispatchPhase,
|
||||
AnyDrag, AnyWindowHandle, AppContext as _, Bounds, ContentMask, Context, DispatchPhase,
|
||||
DragMoveEvent, Empty, ExternalDragPayload, ExternalPaths, FileDragPaths, FileDropEvent,
|
||||
FocusHandle, InputEvent as _, InteractiveElement as _, IntoElement, KeyDownEvent,
|
||||
Keystroke, LongPressEvent, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement,
|
||||
Pixels, PlatformInput, Point, Render, RequestFrameOptions, ScaledPixels,
|
||||
FocusHandle, GlobalCursorState, InputEvent as _, InteractiveElement as _, IntoElement,
|
||||
KeyDownEvent, Keystroke, LongPressEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
ParentElement, Pixels, PlatformInput, Point, Render, RequestFrameOptions, ScaledPixels,
|
||||
StatefulInteractiveElement as _, Styled, TestAppContext, TouchDragEvent, TouchEvent,
|
||||
TouchId, TouchPhase, Underline, UnderlineStyle, Window, WindowAppearance, WindowOptions,
|
||||
canvas, div, hsla, point, px, size,
|
||||
@@ -9109,6 +9115,65 @@ mod tests {
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_drag_release_outside_windows_notifies_handler(cx: &mut TestAppContext) {
|
||||
let dropped_position = Rc::new(RefCell::new(None));
|
||||
let observed_position = dropped_position.clone();
|
||||
cx.update(|cx| {
|
||||
cx.set_drag_dropped_outside_handler(move |_drag, position, _cx| {
|
||||
*observed_position.borrow_mut() = Some(position);
|
||||
});
|
||||
});
|
||||
|
||||
let window: AnyWindowHandle = cx.add_window(|_, _| EmptyView).into();
|
||||
cx.set_global_cursor_state(Some(GlobalCursorState {
|
||||
position: point(px(10.), px(10.)),
|
||||
left_button_pressed: true,
|
||||
}));
|
||||
cx.update_window(window, |_, window, cx| {
|
||||
cx.active_drag = Some(AnyDrag {
|
||||
value: Arc::new("payload"),
|
||||
view: cx.new(|_| Empty).into(),
|
||||
cursor_offset: point(px(0.), px(0.)),
|
||||
cursor_style: None,
|
||||
external_payload_source: None,
|
||||
});
|
||||
cx.ensure_drag_poll_running();
|
||||
window.refresh();
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().advance_clock(Duration::from_millis(50));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
cx.read(|cx| cx.drag_hover_window),
|
||||
Some(Some(window.window_id()))
|
||||
);
|
||||
|
||||
// While the button stays pressed outside every window, the drag lives
|
||||
// on but no window is asked to paint the ghost.
|
||||
cx.set_global_cursor_state(Some(GlobalCursorState {
|
||||
position: point(px(10_000.), px(10_000.)),
|
||||
left_button_pressed: true,
|
||||
}));
|
||||
cx.executor().advance_clock(Duration::from_millis(50));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(cx.read(|cx| cx.drag_hover_window), Some(None));
|
||||
assert!(cx.read(|cx| cx.active_drag.is_some()));
|
||||
|
||||
// Releasing outside every window hands the drag to the handler.
|
||||
cx.set_global_cursor_state(Some(GlobalCursorState {
|
||||
position: point(px(10_000.), px(10_000.)),
|
||||
left_button_pressed: false,
|
||||
}));
|
||||
cx.executor().advance_clock(Duration::from_millis(50));
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
*dropped_position.borrow(),
|
||||
Some(point(px(10_000.), px(10_000.)))
|
||||
);
|
||||
assert!(cx.read(|cx| cx.active_drag.is_none()));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, any(feature = "inspector", debug_assertions)))]
|
||||
|
||||
@@ -7,8 +7,8 @@ use gpui_util::ResultExt;
|
||||
use crate::linux::headless::window::{HeadlessDisplay, HeadlessWindow};
|
||||
use crate::linux::{LinuxClient, LinuxCommon, LinuxKeyboardLayout};
|
||||
use gpui::{
|
||||
AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, PlatformKeyboardLayout,
|
||||
PlatformWindow, WindowParams,
|
||||
AnyWindowHandle, CursorStyle, DisplayId, GlobalCursorState, PlatformDisplay,
|
||||
PlatformKeyboardLayout, PlatformWindow, WindowParams,
|
||||
};
|
||||
|
||||
pub struct HeadlessClientState {
|
||||
@@ -97,6 +97,10 @@ impl LinuxClient for HeadlessClient {
|
||||
None
|
||||
}
|
||||
|
||||
fn global_cursor_state(&self) -> Option<GlobalCursorState> {
|
||||
None
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
_handle: AnyWindowHandle,
|
||||
|
||||
@@ -33,10 +33,10 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
||||
use crate::linux::{LinuxDispatcher, PriorityQueueCalloopReceiver};
|
||||
use gpui::{
|
||||
Action, ActivityGuard, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle,
|
||||
DisplayId, ForegroundExecutor, Keymap, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
|
||||
PlatformWindow, Result, RunnableVariant, Task, ThermalState, WindowAppearance,
|
||||
WindowButtonLayout, WindowParams,
|
||||
DisplayId, ForegroundExecutor, GlobalCursorState, Keymap, Menu, MenuItem, OwnedMenu,
|
||||
PathPromptOptions, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
|
||||
PlatformTextSystem, PlatformWindow, Result, RunnableVariant, Task, ThermalState,
|
||||
WindowAppearance, WindowButtonLayout, WindowParams,
|
||||
};
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use gpui::{Pixels, Point, px};
|
||||
@@ -101,6 +101,7 @@ pub(crate) trait LinuxClient {
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
||||
fn active_window(&self) -> Option<AnyWindowHandle>;
|
||||
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>>;
|
||||
fn global_cursor_state(&self) -> Option<GlobalCursorState>;
|
||||
fn run(&self);
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
@@ -436,6 +437,10 @@ impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
|
||||
self.inner.window_stack()
|
||||
}
|
||||
|
||||
fn global_cursor_state(&self) -> Option<GlobalCursorState> {
|
||||
self.inner.global_cursor_state()
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
|
||||
@@ -97,9 +97,9 @@ use crate::linux::{
|
||||
};
|
||||
use gpui::{
|
||||
AnyWindowHandle, Bounds, Capslock, CursorStyle, DevicePixels, DisplayId, ExternalDragPayload,
|
||||
FileDragPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
|
||||
MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput,
|
||||
FileDragPaths, FileDropEvent, ForegroundExecutor, GlobalCursorState, KeyDownEvent, KeyUpEvent,
|
||||
Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent,
|
||||
MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput,
|
||||
PlatformKeyboardLayout, PlatformWindow, Point, ScrollDelta, ScrollWheelEvent, SharedString,
|
||||
Size, TouchPhase, WindowButtonLayout, WindowKind, WindowParams, point, profiler, px, size,
|
||||
};
|
||||
@@ -1368,6 +1368,10 @@ impl LinuxClient for WaylandClient {
|
||||
None
|
||||
}
|
||||
|
||||
fn global_cursor_state(&self) -> Option<GlobalCursorState> {
|
||||
None
|
||||
}
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
"Wayland"
|
||||
}
|
||||
|
||||
@@ -60,10 +60,11 @@ use crate::linux::{
|
||||
use crate::linux::{LinuxCommon, LinuxKeyboardLayout, X11Window, modifiers_from_xinput_info};
|
||||
|
||||
use gpui::{
|
||||
AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, Pixels, PlatformDisplay, PlatformInput,
|
||||
PlatformKeyboardLayout, PlatformWindow, Point, RequestFrameOptions, ScrollDelta, Size,
|
||||
TouchPhase, WindowButtonLayout, WindowParams, WindowVisibility, point, px,
|
||||
AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent,
|
||||
GlobalCursorState, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
|
||||
PlatformDisplay, PlatformInput, PlatformKeyboardLayout, PlatformWindow, Point,
|
||||
RequestFrameOptions, ScrollDelta, Size, TouchPhase, WindowButtonLayout, WindowParams,
|
||||
WindowVisibility, point, px,
|
||||
};
|
||||
use gpui_wgpu::{CompositorGpuHint, GpuContext};
|
||||
|
||||
@@ -1878,6 +1879,24 @@ impl LinuxClient for X11Client {
|
||||
Some(handles)
|
||||
}
|
||||
|
||||
fn global_cursor_state(&self) -> Option<GlobalCursorState> {
|
||||
let state = self.0.borrow();
|
||||
let root = state.xcb_connection.setup().roots[state.x_root_index].root;
|
||||
let scale_factor = state.scale_factor;
|
||||
let reply = get_reply(
|
||||
|| "Failed to query pointer position",
|
||||
state.xcb_connection.query_pointer(root),
|
||||
)
|
||||
.log_err()?;
|
||||
Some(GlobalCursorState {
|
||||
position: point(
|
||||
px(reply.root_x as f32 / scale_factor),
|
||||
px(reply.root_y as f32 / scale_factor),
|
||||
),
|
||||
left_button_pressed: reply.mask.contains(xproto::KeyButMask::BUTTON1),
|
||||
})
|
||||
}
|
||||
|
||||
fn window_identifier(&self) -> impl Future<Output = Option<WindowIdentifier>> + Send + 'static {
|
||||
let state = self.0.borrow();
|
||||
state
|
||||
|
||||
Reference in new issue
Block a user