Cursors: paint the tool cursors on Wayland, where the compositor showed the bitmap twice too big (fixes #1645)
This commit is contained in:
parent
3412d3a073
commit
e50bd58146
|
|
@ -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.
|
/// The host's services; `ui` is woken when a file dialog answers.
|
||||||
fn services(parent: Parent, ui: egui::Context) -> Services {
|
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);
|
let (p1, p2, p3, p4, p5) = (parent.clone(), parent.clone(), parent.clone(), parent.clone(), parent);
|
||||||
Services {
|
Services {
|
||||||
pick_open: Some(Box::new(move |pick: &FilePick| pick_now(PickRequest::Open(pick.clone()), &p1).into_iter().next())),
|
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.
|
// 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),
|
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"),
|
dialogs_are_sheets: cfg!(target_os = "macos"),
|
||||||
|
wayland,
|
||||||
read: Some(Box::new(|p: &str| std::fs::read(p).map_err(|e| e.to_string()))),
|
read: Some(Box::new(|p: &str| std::fs::read(p).map_err(|e| e.to_string()))),
|
||||||
write: Some(Box::new(write_file)),
|
write: Some(Box::new(write_file)),
|
||||||
// Background Save and Export write from a worker thread.
|
// Background Save and Export write from a worker thread.
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||||
match ui.input(|i| i.pointer.hover_pos()) {
|
match ui.input(|i| i.pointer.hover_pos()) {
|
||||||
// An OS cursor: the system moves it at once, where a painted one trails the pointer
|
// 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.
|
||||||
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;
|
let ppp = ui.ctx().pixels_per_point() / ui_scale;
|
||||||
ui.ctx().set_cursor_image(app.canvas.cursors.get(c, ppp));
|
ui.ctx().set_cursor_image(app.canvas.cursors.get(c, ppp));
|
||||||
cursor_icon(c)
|
cursor_icon(c)
|
||||||
|
|
|
||||||
|
|
@ -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`]).
|
/// bitmap cursors there): those paint them into the window ([`paint`]).
|
||||||
pub const OS_CURSORS: bool = !cfg!(any(target_os = "macos", target_arch = "wasm32"));
|
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
|
/// 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
|
/// once, and handing the same `Arc` every frame keeps the window's OS cursor instead of making a
|
||||||
/// new one.
|
/// new one.
|
||||||
|
|
@ -655,6 +663,21 @@ mod tests {
|
||||||
assert!(off.platform_output.cursor_image.is_none());
|
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
|
/// User Interface › Scale Cursor Proportional to UI (#394): off, the tool cursors keep their
|
||||||
/// size whatever the UI Scaling; on, they grow with it.
|
/// size whatever the UI Scaling; on, they grow with it.
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -692,6 +715,13 @@ mod tests {
|
||||||
assert!(full.contains(pos2(100.0, 100.0)) && half.contains(pos2(100.0, 100.0)), "{full:?} {half:?}");
|
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]
|
#[test]
|
||||||
fn arrow_hotspot_is_tip() {
|
fn arrow_hotspot_is_tip() {
|
||||||
let pts = arrow_points(pos2(10.0, 20.0));
|
let pts = arrow_points(pos2(10.0, 20.0));
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,9 @@ pub struct Services {
|
||||||
/// [`Self::start_pick`]'s dialogs are sheets on the window (macOS): while one is open the app
|
/// [`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.
|
/// neither quits nor closes documents, as under the modal dialog before.
|
||||||
pub dialogs_are_sheets: bool,
|
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.
|
/// Cached canvas raster.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue