From db509dc4d984e3843bccc8edf17f52ecf66ec4e5 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Mon, 12 Oct 2026 12:19:13 +0200 Subject: [PATCH] fix(ui): show a paint-bucket cursor for the Paint Bucket tool (#3968) (#3989) --- crates/ui-egui/src/canvas.rs | 16 ++++++++++++++++ crates/ui-egui/src/input_tests.rs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 304f3b2f0e..19f1cbf4c0 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3527,6 +3527,14 @@ pub fn canvas_view(app: &mut PhotocraftApp, ui: &mut egui::Ui, idx: usize, rect: pipette_cursor(ui.ctx(), p) } } + // The Paint Bucket: a bucket, or the crosshair for Precise Other Cursors (#3968). + Tool::PaintBucket => { + if app.session.prefs().cursors.other == photocraft_engine::prefs::OtherCursor::Precise { + egui::CursorIcon::Crosshair + } else { + paint_bucket_cursor(ui.ctx(), p) + } + } // Preferences › Cursors › Other Cursors: Precise shows a crosshair for every tool. Tool::Move | Tool::Type | Tool::VerticalType | Tool::HorizontalTypeMask | Tool::VerticalTypeMask if app.session.prefs().cursors.other == photocraft_engine::prefs::OtherCursor::Precise => @@ -4346,6 +4354,14 @@ pub(crate) fn pipette_cursor(ctx: &egui::Context, p: Pos2) -> egui::CursorIcon { egui::CursorIcon::None } +/// The Paint Bucket's cursor: a bucket whose pouring corner is the filled pixel. Draws it at `p` +/// and returns the OS cursor to set (hidden). +pub(crate) fn paint_bucket_cursor(ctx: &egui::Context, p: Pos2) -> egui::CursorIcon { + // The left corner of the icon's bucket is at (2, 12) of its 24-unit box. + crate::icons::cursor(ctx, "paint-bucket", p, vec2(2.0, 12.0) / 24.0, 22.0); + egui::CursorIcon::None +} + /// Decided when the press starts, so ⌥ pressed or released mid-stroke never switches between /// painting and sampling. fn alt_eyedropper(app: &mut PhotocraftApp, ev: ToolEvent, mods: egui::Modifiers) -> bool { diff --git a/crates/ui-egui/src/input_tests.rs b/crates/ui-egui/src/input_tests.rs index bb87d477e0..956a9de9ba 100644 --- a/crates/ui-egui/src/input_tests.rs +++ b/crates/ui-egui/src/input_tests.rs @@ -564,6 +564,22 @@ fn eyedropper_and_alt_sampling_show_a_pipette() { assert_eq!(precise(&mut h), (egui::CursorIcon::Crosshair, desktop)); } +/// The Paint Bucket shows a bucket instead of the crosshair (#3968); Precise Other Cursors keeps +/// the crosshair. +#[test] +fn paint_bucket_shows_a_bucket_cursor() { + let mut h = harness(); + let p = h.state().last_canvas_rect.center(); + h.state_mut().ui.tool = crate::state::Tool::PaintBucket; + h.hover_at(p); + h.run_steps(2); + assert_eq!(h.output().platform_output.cursor_icon, egui::CursorIcon::None, "the bucket replaces the pointer"); + h.state_mut().run("prefs.set", json!({"values": {"cursors.other": "precise"}})).unwrap(); + h.hover_at(p); + h.run_steps(2); + assert_eq!(h.output().platform_output.cursor_icon, egui::CursorIcon::Crosshair, "Precise keeps the crosshair"); +} + /// The Hand tool, and Space held over another tool, show an open hand while hovering and a fist /// while they pan (#2196). The OS draws those everywhere but Windows, where winit maps `Grab` to /// the four-arrow move cursor, so the app hands it a bitmap there (`Crosshair` is the fallback).