Project: opening a comp brings up its Composition panel (#90)
Opening a comp (Project double-click, Open Composition, a Timeline tab) now reopens the Composition panel if it was closed and brings it (and the Timeline) to the front, as in After Effects. A closed Composition panel also reopens in the centre (with the Layer panel or other viewers, else above the Timeline) instead of beside Effects & Presets, which also fixes Window > Composition. App::raise_panel opens/fronts a panel without moving the focus; show_panel builds on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit adf2c6dac78ce426ea0bf6b4199b7238d8ad99dd)
This commit is contained in:
parent
a46ebf4dc7
commit
aa31ffc2fb
|
|
@ -381,17 +381,41 @@ impl EffectcraftApp {
|
|||
v
|
||||
}
|
||||
|
||||
/// Show panel `p` (opening it in its usual place if it is closed), bring it to the front and
|
||||
/// give it the focus: Window ▸ <panel>.
|
||||
pub fn show_panel(&mut self, p: PanelKind) {
|
||||
if let Some(f) = self.ui.floating.iter_mut().find(|f| f.panels.contains(&p)) {
|
||||
f.active = f.panels.iter().position(|x| *x == p).unwrap_or(0);
|
||||
self.ui.focused = p;
|
||||
return;
|
||||
}
|
||||
if self.ui.maximized.is_some_and(|m| m != p) {
|
||||
if !self.ui.floating.iter().any(|f| f.panels.contains(&p)) && self.ui.maximized.is_some_and(|m| m != p) {
|
||||
self.ui.maximized = None;
|
||||
}
|
||||
self.raise_panel(p);
|
||||
self.ui.focused = p;
|
||||
}
|
||||
|
||||
/// Bring panel `p` to the front of its group, opening it in its usual place if it is closed,
|
||||
/// without moving the focus (a comp opening brings up its viewer, an effect applied brings up
|
||||
/// Effect Controls).
|
||||
pub fn raise_panel(&mut self, p: PanelKind) {
|
||||
if let Some(f) = self.ui.floating.iter_mut().find(|f| f.panels.contains(&p)) {
|
||||
f.active = f.panels.iter().position(|x| *x == p).unwrap_or(0);
|
||||
return;
|
||||
}
|
||||
if !self.ui.dock.contains(p) {
|
||||
// The Composition panel lives in the centre, with the Layer panel and other viewers;
|
||||
// with none of those left, above the Timeline.
|
||||
let centre = |d: &dock::DockNode| {
|
||||
let mut v = Vec::new();
|
||||
d.panels(&mut v);
|
||||
v.into_iter().find(|q| matches!(q, PanelKind::Layer | PanelKind::Viewer(_) | PanelKind::Flowchart | PanelKind::Footage))
|
||||
};
|
||||
let near = match p {
|
||||
PanelKind::Composition => match centre(&self.ui.dock) {
|
||||
Some(q) => q,
|
||||
None if self.ui.dock.dock_panel(p, PanelKind::Timeline, dock::Zone::Top) => {
|
||||
self.ui.dock.activate(p);
|
||||
return;
|
||||
}
|
||||
None => PanelKind::EffectsPresets,
|
||||
},
|
||||
PanelKind::Layer | PanelKind::Flowchart | PanelKind::Viewer(_) => PanelKind::Composition,
|
||||
PanelKind::RenderQueue => PanelKind::Timeline,
|
||||
PanelKind::EffectControls | PanelKind::History => PanelKind::Project,
|
||||
|
|
@ -400,7 +424,6 @@ impl EffectcraftApp {
|
|||
self.ui.dock.open_near(p, near);
|
||||
}
|
||||
self.ui.dock.activate(p);
|
||||
self.ui.focused = p;
|
||||
}
|
||||
|
||||
pub fn render_source(&self) -> RenderSource {
|
||||
|
|
|
|||
|
|
@ -192,10 +192,12 @@ pub fn on_open_comp(app: &mut EffectcraftApp, comp: ItemId) {
|
|||
};
|
||||
}
|
||||
app.ui.viewers.insert(app.ui.active_viewer, Some(comp.0));
|
||||
// The comp's viewer and its Timeline tab come to the front (unless locked).
|
||||
// The comp's viewer and its Timeline tab come to the front (unless locked), reopened if they
|
||||
// were closed (as in After Effects).
|
||||
for p in [active_panel(app), PanelKind::Timeline] {
|
||||
if !app.ui.locked_tabs.contains(&p.id()) {
|
||||
app.ui.dock.activate(p);
|
||||
let closed = !app.ui.dock.contains(p) && !app.ui.floating.iter().any(|f| f.panels.contains(&p));
|
||||
if closed || !app.ui.locked_tabs.contains(&p.id()) {
|
||||
app.raise_panel(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
//! Panels that come up on their own (egui_kittest), as in After Effects: opening a comp brings up
|
||||
//! its Composition panel even when it was closed (#90).
|
||||
|
||||
use effectcraft_engine::Session;
|
||||
use effectcraft_engine::project::ItemId;
|
||||
use effectcraft_ui_egui::EffectcraftApp;
|
||||
use effectcraft_ui_egui::dock::{DockNode, PanelKind};
|
||||
use egui::{Event, Pos2, pos2};
|
||||
use egui_kittest::Harness;
|
||||
use serde_json::json;
|
||||
|
||||
/// Comp "Main" (open) holding a solid, and comp "Other" (not open).
|
||||
fn harness() -> (Harness<'static, EffectcraftApp>, u64, u64) {
|
||||
let mut s = Session::default();
|
||||
let other = s.execute("comp.new", json!({"name": "Other", "width": 320, "height": 180, "duration": 4})).unwrap()["comp"].as_u64().unwrap();
|
||||
let main = s.execute("comp.new", json!({"name": "Main", "width": 320, "height": 180, "duration": 4})).unwrap()["comp"].as_u64().unwrap();
|
||||
s.execute("layer.newSolid", json!({"name": "Red", "color": [1.0, 0.0, 0.0, 1.0]})).unwrap();
|
||||
s.state.open_comps = vec![ItemId(main)];
|
||||
s.state.active_comp = Some(ItemId(main));
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(s));
|
||||
h.run_steps(3);
|
||||
(h, other, main)
|
||||
}
|
||||
|
||||
fn rect(h: &Harness<'_, EffectcraftApp>, id: &str) -> egui::Rect {
|
||||
let e = h.state().auto.previous.iter().chain(h.state().auto.elements.iter()).find(|e| e.id == id).cloned().unwrap_or_else(|| {
|
||||
let have: Vec<&String> = h.state().auto.elements.iter().map(|e| &e.id).collect();
|
||||
panic!("no element {id}; have {have:?}")
|
||||
});
|
||||
egui::Rect::from_min_size(pos2(e.rect[0], e.rect[1]), egui::vec2(e.rect[2], e.rect[3]))
|
||||
}
|
||||
|
||||
fn click_n(h: &mut Harness<'_, EffectcraftApp>, p: Pos2, n: u32) {
|
||||
h.event(Event::PointerMoved(p));
|
||||
h.step();
|
||||
for _ in 0..n {
|
||||
h.input_mut().events.push(Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() });
|
||||
h.input_mut().events.push(Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() });
|
||||
}
|
||||
h.step();
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
/// The tabs of the docked group holding `p`, in order.
|
||||
fn group_of(n: &DockNode, p: PanelKind) -> Option<Vec<PanelKind>> {
|
||||
match n {
|
||||
DockNode::Split { a, b, .. } => group_of(a, p).or_else(|| group_of(b, p)),
|
||||
DockNode::Tabs { panels, .. } => panels.contains(&p).then(|| panels.clone()),
|
||||
DockNode::Stack { entries } => entries.iter().any(|e| e.panel == p).then(|| entries.iter().map(|e| e.panel).collect()),
|
||||
}
|
||||
}
|
||||
|
||||
fn invoke(h: &mut Harness<'_, EffectcraftApp>, id: &str, params: serde_json::Value) {
|
||||
let ctx = h.ctx.clone();
|
||||
effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, id, params).unwrap();
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
/// Issue #90: with the Composition panel closed (the centre shows the Layer panel), opening a
|
||||
/// comp (double-click in the Project panel, or Open Composition) brings the Composition panel
|
||||
/// back, in front, where it normally lives (with the Layer panel), and so does Window ▸
|
||||
/// Composition.
|
||||
#[test]
|
||||
fn opening_a_comp_brings_up_a_closed_composition_panel() {
|
||||
let (mut h, other, main) = harness();
|
||||
assert_eq!(group_of(&h.state().ui.dock, PanelKind::Composition), Some(vec![PanelKind::Composition, PanelKind::Layer]));
|
||||
invoke(&mut h, "window.closePanel", json!({"panel": "Composition"}));
|
||||
assert!(!h.state().ui.dock.contains(PanelKind::Composition));
|
||||
assert!(h.state().ui.dock.is_visible(PanelKind::Layer), "the centre shows the Layer panel");
|
||||
|
||||
// Double-click "Other" in the Project panel.
|
||||
let name = rect(&h, &format!("project.item.{other}.name"));
|
||||
click_n(&mut h, name.center(), 2);
|
||||
assert_eq!(h.state().session.active_comp_id(), Some(ItemId(other)));
|
||||
let dock = &h.state().ui.dock;
|
||||
assert!(dock.is_visible(PanelKind::Composition), "the Composition panel is back and in front");
|
||||
assert_eq!(group_of(dock, PanelKind::Composition), Some(vec![PanelKind::Layer, PanelKind::Composition]), "back in the centre");
|
||||
assert!(h.state().auto.elements.iter().any(|e| e.id == "panel.Composition"), "the Composition panel is drawn");
|
||||
|
||||
// Open Composition (the Project panel's context menu) with the panel closed again.
|
||||
invoke(&mut h, "window.closePanel", json!({"panel": "Composition"}));
|
||||
invoke(&mut h, "comp.open", json!({"comp": main}));
|
||||
assert_eq!(h.state().session.active_comp_id(), Some(ItemId(main)));
|
||||
assert!(h.state().ui.dock.is_visible(PanelKind::Composition));
|
||||
assert_eq!(group_of(&h.state().ui.dock, PanelKind::Composition), Some(vec![PanelKind::Layer, PanelKind::Composition]));
|
||||
|
||||
// Window ▸ Composition reopens it in the centre too (not beside Effects & Presets).
|
||||
invoke(&mut h, "window.closePanel", json!({"panel": "Composition"}));
|
||||
invoke(&mut h, "window.panel", json!({"panel": "Composition"}));
|
||||
assert_eq!(group_of(&h.state().ui.dock, PanelKind::Composition), Some(vec![PanelKind::Layer, PanelKind::Composition]));
|
||||
assert!(h.state().ui.dock.is_visible(PanelKind::Composition));
|
||||
}
|
||||
|
||||
/// Issue #90 in a workspace without a Layer panel: closing Composition leaves only the Timeline,
|
||||
/// and opening a comp brings the viewer back above it.
|
||||
#[test]
|
||||
fn opening_a_comp_in_the_minimal_workspace_docks_the_viewer_above_the_timeline() {
|
||||
let (mut h, other, _) = harness();
|
||||
invoke(&mut h, "window.workspace", json!({"name": "Minimal"}));
|
||||
invoke(&mut h, "window.closePanel", json!({"panel": "Composition"}));
|
||||
assert!(!h.state().ui.dock.contains(PanelKind::Composition));
|
||||
invoke(&mut h, "comp.open", json!({"comp": other}));
|
||||
match &h.state().ui.dock {
|
||||
DockNode::Split { vertical: true, a, b, .. } => {
|
||||
assert_eq!(group_of(a, PanelKind::Composition), Some(vec![PanelKind::Composition]), "the viewer is on top");
|
||||
assert!(b.contains(PanelKind::Timeline));
|
||||
}
|
||||
other => panic!("expected the viewer above the Timeline, got {other:?}"),
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue