fix(ui-egui): a click on the bottom layer's Smart Filters selects them again (#8764) (#8974)

* fix(ui-egui): a click on the bottom layer's Smart Filters selects them again (#8764)

The empty space below the last Layers panel row deselects every layer on a
click (#8100). It started under the last layer's own row, so it covered that
layer's effect and Smart Filter rows and, registered after them, took their
clicks: on the bottom smart object (a new document's Layer 0), clicking a
filter deselected everything instead of selecting it.

The click area now starts under everything drawn for the last layer. The
drop zone below it (#1721) is unchanged.

* Contributors: credit drunkeninja42 as Kushagra Sharma
This commit is contained in:
Kushagra Sharma
2026-10-23 17:38:02 -04:00
committed by GitHub
parent 8d1926433f
commit 7e3594b822
3 changed files with 32 additions and 2 deletions
+3
View File
@@ -251,3 +251,6 @@ sync_github_name = true
[people.benandez]
real_name = "Martin Ball"
[people.drunkeninja42]
real_name = "Kushagra Sharma"
+7 -2
View File
@@ -2838,6 +2838,9 @@ fn layers(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
crate::smart_ui::filter_rows(app, ui, &doc, l, depth, &mut actions);
}
}
// The bottom of everything drawn: the last layer's effect and Smart Filter rows sit
// under its own row (#8764).
let rows_bottom = ui.min_rect().bottom();
// #1721: the list takes drops everywhere, not only on a row (Photoshop): the space
// between two rows lands above the lower one, the empty space below the bottom-most
// row below it. Both copy with ⌥ held, move without.
@@ -2848,8 +2851,10 @@ fn layers(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
if let Some(&(last, last_rect)) = drawn.last() {
let below = Rect::from_min_max(pos2(last_rect.left(), last_rect.bottom()), pos2(last_rect.right(), ui.clip_rect().bottom()));
layer_drop_zone(app, &ctx, ui, below, (last, last_rect), "below", &mut actions);
// A click on that empty space deselects every layer, as in Photoshop (#8100).
if below.height() > 0.0 && ui.interact(below, ui.id().with("layers-empty-space"), Sense::click()).clicked() && !selection.is_empty() {
// A click on that empty space deselects every layer, as in Photoshop (#8100). It
// starts under the last layer's sub-rows, which take their own clicks.
let empty = Rect::from_min_max(pos2(below.left(), rows_bottom), below.max);
if empty.height() > 0.0 && ui.interact(empty, ui.id().with("layers-empty-space"), Sense::click()).clicked() && !selection.is_empty() {
actions.push(("select.deselectLayers".into(), json!({})));
}
}
@@ -317,3 +317,25 @@ fn with_the_layers_panel_closed_delete_leaves_the_filter() {
h.run_steps(4);
assert!(h.state().session.active().unwrap().doc.layer(LayerId(a)).is_none(), "Delete reached Smart A itself");
}
/// #8764: the bottom layer's filter rows sit where the empty space below the last row begins, so
/// that space's deselect-all click (#8100) must start under them: a click on a filter of a new
/// document's only layer, converted for Smart Filters, selects the filter.
#[test]
fn a_click_selects_a_filter_of_the_bottom_layer() {
let mut s = photocraft_engine::Session::new();
s.execute("file.new", json!({"width": 16, "height": 16})).unwrap();
let id = s.execute("filter.convertForSmartFilters", json!({})).unwrap()["layer"].as_u64().unwrap();
s.execute("filter.blur.gaussianBlur", json!({"radius": 1})).unwrap();
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 900.0)).with_max_steps(64).with_step_dt(1.0 / 60.0).build_eframe(|cc| {
PhotocraftApp::setup_context(&cc.egui_ctx, Default::default());
PhotocraftApp::new(s, crate::Services::default())
});
h.run_steps(4);
let blur = at(&h, "Gaussian Blur");
click_at(&mut h, blur);
let st = h.state().session.active().unwrap();
assert_eq!(st.active_layer, Some(LayerId(id)), "the smart object stays active");
assert_eq!(st.selected_layers(), [LayerId(id)], "the click did not deselect the layers");
assert!(selected(&h, "Gaussian Blur"));
}