mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
Viewer: keep hidden AV layers' controls visible (#1769)
This commit is contained in:
@@ -1011,9 +1011,12 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
painter.add(egui::Shape::closed_line(outline.into_iter().map(|p| map.to_screen(p)).collect(), Stroke::new(1.0, col)));
|
||||
}
|
||||
}
|
||||
// A layer whose Video switch is off has no box, handles or paths in the viewer (a hidden
|
||||
// null disappears from the comp, as in After Effects), even while it is selected.
|
||||
for l in comp.layers.iter().filter(|l| selected.contains(&l.id) && l.is_active_at(time) && l.switches.video) {
|
||||
// The eye hides a layer's pixels, not its controls: a selected AV layer keeps its box,
|
||||
// handles, anchor, motion path and mask/shape paths so it stays editable over the visible
|
||||
// artwork (#1769). Nulls, cameras and lights have no pixels and disappear from the viewer
|
||||
// when hidden (#1332/#1642). Picking still uses `selectable_layers`, so a hidden layer is
|
||||
// selected from the Timeline; its controls are interactive once selected.
|
||||
for l in comp.layers.iter().filter(|l| selected.contains(&l.id) && l.is_active_at(time) && (l.switches.video || l.source.is_av())) {
|
||||
let col = if label_handles { t.label(l.label) } else { t.accent };
|
||||
// Motion path of animated position (keyframes and spatial tangents are draggable).
|
||||
ov::motion_path(app, &painter, &map, &ectx, l, col, &mut key_hits, &mut motion_frames);
|
||||
|
||||
@@ -759,6 +759,74 @@ fn path_editing_hides_layer_bounds_and_keeps_corner_vertices_editable() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #1769: the eye hides a layer's pixels, not the controls of a selected AV layer: a hidden shape
|
||||
/// layer keeps its transform box, anchor and shape path, and its handles still scale it.
|
||||
#[test]
|
||||
fn hidden_shape_layer_keeps_its_controls_and_can_still_scale() {
|
||||
let mut h = harness();
|
||||
let (layer, path) = {
|
||||
let r = h
|
||||
.state_mut()
|
||||
.session
|
||||
.execute(
|
||||
"shape.newPath",
|
||||
json!({
|
||||
"vertices": [[200.0, 100.0], [400.0, 100.0], [400.0, 260.0], [200.0, 260.0]],
|
||||
"closed": true,
|
||||
"fill": false,
|
||||
"stroke": "#ffffff",
|
||||
"strokeWidth": 4
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
(r["layer"].as_u64().unwrap(), r["path"].as_u64().unwrap())
|
||||
};
|
||||
h.state_mut().session.execute("layer.select", json!({"layers": [layer]})).unwrap();
|
||||
h.state_mut().session.state.selected_vertices.clear();
|
||||
h.state_mut().session.execute("layer.setSwitch", json!({"layers": [layer], "switch": "video", "value": false})).unwrap();
|
||||
h.run_steps(2);
|
||||
let shown = h.state().session.active_comp().unwrap().layer(LayerId(layer)).unwrap().clone();
|
||||
assert!(!shown.switches.video);
|
||||
assert!(shown.source.is_av());
|
||||
assert_eq!(h.state().session.state.selected_layers, vec![LayerId(layer)]);
|
||||
assert!(h.state().auto.find(&format!("viewer.handle.{layer}.0")).is_some(), "a hidden shape keeps its transform box");
|
||||
assert!(h.state().auto.find(&format!("viewer.anchor.{layer}")).is_some(), "and its anchor");
|
||||
assert!(h.state().auto.find(&format!("viewer.shapePath.{path}.vertex.0")).is_some(), "and its shape path");
|
||||
let scale = |h: &Harness<'_, EffectcraftApp>| {
|
||||
h.state().session.active_comp().unwrap().layer(LayerId(layer)).unwrap().props.prop("transform/scale").unwrap().value.as_vec3()
|
||||
};
|
||||
let before = scale(&h);
|
||||
let from = rect(&h, &format!("viewer.handle.{layer}.5")).center();
|
||||
drag(&mut h, from, from + vec2(24.0, 0.0));
|
||||
assert_ne!(scale(&h), before, "a hidden layer's handle still scales it");
|
||||
}
|
||||
|
||||
/// #1769: a hidden masked layer keeps its mask path and vertices, and they remain editable.
|
||||
#[test]
|
||||
fn hidden_masked_layer_keeps_its_mask_path_editable() {
|
||||
let mut h = harness();
|
||||
let plate = layer_id(&h, "Plate");
|
||||
let corners = [[100.0, 60.0], [500.0, 60.0], [500.0, 300.0], [100.0, 300.0]];
|
||||
let mask = {
|
||||
let s = &mut h.state_mut().session;
|
||||
s.execute("layer.select", json!({"layers": [plate]})).unwrap();
|
||||
let m = s.execute("mask.new", json!({"layer": plate, "vertices": corners, "closed": true})).unwrap()["mask"].as_u64().unwrap();
|
||||
s.state.selected_vertices.clear();
|
||||
s.execute("layer.setSwitch", json!({"layers": [plate], "switch": "video", "value": false})).unwrap();
|
||||
m
|
||||
};
|
||||
h.run_steps(2);
|
||||
assert!(h.state().auto.find(&format!("viewer.handle.{plate}.0")).is_some(), "a hidden masked layer keeps its transform box");
|
||||
let vertex = format!("viewer.mask.{mask}.vertex.0");
|
||||
assert!(h.state().auto.find(&vertex).is_some(), "and its mask path");
|
||||
let before = mask_path(&h, "Plate");
|
||||
let from = rect(&h, &vertex).center();
|
||||
drag(&mut h, from, from + vec2(15.0, 12.0));
|
||||
let after = mask_path(&h, "Plate");
|
||||
assert_ne!(after.vertices[0], before.vertices[0], "the hidden layer's mask vertex can still be moved");
|
||||
assert_eq!(after.vertices[1..], before.vertices[1..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_tools_keep_bounds_on_layers_without_visible_paths() {
|
||||
let mut h = harness();
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ feel and platform differences, and the 26.x interface changes are not in yet.
|
||||
|---|---|---:|---|
|
||||
| Menu bar | After Effects' menus, order, submenus and labels; native on macOS; keyboard-operable in-window bar | 95% | 587 entries in `crates/engine/src/menus.rs`; Adobe-service entries omitted by design; `ui_menus` tests (#279) |
|
||||
| Panels and workspaces | Every After Effects panel; all standard workspaces; drag-to-dock, tabs, floating panels, maximise | 75% | `ui_tabs`, `ui_panels`; panels move into their own native windows on any screen (#486 open: no dragging tabs between those windows); #302, #301 and #320 (docking, stacked-panel headers, workspace persistence) are closed |
|
||||
| Timeline | Switches, modes, parent & link, twirl-downs, reveal shortcuts (U, UU, P, S, R, T, A, E, M, F, Alt+Shift…), pick whips, graph editor, work area, markers | 75% | `ui_keyframes`, `ui_timeline_*`; expand precomps in place (#601); #456, #457, #569 and #355 (Graph Editor jitter and buttons, negative rotation, bulk keyframing) are closed; Proportional Scrubbing (26.2: Ctrl+Alt / Cmd+Opt scrub of hot text spreads the change over the selected layers by selection order, #770) matched for layers selected as a whole, not yet for individually selected properties; every layer has the Video switch, nulls, cameras and lights included (a null with its eye off has no box or handles in the viewer and isn't picked there) |
|
||||
| Timeline | Switches, modes, parent & link, twirl-downs, reveal shortcuts (U, UU, P, S, R, T, A, E, M, F, Alt+Shift…), pick whips, graph editor, work area, markers | 75% | `ui_keyframes`, `ui_timeline_*`; expand precomps in place (#601); #456, #457, #569 and #355 (Graph Editor jitter and buttons, negative rotation, bulk keyframing) are closed; Proportional Scrubbing (26.2: Ctrl+Alt / Cmd+Opt scrub of hot text spreads the change over the selected layers by selection order, #770) matched for layers selected as a whole, not yet for individually selected properties; every layer has the Video switch, nulls, cameras and lights included (a hidden null, camera or light has no box or handles in the viewer and isn't picked there; other selected hidden layers keep their layer controls and paths, #1769) |
|
||||
| Composition viewer | Selection, handles with direction-aware cursors, rulers, guides, grids, snapping, channels, exposure, region of interest, snapshots, 3D views, Spacebar hand | 70% | `ui_viewer`, `ui_viewers`; Cmd/Ctrl +/− doesn't zoom (#570); anchor point grab on Linux (#492); #352 (snapping) is closed; no 3D transform gizmos (#392, #347); percentage guides (26.5) exist as View ▸ Add Percentage Guide and Convert Guides to Percentage / Pixels (a guide stores a percent of the comp, so it follows comp-size changes; behaviour inferred from the release note, not compared with After Effects); Copy Frame to Clipboard (26.3) is in the Composition menu and the viewer's context menu |
|
||||
| Tools panel | All After Effects tools; long-press flyouts; Tool Creates Shape / Mask; Fill / Stroke options | 80% | #213, #227 fixed; #553 (pen and vertex tools on motion paths) is closed |
|
||||
| Nudging and modifiers | Arrow nudge (1 px, Shift 10 px), Alt/Option duplicates and slips, Shift constrains | 75% | #290 nudging fixed; #491 (Linux multi-layer edits) is closed |
|
||||
|
||||
Reference in New Issue
Block a user