Detailed changes
@@ -150,6 +150,8 @@
//
// Default: not set, defaults to "bar"
"cursor_shape": null,
+ // Determines whether the mouse cursor is hidden when typing in an editor or input box.
+ "hide_mouse_while_typing": true,
// How to highlight the current line in the editor.
//
// 1. Don't highlight the current line:
@@ -716,6 +716,8 @@ pub struct Editor {
toggle_fold_multiple_buffers: Task<()>,
_scroll_cursor_center_top_bottom_task: Task<()>,
serialize_selections: Task<()>,
+ mouse_cursor_hidden: bool,
+ hide_mouse_while_typing: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1432,6 +1434,10 @@ impl Editor {
serialize_selections: Task::ready(()),
text_style_refinement: None,
load_diff_task: load_uncommitted_diff,
+ mouse_cursor_hidden: false,
+ hide_mouse_while_typing: EditorSettings::get_global(cx)
+ .hide_mouse_while_typing
+ .unwrap_or(true),
};
this.tasks_update_task = Some(this.refresh_runnables(window, cx));
this._subscriptions.extend(project_subscriptions);
@@ -2764,6 +2770,8 @@ impl Editor {
return;
}
+ self.mouse_cursor_hidden = self.hide_mouse_while_typing;
+
let selections = self.selections.all_adjusted(cx);
let mut bracket_inserted = false;
let mut edits = Vec::new();
@@ -14355,6 +14363,11 @@ impl Editor {
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
+ self.hide_mouse_while_typing = editor_settings.hide_mouse_while_typing.unwrap_or(true);
+
+ if !self.hide_mouse_while_typing {
+ self.mouse_cursor_hidden = false;
+ }
}
if old_cursor_shape != self.cursor_shape {
@@ -37,6 +37,7 @@ pub struct EditorSettings {
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
pub jupyter: Jupyter,
+ pub hide_mouse_while_typing: Option<bool>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -270,6 +271,10 @@ pub struct EditorSettingsContent {
///
/// Default: None
pub cursor_shape: Option<CursorShape>,
+ /// Determines whether the mouse cursor should be hidden while typing in an editor or input box.
+ ///
+ /// Default: true
+ pub hide_mouse_while_typing: Option<bool>,
/// How to highlight the current line in the editor.
///
/// Default: all
@@ -848,6 +848,7 @@ impl EditorElement {
let modifiers = event.modifiers;
let gutter_hovered = gutter_hitbox.is_hovered(window);
editor.set_gutter_hovered(gutter_hovered, cx);
+ editor.mouse_cursor_hidden = false;
// Don't trigger hover popover if mouse is hovering over context menu
if text_hitbox.is_hovered(window) {
@@ -4813,9 +4814,10 @@ impl EditorElement {
bounds: layout.position_map.text_hitbox.bounds,
}),
|window| {
- let cursor_style = if self
- .editor
- .read(cx)
+ let editor = self.editor.read(cx);
+ let cursor_style = if editor.mouse_cursor_hidden {
+ CursorStyle::None
+ } else if editor
.hovered_link_state
.as_ref()
.is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
@@ -6815,6 +6817,7 @@ impl Element for EditorElement {
},
false,
);
+
// Offset the content_bounds from the text_bounds by the gutter margin (which
// is roughly half a character wide) to make hit testing work more like how we want.
let content_origin =
@@ -1227,6 +1227,9 @@ pub enum CursorStyle {
/// A cursor indicating that the operation will result in a context menu
/// corresponds to the CSS cursor value `context-menu`
ContextualMenu,
+
+ /// Hide the cursor
+ None,
}
impl Default for CursorStyle {
@@ -666,6 +666,12 @@ impl CursorStyle {
CursorStyle::DragLink => "alias",
CursorStyle::DragCopy => "copy",
CursorStyle::ContextualMenu => "context-menu",
+ CursorStyle::None => {
+ #[cfg(debug_assertions)]
+ panic!("CursorStyle::None should be handled separately in the client");
+ #[cfg(not(debug_assertions))]
+ "default"
+ }
}
.to_string()
}
@@ -35,6 +35,12 @@ impl CursorStyle {
CursorStyle::DragLink => Shape::Alias,
CursorStyle::DragCopy => Shape::Copy,
CursorStyle::ContextualMenu => Shape::ContextMenu,
+ CursorStyle::None => {
+ #[cfg(debug_assertions)]
+ panic!("CursorStyle::None should be handled separately in the client");
+ #[cfg(not(debug_assertions))]
+ Shape::Default
+ }
}
}
}
@@ -667,7 +667,13 @@ impl LinuxClient for WaylandClient {
let serial = state.serial_tracker.get(SerialKind::MouseEnter);
state.cursor_style = Some(style);
- if let Some(cursor_shape_device) = &state.cursor_shape_device {
+ if let CursorStyle::None = style {
+ let wl_pointer = state
+ .wl_pointer
+ .clone()
+ .expect("window is focused by pointer");
+ wl_pointer.set_cursor(serial, None, 0, 0);
+ } else if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, style.to_shape());
} else if let Some(focused_window) = &state.mouse_focused_window {
// cursor-shape-v1 isn't supported, set the cursor using a surface.
@@ -1438,13 +1438,16 @@ impl LinuxClient for X11Client {
let cursor = match state.cursor_cache.get(&style) {
Some(cursor) => *cursor,
None => {
- let Some(cursor) = state
- .cursor_handle
- .load_cursor(&state.xcb_connection, &style.to_icon_name())
- .log_err()
- else {
+ let Some(cursor) = (match style {
+ CursorStyle::None => create_invisible_cursor(&state.xcb_connection).log_err(),
+ _ => state
+ .cursor_handle
+ .load_cursor(&state.xcb_connection, &style.to_icon_name())
+ .log_err(),
+ }) else {
return;
};
+
state.cursor_cache.insert(style, cursor);
cursor
}
@@ -1938,3 +1941,19 @@ fn make_scroll_wheel_event(
touch_phase: TouchPhase::default(),
}
}
+
+fn create_invisible_cursor(
+ connection: &XCBConnection,
+) -> anyhow::Result<crate::platform::linux::x11::client::xproto::Cursor> {
+ let empty_pixmap = connection.generate_id()?;
+ let root = connection.setup().roots[0].root;
+ connection.create_pixmap(1, empty_pixmap, root, 1, 1)?;
+
+ let cursor = connection.generate_id()?;
+ connection.create_cursor(cursor, empty_pixmap, empty_pixmap, 0, 0, 0, 0, 0, 0, 0, 0)?;
+
+ connection.free_pixmap(empty_pixmap)?;
+
+ connection.flush()?;
+ Ok(cursor)
+}
@@ -938,6 +938,7 @@ impl Platform for MacPlatform {
CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
+ CursorStyle::None => msg_send![class!(NSCursor), setHiddenUntilMouseMoves:YES],
};
let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
@@ -1121,7 +1121,19 @@ fn handle_nc_mouse_up_msg(
}
fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
- state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0 as _);
+ let mut state = state_ptr.state.borrow_mut();
+ let had_cursor = state.current_cursor.is_some();
+
+ state.current_cursor = if lparam.0 == 0 {
+ None
+ } else {
+ Some(HCURSOR(lparam.0 as _))
+ };
+
+ if had_cursor != state.current_cursor.is_some() {
+ unsafe { SetCursor(state.current_cursor.as_ref()) };
+ }
+
Some(0)
}
@@ -1132,7 +1144,9 @@ fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Op
) {
return None;
}
- unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
+ unsafe {
+ SetCursor(state_ptr.state.borrow().current_cursor.as_ref());
+ };
Some(1)
}
@@ -50,7 +50,7 @@ pub(crate) struct WindowsPlatformState {
callbacks: PlatformCallbacks,
menus: Vec<OwnedMenu>,
// NOTE: standard cursor handles don't need to close.
- pub(crate) current_cursor: HCURSOR,
+ pub(crate) current_cursor: Option<HCURSOR>,
}
#[derive(Default)]
@@ -506,11 +506,11 @@ impl Platform for WindowsPlatform {
fn set_cursor_style(&self, style: CursorStyle) {
let hcursor = load_cursor(style);
let mut lock = self.state.borrow_mut();
- if lock.current_cursor.0 != hcursor.0 {
+ if lock.current_cursor.map(|c| c.0) != hcursor.map(|c| c.0) {
self.post_message(
WM_GPUI_CURSOR_STYLE_CHANGED,
WPARAM(0),
- LPARAM(hcursor.0 as isize),
+ LPARAM(hcursor.map_or(0, |c| c.0 as isize)),
);
lock.current_cursor = hcursor;
}
@@ -613,7 +613,7 @@ impl Drop for WindowsPlatform {
pub(crate) struct WindowCreationInfo {
pub(crate) icon: HICON,
pub(crate) executor: ForegroundExecutor,
- pub(crate) current_cursor: HCURSOR,
+ pub(crate) current_cursor: Option<HCURSOR>,
pub(crate) windows_version: WindowsVersion,
pub(crate) validation_number: usize,
pub(crate) main_receiver: flume::Receiver<Runnable>,
@@ -105,7 +105,7 @@ pub(crate) fn windows_credentials_target_name(url: &str) -> String {
format!("zed:url={}", url)
}
-pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR {
+pub(crate) fn load_cursor(style: CursorStyle) -> Option<HCURSOR> {
static ARROW: OnceLock<SafeCursor> = OnceLock::new();
static IBEAM: OnceLock<SafeCursor> = OnceLock::new();
static CROSS: OnceLock<SafeCursor> = OnceLock::new();
@@ -126,17 +126,20 @@ pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR {
| CursorStyle::ResizeUpDown
| CursorStyle::ResizeRow => (&SIZENS, IDC_SIZENS),
CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
+ CursorStyle::None => return None,
_ => (&ARROW, IDC_ARROW),
};
- *(*lock.get_or_init(|| {
- HCURSOR(
- unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
- .log_err()
- .unwrap_or_default()
- .0,
- )
- .into()
- }))
+ Some(
+ *(*lock.get_or_init(|| {
+ HCURSOR(
+ unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
+ .log_err()
+ .unwrap_or_default()
+ .0,
+ )
+ .into()
+ })),
+ )
}
/// This function is used to configure the dark mode for the window built-in title bar.
@@ -49,7 +49,7 @@ pub struct WindowsWindowState {
pub click_state: ClickState,
pub system_settings: WindowsSystemSettings,
- pub current_cursor: HCURSOR,
+ pub current_cursor: Option<HCURSOR>,
pub nc_button_pressed: Option<u32>,
pub display: WindowsDisplay,
@@ -77,7 +77,7 @@ impl WindowsWindowState {
hwnd: HWND,
transparent: bool,
cs: &CREATESTRUCTW,
- current_cursor: HCURSOR,
+ current_cursor: Option<HCURSOR>,
display: WindowsDisplay,
gpu_context: &BladeContext,
) -> Result<Self> {
@@ -352,7 +352,7 @@ struct WindowCreateContext<'a> {
transparent: bool,
is_movable: bool,
executor: ForegroundExecutor,
- current_cursor: HCURSOR,
+ current_cursor: Option<HCURSOR>,
windows_version: WindowsVersion,
validation_number: usize,
main_receiver: flume::Receiver<Runnable>,
@@ -3233,6 +3233,7 @@ impl Window {
keystroke,
&dispatch_path,
);
+
if !match_result.to_replay.is_empty() {
self.replay_pending_input(match_result.to_replay, cx)
}
@@ -326,6 +326,13 @@ pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeLeft);
self
}
+
+ /// Sets cursor style when hovering over an element to `none`.
+ /// [Docs](https://tailwindcss.com/docs/cursor)
+ #visibility fn cursor_none(mut self, cursor: CursorStyle) -> Self {
+ self.style().mouse_cursor = Some(gpui::CursorStyle::None);
+ self
+ }
};
output.into()
@@ -533,6 +533,16 @@ List of `string` values
"cursor_shape": "hollow"
```
+## Hide Mouse While Typing
+
+- Description: Determines whether the mouse cursor should be hidden while typing in an editor or input box.
+- Setting: `hide_mouse_while_typing`
+- Default: `true`
+
+**Options**
+
+`boolean` values
+
## Editor Scrollbar
- Description: Whether or not to show the editor scrollbar and various elements in it.