diff --git a/crates/ui-egui/src/layer_row_ui/tests.rs b/crates/ui-egui/src/layer_row_ui/tests.rs index 7b04e2cf2a..6a9f26454b 100644 --- a/crates/ui-egui/src/layer_row_ui/tests.rs +++ b/crates/ui-egui/src/layer_row_ui/tests.rs @@ -658,3 +658,67 @@ fn clicking_the_lock_icon_on_a_row_unlocks_the_layer() { assert!(doc.background().is_none(), "the Background became a normal layer"); assert!(!doc.layer(bg).unwrap().locks.any()); } + +#[test] +fn eye_column_edges_toggle_visibility_without_selecting_layer() { + // #8467: clicks at the far left or right edge of the eye column must toggle visibility + // rather than falling through to select the row. + let mut s = photocraft_engine::Session::new(); + s.execute("file.new", json!({"width": 64, "height": 48})).unwrap(); + let l1 = s.execute("layer.new.layer", json!({"name": "Top"})).unwrap()["layer"].as_u64().unwrap(); + let l0 = s.execute("layer.new.layer", json!({"name": "Bottom"})).unwrap()["layer"].as_u64().unwrap(); + s.execute("layer.select", json!({"layer": l0})).unwrap(); + + let mut h = harness(s, 1.0, "promedium", 290.0); + let visible = |h: &Harness<'_, PhotocraftApp>, id: u64| h.state().session.active().unwrap().doc.layer(photocraft_doc::LayerId(id)).unwrap().visible; + let selected = |h: &Harness<'_, PhotocraftApp>| h.state().session.active().unwrap().active_layer.unwrap().0; + + let row_for = |h: &Harness<'_, PhotocraftApp>, id: u64| recorded(&h.ctx).into_iter().find(|r| r.layer == id).unwrap().row; + + let top_row = row_for(&h, l1); + // Click at the far left edge of the eye column (left + 1.0, top + 1.0) + let left_edge = pos2(top_row.left() + 1.0, top_row.top() + 1.0); + click(&mut h, left_edge); + + assert!(!visible(&h, l1), "click on left edge of eye column should hide layer"); + assert_eq!(selected(&h), l0, "click on eye column must not select the layer"); + + // Click at the right edge of the eye column (left + 29.0, bottom - 1.0) + let right_edge = pos2(top_row.left() + 29.0, top_row.bottom() - 1.0); + click(&mut h, right_edge); + + assert!(visible(&h, l1), "click on right edge of eye column should show layer again"); + assert_eq!(selected(&h), l0, "click on eye column must not select the layer"); +} + +/// #1662: a hidden layer's eye box stays empty, as in Photoshop (only a visible layer paints its +/// eye), in every theme; the widened hit area (#8467) changes nothing that is drawn. +#[test] +fn a_hidden_layers_eye_box_paints_nothing() { + fn bounds(shape: &egui::Shape, out: &mut Vec) { + match shape { + egui::Shape::Vec(shapes) => shapes.iter().for_each(|s| bounds(s, out)), + s => out.push(s.visual_bounding_rect()), + } + } + for theme in ["promedium", "pro", "studio", "studiolight", "classic"] { + let mut s = photocraft_engine::Session::new(); + s.execute("file.new", json!({"width": 64, "height": 48})).unwrap(); + let hidden = s.execute("layer.new.layer", json!({"name": "Hidden"})).unwrap()["layer"].as_u64().unwrap(); + s.execute("layer.setProps", json!({"layer": hidden, "visible": false})).unwrap(); + let shown = s.execute("layer.new.layer", json!({"name": "Shown"})).unwrap()["layer"].as_u64().unwrap(); + let h = harness(s, 1.0, theme, 290.0); + let mut drawn = Vec::new(); + for clipped in &h.output().shapes { + bounds(&clipped.shape, &mut drawn); + } + let rows = recorded(&h.ctx); + let in_eye_box = |id: u64| { + let row = rows.iter().find(|r| r.layer == id).unwrap().row; + let cell = Rect::from_min_max(row.left_top(), pos2(row.left() + 30.0, row.bottom())).shrink(1.0); + drawn.iter().filter(|b| b.is_positive() && cell.contains_rect(**b)).count() + }; + assert!(in_eye_box(shown) > 0, "{theme}: a visible layer paints its eye"); + assert_eq!(in_eye_box(hidden), 0, "{theme}: a hidden layer's eye box is left empty"); + } +} diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index e9301523a1..e6b90e76bc 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -3143,11 +3143,10 @@ fn layer_row( painter.line_segment([rect.left_bottom(), rect.right_bottom()], Stroke::new(1.0, t.separator)); } let mut x = rect.left() + 6.0; - let eye = Rect::from_min_size(pos2(x, rect.center().y - 11.0), vec2(22.0, 22.0)); - // The eye takes drags too (so a drag starting on it never reorders the row): dragging down - // the eyes gives every row swept over the visibility the first eye toggled to. - let eye_resp = ui.interact(eye, ui.id().with(("eye", l.id.0)), Sense::click_and_drag()); - // A hidden layer's eye box is left empty (still clickable). + // The whole eye column takes drags too (so a drag starting on it never reorders the row): dragging down + // the eyes gives every row swept over the visibility the first eye toggled to (#8467). + let eye_resp = ui.interact(eye_cell, ui.id().with(("eye", l.id.0)), Sense::click_and_drag()); + // A hidden layer's eye box is left empty (still clickable; the whole column takes the click, #8467). if l.visible { // Centre the drawing in the column while preserving the visibility hit area. icons::paint(ui, eye_cell, "eye", 15.0, t.layer_label_icon(l.label));