Compare commits

...

6 Commits

Author SHA1 Message Date
smit
40d7d1f1ad fix panic on macos 2025-03-26 22:39:40 +05:30
Kirill Bulatov
72688a7420 Refactor the code to be more generic
Co-authored-by: Agus Zubiaga <hi@aguz.me>
2025-03-26 22:36:13 +05:30
Agus Zubiaga
45764bc943 Reshow cursor when changing setting 2025-03-26 22:36:13 +05:30
Kirill Bulatov
12d6162bd8 Fix Windows impl
Co-authored-by: Agus Zubiaga <hi@aguz.me>
2025-03-26 22:36:13 +05:30
smit
f4c707b01a Support CursorStyle::None in Windows (untested) 2025-03-26 22:36:11 +05:30
smit
3537d8384e rebase 2025-03-26 22:34:53 +05:30
17 changed files with 132 additions and 28 deletions

View File

@@ -155,6 +155,8 @@
// //
// Default: not set, defaults to "bar" // Default: not set, defaults to "bar"
"cursor_shape": null, "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. // How to highlight the current line in the editor.
// //
// 1. Don't highlight the current line: // 1. Don't highlight the current line:

View File

@@ -790,6 +790,8 @@ pub struct Editor {
_scroll_cursor_center_top_bottom_task: Task<()>, _scroll_cursor_center_top_bottom_task: Task<()>,
serialize_selections: Task<()>, serialize_selections: Task<()>,
serialize_folds: Task<()>, serialize_folds: Task<()>,
mouse_cursor_hidden: bool,
hide_mouse_while_typing: bool,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1568,6 +1570,10 @@ impl Editor {
serialize_folds: Task::ready(()), serialize_folds: Task::ready(()),
text_style_refinement: None, text_style_refinement: None,
load_diff_task: load_uncommitted_diff, 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),
}; };
if let Some(breakpoints) = this.breakpoint_store.as_ref() { if let Some(breakpoints) = this.breakpoint_store.as_ref() {
this._subscriptions this._subscriptions
@@ -2999,6 +3005,8 @@ impl Editor {
return; return;
} }
self.mouse_cursor_hidden = self.hide_mouse_while_typing;
let selections = self.selections.all_adjusted(cx); let selections = self.selections.all_adjusted(cx);
let mut bracket_inserted = false; let mut bracket_inserted = false;
let mut edits = Vec::new(); let mut edits = Vec::new();
@@ -16669,6 +16677,11 @@ impl Editor {
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin; self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs; self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default(); 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 { if old_cursor_shape != self.cursor_shape {

View File

@@ -39,6 +39,7 @@ pub struct EditorSettings {
#[serde(default)] #[serde(default)]
pub go_to_definition_fallback: GoToDefinitionFallback, pub go_to_definition_fallback: GoToDefinitionFallback,
pub jupyter: Jupyter, pub jupyter: Jupyter,
pub hide_mouse_while_typing: Option<bool>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -235,6 +236,10 @@ pub struct EditorSettingsContent {
/// ///
/// Default: None /// Default: None
pub cursor_shape: Option<CursorShape>, 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. /// How to highlight the current line in the editor.
/// ///
/// Default: all /// Default: all

View File

@@ -894,6 +894,7 @@ impl EditorElement {
let gutter_hovered = gutter_hitbox.is_hovered(window); let gutter_hovered = gutter_hitbox.is_hovered(window);
editor.set_gutter_hovered(gutter_hovered, cx); editor.set_gutter_hovered(gutter_hovered, cx);
editor.gutter_breakpoint_indicator = None; editor.gutter_breakpoint_indicator = None;
editor.mouse_cursor_hidden = false;
if gutter_hovered { if gutter_hovered {
let new_point = position_map let new_point = position_map
@@ -4636,9 +4637,10 @@ impl EditorElement {
bounds: layout.position_map.text_hitbox.bounds, bounds: layout.position_map.text_hitbox.bounds,
}), }),
|window| { |window| {
let cursor_style = if self let editor = self.editor.read(cx);
.editor let cursor_style = if editor.mouse_cursor_hidden {
.read(cx) CursorStyle::None
} else if editor
.hovered_link_state .hovered_link_state
.as_ref() .as_ref()
.is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty()) .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
@@ -6598,6 +6600,7 @@ impl Element for EditorElement {
}, },
false, false,
); );
// Offset the content_bounds from the text_bounds by the gutter margin (which // 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. // is roughly half a character wide) to make hit testing work more like how we want.
let content_origin = let content_origin =

View File

@@ -1228,6 +1228,9 @@ pub enum CursorStyle {
/// A cursor indicating that the operation will result in a context menu /// A cursor indicating that the operation will result in a context menu
/// corresponds to the CSS cursor value `context-menu` /// corresponds to the CSS cursor value `context-menu`
ContextualMenu, ContextualMenu,
/// Hide the cursor
None,
} }
impl Default for CursorStyle { impl Default for CursorStyle {

View File

@@ -666,6 +666,12 @@ impl CursorStyle {
CursorStyle::DragLink => "alias", CursorStyle::DragLink => "alias",
CursorStyle::DragCopy => "copy", CursorStyle::DragCopy => "copy",
CursorStyle::ContextualMenu => "context-menu", 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() .to_string()
} }

View File

@@ -35,6 +35,12 @@ impl CursorStyle {
CursorStyle::DragLink => Shape::Alias, CursorStyle::DragLink => Shape::Alias,
CursorStyle::DragCopy => Shape::Copy, CursorStyle::DragCopy => Shape::Copy,
CursorStyle::ContextualMenu => Shape::ContextMenu, 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
}
} }
} }
} }

View File

@@ -667,7 +667,13 @@ impl LinuxClient for WaylandClient {
let serial = state.serial_tracker.get(SerialKind::MouseEnter); let serial = state.serial_tracker.get(SerialKind::MouseEnter);
state.cursor_style = Some(style); 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()); cursor_shape_device.set_shape(serial, style.to_shape());
} else if let Some(focused_window) = &state.mouse_focused_window { } else if let Some(focused_window) = &state.mouse_focused_window {
// cursor-shape-v1 isn't supported, set the cursor using a surface. // cursor-shape-v1 isn't supported, set the cursor using a surface.

View File

@@ -1438,13 +1438,16 @@ impl LinuxClient for X11Client {
let cursor = match state.cursor_cache.get(&style) { let cursor = match state.cursor_cache.get(&style) {
Some(cursor) => *cursor, Some(cursor) => *cursor,
None => { None => {
let Some(cursor) = state let Some(cursor) = (match style {
.cursor_handle CursorStyle::None => create_invisible_cursor(&state.xcb_connection).log_err(),
.load_cursor(&state.xcb_connection, &style.to_icon_name()) _ => state
.log_err() .cursor_handle
else { .load_cursor(&state.xcb_connection, &style.to_icon_name())
.log_err(),
}) else {
return; return;
}; };
state.cursor_cache.insert(style, cursor); state.cursor_cache.insert(style, cursor);
cursor cursor
} }
@@ -1938,3 +1941,19 @@ fn make_scroll_wheel_event(
touch_phase: TouchPhase::default(), 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)
}

View File

@@ -891,6 +891,11 @@ impl Platform for MacPlatform {
/// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor). /// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {
unsafe { unsafe {
if style == CursorStyle::None {
let _: () = msg_send![class!(NSCursor), setHiddenUntilMouseMoves:YES];
return;
}
let new_cursor: id = match style { let new_cursor: id = match style {
CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor], CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor], CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
@@ -925,6 +930,7 @@ impl Platform for MacPlatform {
CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor], CursorStyle::DragLink => msg_send![class!(NSCursor), dragLinkCursor],
CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor], CursorStyle::DragCopy => msg_send![class!(NSCursor), dragCopyCursor],
CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor], CursorStyle::ContextualMenu => msg_send![class!(NSCursor), contextualMenuCursor],
CursorStyle::None => unreachable!(),
}; };
let old_cursor: id = msg_send![class!(NSCursor), currentCursor]; let old_cursor: id = msg_send![class!(NSCursor), currentCursor];

View File

@@ -1127,7 +1127,19 @@ fn handle_nc_mouse_up_msg(
} }
fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> { 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) Some(0)
} }
@@ -1138,7 +1150,9 @@ fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Op
) { ) {
return None; return None;
} }
unsafe { SetCursor(Some(state_ptr.state.borrow().current_cursor)) }; unsafe {
SetCursor(state_ptr.state.borrow().current_cursor.as_ref());
};
Some(1) Some(1)
} }

View File

@@ -54,7 +54,7 @@ pub(crate) struct WindowsPlatformState {
menus: Vec<OwnedMenu>, menus: Vec<OwnedMenu>,
dock_menu_actions: Vec<Box<dyn Action>>, dock_menu_actions: Vec<Box<dyn Action>>,
// NOTE: standard cursor handles don't need to close. // NOTE: standard cursor handles don't need to close.
pub(crate) current_cursor: HCURSOR, pub(crate) current_cursor: Option<HCURSOR>,
} }
#[derive(Default)] #[derive(Default)]
@@ -558,11 +558,11 @@ impl Platform for WindowsPlatform {
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {
let hcursor = load_cursor(style); let hcursor = load_cursor(style);
let mut lock = self.state.borrow_mut(); 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( self.post_message(
WM_GPUI_CURSOR_STYLE_CHANGED, WM_GPUI_CURSOR_STYLE_CHANGED,
WPARAM(0), WPARAM(0),
LPARAM(hcursor.0 as isize), LPARAM(hcursor.map_or(0, |c| c.0 as isize)),
); );
lock.current_cursor = hcursor; lock.current_cursor = hcursor;
} }
@@ -683,7 +683,7 @@ impl Drop for WindowsPlatform {
pub(crate) struct WindowCreationInfo { pub(crate) struct WindowCreationInfo {
pub(crate) icon: HICON, pub(crate) icon: HICON,
pub(crate) executor: ForegroundExecutor, pub(crate) executor: ForegroundExecutor,
pub(crate) current_cursor: HCURSOR, pub(crate) current_cursor: Option<HCURSOR>,
pub(crate) windows_version: WindowsVersion, pub(crate) windows_version: WindowsVersion,
pub(crate) validation_number: usize, pub(crate) validation_number: usize,
pub(crate) main_receiver: flume::Receiver<Runnable>, pub(crate) main_receiver: flume::Receiver<Runnable>,

View File

@@ -106,7 +106,7 @@ pub(crate) fn windows_credentials_target_name(url: &str) -> String {
format!("zed:url={}", url) 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 ARROW: OnceLock<SafeCursor> = OnceLock::new();
static IBEAM: OnceLock<SafeCursor> = OnceLock::new(); static IBEAM: OnceLock<SafeCursor> = OnceLock::new();
static CROSS: OnceLock<SafeCursor> = OnceLock::new(); static CROSS: OnceLock<SafeCursor> = OnceLock::new();
@@ -127,17 +127,20 @@ pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR {
| CursorStyle::ResizeUpDown | CursorStyle::ResizeUpDown
| CursorStyle::ResizeRow => (&SIZENS, IDC_SIZENS), | CursorStyle::ResizeRow => (&SIZENS, IDC_SIZENS),
CursorStyle::OperationNotAllowed => (&NO, IDC_NO), CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
CursorStyle::None => return None,
_ => (&ARROW, IDC_ARROW), _ => (&ARROW, IDC_ARROW),
}; };
*(*lock.get_or_init(|| { Some(
HCURSOR( *(*lock.get_or_init(|| {
unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) } HCURSOR(
.log_err() unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
.unwrap_or_default() .log_err()
.0, .unwrap_or_default()
) .0,
.into() )
})) .into()
})),
)
} }
/// This function is used to configure the dark mode for the window built-in title bar. /// This function is used to configure the dark mode for the window built-in title bar.

View File

@@ -48,7 +48,7 @@ pub struct WindowsWindowState {
pub click_state: ClickState, pub click_state: ClickState,
pub system_settings: WindowsSystemSettings, pub system_settings: WindowsSystemSettings,
pub current_cursor: HCURSOR, pub current_cursor: Option<HCURSOR>,
pub nc_button_pressed: Option<u32>, pub nc_button_pressed: Option<u32>,
pub display: WindowsDisplay, pub display: WindowsDisplay,
@@ -76,7 +76,7 @@ impl WindowsWindowState {
hwnd: HWND, hwnd: HWND,
transparent: bool, transparent: bool,
cs: &CREATESTRUCTW, cs: &CREATESTRUCTW,
current_cursor: HCURSOR, current_cursor: Option<HCURSOR>,
display: WindowsDisplay, display: WindowsDisplay,
gpu_context: &BladeContext, gpu_context: &BladeContext,
) -> Result<Self> { ) -> Result<Self> {
@@ -351,7 +351,7 @@ struct WindowCreateContext<'a> {
transparent: bool, transparent: bool,
is_movable: bool, is_movable: bool,
executor: ForegroundExecutor, executor: ForegroundExecutor,
current_cursor: HCURSOR, current_cursor: Option<HCURSOR>,
windows_version: WindowsVersion, windows_version: WindowsVersion,
validation_number: usize, validation_number: usize,
main_receiver: flume::Receiver<Runnable>, main_receiver: flume::Receiver<Runnable>,

View File

@@ -3241,6 +3241,7 @@ impl Window {
keystroke, keystroke,
&dispatch_path, &dispatch_path,
); );
if !match_result.to_replay.is_empty() { if !match_result.to_replay.is_empty() {
self.replay_pending_input(match_result.to_replay, cx) self.replay_pending_input(match_result.to_replay, cx)
} }

View File

@@ -326,6 +326,13 @@ pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeLeft); self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeLeft);
self 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() output.into()

View File

@@ -553,6 +553,16 @@ List of `string` values
"cursor_shape": "hollow" "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 ## Editor Scrollbar
- Description: Whether or not to show the editor scrollbar and various elements in it. - Description: Whether or not to show the editor scrollbar and various elements in it.