mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
Second default keys, also in menu lines
F2 Deselect All, Cmd+Shift+T Effect Controls, Cmd+Alt+Left / Right Go to Start / End, Cmd+Shift+/ Add to Render Queue. Effect Controls is a menu entry with bound parameters, which could hold one key: a menu line can now give further default keys after a comma (F3, Cmd+Shift+T); the menus show the first. MenuEntry gains alt_shortcuts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6f5bd0c272
commit
eec84a8095
@@ -878,7 +878,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
cmd!("edit.liftWorkArea", "Lift Work Area", ["Edit"], None, "{layers?}", has_comp, lift),
|
||||
cmd!("edit.extractWorkArea", "Extract Work Area", ["Edit"], None, "{layers?}", has_comp, extract),
|
||||
cmd!("edit.selectAll", "Select All", ["Edit"], Some("Cmd+A"), "{}", has_comp, select_all),
|
||||
cmd!("edit.deselectAll", "Deselect All", ["Edit"], Some("Cmd+Shift+A"), "{}", always_ok, deselect_all),
|
||||
cmd!("edit.deselectAll", "Deselect All", ["Edit"], ["Cmd+Shift+A", "F2"], "{}", always_ok, deselect_all),
|
||||
cmd!("edit.label", "Label", ["Edit", "Label"], None, "{label: Red|Yellow|Aqua|…, layers?, keys?, target?: layers}", always_ok, label),
|
||||
cmd!("edit.selectLabelGroup", "Select Label Group", ["Edit", "Label"], None, "{}", has_props_or_layers_or_items, select_label_group),
|
||||
cmd!("edit.purgeUndo", "Undo", ["Edit", "Purge"], None, "{}", always_ok, purge),
|
||||
|
||||
@@ -998,7 +998,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"renderQueue.add",
|
||||
"Add to Render Queue",
|
||||
["Composition"],
|
||||
Some("Cmd+M"),
|
||||
["Cmd+M", "Cmd+Shift+/"],
|
||||
"{comp?: id|name, template?: Render Settings template, format?: h264|hevc|av1|prores|uncompressed|webm|png|jpeg|tiff|exr|gif|wav|aiff, output?: path|template, log?: errorsOnly|plusSettings|plusPerFrameInfo, quality?: best|draft|1-100 (jpeg, webm), resolution?: full|half|third|quarter|scale, proxyUse?, effects?, solo?, guideLayers?, colorDepth?, frameBlending?, fieldRender?, pulldown?, motionBlur?, timeSpan?: workArea|comp|custom, start?: s, end?: s, duration?: s, frameRate?: fps|null, skipExisting?: bool, storageOverflow?: bool, channels?: rgb|rgba|alpha, color?, bitrate?: kbps, webmBitrate?, keyframeInterval?, proresProfile?, profile?, level?, rateControl?, webmCodec?, audioBitrate?, opusApplication?, crop?, resize?, includeProjectLink?, audio?: auto|on|off, sampleRate?, audioChannels?, audioFormat?, loop?: bool (values as in setRenderSettings / setOutputModule)}",
|
||||
can_add,
|
||||
add
|
||||
|
||||
@@ -178,8 +178,8 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
cmd!("time.forward10", "Forward 10 Frames", [], ["Shift+PageDown", "Cmd+Shift+ArrowRight"], "{}", has_comp, |s, _| step(s, &json!({"frames": 10}))),
|
||||
cmd!("time.back10", "Back 10 Frames", [], ["Shift+PageUp", "Cmd+Shift+ArrowLeft"], "{}", has_comp, |s, _| step(s, &json!({"frames": -10}))),
|
||||
cmd!("time.step", "Step Frames", [], None, "{frames}", has_comp, step),
|
||||
cmd!("time.start", "Go to Start", [], Some("Home"), "{}", has_comp, |s, _| go(s, &json!({"to": "start"}))),
|
||||
cmd!("time.end", "Go to End", [], Some("End"), "{}", has_comp, |s, _| go(s, &json!({"to": "end"}))),
|
||||
cmd!("time.start", "Go to Start", [], ["Home", "Cmd+Alt+ArrowLeft"], "{}", has_comp, |s, _| go(s, &json!({"to": "start"}))),
|
||||
cmd!("time.end", "Go to End", [], ["End", "Cmd+Alt+ArrowRight"], "{}", has_comp, |s, _| go(s, &json!({"to": "end"}))),
|
||||
cmd!("time.layerIn", "Go to Layer In Point", [], Some("I"), "{}", has_comp, |s, _| go(s, &json!({"to": "layerIn"}))),
|
||||
cmd!("time.layerOut", "Go to Layer Out Point", [], Some("O"), "{}", has_comp, |s, _| go(s, &json!({"to": "layerOut"}))),
|
||||
cmd!(
|
||||
|
||||
+21
-11
@@ -18,10 +18,11 @@
|
||||
//! ```
|
||||
//!
|
||||
//! An entry's shortcut is the command's default shortcut unless the line gives one (needed for
|
||||
//! entries with bound parameters). Adobe-service entries (Team Projects, Libraries, Bridge, Media
|
||||
//! Encoder, Behance, Creative Cloud…) are intentionally absent; the Essential Graphics workspace
|
||||
//! uses the Properties panel and the Essential Graphics panel, whose templates use EffectCraft's
|
||||
//! own open `.ectemplate` format.
|
||||
//! entries with bound parameters). A line can give further default keys after a comma
|
||||
//! (`| F3, Cmd+Shift+T`); the menus show the first. Adobe-service entries (Team Projects,
|
||||
//! Libraries, Bridge, Media Encoder, Behance, Creative Cloud…) are intentionally absent; the
|
||||
//! Essential Graphics workspace uses the Properties panel and the Essential Graphics panel, whose
|
||||
//! templates use EffectCraft's own open `.ectemplate` format.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
@@ -61,7 +62,7 @@ fn file_name(p: &str) -> String {
|
||||
}
|
||||
|
||||
fn dyn_entry(label: String, command: &str, params: Value) -> MenuEntry {
|
||||
MenuEntry { label, command: command.into(), params, shortcut: None }
|
||||
MenuEntry { label, command: command.into(), params, shortcut: None, alt_shortcuts: vec![] }
|
||||
}
|
||||
|
||||
/// The bindable holding shortcut `keys` in the active preset (its label).
|
||||
@@ -175,6 +176,9 @@ pub struct MenuEntry {
|
||||
#[serde(skip_serializing_if = "Value::is_null")]
|
||||
pub params: Value,
|
||||
pub shortcut: Option<String>,
|
||||
/// Further default shortcuts (the menus show `shortcut`).
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub alt_shortcuts: Vec<String>,
|
||||
}
|
||||
|
||||
impl MenuEntry {
|
||||
@@ -434,8 +438,9 @@ pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
|
||||
};
|
||||
// Entries with bound params state their own shortcut; plain entries use the command's.
|
||||
let inherited = if params.is_null() { commands::find(&command).and_then(|c| c.shortcut).map(str::to_string) } else { None };
|
||||
let shortcut = parts.next().map(|s| s.trim().to_string()).or(inherited);
|
||||
out.push(MenuNode::Item(MenuEntry { label, command, params, shortcut }));
|
||||
let mut keys = parts.next().into_iter().flat_map(|s| s.split(", ")).map(|s| s.trim().to_string());
|
||||
let shortcut = keys.next().or(inherited);
|
||||
out.push(MenuNode::Item(MenuEntry { label, command, params, shortcut, alt_shortcuts: keys.collect() }));
|
||||
}
|
||||
None => {
|
||||
let children = build(lines, i, depth + 1, mac)?;
|
||||
@@ -456,8 +461,13 @@ pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
|
||||
fn effect_categories() -> Vec<MenuNode> {
|
||||
let mut cats: Vec<(&str, Vec<MenuNode>)> = vec![];
|
||||
for e in crate::effects::all() {
|
||||
let entry =
|
||||
MenuNode::Item(MenuEntry { label: e.name.into(), command: "effect.apply".into(), params: serde_json::json!({"effect": e.id}), shortcut: None });
|
||||
let entry = MenuNode::Item(MenuEntry {
|
||||
label: e.name.into(),
|
||||
command: "effect.apply".into(),
|
||||
params: serde_json::json!({"effect": e.id}),
|
||||
shortcut: None,
|
||||
alt_shortcuts: vec![],
|
||||
});
|
||||
match cats.iter_mut().find(|(c, _)| *c == e.category) {
|
||||
Some((_, v)) => v.push(entry),
|
||||
None => cats.push((e.category, vec![entry])),
|
||||
@@ -954,7 +964,7 @@ Layer
|
||||
Pre-compose... | layer.precompose
|
||||
Scene Edit Detection... | layer.sceneEditDetection
|
||||
Effect
|
||||
Effect Controls | window.panel {"panel":"effectControls"} | F3
|
||||
Effect Controls | window.panel {"panel":"effectControls"} | F3, Cmd+Shift+T
|
||||
Last Effect | effect.applyLast
|
||||
Remove All | effect.removeAll
|
||||
---
|
||||
@@ -1203,7 +1213,7 @@ Window
|
||||
Wiggler | window.panel {"panel":"wiggler"}
|
||||
---
|
||||
Composition | window.panel {"panel":"composition"}
|
||||
Effect Controls | window.panel {"panel":"effectControls"} | F3
|
||||
Effect Controls | window.panel {"panel":"effectControls"} | F3, Cmd+Shift+T
|
||||
Flowchart | window.panel {"panel":"flowchart"} | Cmd+F11
|
||||
Footage | window.panel {"panel":"footage"}
|
||||
Layer | window.panel {"panel":"layer"}
|
||||
|
||||
@@ -90,7 +90,8 @@ pub fn bindables(ui: &[UiCommand]) -> Vec<Bindable> {
|
||||
let key = binding_key(&e.command, &e.params);
|
||||
// A plain entry also gets its command's further default keys.
|
||||
let alt = if e.params.is_null() { crate::commands::find(&e.command).map_or(&[][..], |c| c.alt_shortcuts) } else { &[] };
|
||||
let defaults = e.shortcut.as_deref().into_iter().chain(alt.iter().copied()).filter_map(normalize).collect();
|
||||
let defaults =
|
||||
e.shortcut.as_deref().into_iter().chain(e.alt_shortcuts.iter().map(String::as_str)).chain(alt.iter().copied()).filter_map(normalize).collect();
|
||||
push(
|
||||
Bindable {
|
||||
key,
|
||||
|
||||
@@ -661,7 +661,13 @@ fn dynamic_menus_and_window_viewer_labels() {
|
||||
assert_eq!(crate::menus::dynamic(&s, "history", &Default::default()).0.len(), s.history.undo.len());
|
||||
let a = solid(&mut s, "#ff0000");
|
||||
s.state.selected_layers = vec![LayerId(a)];
|
||||
let entry = |cmd: &str, panel: &str| crate::menus::MenuEntry { label: "x".into(), command: cmd.into(), params: json!({"panel": panel}), shortcut: None };
|
||||
let entry = |cmd: &str, panel: &str| crate::menus::MenuEntry {
|
||||
label: "x".into(),
|
||||
command: cmd.into(),
|
||||
params: json!({"panel": panel}),
|
||||
shortcut: None,
|
||||
alt_shortcuts: vec![],
|
||||
};
|
||||
assert_eq!(crate::menus::entry_label(&s, &entry("window.panel", "composition")), "Composition: Main");
|
||||
assert_eq!(crate::menus::entry_label(&s, &entry("window.panel", "timeline")), "Timeline: Main");
|
||||
assert!(crate::menus::entry_label(&s, &entry("window.panel", "layer")).starts_with("Layer: "));
|
||||
|
||||
@@ -430,6 +430,26 @@ fn default_keys_for_commands_that_had_none() {
|
||||
assert!(t.all_conflicts().is_empty(), "{:?}", t.all_conflicts());
|
||||
}
|
||||
|
||||
/// Second default keys: on the command, or after a comma in a menu line with bound parameters.
|
||||
#[test]
|
||||
fn second_default_keys() {
|
||||
let s = Session::default();
|
||||
let t = s.shortcuts();
|
||||
for (key, want) in [
|
||||
("edit.deselectAll", ["Cmd+Shift+A", "F2"]),
|
||||
("time.start", ["Home", "Cmd+Alt+ArrowLeft"]),
|
||||
("time.end", ["End", "Cmd+Alt+ArrowRight"]),
|
||||
("renderQueue.add", ["Cmd+M", "Cmd+Shift+/"]),
|
||||
(r#"window.panel {"panel":"effectControls"}"#, ["F3", "Cmd+Shift+T"]),
|
||||
] {
|
||||
assert_eq!(t.keys.get(key).map(Vec::as_slice), Some(want.map(String::from).as_slice()), "{key}");
|
||||
}
|
||||
// The menus keep showing the first key.
|
||||
assert_eq!(t.shortcut_of("window.panel", &json!({"panel": "effectControls"})), Some("F3"));
|
||||
assert_eq!(t.shortcut_of("edit.deselectAll", &json!(null)), Some("Cmd+Shift+A"));
|
||||
assert!(t.all_conflicts().is_empty(), "{:?}", t.all_conflicts());
|
||||
}
|
||||
|
||||
/// Cmd+Left / Cmd+Right Arrow step a frame like Page Up / Page Down (Shift: 10 frames).
|
||||
#[test]
|
||||
fn cmd_arrows_step_frames_like_page_keys() {
|
||||
|
||||
+36
-17
@@ -6591,7 +6591,8 @@ mod tests {
|
||||
assert_eq!(label(&app, "", source), translated);
|
||||
}
|
||||
assert_eq!(label(&app, "unknown.command", "Text"), "Text");
|
||||
let panel = MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel": "layer"}), shortcut: None };
|
||||
let panel =
|
||||
MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel": "layer"}), shortcut: None, alt_shortcuts: vec![] };
|
||||
assert_eq!(entry(&app, &panel, "Layer: File Čeština".into()), "Vrstva: File Čeština");
|
||||
assert_eq!(
|
||||
submenu(&app, "Assign Shortcut to Workspace", "Assign Shortcut to “File Čeština” Workspace".into()),
|
||||
@@ -6607,8 +6608,8 @@ mod tests {
|
||||
fn czech_undo_redo_keeps_history_labels_verbatim() {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
app.session.execute("prefs.set", json!({"key": "general.language", "value": "cs"})).unwrap();
|
||||
let undo = MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None };
|
||||
let redo = MenuEntry { label: "Redo".into(), command: "edit.redo".into(), params: json!({}), shortcut: None };
|
||||
let undo = MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] };
|
||||
let redo = MenuEntry { label: "Redo".into(), command: "edit.redo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] };
|
||||
assert_eq!(entry(&app, &undo, "Can't Undo".into()), "Není co vrátit");
|
||||
assert_eq!(entry(&app, &redo, "Can't Redo".into()), "Není co zopakovat");
|
||||
app.session.history.undo.push(("File Čeština".into(), app.session.project.clone()));
|
||||
@@ -6627,7 +6628,7 @@ mod tests {
|
||||
("Reveal in Explorer", "Zobrazit v Průzkumníku"),
|
||||
("Reveal in File Manager", "Zobrazit ve správci souborů"),
|
||||
] {
|
||||
let e = MenuEntry { label: source.into(), command: command.into(), params: json!({}), shortcut: None };
|
||||
let e = MenuEntry { label: source.into(), command: command.into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] };
|
||||
assert_eq!(label(&app, command, source), translated);
|
||||
assert_eq!(entry(&app, &e, source.into()), translated);
|
||||
assert_eq!(label(&app, "file.openRecent", source), source);
|
||||
@@ -6722,7 +6723,8 @@ mod tests {
|
||||
("Reveal in Explorer", "Показати у Провіднику"),
|
||||
("Reveal in File Manager", "Показати у файловому менеджері"),
|
||||
] {
|
||||
let e = MenuEntry { label: source.into(), command: command.into(), params: json!({"item": "My file.mp4"}), shortcut: None };
|
||||
let e =
|
||||
MenuEntry { label: source.into(), command: command.into(), params: json!({"item": "My file.mp4"}), shortcut: None, alt_shortcuts: vec![] };
|
||||
assert_eq!(label(&app, command, source), translated);
|
||||
assert_eq!(entry(&app, &e, source.into()), translated);
|
||||
assert_eq!(label(&app, "file.openRecent", source), source);
|
||||
@@ -6751,7 +6753,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None },
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Layer: File".into()
|
||||
),
|
||||
"レイヤー: File"
|
||||
@@ -6854,13 +6856,17 @@ mod tests {
|
||||
// Untranslated keys and dynamic labels stay verbatim.
|
||||
assert_eq!(label(&app, "unknown.command", "Text"), "Text");
|
||||
assert_eq!(
|
||||
entry(&app, &MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None }, "Undo Comp1".into()),
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Undo Comp1".into()
|
||||
),
|
||||
"无可撤销的操作"
|
||||
);
|
||||
assert_eq!(
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None },
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Layer: File".into()
|
||||
),
|
||||
"图层: File"
|
||||
@@ -6894,13 +6900,17 @@ mod tests {
|
||||
// Untranslated keys and dynamic labels stay verbatim.
|
||||
assert_eq!(label(&app, "unknown.command", "Text"), "Text");
|
||||
assert_eq!(
|
||||
entry(&app, &MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None }, "Undo Comp1".into()),
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Undo Comp1".into()
|
||||
),
|
||||
"沒有可復原的操作"
|
||||
);
|
||||
assert_eq!(
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None },
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Layer: File".into()
|
||||
),
|
||||
"圖層: File"
|
||||
@@ -6943,7 +6953,8 @@ mod tests {
|
||||
assert_eq!(label(&app, "layer.addTextAnimator", "Tracking"), "Міжлітерний інтервал");
|
||||
assert_eq!(label(&app, "edit.purgeUndo", "Undo"), "Історія скасувань");
|
||||
assert_eq!(label(&app, "unknown.command", "Text"), "Text");
|
||||
let panel = MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None };
|
||||
let panel =
|
||||
MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None, alt_shortcuts: vec![] };
|
||||
assert_eq!(entry(&app, &panel, "Layer: File".into()), "Шар: File");
|
||||
assert_eq!(
|
||||
submenu(&app, "Assign Shortcut to Workspace", "Assign Shortcut to “File Україна” Workspace".into()),
|
||||
@@ -6958,8 +6969,8 @@ mod tests {
|
||||
fn ukrainian_undo_redo_keeps_history_labels_verbatim() {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
app.session.execute("prefs.set", json!({"key":"general.language", "value":"uk"})).unwrap();
|
||||
let undo = MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None };
|
||||
let redo = MenuEntry { label: "Redo".into(), command: "edit.redo".into(), params: json!({}), shortcut: None };
|
||||
let undo = MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] };
|
||||
let redo = MenuEntry { label: "Redo".into(), command: "edit.redo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] };
|
||||
assert_eq!(entry(&app, &undo, "Can't Undo".into()), "Немає дій для скасування");
|
||||
assert_eq!(entry(&app, &redo, "Can't Redo".into()), "Немає дій для повторення");
|
||||
app.session.history.undo.push(("File Україна".into(), app.session.project.clone()));
|
||||
@@ -6995,13 +7006,17 @@ mod tests {
|
||||
// Untranslated keys and dynamic labels stay verbatim.
|
||||
assert_eq!(label(&app, "unknown.command", "Text"), "Text");
|
||||
assert_eq!(
|
||||
entry(&app, &MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None }, "Undo Comp1".into()),
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Undo Comp1".into()
|
||||
),
|
||||
"No se puede deshacer"
|
||||
);
|
||||
assert_eq!(
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None },
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Layer: File".into()
|
||||
),
|
||||
"Capa: File"
|
||||
@@ -7036,13 +7051,17 @@ mod tests {
|
||||
// Untranslated keys and dynamic labels stay verbatim.
|
||||
assert_eq!(label(&app, "unknown.command", "Text"), "Text");
|
||||
assert_eq!(
|
||||
entry(&app, &MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None }, "Undo Comp1".into()),
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Undo".into(), command: "edit.undo".into(), params: json!({}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Undo Comp1".into()
|
||||
),
|
||||
"Não há nada para desfazer"
|
||||
);
|
||||
assert_eq!(
|
||||
entry(
|
||||
&app,
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None },
|
||||
&MenuEntry { label: "Layer".into(), command: "window.panel".into(), params: json!({"panel":"layer"}), shortcut: None, alt_shortcuts: vec![] },
|
||||
"Layer: File".into()
|
||||
),
|
||||
"Camada: File"
|
||||
|
||||
Reference in New Issue
Block a user