From 3eb4d346fe88efe475daa88e0e042ca3680a386c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 11 Oct 2026 00:36:15 -0400 Subject: [PATCH] TitleBar: a press its buttons claim never moves the window The drag area armed a window move on every press, buttons included, so a click on Linux's drawn buttons that moved a pixel started a move and the compositor kept the release. It now arms only on unclaimed presses. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/shell/chrome.rs | 7 ++-- src/shell/mod.rs | 2 ++ src/shell/title_tests.rs | 73 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/shell/title_tests.rs diff --git a/src/shell/chrome.rs b/src/shell/chrome.rs index 3f940ef..78bff8d 100644 --- a/src/shell/chrome.rs +++ b/src/shell/chrome.rs @@ -21,8 +21,11 @@ pub fn drag_region(id: impl Into, window: &mut Window, cx: &mut App) div() .id(id) .window_control_area(WindowControlArea::Drag) - .on_mouse_down(MouseButton::Left, move |_, _, cx| { - down.update(cx, |pressed, _| *pressed = true) + .on_mouse_down(MouseButton::Left, move |_, window, cx| { + // A button inside claims its own press. + if !window.default_prevented() { + down.update(cx, |pressed, _| *pressed = true) + } }) .on_mouse_move(move |_, window, cx| { if *moved.read(cx) { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 787458b..9743870 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -11,6 +11,8 @@ mod switcher; mod tabs; #[cfg(all(test, feature = "test-support"))] mod tests; +#[cfg(all(test, feature = "test-support"))] +mod title_tests; mod update; mod vibrancy; mod windows; diff --git a/src/shell/title_tests.rs b/src/shell/title_tests.rs new file mode 100644 index 0000000..3761941 --- /dev/null +++ b/src/shell/title_tests.rs @@ -0,0 +1,73 @@ +use gpui::{ + Context, IntoElement, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + ParentElement, Point, Render, TestAppContext, Window, div, point, prelude::*, px, +}; + +use super::TitleBar; +use crate::theme::{Platform, Theme}; + +struct Bar { + closes: usize, +} + +impl Render for Bar { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let view = cx.entity(); + div().w(px(600.0)).child( + TitleBar::new("bar") + .platform(Platform::Linux) + .on_close(move |_, cx| view.update(cx, |bar, _| bar.closes += 1)), + ) + } +} + +/// Presses at `at`, moves two pixels, releases. +fn wobble(at: Point, cx: &mut gpui::VisualTestContext) { + let moved = at + point(px(2.0), px(1.0)); + cx.simulate_event(MouseDownEvent { + button: MouseButton::Left, + position: at, + modifiers: Modifiers::none(), + click_count: 1, + first_mouse: false, + }); + cx.simulate_event(MouseMoveEvent { + position: moved, + pressed_button: Some(MouseButton::Left), + modifiers: Modifiers::none(), + }); + cx.simulate_event(MouseUpEvent { + button: MouseButton::Left, + position: moved, + modifiers: Modifiers::none(), + click_count: 1, + }); + cx.run_until_parked(); +} + +/// A press that wobbles on Linux's close button still closes; it never moves the window. +#[gpui::test] +fn a_wobbling_press_on_a_linux_button_clicks_it(cx: &mut TestAppContext) { + cx.update(Theme::init); + let (view, cx) = cx.add_window_view(|_, _| Bar { closes: 0 }); + cx.run_until_parked(); + let controls = cx + .debug_bounds("titlebar-controls") + .expect("drawn controls"); + // Close is the last of the round buttons. + wobble(point(controls.right() - px(8.0), controls.center().y), cx); + assert_eq!(view.read_with(cx, |bar, _| bar.closes), 1); +} + +/// The bar's empty space still moves the window (the test window panics on a move). +#[gpui::test] +#[should_panic(expected = "not implemented")] +fn a_wobbling_press_on_the_bar_moves_the_window(cx: &mut TestAppContext) { + cx.update(Theme::init); + let (_, cx) = cx.add_window_view(|_, _| Bar { closes: 0 }); + cx.run_until_parked(); + let controls = cx + .debug_bounds("titlebar-controls") + .expect("drawn controls"); + wobble(point(controls.left() - px(200.0), controls.center().y), cx); +}