From 6ae732a5fc4a17b30abd835f5e976f30d388e92b Mon Sep 17 00:00:00 2001 From: Josh Robson Chase Date: Tue, 13 Jan 2026 11:06:57 -0500 Subject: [PATCH] workspace: implement focus-follows-mouse for panes --- Cargo.lock | 8 ++++++++ Cargo.toml | 1 + assets/settings/default.json | 2 ++ crates/focus_follows_mouse/Cargo.toml | 14 ++++++++++++++ crates/focus_follows_mouse/LICENSE-GPL | 1 + .../src/focus_follows_mouse.rs | 17 +++++++++++++++++ crates/settings/src/vscode_import.rs | 1 + crates/settings_content/src/workspace.rs | 3 +++ crates/workspace/Cargo.toml | 1 + crates/workspace/src/pane.rs | 11 ++++++++++- crates/workspace/src/workspace_settings.rs | 2 ++ 11 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 crates/focus_follows_mouse/Cargo.toml create mode 100644 crates/focus_follows_mouse/LICENSE-GPL create mode 100644 crates/focus_follows_mouse/src/focus_follows_mouse.rs diff --git a/Cargo.lock b/Cargo.lock index 07a058a4032ecea1d85c2571c246767a373e0193..a277f017108ce7d3f7bd510fa00d7b1914696df4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6451,6 +6451,13 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "focus_follows_mouse" +version = "0.1.0" +dependencies = [ + "gpui", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -21521,6 +21528,7 @@ dependencies = [ "component", "db", "feature_flags", + "focus_follows_mouse", "fs", "futures 0.3.31", "git", diff --git a/Cargo.toml b/Cargo.toml index 17efe21800962cf5c7cd1b21b2e7c7a0c8df4c12..93242885ef2437c5bbcd544a26ebf0eb0f734b15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ members = [ "crates/feedback", "crates/file_finder", "crates/file_icons", + "crates/focus_follows_mouse", "crates/fs", "crates/fs_benchmarks", "crates/fuzzy", diff --git a/assets/settings/default.json b/assets/settings/default.json index 7ef69bc1675947ba3d07dc7b523658902459996b..ab8aa001af875e2668bad4bbb33e83317a0b70c6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -225,6 +225,8 @@ // 3. Hide on both typing and cursor movement: // "on_typing_and_movement" "hide_mouse": "on_typing_and_movement", + // Determines whether the focused panel follows the mouse location. + "focus_follows_mouse": false, // Determines how snippets are sorted relative to other completion items. // // 1. Place snippets at the top of the completion list: diff --git a/crates/focus_follows_mouse/Cargo.toml b/crates/focus_follows_mouse/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..e5be3eee0edcad4b5a7911e5fd8964e403d0408d --- /dev/null +++ b/crates/focus_follows_mouse/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "focus_follows_mouse" +version = "0.1.0" +publish.workspace = true +edition.workspace = true + +[lib] +path = "src/focus_follows_mouse.rs" + +[dependencies] +gpui.workspace = true + +[lints] +workspace = true diff --git a/crates/focus_follows_mouse/LICENSE-GPL b/crates/focus_follows_mouse/LICENSE-GPL new file mode 100644 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/focus_follows_mouse/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/focus_follows_mouse/src/focus_follows_mouse.rs b/crates/focus_follows_mouse/src/focus_follows_mouse.rs new file mode 100644 index 0000000000000000000000000000000000000000..23d18b8c4155532885fccf6d982e39f1fc2a13a0 --- /dev/null +++ b/crates/focus_follows_mouse/src/focus_follows_mouse.rs @@ -0,0 +1,17 @@ +use gpui::{Context, Focusable, StatefulInteractiveElement}; + +pub trait FocusFollowsMouse: StatefulInteractiveElement { + fn focus_follows_mouse(self, enabled: bool, cx: &Context) -> Self { + if enabled { + self.on_hover(cx.listener(move |this, enter, window, cx| { + if *enter { + window.focus(&this.focus_handle(cx), cx); + } + })) + } else { + self + } + } +} + +impl FocusFollowsMouse for T {} diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index fda63ced4c740291554034e461b3469c8809008d..32c80ca068ea6c51a4fd7e5e5260e23d890bcc08 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -996,6 +996,7 @@ impl VsCodeSettings { } }), zoomed_padding: None, + focus_follows_mouse: None, } } diff --git a/crates/settings_content/src/workspace.rs b/crates/settings_content/src/workspace.rs index 92dc6679e60fc5d54b24afafa4daa00600c066f2..61eb68b5b80461e34045455f4a28e14fd5d92a25 100644 --- a/crates/settings_content/src/workspace.rs +++ b/crates/settings_content/src/workspace.rs @@ -122,6 +122,9 @@ pub struct WorkspaceSettingsContent { /// What draws window decorations/titlebar, the client application (Zed) or display server /// Default: client pub window_decorations: Option, + /// Whether the focused panel follows the mouse location + /// Default: false + pub focus_follows_mouse: Option, } #[with_fallible_options] diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index d5e9400353eee50b3c5734a31684abdb0149caa0..ae36e89770ff449a34d50896514fb61d4c853698 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -67,6 +67,7 @@ util.workspace = true uuid.workspace = true vim_mode_setting.workspace = true zed_actions.workspace = true +focus_follows_mouse = { version = "0.1.0", path = "../focus_follows_mouse" } [target.'cfg(target_os = "windows")'.dependencies] windows.workspace = true diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2d9f8be5c363b5b5c10c5432a543ae46de7611d2..900b02060a22c790671983b464eafe9f9f47241e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -15,6 +15,7 @@ use crate::{ }; use anyhow::Result; use collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use focus_follows_mouse::FocusFollowsMouse; use futures::{StreamExt, stream::FuturesUnordered}; use gpui::{ Action, AnyElement, App, AsyncWindowContext, ClickEvent, ClipboardItem, Context, Corner, Div, @@ -413,6 +414,7 @@ pub struct Pane { pinned_tab_count: usize, diagnostics: HashMap, zoom_out_on_close: bool, + focus_follows_mouse: bool, diagnostic_summary_update: Task<()>, /// If a certain project item wants to get recreated with specific data, it can persist its data before the recreation here. pub project_item_restoration_data: HashMap>, @@ -585,6 +587,7 @@ impl Pane { pinned_tab_count: 0, diagnostics: Default::default(), zoom_out_on_close: true, + focus_follows_mouse: WorkspaceSettings::get_global(cx).focus_follows_mouse, diagnostic_summary_update: Task::ready(()), project_item_restoration_data: HashMap::default(), welcome_page: None, @@ -752,7 +755,6 @@ impl Pane { fn settings_changed(&mut self, window: &mut Window, cx: &mut Context) { let tab_bar_settings = TabBarSettings::get_global(cx); - let new_max_tabs = WorkspaceSettings::get_global(cx).max_tabs; if let Some(display_nav_history_buttons) = self.display_nav_history_buttons.as_mut() { *display_nav_history_buttons = tab_bar_settings.show_nav_history_buttons; @@ -765,6 +767,12 @@ impl Pane { self.nav_history.0.lock().preview_item_id = None; } + let workspace_settings = WorkspaceSettings::get_global(cx); + + self.focus_follows_mouse = workspace_settings.focus_follows_mouse; + + let new_max_tabs = workspace_settings.max_tabs; + if self.use_max_tabs && new_max_tabs != self.max_tabs { self.max_tabs = new_max_tabs; self.close_items_on_settings_change(window, cx); @@ -4429,6 +4437,7 @@ impl Render for Pane { placeholder.child(self.welcome_page.clone().unwrap()) } } + .focus_follows_mouse(self.focus_follows_mouse, cx) }) .child( // drag target diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 5575af3d7cf07fd7afd22ddbb78a620bab775714..b0f2b7d703b78fa7a942a5bdffbce35cc0ecf1fb 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -35,6 +35,7 @@ pub struct WorkspaceSettings { pub use_system_window_tabs: bool, pub zoomed_padding: bool, pub window_decorations: settings::WindowDecorations, + pub focus_follows_mouse: bool, } #[derive(Copy, Clone, PartialEq, Debug, Default)] @@ -113,6 +114,7 @@ impl Settings for WorkspaceSettings { use_system_window_tabs: workspace.use_system_window_tabs.unwrap(), zoomed_padding: workspace.zoomed_padding.unwrap(), window_decorations: workspace.window_decorations.unwrap(), + focus_follows_mouse: workspace.focus_follows_mouse.unwrap(), } } }