mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Cursors: the canvas shows only the system pointer on macOS while another app or a sheet has the focus
On macOS the canvas paints the tool cursor into the window and hides the system pointer. winit hides the pointer with a cursor rect that holds a transparent cursor, and AppKit applies cursor rects only in the key window. Over a VectorCraft window behind another app, or under its own File › Open… sheet, macOS showed its arrow next to the painted tool cursor. On macOS the canvas now paints the tool cursor only while the window has the focus. Without the focus it sets the tool's system cursor icon, and the system pointer shows alone. Windows, X11, Wayland and the web are unchanged. Part of #2020. Docs: docs/target-app-parity.md.
This commit is contained in:
@@ -530,13 +530,21 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let ui_scale = if app.session.prefs.scale_cursor_with_ui { 1.0 } else { ui.ctx().zoom_factor() };
|
||||
match ui.input(|i| i.pointer.hover_pos()) {
|
||||
// An OS cursor: the system moves it at once, where a painted one trails the pointer
|
||||
// (#444). The system cursor stands in for cursors without a glyph.
|
||||
// (#444). The system cursor stands in for cursors without a glyph. Over a window
|
||||
// without the focus, macOS shows its own pointer whichever cursor the window sets,
|
||||
// and the glyph is not painted there.
|
||||
Some(_) if custom && crate::cursors::os_cursors(crate::cursors::OS_CURSORS, app.services.wayland) => {
|
||||
let ppp = ui.ctx().pixels_per_point() / ui_scale;
|
||||
ui.ctx().set_cursor_image(app.canvas.cursors.get(c, ppp));
|
||||
cursor_icon(c)
|
||||
}
|
||||
Some(hp) if custom && crate::cursors::paint(&painter, c, hp, 1.0 / ui_scale) => egui::CursorIcon::None,
|
||||
Some(hp)
|
||||
if custom
|
||||
&& crate::cursors::paint_glyphs(crate::cursors::HIDES_ONLY_WHEN_KEY, ui.input(|i| i.focused))
|
||||
&& crate::cursors::paint(&painter, c, hp, 1.0 / ui_scale) =>
|
||||
{
|
||||
egui::CursorIcon::None
|
||||
}
|
||||
_ => cursor_icon(c),
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -395,6 +395,18 @@ pub fn os_cursors(build: bool, wayland: bool) -> bool {
|
||||
build && !wayland
|
||||
}
|
||||
|
||||
/// Does the window hide the pointer only while it is the key window? macOS does: winit hides it with
|
||||
/// a cursor rect holding a transparent cursor, and macOS applies the rect only in the key window.
|
||||
/// Over a window behind another app or under a sheet, the system pointer shows (#2020).
|
||||
pub const HIDES_ONLY_WHEN_KEY: bool = cfg!(target_os = "macos");
|
||||
|
||||
/// Does the window paint the glyphs over a hidden pointer, `focused` or not? Not where the system
|
||||
/// shows its pointer over a window without the focus ([`HIDES_ONLY_WHEN_KEY`]): the system pointer
|
||||
/// shows alone there.
|
||||
pub fn paint_glyphs(hides_only_when_key: bool, focused: bool) -> bool {
|
||||
focused || !hides_only_when_key
|
||||
}
|
||||
|
||||
/// The cursor bitmaps handed to the OS, by (cursor, pixels per point): each glyph is rasterized
|
||||
/// once, and handing the same `Arc` every frame keeps the window's OS cursor instead of making a
|
||||
/// new one.
|
||||
@@ -650,6 +662,20 @@ mod tests {
|
||||
out
|
||||
}
|
||||
|
||||
/// One [`frame`] with `focused` set: true for a window that has the focus, false while another app
|
||||
/// or a sheet has it.
|
||||
fn focus_frame(app: &mut VectorcraftApp, ctx: &egui::Context, focused: bool, events: Vec<egui::Event>) -> egui::FullOutput {
|
||||
let time = Some(ctx.input(|i| i.time) + 0.1);
|
||||
let screen_rect = Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0)));
|
||||
let raw = egui::RawInput { screen_rect, time, focused, events, ..Default::default() };
|
||||
let mut out = ctx.run_ui(raw, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_canvas_hands_the_tool_cursor_to_the_os_and_idles_while_the_pointer_rests() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
@@ -701,6 +727,32 @@ mod tests {
|
||||
assert_eq!(out.platform_output.cursor_icon, egui::CursorIcon::None);
|
||||
}
|
||||
|
||||
/// On macOS a window without the focus shows the system pointer over the canvas, and the glyph
|
||||
/// is not painted next to it (#2020). With the focus back, the canvas sets `CursorIcon::None` again
|
||||
/// on the next frame, without a pointer move.
|
||||
#[test]
|
||||
fn a_window_without_the_focus_shows_the_system_cursor_alone_on_macos() {
|
||||
// Wayland: every desktop build paints the glyphs.
|
||||
let mut app = VectorcraftApp::new(Session::new(), crate::Services { wayland: true, ..Default::default() });
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
app.run("tool.select", json!({"tool": "rectangle"})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
for _ in 0..3 {
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
}
|
||||
let over = app.canvas_rect.unwrap().center();
|
||||
let key = focus_frame(&mut app, &ctx, true, vec![egui::Event::PointerMoved(over)]);
|
||||
assert_eq!(key.platform_output.cursor_icon, egui::CursorIcon::None, "with the focus: the glyph alone");
|
||||
let away = focus_frame(&mut app, &ctx, false, vec![egui::Event::WindowFocused(false), egui::Event::PointerMoved(over + vec2(5.0, 3.0))]);
|
||||
if HIDES_ONLY_WHEN_KEY {
|
||||
assert_eq!(away.platform_output.cursor_icon, egui::CursorIcon::Crosshair, "macOS without the focus: the system pointer alone");
|
||||
} else {
|
||||
assert_eq!(away.platform_output.cursor_icon, egui::CursorIcon::None, "other platforms: the glyph without the focus too");
|
||||
}
|
||||
let back = focus_frame(&mut app, &ctx, true, vec![egui::Event::WindowFocused(true)]);
|
||||
assert_eq!(back.platform_output.cursor_icon, egui::CursorIcon::None, "the focus back: the glyph alone");
|
||||
}
|
||||
|
||||
/// User Interface › Scale Cursor Proportional to UI (#394): off, the tool cursors keep their
|
||||
/// size whatever the UI Scaling; on, they grow with it.
|
||||
#[test]
|
||||
@@ -745,6 +797,13 @@ mod tests {
|
||||
assert!(!os_cursors(false, false) && !os_cursors(false, true), "macOS, the web");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_windows_paint_the_glyphs_only_with_the_focus() {
|
||||
assert!(paint_glyphs(true, true), "macOS with the focus");
|
||||
assert!(!paint_glyphs(true, false), "macOS without the focus: the system pointer shows (#2020)");
|
||||
assert!(paint_glyphs(false, true) && paint_glyphs(false, false), "Wayland, the web: painted with the focus or without");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_hotspot_is_tip() {
|
||||
let pts = arrow_points(pos2(10.0, 20.0));
|
||||
|
||||
@@ -608,7 +608,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
anchor or with Alt held over a path (convert) and over another open path's end (join). The Eyedropper has an original pipette glyph with its tip at the hotspot;
|
||||
Use Precise Cursors restores the crosshair. The same glyph feeds OS bitmaps and painted
|
||||
cursors. DPI/hotspot and preference checks in `cursors.rs` and `tests_prefs.rs` cover #1269;
|
||||
actual OS display and Caps Lock remain unverified. Behaviour reference: [public tool-pointer documentation](https://helpx.adobe.com/illustrator/using/tools.html#change_tool_pointers). Tool cursors are painted into the window on Wayland (#1652; not run on Linux, #1645 stays open until confirmed). A Precise Cursors command toggles the preference and can take a shortcut (#1663).
|
||||
actual OS display and Caps Lock remain unverified. Behaviour reference: [public tool-pointer documentation](https://helpx.adobe.com/illustrator/using/tools.html#change_tool_pointers). Tool cursors are painted into the window on Wayland (#1652; not run on Linux, #1645 stays open until confirmed). A Precise Cursors command toggles the preference and can take a shortcut (#1663). On macOS the tool cursor is painted only while the window has the focus. Over a window behind another app, or under the File › Open… sheet, the system pointer shows alone instead of next to the painted cursor (#2020).
|
||||
- **Performance:** 20k shapes + 1k texts render in 27 ms per full-retina frame (7.8 ms zoomed), 7× faster than the first version. The UI thread never blocks. The web build is 7.1 MB gzipped. Print-size images (more than 512 MB decoded, up to 2 GB) draw in the canvas and in raster exports and can be trimmed with Crop Image; an image with a side past 65,535 px is shrunk to fit, a raster export says when an image could not be decoded, and Crop Image's trim leaves CMYK images alone (#1517). On Windows `vectorcraft-cli perf` reports the cores busy and, with `--deep`, its working set, and warns on a busy machine as it does on macOS and Linux (#1502).
|
||||
With `--deep`, each memory row of `vectorcraft-cli perf` also gives the peak so far: the peak working set on Windows and the peak resident memory (`VmHWM`) on Linux. A new last row gives the memory after the SVG export and the save → open → save at the end of the run.
|
||||
The renderer composes the two ink planes of a CMYK document that shows transparency into screen colors on up to as many threads as it rasterizes with, instead of on one. At 2880 × 1800 on 8 threads, a page of 300 shapes at 20% to 90% opacity takes 30 ms a frame instead of 55 ms, and 50,000 paths with one at 99% opacity take 258 ms instead of 361 ms (`vectorcraft-cli bench`), with the same pixels.
|
||||
|
||||
Reference in New Issue
Block a user