From 55a2dffcaaf34d51cc145abe39ed1b018784fa81 Mon Sep 17 00:00:00 2001 From: David Mann Date: Fri, 6 Nov 2026 03:13:29 +0100 Subject: [PATCH] Batch 18 (CodeRabbit): Layers empty-list menu under a filter; the pen's eraser end ends Add New Artboard - #11500: a filter that matches no layer draws no row, so the empty-area right-click was never created; the whole empty list now takes it. - #11532: flipping the pen to its eraser end is a tool switch, so Add New Artboard's placing mode ends as with any other switch. Tests for both (the Layers one fails without the fix). Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/input/stylus.rs | 2 ++ crates/ui-egui/src/input/stylus/tests.rs | 19 +++++++++++++++++++ .../src/layers_empty_area_menu_tests.rs | 13 +++++++++++++ crates/ui-egui/src/panels/layers.rs | 8 ++++++++ 4 files changed, 42 insertions(+) diff --git a/crates/ui-egui/src/input/stylus.rs b/crates/ui-egui/src/input/stylus.rs index 73191e4ebd..b6725af5ea 100644 --- a/crates/ui-egui/src/input/stylus.rs +++ b/crates/ui-egui/src/input/stylus.rs @@ -336,6 +336,8 @@ impl Stylus { Some(true) if app.ui.tool != Tool::Eraser => { app.stylus.tool_before_eraser = Some(app.ui.tool); app.ui.tool = Tool::Eraser; + // A tool switch like any other: Add New Artboard's placing mode ends (#11386). + crate::tool_memory::ended_by_tool(app); true } Some(false) => match app.stylus.tool_before_eraser.take() { diff --git a/crates/ui-egui/src/input/stylus/tests.rs b/crates/ui-egui/src/input/stylus/tests.rs index f5c872f4af..8e1a71dfa4 100644 --- a/crates/ui-egui/src/input/stylus/tests.rs +++ b/crates/ui-egui/src/input/stylus/tests.rs @@ -350,3 +350,22 @@ fn eraser_end_switches_tools_once_and_never_mid_drag() { assert!(!Stylus::sync_eraser_tool(&mut app)); assert_eq!(app.ui.tool, Tool::Eraser); } + +/// The pen's eraser end is a tool switch like any other: Add New Artboard's placing mode ends +/// (#11386), and coming back to the Artboard tool on the tip leaves it off. +#[test] +fn the_eraser_end_ends_the_artboard_placing_mode() { + use crate::state::Tool; + let mut app = crate::PhotocraftApp::new(photocraft_engine::Session::new(), Default::default()); + app.session.execute("file.new", serde_json::json!({"width": 60, "height": 60})).unwrap(); + app.ui.tool = Tool::Artboard; + app.ui.artboard_tool.adding = true; + let pen = |eraser| Some(PenSample { pressure: 0.5, eraser, ..Default::default() }); + app.stylus.feed.set(pen(true)); + assert!(Stylus::sync_eraser_tool(&mut app)); + assert!(!app.ui.artboard_tool.adding, "the eraser end ends the mode"); + app.stylus.feed.set(pen(false)); + assert!(Stylus::sync_eraser_tool(&mut app)); + assert_eq!(app.ui.tool, Tool::Artboard); + assert!(!app.ui.artboard_tool.adding); +} diff --git a/crates/ui-egui/src/layers_empty_area_menu_tests.rs b/crates/ui-egui/src/layers_empty_area_menu_tests.rs index ac78f9e57a..10b0c52212 100644 --- a/crates/ui-egui/src/layers_empty_area_menu_tests.rs +++ b/crates/ui-egui/src/layers_empty_area_menu_tests.rs @@ -169,3 +169,16 @@ fn a_right_click_on_a_layer_row_still_opens_the_layer_menu() { assert!(h.query_by_label("Duplicate Layer…").is_some() || h.query_by_label("Duplicate Layer").is_some(), "the layer menu opened"); assert!(h.query_by_label("No Thumbnails").is_none(), "not the empty-area menu"); } + +/// A filter that matches no layer draws no row: the whole list is empty space, and its +/// right-click still opens the menu. +#[test] +fn with_no_row_shown_by_the_filter_the_right_click_still_opens_the_menu() { + let mut h = panel(true); + h.state_mut().ui.layer_filter_by = crate::layer_filter_ui::FilterBy::Name; + h.state_mut().ui.layer_filter_name = "no layer has this name".into(); + h.run_steps(4); + assert!(h.query_by_label("Layer 1").is_none(), "the filter hides every row"); + open_menu(&mut h); + assert!(menu_open(&h), "the empty list's right-click opens the menu"); +} diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index ae82d53714..0c2b6f8136 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -322,6 +322,14 @@ pub(super) fn layers(app: &mut PhotocraftApp, ui: &mut egui::Ui) { // A right-click there opens the thumbnail size and clipping choices (#11404). response.context_menu(|ui| super::layers_empty_menu::show(app, ui)); } + } else { + // No row drawn (a filter that matches no layer): the whole list is empty space, + // and its right-click opens the same menu. + let empty = Rect::from_min_max(pos2(ui.max_rect().left(), rows_bottom), pos2(ui.max_rect().right(), ui.clip_rect().bottom())); + if empty.height() > 0.0 && empty.width() > 0.0 { + let response = ui.interact(empty, ui.scope_id().with("layers-empty-space"), Sense::click()); + response.context_menu(|ui| super::layers_empty_menu::show(app, ui)); + } } // Tab / ⇧Tab in a rename: rename the layer below / above next, as in Photoshop. The // Background isn't renamed in place (double-clicking it converts it), so it ends there.