Skip to content

Commit a3107ed

Browse files
committed
feat: hand cursor on Ctrl/Cmd-hover over a clickable link or GitHub ref
1 parent 04ec6dd commit a3107ed

1 file changed

Lines changed: 51 additions & 1 deletion

File tree

src/shell.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use winit::dpi::{PhysicalPosition, PhysicalSize};
3333
use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
3434
use winit::event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy};
3535
use winit::keyboard::{Key, KeyCode, ModifiersState, NamedKey, PhysicalKey};
36-
use winit::window::{Theme, Window, WindowId};
36+
use winit::window::{CursorIcon, Theme, Window, WindowId};
3737

3838
use winit::event_loop::ControlFlow;
3939

@@ -322,6 +322,9 @@ struct App {
322322
modifiers: ModifiersState,
323323
mouse_pos: (f32, f32),
324324
mouse_down: bool,
325+
/// The OS cursor icon currently set on the window, so hover updates only call
326+
/// `set_cursor` on a real change (Ctrl-hover over a link → pointer, else default).
327+
cursor_icon: CursorIcon,
325328
/// Click-count tracking for double/triple-click select (winit doesn't provide it):
326329
/// the last press's time + position, and the running count (cycles 1→2→3).
327330
last_click: Option<(std::time::Instant, (f32, f32))>,
@@ -415,6 +418,7 @@ impl App {
415418
modifiers: ModifiersState::empty(),
416419
mouse_pos: (0.0, 0.0),
417420
mouse_down: false,
421+
cursor_icon: CursorIcon::Default,
418422
last_click: None,
419423
click_count: 0,
420424
drag_scroll_dy: 0.0,
@@ -1094,6 +1098,34 @@ fn ac_row_at(rects: &[ScreenRect], pos: (f32, f32)) -> Option<usize> {
10941098
rects.iter().position(|r| rect_contains(r, pos))
10951099
}
10961100

1101+
/// Point the OS cursor at whatever's under the pointer: a hand (`Pointer`) when Ctrl/Cmd
1102+
/// is held over a Ctrl-clickable link (same hit-test → `link_at` path as the click), else
1103+
/// the default arrow. Only calls `set_cursor` on a change. Gated on the modifier first so
1104+
/// the (snapshot-building) `link_at` lookup runs only during an actual Ctrl-hover.
1105+
fn update_link_cursor(
1106+
doc_engine: &mut DocEngine,
1107+
modifiers: ModifiersState,
1108+
mouse_pos: (f32, f32),
1109+
current: &mut CursorIcon,
1110+
window: &Window,
1111+
) {
1112+
let over_link = (modifiers.control_key() || modifiers.super_key())
1113+
&& doc_engine
1114+
.doc
1115+
.as_ref()
1116+
.and_then(|d| d.hit_test(mouse_pos.0, mouse_pos.1))
1117+
.is_some_and(|off| doc_engine.editor.link_at(off).is_some());
1118+
let icon = if over_link {
1119+
CursorIcon::Pointer
1120+
} else {
1121+
CursorIcon::Default
1122+
};
1123+
if icon != *current {
1124+
*current = icon;
1125+
window.set_cursor(icon);
1126+
}
1127+
}
1128+
10971129
/// Paint the clipped document body (diff backgrounds, quote gutters, rules, images,
10981130
/// selection, glyphs, caret) plus the bottom status bar into `scene`. Overlays
10991131
/// (hover popover / autocomplete) differ per caller and are drawn separately. Shared by
@@ -1564,6 +1596,14 @@ impl ApplicationHandler<WritEvent> for App {
15641596
}
15651597
WindowEvent::ModifiersChanged(mods) => {
15661598
self.modifiers = mods.state();
1599+
// Pressing/releasing Ctrl while stationary over a link flips the pointer.
1600+
update_link_cursor(
1601+
&mut self.doc_engine,
1602+
self.modifiers,
1603+
self.mouse_pos,
1604+
&mut self.cursor_icon,
1605+
&state.window,
1606+
);
15671607
}
15681608
WindowEvent::Ime(winit::event::Ime::Enabled) => {
15691609
self.doc_engine.preedit = None;
@@ -1729,6 +1769,16 @@ impl ApplicationHandler<WritEvent> for App {
17291769
state.window.request_redraw();
17301770
}
17311771
}
1772+
// Ctrl-hover over a link shows the pointer cursor (skip while drag-selecting).
1773+
if !self.mouse_down {
1774+
update_link_cursor(
1775+
&mut self.doc_engine,
1776+
self.modifiers,
1777+
self.mouse_pos,
1778+
&mut self.cursor_icon,
1779+
&state.window,
1780+
);
1781+
}
17321782
}
17331783
WindowEvent::MouseInput {
17341784
state: ElementState::Pressed,

0 commit comments

Comments
 (0)