focus_follows_mouse.rs

 1use std::sync::LazyLock;
 2
 3use gpui::{
 4    AnyWindowHandle, AppContext, Context, FocusHandle, Focusable, StatefulInteractiveElement, Task,
 5};
 6use parking_lot::Mutex;
 7
 8use crate::workspace_settings;
 9
10#[derive(Default)]
11struct FfmState {
12    // The window and element to be focused
13    handles: Option<(AnyWindowHandle, FocusHandle)>,
14    // The debounced task which will do the focusing
15    debounce_task: Option<Task<()>>,
16}
17
18// Global focus-follows-mouse state.
19static FFM_STATE: LazyLock<Mutex<FfmState>> = LazyLock::new(Default::default);
20
21pub trait FocusFollowsMouse<E: Focusable>: StatefulInteractiveElement {
22    fn focus_follows_mouse(
23        self,
24        settings: workspace_settings::FocusFollowsMouse,
25        cx: &Context<E>,
26    ) -> Self {
27        if settings.enabled {
28            self.on_hover(cx.listener(move |this, enter, window, cx| {
29                if *enter {
30                    let window_handle = window.window_handle();
31                    let focus_handle = this.focus_handle(cx);
32
33                    let mut state = FFM_STATE.lock();
34
35                    // Set the window/element to be focused to the most recent hovered element.
36                    state.handles.replace((window_handle, focus_handle));
37
38                    // Start a task to focus the most recent target after the debounce period
39                    state
40                        .debounce_task
41                        .replace(cx.spawn(async move |_this, cx| {
42                            cx.background_executor().timer(settings.debounce).await;
43
44                            let mut state = FFM_STATE.lock();
45                            let Some((window, focus)) = state.handles.take() else {
46                                return;
47                            };
48
49                            let _ = cx.update_window(window, move |_view, window, cx| {
50                                window.focus(&focus, cx);
51                            });
52                        }));
53                }
54            }))
55        } else {
56            self
57        }
58    }
59}
60
61impl<E: Focusable, T: StatefulInteractiveElement> FocusFollowsMouse<E> for T {}