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 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-09 22:13:31 +07:00
parent 9144eef269
commit 82f0cd9668
7 changed files with 3 additions and 89 deletions

View File

@ -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<Page> {
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),
],

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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());

View File

@ -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();

View File

@ -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