M3.13: menu bar: Group/Ungroup Shapes in the Layer menu, Window ▸ Learn, flowchart shortcuts, Center In View shortcut, Reveal in Explorer on Windows

- Group Shapes (Ctrl+G) / Ungroup Shapes (Ctrl+Shift+G) sit in the Layer menu itself, as in
  After Effects (they were under Mask and Shape Path).
- Window ▸ Learn opens the Home screen's Learn tab.
- Composition ▸ Composition Flowchart is Ctrl+Shift+F11 and Window ▸ Flowchart Ctrl+F11 (the
  flowchart had the panel's shortcut and the panel none).
- Layer ▸ Transform ▸ Center In View shows Ctrl+Home.
- "Reveal in Finder" reads "Reveal in Explorer" on Windows and "Reveal in File Manager" on
  Linux (`menus::platform_label`).
- Docs: parity (View ▸ New Viewer noted as not yet).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-04 23:58:00 +07:00
1 parent a34d0c421b
commit 6fc39eb133
5 files changed
+55 -19

No files matched your search

+1 -1
View File
@@ -85,7 +85,7 @@ pub fn specs() -> Vec<CommandSpec> {
fe!("window.saveWorkspaceAs", "Save as New Workspace...", ["Window", "Workspace"], None, "{name}", always),
fe!("window.editWorkspaces", "Edit Workspaces...", ["Window", "Workspace"], None, "{name, rename?: new name, delete?: bool}", always),
fe!("window.resetWorkspace", "Reset to Saved Layout", ["Window", "Workspace"], None, "{}", always),
fe!("comp.flowchart", "Composition Flowchart", ["Composition"], Some("Cmd+F11"), "{}", always),
fe!("comp.flowchart", "Composition Flowchart", ["Composition"], Some("Cmd+Shift+F11"), "{}", always),
fe!("comp.miniFlowchart", "Composition Mini-Flowchart", ["Composition"], Some("Tab"), "{}", has_comp),
fe!("layer.openLayer", "Open Layer", ["Layer"], None, "{layer?}", has_layers),
fe!("effect.manage", "Manage Effects...", ["Effect"], None, "{}", always),
+2 -10
View File
@@ -450,16 +450,8 @@ pub fn specs() -> Vec<CommandSpec> {
vec![
cmd!("path.rotoBezier", "RotoBezier", ["Layer", "Mask and Shape Path"], None, "{layer?, mask?, value?}", has_layers, roto_bezier),
cmd!("path.convertToBezier", "Convert To Bezier Path", ["Layer", "Mask and Shape Path"], None, "{layer?, mask?}", has_layers, convert_to_bezier),
cmd!("path.groupShapes", "Group Shapes", ["Layer", "Mask and Shape Path"], Some("Cmd+G"), "{layer?, items?: [uid]}", has_shape_items, group_shapes),
cmd!(
"path.ungroupShapes",
"Ungroup Shapes",
["Layer", "Mask and Shape Path"],
Some("Cmd+Shift+G"),
"{layer?, items?: [group uid]}",
has_shape_items,
ungroup_shapes
),
cmd!("path.groupShapes", "Group Shapes", ["Layer"], Some("Cmd+G"), "{layer?, items?: [uid]}", has_shape_items, group_shapes),
cmd!("path.ungroupShapes", "Ungroup Shapes", ["Layer"], Some("Cmd+Shift+G"), "{layer?, items?: [group uid]}", has_shape_items, ungroup_shapes),
cmd!(
"path.setFirstVertex",
"Set First Vertex",
+18 -8
View File
@@ -344,6 +344,15 @@ pub fn checked(s: &Session, command: &str, params: &Value) -> Option<bool> {
}
}
/// Labels that name the platform's own tools: "Reveal in Finder" is "Reveal in Explorer" on
/// Windows (as in After Effects) and "Reveal in File Manager" elsewhere.
fn platform_label(label: &str, mac: bool) -> String {
match label.strip_prefix("Reveal in Finder") {
Some(rest) if !mac => format!("Reveal in {}{rest}", if cfg!(target_os = "windows") { "Explorer" } else { "File Manager" }),
_ => label.to_string(),
}
}
/// Parse the tree text. `mac` selects `[mac]` / `[!mac]` lines.
pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
struct Line<'a> {
@@ -378,7 +387,7 @@ pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
}
lines.push(Line { depth, body });
}
fn build(lines: &[Line], i: &mut usize, depth: usize) -> Result<Vec<MenuNode>, String> {
fn build(lines: &[Line], i: &mut usize, depth: usize, mac: bool) -> Result<Vec<MenuNode>, String> {
let mut out = vec![];
while *i < lines.len() {
let l = &lines[*i];
@@ -402,7 +411,7 @@ pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
continue;
}
let mut parts = l.body.split(" | ");
let label = parts.next().unwrap_or_default().trim().to_string();
let label = platform_label(parts.next().unwrap_or_default().trim(), mac);
match parts.next() {
Some(cmd) => {
let cmd = cmd.trim();
@@ -416,7 +425,7 @@ pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
out.push(MenuNode::Item(MenuEntry { label, command, params, shortcut }));
}
None => {
let children = build(lines, i, depth + 1)?;
let children = build(lines, i, depth + 1, mac)?;
if children.is_empty() {
return Err(format!("`{label}` has neither a command nor children"));
}
@@ -427,7 +436,7 @@ pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
Ok(out)
}
let mut i = 0;
build(&lines, &mut i, 0)
build(&lines, &mut i, 0, mac)
}
/// Effect ▸ category submenus from the effect registry.
@@ -744,8 +753,6 @@ Layer
RotoBezier | path.rotoBezier
Closed | mask.setClosed
Convert To Bezier Path | path.convertToBezier
Group Shapes | path.groupShapes
Ungroup Shapes | path.ungroupShapes
Set First Vertex | path.setFirstVertex
Free Transform Points | path.freeTransform
Quality
@@ -782,7 +789,7 @@ Layer
---
Flip Horizontal | layer.transform {"op":"flipH"}
Flip Vertical | layer.transform {"op":"flipV"}
Center In View | layer.transform {"op":"center"}
Center In View | layer.transform {"op":"center"} | Cmd+Home
Center Anchor Point in Layer Content | layer.centerAnchor
Fit to Comp | layer.transform {"op":"fit"} | Cmd+Alt+F
Fit to Comp Width | layer.transform {"op":"fitWidth"} | Cmd+Alt+Shift+H
@@ -883,6 +890,8 @@ Layer
Color Overlay | layer.style.colorOverlay
Gradient Overlay | layer.style.gradientOverlay
Stroke | layer.style.stroke
Group Shapes | path.groupShapes
Ungroup Shapes | path.ungroupShapes
---
Arrange
Bring Layer to Front | layer.arrange {"to":"front"} | Cmd+Shift+]
@@ -1151,6 +1160,7 @@ Window
Effects & Presets | window.panel {"panel":"effectsPresets"} | Cmd+5
Essential Graphics | window.panel {"panel":"essentialGraphics"}
Info | window.panel {"panel":"info"} | Cmd+2
Learn | help.inAppTutorials
Lumetri Scopes | window.panel {"panel":"lumetriScopes"}
Mask Interpolation | window.panel {"panel":"maskInterpolation"}
Media Browser | window.panel {"panel":"mediaBrowser"}
@@ -1169,7 +1179,7 @@ Window
---
Composition | window.panel {"panel":"composition"}
Effect Controls | window.panel {"panel":"effectControls"} | F3
Flowchart | window.panel {"panel":"flowchart"}
Flowchart | window.panel {"panel":"flowchart"} | Cmd+F11
Footage | window.panel {"panel":"footage"}
Layer | window.panel {"panel":"layer"}
Project | window.panel {"panel":"project"} | Cmd+0
+24
View File
@@ -155,3 +155,27 @@ fn parse_rejects_bad_trees() {
assert!(parse("File\n Odd | file.save\n", true).is_err());
assert!(parse("File\n Bad | file.save {nope}\n", true).is_err());
}
#[test]
fn m3_13_entries_follow_after_effects() {
let find = |mac: bool, path: &[&str], label: &str| platform_entries(mac).into_iter().find(|(p, e)| p == path && e.label == label).map(|(_, e)| e);
// Group / Ungroup Shapes sit in the Layer menu itself.
for mac in [true, false] {
assert_eq!(find(mac, &["Layer"], "Group Shapes").unwrap().shortcut.as_deref(), Some("Cmd+G"));
assert_eq!(find(mac, &["Layer"], "Ungroup Shapes").unwrap().shortcut.as_deref(), Some("Cmd+Shift+G"));
assert!(find(mac, &["Layer", "Mask and Shape Path"], "Group Shapes").is_none());
// Composition Flowchart Ctrl+Shift+F11, Window ▸ Flowchart Ctrl+F11; Window ▸ Learn.
assert_eq!(find(mac, &["Composition"], "Composition Flowchart").unwrap().shortcut.as_deref(), Some("Cmd+Shift+F11"));
assert_eq!(find(mac, &["Window"], "Flowchart").unwrap().shortcut.as_deref(), Some("Cmd+F11"));
assert_eq!(find(mac, &["Window"], "Learn").unwrap().command, "help.inAppTutorials");
assert_eq!(find(mac, &["Layer", "Transform"], "Center In View").unwrap().shortcut.as_deref(), Some("Cmd+Home"));
}
// "Reveal in Finder" names the platform's file browser.
assert!(find(true, &["File"], "Reveal in Finder").is_some());
let other = if cfg!(target_os = "windows") { "Reveal in Explorer" } else { "Reveal in File Manager" };
for path in [&["File"][..], &["Layer"], &["Layer", "Reveal"]] {
assert!(find(false, path, other).is_some(), "{path:?}");
assert!(find(false, path, "Reveal in Finder").is_none(), "{path:?}");
}
assert_eq!(platform_label("Reveal Layer Source in Project", false), "Reveal Layer Source in Project");
}
+10
View File
@@ -383,6 +383,16 @@ Browser in the web app (landed in M13.10: browser storage and File System Access
wrong signatures and versions, fuel exhaustion, traps and bit-identical output; script
`Socket` validates ports, and ScriptUI resource strings accept trailing array commas.
## Update: M3.13 menu bar
Group Shapes (Ctrl+G) and Ungroup Shapes (Ctrl+Shift+G) moved to the Layer menu itself, where
After Effects has them; Window ▸ Learn opens the Home screen's Learn tab; Composition ▸
Composition Flowchart is Ctrl+Shift+F11 and Window ▸ Flowchart Ctrl+F11 (the flowchart had
Window's shortcut); Layer ▸ Transform ▸ Center In View shows Ctrl+Home; "Reveal in Finder" reads
"Reveal in Explorer" on Windows (and "Reveal in File Manager" on Linux). Not yet: View ▸ New
Viewer (several unlocked Composition viewers at once; View ▸ Split with New Locked Viewer adds
the one locked viewer the interface has).
## Update: M3.12 Pre-compose and New Comp from Selection
- **Pre-compose** makes the new composition with the original's settings (pixel aspect,