From 5e96b2a38598d52f1444c59982c4cbb11dd232fc Mon Sep 17 00:00:00 2001 From: Zinadin Zidan Date: Thu, 5 Nov 2026 18:05:43 +0600 Subject: [PATCH] Timeline: the marker bin's double-click needs both clicks on the bin (#1826) egui takes any click within a few points of the last one for the first click of a double-click, so a click on the ruler right beside the bin and then one on the bin added a marker. The bin now remembers when it was last clicked and adds one only when the first click was on it too (or both came in one frame over it). Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/markers_ui.rs | 37 +++++++++++++++++++-- crates/ui-egui/tests/it/ui_timeline_zoom.rs | 12 +++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/markers_ui.rs b/crates/ui-egui/src/panels/markers_ui.rs index 5bdd04ad4..c23a6605c 100644 --- a/crates/ui-egui/src/panels/markers_ui.rs +++ b/crates/ui-egui/src/panels/markers_ui.rs @@ -435,8 +435,9 @@ pub(crate) fn marker_bin(app: &mut EffectcraftApp, ui: &mut egui::Ui, comp: &Com // The bin senses drags only, so a drag starts as soon as it is pressed: sensing clicks too // would hold that back until the pointer moved, and a drag out released within that frame // would add nothing. The double-click is read from the pointer instead. - let double_clicked = resp.contains_pointer() && ui.input(|i| i.pointer.button_double_clicked(egui::PointerButton::Primary)); - if double_clicked && let Err(e) = app.session.execute("comp.addMarker", json!({})) { + if bin_double_clicked(ui, resp.id, bin, resp.contains_pointer()) + && let Err(e) = app.session.execute("comp.addMarker", json!({})) + { app.ui.status = e.to_string(); } app.auto.add("timeline.markerBin", bin, "Composition marker bin"); @@ -463,6 +464,38 @@ pub(crate) fn marker_bin(app: &mut EffectcraftApp, ui: &mut egui::Ui, comp: &Com } } +/// Whether the marker bin `bin` (widget `id`, under the pointer when `here`) was double-clicked +/// this frame: egui's double-click, with its first click on the bin too. egui takes any click +/// within a few points of the last for the first one, so a click on the ruler right beside the +/// bin and then one on the bin would count. The bin remembers when it was last clicked; two +/// clicks on it within one frame need no memory. +fn bin_double_clicked(ui: &egui::Ui, id: egui::Id, bin: Rect, here: bool) -> bool { + use egui::{Event, PointerButton}; + if !here { + return false; + } + let (time, clicked, double, releases) = ui.input(|i| { + let releases = i + .events + .iter() + .filter(|e| matches!(e, Event::PointerButton { button: PointerButton::Primary, pressed: false, pos, .. } if bin.contains(*pos))) + .count(); + (i.time, i.pointer.button_clicked(PointerButton::Primary), i.pointer.button_double_clicked(PointerButton::Primary), releases) + }); + if !clicked { + return false; + } + let delay = ui.ctx().options(|o| o.input_options.max_double_click_delay); + let id = id.with("last click"); + let last = ui.data_mut(|d| { + let last = d.get_temp::(id); + d.insert_temp(id, time); + last + }); + let last = last.unwrap_or(f64::NEG_INFINITY); + double && (releases >= 2 || time - last < delay) +} + /// Layer markers on a layer's row `r`; on a precomp layer, its comp's markers too (read-only, /// outlined: hover names them, a double-click opens the nested comp at the marker). pub(crate) fn layer_markers(app: &mut EffectcraftApp, ui: &mut egui::Ui, clip: Rect, comp: &Comp, layer: &Layer, tm: TMap, r: Rect) { diff --git a/crates/ui-egui/tests/it/ui_timeline_zoom.rs b/crates/ui-egui/tests/it/ui_timeline_zoom.rs index 78535e3dd..f8a5783f2 100644 --- a/crates/ui-egui/tests/it/ui_timeline_zoom.rs +++ b/crates/ui-egui/tests/it/ui_timeline_zoom.rs @@ -431,6 +431,18 @@ fn double_clicking_the_marker_bin_adds_a_comp_marker_at_the_current_time() { h.step(); h.run_steps(3); assert_eq!(markers(&h), vec![21.5, 22.0], "a double-click released while the bin is dragged"); + // A click on the ruler right beside the bin and then one on the bin, in one frame: egui + // takes them for a double-click (they are a few points apart), but only one was on the bin. + let (beside, on) = (pos2(bin.min.x - 2.0, bin.center().y), pos2(bin.min.x + 2.0, bin.center().y)); + assert!(ruler.contains(beside) && !bin.contains(beside) && bin.contains(on)); + h.event(Event::PointerMoved(beside)); + h.step(); + for (pos, pressed) in [(beside, true), (beside, false), (on, true), (on, false)] { + h.input_mut().events.push(Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Modifiers::NONE }); + } + h.step(); + h.run_steps(3); + assert_eq!(markers(&h), vec![21.5, 22.0], "one click beside the bin and one on it"); // Dragged out 3 s into the visible span (from 10 s at 200 px/s): a marker at 13 s too. drag(&mut h, bin.center(), pos2(ruler.min.x + 6.0 + 600.0, ruler.center().y)); assert_eq!(markers(&h), vec![13.0, 21.5, 22.0]);