diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index de2c2b9d..1068acab 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -641,13 +641,24 @@ pub fn keyframe( left: effectcraft_engine::keyframe::KeyHalf, right: effectcraft_engine::keyframe::KeyHalf, fill: Color32, - outline: Color32, ) { + painter.extend(keyframe_shapes(center, size, left, right, fill)); +} + +/// The halves of a [`keyframe`] glyph. Flat, without a dark outline (as in After Effects), so +/// keys that overlap read as one shape instead of a tangle of borders (#284); the hairline in +/// the fill colour closes the seam between the halves. +fn keyframe_shapes( + center: Pos2, + size: f32, + left: effectcraft_engine::keyframe::KeyHalf, + right: effectcraft_engine::keyframe::KeyHalf, + fill: Color32, +) -> Vec { use effectcraft_engine::keyframe::KeyHalf as H; let r = size / 2.0; - let half = |pts: Vec| { - painter.add(PathShape::convex_polygon(pts, fill, Stroke::new(1.0, outline))); - }; + let mut out = Vec::with_capacity(2); + let mut half = |pts: Vec| out.push(PathShape::convex_polygon(pts, fill, Stroke::new(0.5, fill)).into()); let c = center; for (side, h) in [(-1.0f32, left), (1.0, right)] { match h { @@ -678,4 +689,26 @@ pub fn keyframe( } } } + out +} + +#[cfg(test)] +mod tests { + use effectcraft_engine::keyframe::KeyHalf as H; + + /// #284: keyframes have no dark border (overlapping keys looked messy): each half is filled + /// and edged in the key's own colour. + #[test] + fn keyframes_are_drawn_without_a_dark_outline() { + let fill = egui::Color32::from_gray(0xc0); + for (l, r) in [(H::Linear, H::Linear), (H::Bezier, H::Hold), (H::Auto, H::Linear)] { + let shapes = super::keyframe_shapes(egui::pos2(10.0, 10.0), 11.0, l, r, fill); + assert_eq!(shapes.len(), 2); + for s in shapes { + let egui::Shape::Path(p) = s else { panic!("a path: {s:?}") }; + assert_eq!(p.fill, fill); + assert!(p.stroke.width <= 0.5 && p.stroke.color == egui::epaint::ColorMode::Solid(fill), "{:?}", p.stroke); + } + } + } } diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index 457a9396..bdedd202 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -2255,7 +2255,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Roving keys: a small dot (their time follows their neighbours). gp.circle_filled(c, 2.5, kcol); } else { - icons::keyframe(&gp, c, 11.0, icon.left, icon.right, kcol, Color32::from_black_alpha(200)); + icons::keyframe(&gp, c, 11.0, icon.left, icon.right, kcol); } let kr = Rect::from_center_size(c, vec2(12.0, 14.0)); let kresp = ui.interact(kr, egui::Id::new(("key", uid, k.time.0)), Sense::click_and_drag()); diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 1a9e1a1a..bfe4de75 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -187,6 +187,8 @@ impl Tokens { tl_ruler_bg: Color32::from_rgb(0xe2, 0xe2, 0xe2), tl_ruler_text: Color32::from_rgb(0x50, 0x50, 0x50), pasteboard: Color32::from_rgb(0xa8, 0xa8, 0xa8), + // Keyframes have no outline: dark enough to stand out on the light time graph. + keyframe: Color32::from_rgb(0x6c, 0x6c, 0x6c), ..dark }, }