Timeline: click a keyframe diamond to snap the playhead to it
render_layers now records each drawn keyframe diamond's screen rect + exact time in `keyframe_diamond_hits`; handle_input hit-tests a click against them and sets the playhead (and seeks the audio controller) to the keyframe's exact time. Uses the previous frame's rects — diamonds don't move between frames, so the click lands right — which sidesteps the input-before-render ordering and the drag/scroll Y math. Works for both raster and vector keyframes.
This commit is contained in:
parent
2cbaf67583
commit
3188fc8bb6
|
|
@ -251,6 +251,11 @@ pub struct TimelinePane {
|
|||
/// Vertical scroll offset (in pixels)
|
||||
viewport_scroll_y: f32,
|
||||
|
||||
/// Clickable keyframe-diamond hit targets `(screen_rect, keyframe_time)` collected
|
||||
/// during the last `render_layers`. Used by `handle_input` (next frame) to snap the
|
||||
/// playhead exactly to a keyframe when its diamond is clicked.
|
||||
keyframe_diamond_hits: Vec<(egui::Rect, f64)>,
|
||||
|
||||
/// Total duration of the animation
|
||||
duration: f64,
|
||||
|
||||
|
|
@ -773,6 +778,7 @@ impl TimelinePane {
|
|||
pixels_per_second: 100.0,
|
||||
viewport_start_time: 0.0,
|
||||
viewport_scroll_y: 0.0,
|
||||
keyframe_diamond_hits: Vec::new(),
|
||||
duration: 10.0, // Default 10 seconds
|
||||
is_scrubbing: false,
|
||||
is_panning: false,
|
||||
|
|
@ -2730,6 +2736,8 @@ impl TimelinePane {
|
|||
) -> (Vec<(egui::Rect, uuid::Uuid, f64, f32)>, Vec<AutomationLaneRender>) {
|
||||
let painter = ui.painter().clone();
|
||||
let mut pending_lane_renders: Vec<AutomationLaneRender> = Vec::new();
|
||||
// Rebuilt each frame; consumed by handle_input (next frame) for click-to-seek.
|
||||
self.keyframe_diamond_hits.clear();
|
||||
|
||||
// Collect video clip rects for hover detection (to avoid borrow conflicts)
|
||||
let mut video_clip_hovers: Vec<(egui::Rect, uuid::Uuid, f64, f32)> = Vec::new();
|
||||
|
|
@ -3972,6 +3980,11 @@ impl TimelinePane {
|
|||
color,
|
||||
egui::Stroke::new(1.0, theme.border_color(&["#timeline", ".keyframe-diamond"], ui.ctx(), egui::Color32::from_rgb(180, 150, 50))),
|
||||
));
|
||||
// Record a (slightly enlarged) clickable area for click-to-seek.
|
||||
self.keyframe_diamond_hits.push((
|
||||
egui::Rect::from_center_size(egui::pos2(cx, cy), egui::vec2(13.0, 13.0)),
|
||||
kf.time,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3996,6 +4009,11 @@ impl TimelinePane {
|
|||
color,
|
||||
egui::Stroke::new(1.0, theme.border_color(&["#timeline", ".keyframe-diamond"], ui.ctx(), egui::Color32::from_rgb(180, 150, 50))),
|
||||
));
|
||||
// Record a (slightly enlarged) clickable area for click-to-seek.
|
||||
self.keyframe_diamond_hits.push((
|
||||
egui::Rect::from_center_size(egui::pos2(cx, cy), egui::vec2(13.0, 13.0)),
|
||||
kf.time,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4849,6 +4867,23 @@ impl TimelinePane {
|
|||
}
|
||||
}
|
||||
|
||||
// Click a keyframe diamond → snap the playhead exactly to that keyframe.
|
||||
// (Hit targets come from the previous frame's render_layers; diamonds don't
|
||||
// move between frames, so a click lands on the right one.)
|
||||
if response.clicked() && !alt_held {
|
||||
if let Some(pos) = response.interact_pointer_pos() {
|
||||
if let Some(&(_, kf_time)) =
|
||||
self.keyframe_diamond_hits.iter().find(|(r, _)| r.contains(pos))
|
||||
{
|
||||
*playback_time = kf_time;
|
||||
if let Some(controller_arc) = audio_controller {
|
||||
let mut controller = controller_arc.lock().unwrap();
|
||||
controller.seek(kf_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get mouse position relative to content area
|
||||
let mouse_pos = response.hover_pos().unwrap_or(content_rect.center());
|
||||
let mouse_x = (mouse_pos.x - content_rect.min.x).max(0.0);
|
||||
|
|
|
|||
Loading…
Reference in New Issue