focus_follows_mouse.rs

 1use gpui::{
 2    AnyWindowHandle, AppContext as _, Context, FocusHandle, Focusable, Global,
 3    StatefulInteractiveElement, Task,
 4};
 5
 6use crate::workspace_settings;
 7
 8#[derive(Default)]
 9struct FfmState {
10    // The window and element to be focused
11    handles: Option<(AnyWindowHandle, FocusHandle)>,
12    // The debounced task which will do the focusing
13    _debounce_task: Option<Task<()>>,
14}
15
16impl Global for FfmState {}
17
18pub trait FocusFollowsMouse<E: Focusable>: StatefulInteractiveElement {
19    fn focus_follows_mouse(
20        self,
21        settings: workspace_settings::FocusFollowsMouse,
22        cx: &Context<E>,
23    ) -> Self {
24        if settings.enabled {
25            self.on_hover(cx.listener(move |this, enter, window, cx| {
26                if *enter {
27                    let window_handle = window.window_handle();
28                    let focus_handle = this.focus_handle(cx);
29
30                    let state = cx.try_global::<FfmState>();
31
32                    // Only replace the target if the new handle doesn't contain the existing one.
33                    // This ensures that hovering over a parent (e.g., Dock) doesn't override
34                    // a more specific child target (e.g., a Pane inside the Dock).
35                    let should_replace = state
36                        .and_then(|s| s.handles.as_ref())
37                        .map(|(_, existing)| !focus_handle.contains(existing, window))
38                        .unwrap_or(true);
39
40                    if !should_replace {
41                        return;
42                    }
43
44                    let debounce_task = cx.spawn(async move |_this, cx| {
45                        cx.background_executor().timer(settings.debounce).await;
46
47                        cx.update(|cx| {
48                            let state = cx.default_global::<FfmState>();
49                            let Some((window, focus)) = state.handles.take() else {
50                                return;
51                            };
52
53                            let _ = cx.update_window(window, move |_view, window, cx| {
54                                window.focus(&focus, cx);
55                            });
56                        });
57                    });
58
59                    cx.set_global(FfmState {
60                        handles: Some((window_handle, focus_handle)),
61                        _debounce_task: Some(debounce_task),
62                    });
63                }
64            }))
65        } else {
66            self
67        }
68    }
69}
70
71impl<E: Focusable, T: StatefulInteractiveElement> FocusFollowsMouse<E> for T {}