From 82f0cd966866de3d17785251d2ba4a56b5e796b2 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Fri, 9 Oct 2026 22:13:31 +0700 Subject: [PATCH] UI: Effect Controls and the Timeline have no parameter row separators, as in After Effects (#402) #402 made Effect Controls' rows compact and aligned, as After Effects draws them, and also added thin separator lines between parameter rows in Effect Controls and the Timeline, with a Settings > Appearance > Show Parameter Row Separators preference. After Effects has neither, so the lines, the preference and the Tokens field go; the compact rows, the alignment and the automation ids stay. Saved settings with the old key load as before (unknown keys are ignored). Test: ui_parameter_spacing keeps the row geometry, hit target and nesting tests; the separator tests go with the feature. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/prefs.rs | 2 - crates/ui-egui/src/panels/effect_controls.rs | 3 - crates/ui-egui/src/panels/timeline.rs | 4 -- crates/ui-egui/src/theme.rs | 3 - crates/ui-egui/src/widgets.rs | 8 --- crates/ui-egui/tests/ui_parameter_spacing.rs | 69 +------------------- docs/preferences.md | 3 +- 7 files changed, 3 insertions(+), 89 deletions(-) diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index 1ce7be0e..4ee6cc97 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -146,7 +146,6 @@ page!(Appearance { brightness: f64 = 0.0, /// The size of the whole interface in percent (75–200), on top of the display's own scale. ui_scale: u32 = 100, - parameter_row_separators: bool = true, use_label_color_for_handles: bool = true, use_label_color_for_tabs: bool = true, cycle_mask_colors: bool = true, @@ -1023,7 +1022,6 @@ pub fn pages() -> Vec { s("appearance.useLabelColorForTabs", "Use Label Color for Related Tabs", B, true), s("appearance.cycleMaskColors", "Cycle Mask Colors", B, true), s("appearance.useGradients", "Use Gradients", B, true), - s("appearance.parameterRowSeparators", "Show Parameter Row Separators", B, true), Section("Menu Bar"), s("appearance.inWindowMenuBarMac", "Use In-Window Menu Bar on macOS", B, true), ], diff --git a/crates/ui-egui/src/panels/effect_controls.rs b/crates/ui-egui/src/panels/effect_controls.rs index 0d8f983b..e5dbc2b8 100644 --- a/crates/ui-egui/src/panels/effect_controls.rs +++ b/crates/ui-egui/src/panels/effect_controls.rs @@ -567,8 +567,6 @@ fn group_rows( } } } - // Expanded sliders/dials belong to their parameter; separate after the whole block. - widgets::parameter_separator(p, Rect::from_min_max(r.min, pos2(r.max.x, *y)), &t); } Node::Group(sg) => { // Paint strokes live in the Timeline only (AE's Paint shows Paint on Transparent). @@ -577,7 +575,6 @@ fn group_rows( } *y += ROW; let open = !app.ui.fx_closed.contains(&sg.uid); - widgets::parameter_separator(p, r, &t); let tw = Rect::from_center_size(pos2(r.min.x + PARAM_NAME_X - 12.0 + TREE_INDENT * depth as f32, r.center().y), vec2(12.0, 12.0)); if r.max.y >= rect.min.y && r.min.y <= rect.max.y { app.auto.add(&format!("effectControls.row.{}", sg.uid), r, &sg.name); diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index 1ae0f611..69139d3b 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -2458,10 +2458,6 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } } } - if !matches!(row.kind, RowKind::Layer) { - widgets::parameter_separator(&lp, left, &t); - widgets::parameter_separator(&gp, Rect::from_min_max(pos2(graph_x0, r.min.y), r.max), &t); - } } ui.set_clip_rect(full_clip); // The background responses sit below rows, bars and editors, preserving their menus. diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 04524897..7b13112f 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -75,7 +75,6 @@ pub struct Tokens { pub gap: f32, pub tab_h: f32, pub row_h: f32, - pub parameter_separators: bool, /// Settings ▸ Appearance ▸ Use Gradients: panel tab strips and buttons get a soft vertical /// gradient (off: flat fills). pub gradients: bool, @@ -147,7 +146,6 @@ impl Tokens { gap: 4.0, tab_h: 30.0, row_h: 19.0, - parameter_separators: true, gradients: true, }; match kind { @@ -218,7 +216,6 @@ impl Tokens { pub fn from_prefs(p: &effectcraft_engine::prefs::Prefs) -> Tokens { let kind = ThemeKind::from_name(&p.appearance.theme).unwrap_or_default(); let mut t = Tokens::for_kind(kind).with_brightness(p.appearance.brightness as f32); - t.parameter_separators = p.appearance.parameter_row_separators; for (i, l) in effectcraft_color::Label::ALL.iter().enumerate() { let [r, g, b] = p.label_rgb(*l); t.labels[i] = Color32::from_rgb(r, g, b); diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 2d03c8be..003cb9ad 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -112,14 +112,6 @@ pub fn select_all(ctx: &egui::Context, id: egui::Id, text: &str) { state.store(ctx, id); } -/// A parameter boundary. It only paints: row layout owns both drawing and hit testing. -pub fn parameter_separator(p: &egui::Painter, r: Rect, t: &Tokens) { - let y = r.max.y - 0.5; - if t.parameter_separators && p.clip_rect().y_range().contains(y) { - p.line_segment([pos2(r.min.x, y), pos2(r.max.x, y)], Stroke::new(1.0, t.app_bg)); - } -} - /// A colour swatch; returns the response (click opens the picker in the caller). pub fn swatch(ui: &mut Ui, rect: Rect, c: [f32; 4], id: egui::Id, t: &Tokens) -> Response { let resp = ui.interact(rect, id, Sense::click()); diff --git a/crates/ui-egui/tests/ui_parameter_spacing.rs b/crates/ui-egui/tests/ui_parameter_spacing.rs index 4abf502f..71b70a14 100644 --- a/crates/ui-egui/tests/ui_parameter_spacing.rs +++ b/crates/ui-egui/tests/ui_parameter_spacing.rs @@ -1,10 +1,5 @@ -use effectcraft_engine::{Session, prefs::Prefs}; -use effectcraft_ui_egui::{ - EffectcraftApp, - dock::PanelKind, - theme::{ThemeKind, Tokens}, - widgets, -}; +use effectcraft_engine::Session; +use effectcraft_ui_egui::{EffectcraftApp, dock::PanelKind}; use egui::{Event, Modifiers, PointerButton, pos2, vec2}; use egui_kittest::Harness; use serde_json::json; @@ -54,66 +49,6 @@ fn fixed_compact_rows_preserve_checkbox_and_point_hit_targets() { assert!(h.state().ui.fx_pick.is_none()); } -#[test] -fn separator_preference_is_saved_and_resettable() { - let mut p = Prefs::default(); - p.set("appearance.parameterRowSeparators", json!(false)).unwrap(); - assert_eq!(Prefs::from_json(&p.to_json()), p); - assert!(!Tokens::from_prefs(&p).parameter_separators); - p.reset(Some("appearance")).unwrap(); - assert!(p.appearance.parameter_row_separators); - assert!(Prefs::from_json(r#"{"appearance":{"theme":"light"}}"#).appearance.parameter_row_separators); -} - -#[test] -fn cancelling_separator_preview_preserves_fixed_row_geometry_and_project() { - let (a, _, checkbox, _) = setup(); - let mut h = Harness::builder().with_size(vec2(1500.0, 1000.0)).build_eframe(|_| a); - h.run_steps(4); - let row = format!("effectControls.row.{checkbox}"); - let before = h.state().auto.find(&row).unwrap().rect; - let project = h.state().session.project.clone(); - let ctx = h.ctx.clone(); - effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "app.settings", json!({"page":"appearance"})).unwrap(); - h.state_mut().session.execute("prefs.set", json!({"key":"appearance.parameterRowSeparators","value":false})).unwrap(); - h.run_steps(3); - assert!(!h.state().tokens.parameter_separators); - assert_eq!(h.state().auto.find(&row).unwrap().rect, before); - click(&mut h, "settings.cancel"); - assert!(h.state().tokens.parameter_separators); - assert_eq!(h.state().auto.find(&row).unwrap().rect, before); - assert!(std::sync::Arc::ptr_eq(&project, &h.state().session.project)); -} - -#[test] -fn row_separator_uses_the_theme_and_can_be_disabled() { - for kind in [ThemeKind::Dark, ThemeKind::Darker, ThemeKind::Light] { - for enabled in [true, false] { - let ctx = egui::Context::default(); - let mut t = Tokens::for_kind(kind); - t.parameter_separators = enabled; - let mut out = ctx.run_ui(Default::default(), |_| { - let painter = ctx.layer_painter(egui::LayerId::background()); - widgets::parameter_separator(&painter, egui::Rect::from_min_size(pos2(10.0, 20.0), vec2(200.0, 24.0)), &t); - }); - let lines: Vec<_> = out - .shapes - .iter() - .filter_map(|s| match s.shape { - egui::Shape::LineSegment { points, stroke } => Some((points, stroke)), - _ => None, - }) - .collect(); - assert_eq!(lines.len(), usize::from(enabled)); - if enabled { - assert_eq!(lines[0].0, [pos2(10.0, 43.5), pos2(210.0, 43.5)]); - assert_eq!(lines[0].1.color, t.app_bg); - } - out.textures_delta.clear(); - } - } -} - #[test] fn nested_groups_keep_indentation_value_alignment_and_effect_gaps_at_fixed_compact_spacing() { let mut s = Session::default(); diff --git a/docs/preferences.md b/docs/preferences.md index f6f1180a..9e3d4147 100644 --- a/docs/preferences.md +++ b/docs/preferences.md @@ -58,7 +58,6 @@ Commands (CLI, MCP, control channel): - `appearance.brightness`: Brightness - `appearance.uiScale`: UI Scale, the size of the whole interface (75–200 %, on top of the display's own scaling), applied as soon as it is chosen -- `appearance.parameterRowSeparators`: thin dark boundaries between parameter/group rows in Effect Controls and the timeline (default on). Expanded sliders and dials are kept within their parameter block. Effect Controls indents each group level beneath its effect, aligns sibling names and values, and preserves the gap before the next effect. - `appearance.useLabelColorForHandles`: Use Label Color for Layer Handles and Paths - `grids.gridColor`: Color - `grids.gridSpacing`: Gridline Every @@ -166,7 +165,7 @@ step with the schema. - `scripting.enableJsDebugger`: the JavaScript engine has no step debugger; script errors report their file and line in the Script Console. -Effect Controls uses compact, fixed 20 pt parameter rows; the timeline keeps its fixed 19 pt rows. These logical sizes follow the existing whole-interface UI Scale. The separator preference previews immediately; Cancel restores it and OK saves it. Painting and mouse hit tests use the same row geometry. These UI changes do not alter project values or render output. +Effect Controls uses compact, fixed 20 pt parameter rows, as After Effects does: each group level is indented beneath its effect, sibling names and values align, and a gap separates effects. The timeline keeps its fixed 19 pt rows. Both follow the whole-interface UI Scale. ## Keyboard shortcuts