Cursors: paint the tool cursors on Wayland, where the compositor showed the bitmap twice too big (fixes #1645)

This commit is contained in:
Marco Arnulfo authored and bflatastic committed 2026-10-14 02:09:09 +07:00
1 parent 3412d3a073
commit e50bd58146
4 files changed
+37 -1

No files matched your search

+3
View File
@@ -489,6 +489,8 @@ fn start_pick(request: PickRequest, parent: &Parent, ui: &egui::Context) -> Opti
/// The host's services; `ui` is woken when a file dialog answers.
fn services(parent: Parent, ui: egui::Context) -> Services {
use winit::raw_window_handle::{HasWindowHandle as _, RawWindowHandle};
let wayland = parent.as_ref().and_then(|w| w.window_handle().ok()).is_some_and(|h| matches!(h.as_raw(), RawWindowHandle::Wayland(_)));
let (p1, p2, p3, p4, p5) = (parent.clone(), parent.clone(), parent.clone(), parent.clone(), parent);
Services {
pick_open: Some(Box::new(move |pick: &FilePick| pick_now(PickRequest::Open(pick.clone()), &p1).into_iter().next())),
@@ -498,6 +500,7 @@ fn services(parent: Parent, ui: egui::Context) -> Services {
// dialogs run the window's events while they are open.
start_pick: cfg!(unix).then(|| Box::new(move |request: PickRequest| start_pick(request, &p5, &ui)) as vectorcraft_ui_egui::picks::StartPick),
dialogs_are_sheets: cfg!(target_os = "macos"),
wayland,
read: Some(Box::new(|p: &str| std::fs::read(p).map_err(|e| e.to_string()))),
write: Some(Box::new(write_file)),
// Background Save and Export write from a worker thread.
+1 -1
View File
@@ -492,7 +492,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
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.
Some(_) if custom && crate::cursors::OS_CURSORS => {
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)
+30
View File
@@ -386,6 +386,14 @@ pub fn paint(painter: &Painter, c: Cursor, p: Pos2, scale: f32) -> bool {
/// bitmap cursors there): those paint them into the window ([`paint`]).
pub const OS_CURSORS: bool = !cfg!(any(target_os = "macos", target_arch = "wasm32"));
/// Does this window show the glyphs as OS cursors, on a build that can ([`OS_CURSORS`])? Not on
/// Wayland, where the compositor decides how big a cursor bitmap shows: Hyprland shows each pixel
/// as a point, twice too big on a 2× display (#1645). The window paints them there, as PhotoCraft
/// and PdfCraft do.
pub fn os_cursors(build: bool, wayland: bool) -> bool {
build && !wayland
}
/// 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.
@@ -655,6 +663,21 @@ mod tests {
assert!(off.platform_output.cursor_image.is_none());
}
/// On Wayland the glyph is painted and the pointer hidden over the canvas, on every build (#1645).
#[test]
fn a_wayland_window_paints_the_tool_cursor() {
let mut app = VectorcraftApp::new(Session::new(), crate::Services { wayland: true, ..Default::default() });
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
let ctx = egui::Context::default();
for _ in 0..3 {
frame(&mut app, &ctx, vec![]);
}
let over = app.canvas_rect.unwrap().center();
let out = frame(&mut app, &ctx, vec![egui::Event::PointerMoved(over)]);
assert!(out.platform_output.cursor_image.is_none());
assert_eq!(out.platform_output.cursor_icon, egui::CursorIcon::None);
}
/// 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]
@@ -692,6 +715,13 @@ mod tests {
assert!(full.contains(pos2(100.0, 100.0)) && half.contains(pos2(100.0, 100.0)), "{full:?} {half:?}");
}
#[test]
fn wayland_windows_paint_the_glyphs() {
assert!(os_cursors(true, false), "Windows, X11");
assert!(!os_cursors(true, true), "Wayland: the compositor would size the bitmap (#1645)");
assert!(!os_cursors(false, false) && !os_cursors(false, true), "macOS, the web");
}
#[test]
fn arrow_hotspot_is_tip() {
let pts = arrow_points(pos2(10.0, 20.0));
+3
View File
@@ -261,6 +261,9 @@ pub struct Services {
/// [`Self::start_pick`]'s dialogs are sheets on the window (macOS): while one is open the app
/// neither quits nor closes documents, as under the modal dialog before.
pub dialogs_are_sheets: bool,
/// The window is on Wayland: the tool cursors are painted into it instead of handed to the
/// compositor as bitmaps ([`cursors::os_cursors`]).
pub wayland: bool,
}
/// Cached canvas raster.