mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Project panel: Composition Settings… in a comp's right-click menu (fixes #1058)
First in the menu, as in After Effects, and for the right-clicked comp rather than the one in the viewer: the dialog keeps the comp it edits and OK passes it to comp.settings. comp.settings with only {"comp": id} opens the dialog on that comp.
This commit is contained in:
1 parent
470ea7a3af
commit
7304f4c672
5 files changed
+81
-2
No files matched your search
@@ -271,6 +271,13 @@ fn no_params(p: &Value) -> bool {
|
||||
p.as_object().is_none_or(|m| m.is_empty())
|
||||
}
|
||||
|
||||
/// `{"comp": id}` and nothing else: Composition Settings opens on that comp instead of
|
||||
/// applying no settings to it.
|
||||
fn only_comp(p: &Value) -> Option<effectcraft_engine::project::ItemId> {
|
||||
let m = p.as_object().filter(|m| m.len() == 1)?;
|
||||
m.get("comp")?.as_u64().map(effectcraft_engine::project::ItemId)
|
||||
}
|
||||
|
||||
/// Execute a UI or engine command by id.
|
||||
pub fn invoke(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, params: Value) -> Result<Value, String> {
|
||||
// Layer ▸ New ▸ Text (menu, shortcut, context menus) puts an insertion point in the centre of
|
||||
@@ -647,6 +654,9 @@ pub fn invoke(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, params: V
|
||||
"app.home" => app.ui.start_screen = !app.ui.start_screen,
|
||||
"app.newComp" | "comp.new" if no_params(¶ms) => crate::panels::dialogs::open_new_comp(app),
|
||||
"app.compSettings" | "comp.settings" if no_params(¶ms) => crate::panels::dialogs::open_comp_settings(app)?,
|
||||
"app.compSettings" | "comp.settings" if only_comp(¶ms).is_some() => {
|
||||
crate::panels::dialogs::open_comp_settings_for(app, only_comp(¶ms).ok_or("missing `comp`")?)?
|
||||
}
|
||||
"app.solidSettings" | "layer.newSolid" if no_params(¶ms) => crate::panels::dialogs::open_new_solid(app)?,
|
||||
"keys.velocity" if no_params(¶ms) => crate::panels::key_dialogs::open_velocity(app)?,
|
||||
"keys.interpolation" if no_params(¶ms) => crate::panels::key_dialogs::open_interpolation(app)?,
|
||||
|
||||
@@ -20,6 +20,9 @@ pub struct DialogState {
|
||||
/// from them, as in After Effects).
|
||||
pub last_new_comp: Option<CompDraft>,
|
||||
pub editing_existing: bool,
|
||||
/// The composition Composition Settings edits (the Project panel's row menu can pick one
|
||||
/// the viewer doesn't show).
|
||||
pub editing_comp: Option<ItemId>,
|
||||
pub solid_name: String,
|
||||
pub solid_color: [f32; 3],
|
||||
pub solid_size: [u32; 2],
|
||||
@@ -75,6 +78,11 @@ pub fn open_new_comp(app: &mut EffectcraftApp) {
|
||||
|
||||
pub fn open_comp_settings(app: &mut EffectcraftApp) -> Result<(), String> {
|
||||
let cid = app.session.active_comp_id().ok_or("no composition is open")?;
|
||||
open_comp_settings_for(app, cid)
|
||||
}
|
||||
|
||||
/// Composition Settings on `cid`, open in the viewer or not.
|
||||
pub fn open_comp_settings_for(app: &mut EffectcraftApp, cid: ItemId) -> Result<(), String> {
|
||||
let c = app.session.project.comp(cid).ok_or("no composition")?;
|
||||
app.dialog_state.comp = CompDraft {
|
||||
name: app.session.project.item(cid).map(|i| i.name.clone()).unwrap_or_default(),
|
||||
@@ -95,6 +103,7 @@ pub fn open_comp_settings(app: &mut EffectcraftApp) -> Result<(), String> {
|
||||
..Default::default()
|
||||
};
|
||||
app.dialog_state.editing_existing = true;
|
||||
app.dialog_state.editing_comp = Some(cid);
|
||||
app.dialog = Some(Dialog::CompSettings);
|
||||
Ok(())
|
||||
}
|
||||
@@ -362,7 +371,10 @@ fn comp_settings(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens, exis
|
||||
app.dialog_state.comp = d;
|
||||
match result {
|
||||
Some(serde_json::Value::Null) => app.dialog = None,
|
||||
Some(params) => {
|
||||
Some(mut params) => {
|
||||
if existing && let (Some(cid), Some(m)) = (app.dialog_state.editing_comp, params.as_object_mut()) {
|
||||
m.insert("comp".into(), json!(cid.0));
|
||||
}
|
||||
let r = if existing { app.session.execute("comp.settings", params) } else { app.session.execute("comp.new", params) };
|
||||
if !existing && r.is_ok() {
|
||||
app.dialog_state.last_new_comp = Some(app.dialog_state.comp.clone());
|
||||
|
||||
@@ -782,6 +782,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
let footage = matches!(it.kind, ItemKind::Footage(_));
|
||||
crate::widgets::context_menu(&resp, |ui| {
|
||||
// First on a comp, as in After Effects; for this comp, not the viewer's (#1058).
|
||||
if matches!(it.kind, ItemKind::Comp(_)) && ui.button(crate::i18n::tr("Composition Settings…")).clicked() {
|
||||
actions.push(("comp.settings".into(), json!({"comp": id.0})));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button(crate::i18n::tr("Delete")).clicked() {
|
||||
actions.push(("project.delete".into(), json!({"items": targets(app, *id, selected)})));
|
||||
ui.close();
|
||||
|
||||
@@ -214,3 +214,55 @@ fn a_short_project_panel_draws() {
|
||||
assert!(h.state().auto.find("project.empty").is_some(), "the Project panel drew at {height} points");
|
||||
}
|
||||
}
|
||||
|
||||
/// #1058: a comp's row menu offers Composition Settings…, as After Effects' does, for the comp
|
||||
/// right-clicked even when the viewer shows another one; OK applies to that comp. Footage has none.
|
||||
#[test]
|
||||
fn comp_context_menu_opens_that_comps_settings() {
|
||||
let (mut h, _, main, _) = harness();
|
||||
let s = &mut h.state_mut().session;
|
||||
let side = ItemId(s.execute("comp.new", json!({"name": "Side", "width": 640, "height": 360, "duration": 2})).unwrap()["comp"].as_u64().unwrap());
|
||||
s.execute("comp.open", json!({"comp": main.0})).unwrap();
|
||||
let footage = Footage { kind: FootageKind::Video, path: "clip.mp4".into(), width: 64, height: 36, has_video: true, ..Default::default() };
|
||||
let clip = std::sync::Arc::make_mut(&mut s.project).add_item("Clip", Default::default(), None, ItemKind::Footage(footage));
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().session.active_comp_id(), Some(main));
|
||||
let right_click = |h: &mut Harness<'_, EffectcraftApp>, p: Pos2| {
|
||||
h.input_mut().events.push(Event::PointerMoved(p));
|
||||
h.step();
|
||||
for pressed in [true, false] {
|
||||
h.input_mut().events.push(Event::PointerButton { pos: p, button: egui::PointerButton::Secondary, pressed, modifiers: Modifiers::NONE });
|
||||
h.step();
|
||||
}
|
||||
h.run_steps(2);
|
||||
};
|
||||
let at = center(&h, &format!("project.item.{}.name", clip.0));
|
||||
right_click(&mut h, at);
|
||||
assert!(h.query_by_label("Rename").is_some(), "the footage menu is open");
|
||||
assert!(h.query_by_label_contains("Composition Settings").is_none(), "footage isn't a comp");
|
||||
h.input_mut().events.push(Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE });
|
||||
h.run_steps(2);
|
||||
let at = center(&h, &format!("project.item.{}.name", side.0));
|
||||
right_click(&mut h, at);
|
||||
let entry = h.query_by_label_contains("Composition Settings").expect("in the comp's menu").rect();
|
||||
click_at(&mut h, entry.center());
|
||||
assert_eq!(h.state().dialog, Some(Dialog::CompSettings));
|
||||
assert_eq!(h.state().session.active_comp_id(), Some(main), "the viewer keeps its comp");
|
||||
// A new name, then OK: Side's own size goes back to Side under it. Main's values in the
|
||||
// dialog, or OK writing to the viewer's comp, would resize or rename the wrong one.
|
||||
click(&mut h, "dialog.comp.name");
|
||||
h.input_mut().events.push(Event::Key { key: egui::Key::A, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::COMMAND });
|
||||
h.input_mut().events.push(Event::Text("Side 2".into()));
|
||||
h.run_steps(2);
|
||||
let undo = h.state().session.history.undo.len();
|
||||
h.input_mut().events.push(Event::Key { key: egui::Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE });
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().dialog, None, "OK closed the dialog");
|
||||
assert_eq!(h.state().session.history.undo.len(), undo + 1, "OK applied the settings");
|
||||
let size = |h: &Harness<'_, EffectcraftApp>, c: ItemId| {
|
||||
let s = &h.state().session;
|
||||
(s.project.item(c).unwrap().name.clone(), s.project.comp(c).unwrap().width, s.project.comp(c).unwrap().height)
|
||||
};
|
||||
assert_eq!(size(&h, side), ("Side 2".into(), 640, 360), "the dialog showed and set the right-clicked comp");
|
||||
assert_eq!(size(&h, main), ("Main".into(), 320, 180), "the open comp is untouched");
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@ feel and platform differences, and the 26.x interface changes are not in yet.
|
||||
| Nudging and modifiers | Arrow nudge (1 px, Shift 10 px), Alt/Option duplicates and slips, Shift constrains | 75% | #290 nudging fixed; Linux multi-layer edits (#491) |
|
||||
| Numeric entry | Scrubbable values, typed entry with Tab / Shift+Tab, angle revolutions, Escape cancels, non-finite rejected | 85% | `ui_angles`, `ui_inline_edit` (#93, #141, #361); rename arrows on Linux (#488) |
|
||||
| Effect Controls | After Effects' twirl-downs, angle dial, point crosshair, eyedropper, curves/levels editors, effect menu on right-click | 70% | `ui_effect_controls`, `ui_fx_editors`; 26.5 rebuilt this panel with icon controls and effect colour labels, not matched |
|
||||
| Context menus | Layer, property, Project, Timeline empty area, work area, labels with colours | 80% | `ui_blank_context_menus`; #227, #290, #407 fixed |
|
||||
| Context menus | Layer, property, Project, Timeline empty area, work area, labels with colours | 80% | `ui_blank_context_menus`, `ui_project_rows`; #227, #290, #407 fixed; a comp's Project row menu has Composition Settings… (#1058) |
|
||||
| Keyboard shortcuts | After Effects' defaults; Keyboard Shortcuts editor that rebinds and saves sets | 75% | Shortcut editor in Settings; adding shortcuts fails on Linux (#467); requests for more (#428, #336); no shortcuts for scripts |
|
||||
| Drag and drop | Project → Timeline / viewer / New Comp / Render Queue; effects onto layers; files from the OS | 65% | `ui_drag_drop`; OS drops fail on Wayland (winit) and some Linux setups (#268); multi-file drops (#552, #351); crash dragging over the import dialog (#511) |
|
||||
| Text editing on canvas | Point and paragraph text, per-character styles, Character / Paragraph panels, OpenType and variable axes | 70% | `ui_text_edit`, `ui_font_search`; paragraph alignment inverted (#316); font search by typing (#583); toolbar font options (#550); the font menus' variable-font filter (26.3) landed (#741) |
|
||||
|
||||
Reference in new issue
Block a user