#1220: Hide layer bounds during path editing

This commit is contained in:
Jannik Wappler
2026-10-13 15:38:06 +03:00
parent 549e9d8251
commit dbc595b0d6
4 changed files with 118 additions and 2 deletions
+8
View File
@@ -1028,6 +1028,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
}
}
// Path editing keeps vertices and tangents visible without the layer's transform
// box competing with them (#1220). The path's own free-transform box is separate.
let editing_path = paths.iter().any(|p| {
p.layer == l.id && (app.ui.tool.is_pen() || (app.ui.tool == Tool::Selection && sel_vertices.iter().any(|v| v.layer == l.id && v.mask == p.uid)))
});
if editing_path {
continue;
}
let Some((m, q, _)) = layer_quad(&ectx, l) else {
// A 3D layer reaching past the camera: the part of its box in front of it.
let outline = layer_outline(&ectx, l);
+97
View File
@@ -677,6 +677,103 @@ fn fill_options_change_the_selected_shape_layer() {
}
}
/// #1220: path tools hide the layer transform box, while path vertices remain editable.
#[test]
fn path_editing_hides_layer_bounds_and_keeps_corner_vertices_editable() {
for mask in [false, true] {
let mut h = harness();
let vertices = [[200.0, 100.0], [400.0, 100.0], [400.0, 260.0], [200.0, 260.0]];
let (layer, uid, kind) = if mask {
let layer = layer_id(&h, "Plate");
h.state_mut().session.execute("layer.select", json!({"layers": [layer]})).unwrap();
let r = h
.state_mut()
.session
.execute("mask.new", json!({"layer": layer, "vertices": vertices, "outTangents": [[40, 0], [0, 0], [0, 0], [0, 0]], "closed": true}))
.unwrap();
(layer, r["mask"].as_u64().unwrap(), "mask")
} else {
let r = h.state_mut().session.execute("shape.newPath", json!({"vertices": vertices, "outTangents": [[40, 0], [0, 0], [0, 0], [0, 0]], "closed": true, "fill": false, "stroke": "#ffffff", "strokeWidth": 4})).unwrap();
(r["layer"].as_u64().unwrap(), r["path"].as_u64().unwrap(), "shapePath")
};
h.state_mut().session.state.selected_vertices.clear();
h.run_steps(2);
let handle = format!("viewer.handle.{layer}.0");
assert!(h.state().auto.find(&handle).is_some(), "ordinary layer selection has transform handles (mask={mask})");
for tool in [Tool::Pen, Tool::PenAdd, Tool::PenDelete, Tool::PenConvert, Tool::MaskFeather] {
h.state_mut().ui.tool = tool;
h.run_steps(2);
assert!(h.state().auto.find(&handle).is_none(), "{tool:?}: hide the layer box");
assert!(h.state().auto.find(&format!("viewer.{kind}.{uid}.vertex.0")).is_some(), "{tool:?}: keep the path point");
}
h.state_mut().ui.tool = Tool::Selection;
h.run_steps(2);
let vertex = format!("viewer.{kind}.{uid}.vertex.0");
let from = rect(&h, &vertex).center();
click(&mut h, from);
h.run_steps(2); // The automation registry also keeps the preceding frame's elements.
assert!(h.state().auto.find(&handle).is_none(), "selecting a vertex starts path editing (mask={mask})");
let position = h.state().session.active_comp().unwrap().layer(LayerId(layer)).unwrap().props.prop("transform/position").unwrap().value.clone();
let path = |h: &Harness<'_, EffectcraftApp>| {
h.state()
.session
.active_comp()
.unwrap()
.layer(LayerId(layer))
.unwrap()
.props
.find_group(uid)
.unwrap()
.get("path")
.unwrap()
.value
.as_path()
.unwrap()
.clone()
};
let before = path(&h);
drag(&mut h, from, from + vec2(15.0, 12.0));
assert_ne!(path(&h).vertices[0], before.vertices[0], "the corner vertex can still be moved");
assert_eq!(path(&h).vertices[1..], before.vertices[1..]);
assert_eq!(h.state().session.active_comp().unwrap().layer(LayerId(layer)).unwrap().props.prop("transform/position").unwrap().value, position);
h.state_mut().session.execute("edit.undo", json!({})).unwrap();
h.run_steps(2);
assert_eq!(path(&h), before, "moving the vertex undoes in one step");
let z = rect(&h, "viewer.comp").width() / 640.0;
let tangent = rect(&h, &vertex).center() + vec2(40.0 * z, 0.0);
drag(&mut h, tangent, tangent + vec2(0.0, 20.0));
assert_ne!(path(&h).out_tangents[0], before.out_tangents[0], "the Bézier handle is still editable");
assert_eq!(path(&h).vertices, before.vertices, "a tangent drag does not move the path points");
h.state_mut().session.execute("edit.undo", json!({})).unwrap();
h.run_steps(2);
assert_eq!(path(&h), before, "the tangent drag undoes in one step");
h.state_mut().ui.tool = Tool::Rotate;
h.run_steps(2);
assert!(h.state().auto.find(&handle).is_some(), "a layer transform tool restores the box");
h.state_mut().ui.tool = Tool::Selection;
h.state_mut().session.state.selected_vertices.clear();
h.run_steps(2);
assert!(h.state().auto.find(&handle).is_some(), "ending vertex selection restores the box");
}
}
#[test]
fn path_tools_keep_bounds_on_layers_without_visible_paths() {
let mut h = harness();
let layer = layer_id(&h, "Box");
h.state_mut().session.execute("layer.select", json!({"layers": [layer]})).unwrap();
h.state_mut().ui.tool = Tool::Pen;
h.run_steps(2);
let handle = format!("viewer.handle.{layer}.0");
assert!(h.state().auto.find(&handle).is_some(), "a layer without a path keeps its box");
h.state_mut().session.execute("mask.new", json!({"layer": layer, "vertices": [[0, 0], [80, 0], [80, 80]], "closed": true})).unwrap();
h.run_steps(2);
assert!(h.state().auto.find(&handle).is_none());
h.state_mut().ui.viewer.show_masks = false;
h.run_steps(2);
assert!(h.state().auto.find(&handle).is_some(), "hidden paths do not suppress the layer controls");
}
#[test]
fn shape_pen_draws_a_closed_shape_layer() {
let mut h = harness();
+5 -1
View File
@@ -1,6 +1,6 @@
# Where EffectCraft falls short
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (issue, PR and catalog references gap 21: percentage guides and effect colour labels in; earlier change: re-audited against `upstream/main` 470ea7a3; ranked gap list from the 10 October re-measure otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (path-editing layer bounds, #1220; issue, PR and catalog references gap 21: percentage guides and effect colour labels in; earlier change: re-audited against `upstream/main` 470ea7a3; ranked gap list from the 10 October re-measure otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
An honest assessment of how far EffectCraft is from being a real replacement for After Effects,
and the work that closes the gap. This document is meant for contributors and agents choosing
@@ -114,6 +114,9 @@ The most important missing piece: it turns every other estimate here into a meas
- Run the headless snapshot and control-channel checks on Linux (X11 and Wayland) and Windows, not
only macOS. Interactions that only fail with real input (docking drags, viewer pan, drag-and-drop
import, inline rename) need scripted input tests.
- Path editing (#1220): selected shape and mask paths suppress the layer transform box while
their points are edited. `ui_viewer` regressions cover Pen-family tools, Selection vertex
dragging and undo, restoration of layer controls, and layers without visible paths.
- Regression evidence for [#66](https://github.com/storytold/effectcraft/issues/66): scripted
pointer tests in `ui_viewer` and `ui_text_edit`, checked headlessly on Linux, cover viewer clicks,
marquee selection and text picking when layers are hidden or excluded by solo. They also check
@@ -322,6 +325,7 @@ Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the vie
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | patch | G2: layer bounds no longer compete with editable shape/mask path points (#1220) |
| 2026-10-12 | minor | Audited against `upstream/main` 470ea7a3: open-issue count and crash list, closed issues removed from rows 1, 3, 5, 6, 8–12, 15–17, 22–24, G8 and G9 (#759 `#include`), localization catalogs recounted (≈ 12% to ≈ 18%); rows 7, 14 and 21 left to open PRs #1102, #792 and #1098 |
| 2026-10-10 | minor | Alpha gate checked: no alpha blockers |
| 2026-10-12 | minor | Gap 21: percentage guides and effect colour labels added (#1095) |
+8 -1
View File
@@ -1,6 +1,6 @@
# UI parity
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (closed issue references re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (path-editing layer bounds, #1220; closed issue references re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026
How closely EffectCraft's interaction matches After Effects: tools, handles, snapping, nudging,
modifier keys, shortcuts, drag behaviour, numeric entry, panels, context menus and feel. Evidence
@@ -33,6 +33,12 @@ feel and platform differences, and the 26.x interface changes are not in yet.
| Accessibility | AccessKit enabled; focus labels tested; Project panel keyboard navigation and accessible names (#776) | 20% | No screen-reader pass or keyboard-navigation audit of the other panels; no contrast check |
| UI scale | Settings ▸ Appearance ▸ UI Scale 75–200% | beyond | After Effects follows the system's scaling (#284) |
Path editing (#1220): the Composition viewer suppresses a selected layer’s transform box while
using a Pen-family tool on a visible path, or while selecting its vertices with the Selection
tool. Path points, Bézier handles and the path’s free-transform controls remain available.
Layer transform tools and ordinary layer selection restore the box. Headless `ui_viewer` tests
cover shape paths and masks, corner-vertex dragging and undo, and layers without visible paths.
## Estimate
**≈ 60–120 h**: the open interaction issues above (25–45 h, about 0.2–0.5 h each with a scripted
@@ -44,6 +50,7 @@ Effects running; most of the rest parallelises.
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | patch | Layer bounds are suppressed during path-point editing (#1220); shape/mask interaction regressions |
| 2026-10-12 | minor | Closed issues (#302, #320, #491, #511, #553 and others) marked as closed instead of open; panels in their own windows (#486) |
| 2026-10-12 | minor | Percentage guides and effect colour labels (26.5) added; rows updated |
| 2026-10-10 | major | First UI parity checklist |