gpui: Fix clicks on window prompts dismissing popovers behind them (#61136)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions

I noticed that when Zed renders prompts in-window (Linux, or
`use_system_prompts: false`), clicking a prompt button also dismisses
the popover behind it. Native macOS dialogs don't have this problem
since the click goes to the OS dialog and the window never sees it.

For example, in the worktree picker:
1. Delete a dirty worktree.
2. Confirm "Force Delete".
3. Mouse down dismissed the picker, which aborted the confirmed
deletion.

This PR makes GPUI-rendered prompts behave the same way as native. If
someone actually wants to observe mouse downs during a prompt, the raw
`window.on_mouse_event` API still sees every event.

macOS:


https://github.com/user-attachments/assets/9f85622f-c0d1-44f2-83f9-8378d9d7d13b


Before GPUI prompt:


https://github.com/user-attachments/assets/d1845ba1-9886-4733-8cf6-ab32424416e1

After GPUI prompt:


https://github.com/user-attachments/assets/27936aca-8de5-4da8-88be-d88dbc1259d8

Release Notes:

- Fixed confirmation dialog buttons dismissing the popover underneath
them on click when using Zed-rendered prompts.
This commit is contained in:
Smit Barmase 2026-07-17 01:15:21 +05:30 committed by GitHub
parent f688bee4fe
commit 058f01fa93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 93 additions and 1 deletions

View file

@ -262,7 +262,10 @@ impl Interactivity {
) {
self.mouse_down_listeners
.push(Box::new(move |event, phase, hitbox, window, cx| {
if phase == DispatchPhase::Capture && !hitbox.contains(&window.mouse_position()) {
if phase == DispatchPhase::Capture
&& !window.has_active_prompt()
&& !hitbox.contains(&window.mouse_position())
{
(listener)(event, window, cx)
}
}));
@ -4523,6 +4526,87 @@ mod tests {
assert!(active_tooltip.borrow().is_none());
}
struct MouseDownOutOwner {
mouse_down_out_count: Rc<RefCell<usize>>,
}
impl Render for MouseDownOutOwner {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let mouse_down_out_count = self.mouse_down_out_count.clone();
div()
.size_full()
.child(div().id("target").w(px(50.)).h(px(50.)).on_mouse_down_out(
move |_, _, _| {
*mouse_down_out_count.borrow_mut() += 1;
},
))
}
}
#[test]
fn mouse_down_out_is_suppressed_while_window_prompt_is_active() {
let mut test_app = TestAppContext::single();
let mouse_down_out_count = Rc::new(RefCell::new(0));
let window = test_app.add_window({
let mouse_down_out_count = mouse_down_out_count.clone();
move |_, _| MouseDownOutOwner {
mouse_down_out_count,
}
});
let any_window: AnyWindowHandle = window.into();
fn dispatch_mouse_down_outside_target(
test_app: &mut TestAppContext,
any_window: AnyWindowHandle,
) {
test_app
.update_window(any_window, |_, window, cx| {
window.dispatch_event(
MouseDownEvent {
position: point(px(75.), px(75.)),
button: MouseButton::Left,
modifiers: Default::default(),
click_count: 1,
first_mouse: false,
}
.to_platform_input(),
cx,
);
})
.unwrap();
}
test_app
.update_window(any_window, |_, window, cx| {
window.draw(cx).clear();
})
.unwrap();
dispatch_mouse_down_outside_target(&mut test_app, any_window);
assert_eq!(
*mouse_down_out_count.borrow(),
1,
"mouse down outside the element should fire mouse-down-out listeners"
);
test_app
.update_window(any_window, |_, window, cx| {
cx.set_prompt_builder(crate::fallback_prompt_renderer);
let _receiver =
window.prompt(crate::PromptLevel::Warning, "message", None, &["Ok"], cx);
assert!(window.has_active_prompt());
window.draw(cx).clear();
})
.unwrap();
dispatch_mouse_down_outside_target(&mut test_app, any_window);
assert_eq!(
*mouse_down_out_count.borrow(),
1,
"mouse down over an active prompt should not fire mouse-down-out listeners"
);
}
#[test]
fn test_write_a11y_info_string_and_numeric_properties() {
let mut interactivity = Interactivity::default();

View file

@ -5343,6 +5343,14 @@ impl Window {
receiver
}
/// Returns whether a prompt rendered by GPUI is currently active in this window.
///
/// This is only true for prompts rendered in the window (see
/// [`App::set_prompt_builder`]), not for platform-native prompt dialogs.
pub fn has_active_prompt(&self) -> bool {
self.prompt.is_some()
}
/// Returns the current context stack.
pub fn context_stack(&self) -> Vec<KeyContext> {
let node_id = self.focus_node_id_in_rendered_frame(self.focus);