From 646b60f4940ef7587b4fbda82bb7dae7e3105183 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Mon, 6 Apr 2026 21:28:56 +0800 Subject: [PATCH 1/6] {"schema":"maestro/commit/1","summary":"smooth frozen selection drag interactions","authority":"manual","breaking":false} --- packages/rsnap-overlay/src/overlay.rs | 303 ++++++++++++++++-- .../src/overlay/aux_window_runtime.rs | 59 ++++ .../rsnap-overlay/src/overlay/hud_runtime.rs | 16 +- .../src/overlay/scroll_preview_runtime.rs | 4 +- .../src/overlay/session_state.rs | 11 + .../src/overlay/tests/rendering_behaviors.rs | 131 +++++++- .../src/overlay/toolbar_runtime.rs | 38 ++- .../src/overlay/window_position_runtime.rs | 35 +- .../src/overlay/window_runtime.rs | 55 +++- 9 files changed, 586 insertions(+), 66 deletions(-) diff --git a/packages/rsnap-overlay/src/overlay.rs b/packages/rsnap-overlay/src/overlay.rs index b37dd990..5d35df2b 100644 --- a/packages/rsnap-overlay/src/overlay.rs +++ b/packages/rsnap-overlay/src/overlay.rs @@ -147,9 +147,9 @@ use self::rendering::{ #[cfg(all(target_os = "macos", test))] use self::session_state::InflightScrollCaptureObservation; use self::session_state::{ - CursorMoveTrace, FrozenSelectionDragState, FrozenToolbarPointerState, FrozenToolbarState, - HudDrawConfig, LiveSampleApplyResult, ScrollCaptureState, SlowOperationLogger, - WindowFreezeCaptureTarget, + CursorMoveTrace, FrozenSelectionDragCursorMoveTiming, FrozenSelectionDragState, + FrozenToolbarPointerState, FrozenToolbarState, HudDrawConfig, LiveSampleApplyResult, + ScrollCaptureState, SlowOperationLogger, WindowFreezeCaptureTarget, }; #[cfg(target_os = "macos")] use self::session_state::{ @@ -754,10 +754,12 @@ pub struct OverlaySession { pending_loupe_outer_pos: Option, loupe_inner_size_points: Option<(u32, u32)>, toolbar_outer_pos: Option, + pending_toolbar_outer_pos: Option, toolbar_inner_size_points: Option<(u32, u32)>, gpu: Option, last_hud_window_move_at: Instant, last_loupe_window_move_at: Instant, + last_toolbar_window_move_at: Instant, last_present_at: Instant, last_live_cursor_poll_at: Instant, last_frozen_cursor_poll_at: Instant, @@ -824,6 +826,7 @@ pub struct OverlaySession { frozen_selection_drag: FrozenSelectionDragState, hud_window_visible: bool, toolbar_window_visible: bool, + skip_toolbar_focus_on_next_show: bool, toolbar_window_warmup_redraws_remaining: u8, loupe_window_visible: bool, loupe_window_warmup_redraws_remaining: u8, @@ -963,11 +966,12 @@ impl OverlaySession { macos_hud_window_config_cache: HashMap::new(), hud_outer_pos: None, pending_hud_outer_pos: None, hud_inner_size_points: None, loupe_outer_pos: None, pending_loupe_outer_pos: None, loupe_inner_size_points: None, - toolbar_outer_pos: None, + toolbar_outer_pos: None, pending_toolbar_outer_pos: None, toolbar_inner_size_points: None, gpu: None, last_hud_window_move_at: Instant::now(), last_loupe_window_move_at: Instant::now(), + last_toolbar_window_move_at: Instant::now(), last_present_at: Instant::now(), last_live_cursor_poll_at: Instant::now(), last_frozen_cursor_poll_at: Instant::now(), @@ -1022,7 +1026,8 @@ impl OverlaySession { toolbar_pointer_local: None, left_mouse_button_down: false, left_mouse_button_down_monitor: None, left_mouse_button_down_global: None, frozen_selection_drag: FrozenSelectionDragState::default(), - hud_window_visible: false, toolbar_window_visible: false, toolbar_window_warmup_redraws_remaining: 0, + hud_window_visible: false, toolbar_window_visible: false, + skip_toolbar_focus_on_next_show: false, toolbar_window_warmup_redraws_remaining: 0, loupe_window_visible: false, loupe_window_warmup_redraws_remaining: 0, scroll_capture: ScrollCaptureState::default(), @@ -1050,6 +1055,7 @@ impl OverlaySession { self.state = runtime.state; self.last_hud_window_move_at = runtime.now; self.last_loupe_window_move_at = runtime.now; + self.last_toolbar_window_move_at = runtime.now; self.last_present_at = runtime.now; self.last_live_cursor_poll_at = runtime.now - CURSOR_POLL_INTERVAL_MIN; self.last_frozen_cursor_poll_at = runtime.now - CURSOR_POLL_INTERVAL_MIN; @@ -1675,6 +1681,7 @@ impl OverlaySession { press_cursor_x: cursor_x, press_cursor_y: cursor_y, }; + self.hide_auxiliary_windows_for_frozen_selection_drag(); true } @@ -1716,7 +1723,7 @@ impl OverlaySession { FrozenSelectionInteractionKind::Resize(corner) => { Self::frozen_selection_resize_cursor_icon(corner) }, - FrozenSelectionInteractionKind::Move => CursorIcon::Default, + FrozenSelectionInteractionKind::Move => CursorIcon::Grabbing, }; } @@ -1731,7 +1738,8 @@ impl OverlaySession { Some(FrozenSelectionInteractionKind::Resize(corner)) => { Self::frozen_selection_resize_cursor_icon(corner) }, - _ => CursorIcon::Default, + Some(FrozenSelectionInteractionKind::Move) => CursorIcon::Grab, + None => CursorIcon::Default, } } @@ -1750,8 +1758,44 @@ impl OverlaySession { } } + pub(super) fn frozen_selection_drag_hides_auxiliary_windows(&self) -> bool { + matches!(self.state.mode, OverlayMode::Frozen) && self.frozen_selection_drag.active + } + + fn hide_auxiliary_windows_for_frozen_selection_drag(&mut self) { + if let Some(hud_window) = self.hud_window.as_ref() { + hud_window.window.set_visible(false); + } + self.hud_window_visible = false; + + if let Some(loupe_window) = self.loupe_window.as_ref() { + loupe_window.window.set_visible(false); + } + self.loupe_window_visible = false; + self.reset_loupe_window_warmup_redraws(); + + if let Some(toolbar_window) = self.toolbar_window.as_ref() { + toolbar_window.window.set_visible(false); + } + self.skip_toolbar_focus_on_next_show = true; + self.toolbar_window_visible = false; + self.toolbar_window_warmup_redraws_remaining = 0; + + if let Some(preview_window) = self.scroll_preview_window.as_ref() { + preview_window.window.set_visible(false); + } + + self.last_present_at = Instant::now(); + } + fn stop_frozen_selection_drag(&mut self) { + let was_active = self.frozen_selection_drag.active; + self.frozen_selection_drag = FrozenSelectionDragState::default(); + + if was_active && let Some(monitor) = self.state.monitor { + self.request_redraw_for_monitor(monitor); + } } fn update_frozen_selection_drag_rect(&mut self, global: GlobalPoint) -> bool { @@ -1928,11 +1972,56 @@ impl OverlaySession { self.toolbar_state.default_slot_position = Some(toolbar_default_pos); self.toolbar_state.floating_position = Some(toolbar_pos); - let _ = self.update_toolbar_outer_position(monitor, toolbar_pos); + let should_trace_frozen_selection_drag_timing = + self.should_trace_frozen_selection_drag_timing(); + let toolbar_position_elapsed = if should_trace_frozen_selection_drag_timing { + let toolbar_position_started_at = Instant::now(); + let _ = self.update_toolbar_outer_position(monitor, toolbar_pos); - self.request_redraw_for_monitor(monitor); - self.request_redraw_toolbar_window(); - self.request_redraw_scroll_preview_window(); + Some(toolbar_position_started_at.elapsed()) + } else { + let _ = self.update_toolbar_outer_position(monitor, toolbar_pos); + + None + }; + + let redraw_request_elapsed = if should_trace_frozen_selection_drag_timing { + let redraw_request_started_at = Instant::now(); + + self.request_redraw_for_monitor(monitor); + self.request_redraw_toolbar_window(); + if self.scroll_capture.active { + self.request_redraw_scroll_preview_window(); + } + + Some(redraw_request_started_at.elapsed()) + } else { + self.request_redraw_for_monitor(monitor); + self.request_redraw_toolbar_window(); + if self.scroll_capture.active { + self.request_redraw_scroll_preview_window(); + } + + None + }; + + if should_trace_frozen_selection_drag_timing { + tracing::trace!( + op = "overlay.frozen_selection_drag.rect_update", + monitor_id = monitor.id, + rect_x = next_rect.x, + rect_y = next_rect.y, + rect_width = next_rect.width, + rect_height = next_rect.height, + toolbar_position_us = + toolbar_position_elapsed.map_or(0, |elapsed| elapsed.as_micros()), + redraw_request_us = redraw_request_elapsed.map_or(0, |elapsed| elapsed.as_micros()), + scroll_capture_active = self.scroll_capture.active, + toolbar_outer_pos = ?self.toolbar_outer_pos, + toolbar_floating_position = ?self.toolbar_state.floating_position, + "Applied frozen selection rect update." + ); + } true } @@ -3004,6 +3093,10 @@ impl OverlaySession { window_id: WindowId, position: PhysicalPosition, ) -> OverlayControl { + let should_trace_frozen_selection_drag_timing = + self.should_trace_frozen_selection_drag_timing(); + let cursor_move_started_at = + should_trace_frozen_selection_drag_timing.then(Instant::now); let old_monitor = self.active_cursor_monitor(); let now = Instant::now(); let Some(overlay_window) = self.windows.get(&window_id) else { @@ -3042,14 +3135,58 @@ impl OverlaySession { }; self.trace_cursor_moved_with_mapping(trace); - self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + let cursor_update_elapsed = if should_trace_frozen_selection_drag_timing { + let cursor_update_started_at = Instant::now(); + self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + + Some(cursor_update_started_at.elapsed()) + } else { + self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + + None + }; let previous_drag_rect = self.state.drag_rect; - self.update_live_drag_rect(monitor, global); - self.update_frozen_selection_drag_rect(global); - self.sync_overlay_cursor_icons(); - self.request_cursor_move_samples(monitor, global); + let live_drag_update_elapsed = if should_trace_frozen_selection_drag_timing { + let live_drag_update_started_at = Instant::now(); + self.update_live_drag_rect(monitor, global); + + Some(live_drag_update_started_at.elapsed()) + } else { + self.update_live_drag_rect(monitor, global); + + None + }; + let (frozen_rect_changed, frozen_drag_update_elapsed) = + if should_trace_frozen_selection_drag_timing { + let frozen_drag_update_started_at = Instant::now(); + let frozen_rect_changed = self.update_frozen_selection_drag_rect(global); + + (frozen_rect_changed, Some(frozen_drag_update_started_at.elapsed())) + } else { + (self.update_frozen_selection_drag_rect(global), None) + }; + let sync_cursor_icons_elapsed = if should_trace_frozen_selection_drag_timing { + let sync_cursor_icons_started_at = Instant::now(); + self.sync_overlay_cursor_icons(); + + Some(sync_cursor_icons_started_at.elapsed()) + } else { + self.sync_overlay_cursor_icons(); + + None + }; + let request_samples_elapsed = if should_trace_frozen_selection_drag_timing { + let request_samples_started_at = Instant::now(); + self.request_cursor_move_samples(monitor, global); + + Some(request_samples_started_at.elapsed()) + } else { + self.request_cursor_move_samples(monitor, global); + + None + }; if let Some(old_monitor) = old_monitor && old_monitor != monitor @@ -3066,6 +3203,25 @@ impl OverlaySession { self.request_redraw_for_monitor(monitor); } + if should_trace_frozen_selection_drag_timing { + self.trace_frozen_selection_drag_cursor_move( + monitor, + old_monitor, + old_cursor, + FrozenSelectionDragCursorMoveTiming { + cursor_update_elapsed: cursor_update_elapsed.unwrap_or_default(), + live_drag_update_elapsed: live_drag_update_elapsed.unwrap_or_default(), + frozen_drag_update_elapsed: frozen_drag_update_elapsed.unwrap_or_default(), + frozen_rect_changed, + sync_cursor_icons_elapsed: sync_cursor_icons_elapsed.unwrap_or_default(), + request_samples_elapsed: request_samples_elapsed.unwrap_or_default(), + total_elapsed: cursor_move_started_at.map_or(Duration::ZERO, |started_at| { + started_at.elapsed() + }), + }, + ); + } + OverlayControl::Continue } @@ -3074,6 +3230,10 @@ impl OverlaySession { window_id: WindowId, old_monitor: Option, ) -> OverlayControl { + let should_trace_frozen_selection_drag_timing = + self.should_trace_frozen_selection_drag_timing(); + let cursor_move_started_at = + should_trace_frozen_selection_drag_timing.then(Instant::now); if self.should_ignore_live_auxiliary_cursor_event(window_id) { return OverlayControl::Continue; } @@ -3100,14 +3260,58 @@ impl OverlaySession { ); } - self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + let cursor_update_elapsed = if should_trace_frozen_selection_drag_timing { + let cursor_update_started_at = Instant::now(); + self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + + Some(cursor_update_started_at.elapsed()) + } else { + self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + + None + }; let previous_drag_rect = self.state.drag_rect; - self.update_live_drag_rect(monitor, global); - self.update_frozen_selection_drag_rect(global); - self.sync_overlay_cursor_icons(); - self.request_cursor_move_samples(monitor, global); + let live_drag_update_elapsed = if should_trace_frozen_selection_drag_timing { + let live_drag_update_started_at = Instant::now(); + self.update_live_drag_rect(monitor, global); + + Some(live_drag_update_started_at.elapsed()) + } else { + self.update_live_drag_rect(monitor, global); + + None + }; + let (frozen_rect_changed, frozen_drag_update_elapsed) = + if should_trace_frozen_selection_drag_timing { + let frozen_drag_update_started_at = Instant::now(); + let frozen_rect_changed = self.update_frozen_selection_drag_rect(global); + + (frozen_rect_changed, Some(frozen_drag_update_started_at.elapsed())) + } else { + (self.update_frozen_selection_drag_rect(global), None) + }; + let sync_cursor_icons_elapsed = if should_trace_frozen_selection_drag_timing { + let sync_cursor_icons_started_at = Instant::now(); + self.sync_overlay_cursor_icons(); + + Some(sync_cursor_icons_started_at.elapsed()) + } else { + self.sync_overlay_cursor_icons(); + + None + }; + let request_samples_elapsed = if should_trace_frozen_selection_drag_timing { + let request_samples_started_at = Instant::now(); + self.request_cursor_move_samples(monitor, global); + + Some(request_samples_started_at.elapsed()) + } else { + self.request_cursor_move_samples(monitor, global); + + None + }; if let Some(old_monitor) = old_monitor && old_monitor != monitor @@ -3124,6 +3328,25 @@ impl OverlaySession { self.request_redraw_for_monitor(monitor); } + if should_trace_frozen_selection_drag_timing { + self.trace_frozen_selection_drag_cursor_move( + monitor, + old_monitor, + old_cursor, + FrozenSelectionDragCursorMoveTiming { + cursor_update_elapsed: cursor_update_elapsed.unwrap_or_default(), + live_drag_update_elapsed: live_drag_update_elapsed.unwrap_or_default(), + frozen_drag_update_elapsed: frozen_drag_update_elapsed.unwrap_or_default(), + frozen_rect_changed, + sync_cursor_icons_elapsed: sync_cursor_icons_elapsed.unwrap_or_default(), + request_samples_elapsed: request_samples_elapsed.unwrap_or_default(), + total_elapsed: cursor_move_started_at.map_or(Duration::ZERO, |started_at| { + started_at.elapsed() + }), + }, + ); + } + OverlayControl::Continue } @@ -3181,6 +3404,42 @@ impl OverlaySession { ); } + fn trace_frozen_selection_drag_cursor_move( + &self, + monitor: MonitorRect, + old_monitor: Option, + old_cursor: Option, + timing: FrozenSelectionDragCursorMoveTiming, + ) { + if !self.should_trace_frozen_selection_drag_timing() { + return; + } + + tracing::trace!( + op = "overlay.frozen_selection_drag.cursor_move_timing", + monitor_id = monitor.id, + old_monitor_id = old_monitor.map(|target| target.id), + old_cursor = ?old_cursor, + cursor = ?self.state.cursor, + interaction = ?self.frozen_selection_drag.interaction, + frozen_rect_changed = timing.frozen_rect_changed, + cursor_update_us = timing.cursor_update_elapsed.as_micros(), + live_drag_update_us = timing.live_drag_update_elapsed.as_micros(), + frozen_drag_update_us = timing.frozen_drag_update_elapsed.as_micros(), + sync_cursor_icons_us = timing.sync_cursor_icons_elapsed.as_micros(), + request_samples_us = timing.request_samples_elapsed.as_micros(), + total_us = timing.total_elapsed.as_micros(), + overlay_window_count = self.windows.len(), + "Frozen selection drag cursor move timing." + ); + } + + fn should_trace_frozen_selection_drag_timing(&self) -> bool { + tracing::enabled!(tracing::Level::TRACE) + && matches!(self.state.mode, OverlayMode::Frozen) + && self.frozen_selection_drag.active + } + fn update_cursor_for_live_move( &mut self, old_monitor: Option, @@ -5341,8 +5600,10 @@ impl OverlaySession { self.scroll_preview_window = None; self.toolbar_inner_size_points = None; self.toolbar_outer_pos = None; + self.pending_toolbar_outer_pos = None; self.hud_window_visible = false; self.toolbar_window_visible = false; + self.skip_toolbar_focus_on_next_show = false; self.toolbar_window_warmup_redraws_remaining = 0; self.loupe_window_visible = false; self.loupe_window_warmup_redraws_remaining = 0; diff --git a/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs b/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs index 73757c00..a81a830b 100644 --- a/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs @@ -302,6 +302,7 @@ impl OverlaySession { self.maybe_apply_pending_hud_window_move(now); self.maybe_apply_pending_loupe_window_move(now); + self.maybe_apply_pending_toolbar_window_move(now); } pub(super) fn maybe_apply_pending_hud_window_move(&mut self, now: Instant) { @@ -354,6 +355,7 @@ impl OverlaySession { pub(super) fn force_apply_pending_hud_and_loupe_moves(&mut self) { self.force_apply_pending_hud_window_move(); self.force_apply_pending_loupe_window_move(); + self.force_apply_pending_toolbar_window_move(); } pub(super) fn maybe_apply_pending_loupe_window_move(&mut self, now: Instant) { @@ -410,6 +412,63 @@ impl OverlaySession { self.last_loupe_window_move_at = now; } + pub(super) fn maybe_apply_pending_toolbar_window_move(&mut self, now: Instant) { + self.apply_pending_toolbar_window_move(now, false); + } + + pub(super) fn force_apply_pending_toolbar_window_move(&mut self) { + self.apply_pending_toolbar_window_move(Instant::now(), true); + } + + pub(super) fn apply_pending_toolbar_window_move(&mut self, now: Instant, force: bool) { + let Some(desired) = self.pending_toolbar_outer_pos else { + return; + }; + if self.frozen_selection_drag_hides_auxiliary_windows() { + return; + } + let elapsed = now.duration_since(self.last_toolbar_window_move_at); + let interval = self + .repaint_interval_for_monitor(self.state.monitor.or(self.active_cursor_monitor())) + .max(HUD_LOUPE_MOVE_INTERVAL_MIN); + + if !force && elapsed < interval { + let delay = interval.saturating_sub(elapsed); + + self.schedule_egui_repaint_after(delay); + + return; + } + + let Some(toolbar_window) = self.toolbar_window.as_ref() else { + return; + }; + let started_at = Instant::now(); + + toolbar_window + .window + .set_outer_position(LogicalPosition::new(desired.x as f64, desired.y as f64)); + + let elapsed = started_at.elapsed(); + + self.slow_op_logger.warn_if_slow( + "overlay.toolbar_window_set_outer_position", + elapsed, + SLOW_OP_WARN_OUTER_POSITION, + || { + format!( + "window_id={:?} pos=({}, {})", + toolbar_window.window.id(), + desired.x, + desired.y + ) + }, + ); + + self.pending_toolbar_outer_pos = None; + self.last_toolbar_window_move_at = now; + } + pub(super) fn schedule_egui_repaint_after(&self, delay: Duration) { let deadline = Instant::now() + delay; let mut next_repaint = diff --git a/packages/rsnap-overlay/src/overlay/hud_runtime.rs b/packages/rsnap-overlay/src/overlay/hud_runtime.rs index 3d79e921..650132fc 100644 --- a/packages/rsnap-overlay/src/overlay/hud_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/hud_runtime.rs @@ -33,6 +33,19 @@ impl OverlaySession { } pub(super) fn maybe_skip_hud_redraw(&mut self) -> Option { + if self.frozen_selection_drag_hides_auxiliary_windows() { + if let Some(hud_window) = self.hud_window.as_ref() + && self.hud_window_visible + { + hud_window.window.set_visible(false); + } + + self.hud_window_visible = false; + self.last_present_at = Instant::now(); + + return Some(OverlayControl::Continue); + } + if self.scroll_capture.active { if let Some(hud_window) = self.hud_window.as_ref() && self.hud_window_visible @@ -327,7 +340,8 @@ impl OverlaySession { } pub(super) fn should_skip_loupe_redraw(&self) -> bool { - self.scroll_capture.active + self.frozen_selection_drag_hides_auxiliary_windows() + || self.scroll_capture.active || self.capture_windows_hidden || !self.state.alt_held || (matches!(self.state.mode, OverlayMode::Live) && self.live_loupe_uses_hud_window()) diff --git a/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs b/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs index d1cfcecc..52646f0b 100644 --- a/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs @@ -142,11 +142,13 @@ impl OverlaySession { } pub(super) fn handle_scroll_preview_redraw_requested(&mut self) -> OverlayControl { + let should_hide_preview = + self.frozen_selection_drag_hides_auxiliary_windows() || !self.scroll_capture.active; let Some(preview_window) = self.scroll_preview_window.as_mut() else { return OverlayControl::Continue; }; - if !self.scroll_capture.active { + if should_hide_preview { preview_window.window.set_visible(false); return OverlayControl::Continue; diff --git a/packages/rsnap-overlay/src/overlay/session_state.rs b/packages/rsnap-overlay/src/overlay/session_state.rs index 7fba85a1..33be83f4 100644 --- a/packages/rsnap-overlay/src/overlay/session_state.rs +++ b/packages/rsnap-overlay/src/overlay/session_state.rs @@ -118,6 +118,17 @@ pub(super) struct CursorMoveTrace { pub(super) source: DeviceCursorPointSource, } +#[derive(Clone, Copy)] +pub(super) struct FrozenSelectionDragCursorMoveTiming { + pub(super) cursor_update_elapsed: Duration, + pub(super) live_drag_update_elapsed: Duration, + pub(super) frozen_drag_update_elapsed: Duration, + pub(super) frozen_rect_changed: bool, + pub(super) sync_cursor_icons_elapsed: Duration, + pub(super) request_samples_elapsed: Duration, + pub(super) total_elapsed: Duration, +} + #[derive(Clone, Copy, Debug)] pub(super) struct HudDrawConfig { pub(super) can_draw_hud: bool, diff --git a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs index 7b800d52..92e18fbc 100644 --- a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs +++ b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs @@ -8,10 +8,10 @@ use winit::window::CursorIcon; use crate::OverlayControl; #[allow(unused_imports)] use crate::overlay::tests::{ - self, ElementState, FrozenCaptureSource, FrozenSelectionDragState, FrozenToolbarState, - FrozenToolbarTool, GlobalPoint, HUD_LOUPE_STRIP_GAP_POINTS, HudTheme, MonitorRect, - MonitorRectPoints, MouseButton, OverlayMode, OverlaySession, OverlayState, PngAction, Pos2, - RawInput, Rect, RectPoints, Rgba, SELECTION_DASHED_BORDER_DASH_LENGTH_PX, + self, Duration, ElementState, FrozenCaptureSource, FrozenSelectionDragState, + FrozenToolbarState, FrozenToolbarTool, GlobalPoint, HUD_LOUPE_STRIP_GAP_POINTS, HudTheme, + MonitorRect, MonitorRectPoints, MouseButton, OverlayMode, OverlaySession, OverlayState, + PngAction, Pos2, RawInput, Rect, RectPoints, Rgba, SELECTION_DASHED_BORDER_DASH_LENGTH_PX, SELECTION_DASHED_BORDER_GAP_LENGTH_PX, SELECTION_DASHED_BORDER_WIDTH_PX, SELECTION_SIZE_BADGE_GAP_PX, SELECTION_SIZE_BADGE_INSIDE_MARGIN_PX, SELECTION_SIZE_BADGE_SCREEN_MARGIN_PX, ScrollSession, SelectionDashedBorderCache, @@ -221,6 +221,90 @@ fn frozen_selection_drag_updates_capture_rect_and_toolbar_position() { assert_eq!(session.toolbar_state.floating_position, Some(expected_toolbar_pos)); } +#[test] +fn frozen_selection_drag_hides_auxiliary_windows_while_active() { + let monitor = tests::test_monitor(); + let capture_rect = RectPoints::new(100, 120, 200, 240); + let mut session = OverlaySession::new(); + + session.state.begin_freeze(monitor); + session.state.finish_freeze(monitor, tests::test_frozen_image()); + session.state.frozen_capture_rect = Some(capture_rect); + session.frozen_capture_source = FrozenCaptureSource::DragRegion; + session.toolbar_state.visible = true; + session.hud_window_visible = true; + session.loupe_window_visible = true; + session.toolbar_window_visible = true; + + assert!(!session.frozen_selection_drag_hides_auxiliary_windows()); + assert!(!session.should_hide_toolbar_window(monitor)); + + assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); + + assert!(session.frozen_selection_drag_hides_auxiliary_windows()); + assert!(!session.hud_window_visible); + assert!(!session.loupe_window_visible); + assert!(!session.toolbar_window_visible); + assert!(session.skip_toolbar_focus_on_next_show); + assert!(session.should_hide_toolbar_window(monitor)); + assert!(!session.should_focus_frozen_toolbar_window_on_show()); + + session.stop_frozen_selection_drag(); + + assert!(!session.frozen_selection_drag_hides_auxiliary_windows()); + assert!(!session.should_hide_toolbar_window(monitor)); + assert!(!session.should_focus_frozen_toolbar_window_on_show()); +} + +#[test] +fn frozen_selection_drag_defers_pending_toolbar_window_move() { + let monitor = tests::test_monitor(); + let capture_rect = RectPoints::new(100, 120, 200, 240); + let mut session = OverlaySession::new(); + + session.state.begin_freeze(monitor); + session.state.finish_freeze(monitor, tests::test_frozen_image()); + session.state.frozen_capture_rect = Some(capture_rect); + session.frozen_capture_source = FrozenCaptureSource::DragRegion; + + assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); + + let last_move_at = session.last_toolbar_window_move_at; + session.pending_toolbar_outer_pos = Some(GlobalPoint::new(220, 260)); + + session.maybe_apply_pending_toolbar_window_move(last_move_at + Duration::from_millis(32)); + + assert_eq!(session.pending_toolbar_outer_pos, Some(GlobalPoint::new(220, 260))); + assert_eq!(session.last_toolbar_window_move_at, last_move_at); +} + +#[test] +fn frozen_selection_drag_skips_toolbar_focus_even_before_first_show() { + let monitor = tests::test_monitor(); + let capture_rect = RectPoints::new(100, 120, 200, 240); + let mut session = OverlaySession::new(); + + session.state.begin_freeze(monitor); + session.state.finish_freeze(monitor, tests::test_frozen_image()); + session.state.frozen_capture_rect = Some(capture_rect); + session.frozen_capture_source = FrozenCaptureSource::DragRegion; + session.toolbar_state.visible = true; + + assert!(!session.toolbar_window_visible); + assert!(!session.skip_toolbar_focus_on_next_show); + assert!(session.should_focus_frozen_toolbar_window_on_show()); + + assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); + + assert!(session.skip_toolbar_focus_on_next_show); + assert!(!session.should_focus_frozen_toolbar_window_on_show()); + + session.stop_frozen_selection_drag(); + + assert!(session.skip_toolbar_focus_on_next_show); + assert!(!session.should_focus_frozen_toolbar_window_on_show()); +} + #[test] fn frozen_selection_resize_updates_capture_rect_and_toolbar_position() { let monitor = tests::test_monitor(); @@ -246,6 +330,19 @@ fn frozen_selection_resize_updates_capture_rect_and_toolbar_position() { assert_eq!(session.toolbar_state.floating_position, Some(expected_toolbar_pos)); } +#[test] +fn toolbar_position_update_queues_pending_move_without_window() { + let monitor = tests::test_monitor(); + let mut session = OverlaySession::new(); + + session.toolbar_inner_size_points = Some((460, 54)); + + assert!(session.update_toolbar_outer_position(monitor, Pos2::new(120.0, 160.0))); + assert_eq!(session.toolbar_state.floating_position, Some(Pos2::new(120.0, 160.0))); + assert_eq!(session.toolbar_outer_pos, Some(GlobalPoint::new(120, 160))); + assert_eq!(session.pending_toolbar_outer_pos, Some(GlobalPoint::new(120, 160))); +} + #[test] fn frozen_selection_resize_preserves_handle_press_offset() { let monitor = tests::test_monitor(); @@ -438,7 +535,7 @@ fn frozen_selection_cursor_icon_uses_corner_resize_hover() { session.state.cursor = Some(GlobalPoint::new(150, 180)); - assert_eq!(session.frozen_selection_cursor_icon_for_monitor(monitor), CursorIcon::Default); + assert_eq!(session.frozen_selection_cursor_icon_for_monitor(monitor), CursorIcon::Grab); } #[test] @@ -465,6 +562,30 @@ fn frozen_selection_cursor_icon_tracks_active_resize_drag() { assert_eq!(session.frozen_selection_cursor_icon_for_monitor(monitor), CursorIcon::SeResize); } +#[test] +fn frozen_selection_cursor_icon_tracks_active_move_drag() { + let monitor = tests::test_monitor(); + let capture_rect = RectPoints::new(100, 120, 200, 240); + let mut session = OverlaySession::new(); + + session.state.begin_freeze(monitor); + session.state.finish_freeze(monitor, tests::test_frozen_image()); + + session.state.frozen_capture_rect = Some(capture_rect); + session.frozen_capture_source = FrozenCaptureSource::DragRegion; + session.frozen_selection_drag = FrozenSelectionDragState { + active: true, + interaction: FrozenSelectionInteractionKind::Move, + anchor_rect: capture_rect, + pointer_offset_x: 50, + pointer_offset_y: 60, + press_cursor_x: 150, + press_cursor_y: 180, + }; + + assert_eq!(session.frozen_selection_cursor_icon_for_monitor(monitor), CursorIcon::Grabbing); +} + #[test] fn frozen_toolbar_default_position_centers_on_capture_rect_midpoint() { let monitor = tests::test_monitor_with_scale(400, 300, 2_000); diff --git a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs index d2aa57bf..519cc093 100644 --- a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs @@ -160,12 +160,20 @@ impl OverlaySession { } pub(super) fn should_hide_toolbar_window(&self, monitor: MonitorRect) -> bool { - !matches!(self.state.mode, OverlayMode::Frozen) + self.frozen_selection_drag_hides_auxiliary_windows() + || !matches!(self.state.mode, OverlayMode::Frozen) || !self.toolbar_state.visible || self.state.frozen_image.is_none() || self.state.monitor != Some(monitor) } + pub(super) fn should_focus_frozen_toolbar_window_on_show(&self) -> bool { + !self.toolbar_window_visible + && !self.skip_toolbar_focus_on_next_show + && matches!(self.state.mode, OverlayMode::Frozen) + && !self.scroll_capture.active + } + pub(super) fn set_toolbar_window_hidden(&mut self) { if let Some(toolbar_window) = self.toolbar_window.as_ref() { toolbar_window.window.set_visible(false); @@ -202,9 +210,7 @@ impl OverlaySession { } #[cfg(target_os = "macos")] { - let should_focus_frozen_keyboard = !self.toolbar_window_visible - && matches!(self.state.mode, OverlayMode::Frozen) - && !self.scroll_capture.active; + let should_focus_frozen_keyboard = self.should_focus_frozen_toolbar_window_on_show(); if !self.toolbar_window_visible { self.maybe_apply_pending_startup_aux_live_stream_filter_upgrade(monitor); @@ -221,6 +227,7 @@ impl OverlaySession { if !self.toolbar_window_visible { self.toolbar_window_visible = true; + self.skip_toolbar_focus_on_next_show = false; self.toolbar_window_warmup_redraws_remaining = TOOLBAR_WINDOW_WARMUP_REDRAWS; } if should_focus_frozen_keyboard { @@ -291,6 +298,7 @@ impl OverlaySession { } pub(super) fn handle_toolbar_window_redraw_requested(&mut self) -> OverlayControl { + let redraw_started_at = Instant::now(); self.event_loop_last_progress_window_id = self.toolbar_window.as_ref().map(|toolbar_window| toolbar_window.window.id()); self.event_loop_last_progress_monitor_id = self.state.monitor.map(|monitor| monitor.id); @@ -303,6 +311,7 @@ impl OverlaySession { }; let toolbar_input = self.toolbar_pointer_state(monitor, self.toolbar_pointer_local); let should_hide_toolbar_window = self.should_hide_toolbar_window(monitor); + let mut position_update_elapsed = None; if should_hide_toolbar_window { self.set_toolbar_window_hidden(); @@ -310,14 +319,19 @@ impl OverlaySession { return OverlayControl::Continue; } + let draw_frame_started_at = Instant::now(); if let Err(err) = self.draw_toolbar_window_frame(monitor, toolbar_input) { return self.exit(OverlayExit::Error(format!("{err:#}"))); } + let draw_frame_elapsed = draw_frame_started_at.elapsed(); self.update_scroll_toolbar_default_position(monitor); if let Some(toolbar_pos) = self.toolbar_state.floating_position { + let position_update_started_at = Instant::now(); let _ = self.update_toolbar_outer_position(monitor, toolbar_pos); + self.force_apply_pending_toolbar_window_move(); + position_update_elapsed = Some(position_update_started_at.elapsed()); } if let Some(action) = self.toolbar_state.pending_action.take() { let control = self.handle_toolbar_action(action); @@ -335,6 +349,22 @@ impl OverlaySession { self.request_redraw_toolbar_window(); } + if tracing::enabled!(tracing::Level::TRACE) + && matches!(self.state.mode, OverlayMode::Frozen) + { + tracing::trace!( + op = "overlay.toolbar_redraw_phase_timing", + monitor_id = monitor.id, + total_us = redraw_started_at.elapsed().as_micros(), + draw_frame_us = draw_frame_elapsed.as_micros(), + position_update_us = + position_update_elapsed.map_or(0, |elapsed| elapsed.as_micros()), + hidden = should_hide_toolbar_window, + frozen_selection_drag_active = self.frozen_selection_drag.active, + "Toolbar redraw phase timing." + ); + } + OverlayControl::Continue } } diff --git a/packages/rsnap-overlay/src/overlay/window_position_runtime.rs b/packages/rsnap-overlay/src/overlay/window_position_runtime.rs index 506ecd2c..3b47675e 100644 --- a/packages/rsnap-overlay/src/overlay/window_position_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/window_position_runtime.rs @@ -1,8 +1,7 @@ #[allow(unused_imports)] use crate::overlay::{ - GlobalPoint, HUD_LOUPE_STRIP_GAP_POINTS, Instant, LogicalPosition, MonitorRect, OverlayMode, - OverlaySession, Pos2, Rect, SLOW_OP_WARN_OUTER_POSITION, TOOLBAR_SCREEN_MARGIN_PX, Vec2, - WindowRenderer, + GlobalPoint, HUD_LOUPE_STRIP_GAP_POINTS, MonitorRect, OverlayMode, OverlaySession, Pos2, Rect, + TOOLBAR_SCREEN_MARGIN_PX, Vec2, WindowRenderer, }; impl OverlaySession { @@ -196,18 +195,17 @@ impl OverlaySession { monitor: MonitorRect, local_pos: Pos2, ) -> bool { - let Some(toolbar_window) = self.toolbar_window.as_ref() else { - return false; - }; - let toolbar_scale = toolbar_window.window.scale_factor().max(1.0); let toolbar_size = if let Some((width, height)) = self.toolbar_inner_size_points { Vec2::new(width as f32, height as f32) - } else { + } else if let Some(toolbar_window) = self.toolbar_window.as_ref() { + let toolbar_scale = toolbar_window.window.scale_factor().max(1.0); let size = toolbar_window.window.inner_size(); let toolbar_w = ((size.width as f64) / toolbar_scale).ceil().max(1.0) as f32; let toolbar_h = ((size.height as f64) / toolbar_scale).ceil().max(1.0) as f32; Vec2::new(toolbar_w, toolbar_h) + } else { + WindowRenderer::frozen_toolbar_size(&self.toolbar_state) }; let screen_rect = Rect::from_min_size(Pos2::ZERO, Vec2::new(monitor.width as f32, monitor.height as f32)); @@ -228,28 +226,9 @@ impl OverlaySession { } self.toolbar_outer_pos = Some(desired); + self.pending_toolbar_outer_pos = Some(desired); self.toolbar_state.floating_position = Some(clamped_local_pos); - let started_at = Instant::now(); - - toolbar_window - .window - .set_outer_position(LogicalPosition::new(desired.x as f64, desired.y as f64)); - self.slow_op_logger.warn_if_slow( - "overlay.toolbar_window_set_outer_position", - started_at.elapsed(), - SLOW_OP_WARN_OUTER_POSITION, - || { - format!( - "window_id={:?} pos=({}, {})", - toolbar_window.window.id(), - desired.x, - desired.y - ) - }, - ); - toolbar_window.window.request_redraw(); - true } } diff --git a/packages/rsnap-overlay/src/overlay/window_runtime.rs b/packages/rsnap-overlay/src/overlay/window_runtime.rs index 3da37d76..e4872077 100644 --- a/packages/rsnap-overlay/src/overlay/window_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/window_runtime.rs @@ -823,6 +823,38 @@ impl OverlaySession { } } + if self.frozen_selection_drag_hides_auxiliary_windows() { + return; + } + + let request_toolbar_window = cfg!(target_os = "macos") + && matches!(self.state.mode, OverlayMode::Frozen) + && self.toolbar_state.visible + && self.state.monitor == Some(monitor) + && self.state.frozen_image.is_some(); + + if tracing::enabled!(tracing::Level::TRACE) + && matches!(self.state.mode, OverlayMode::Frozen) + && self.frozen_selection_drag.active + && self.state.monitor == Some(monitor) + { + let overlay_windows = + self.windows.values().filter(|window| window.monitor == monitor).count(); + + tracing::trace!( + op = "overlay.frozen_selection_drag.redraw_fanout", + monitor_id = monitor.id, + overlay_window_count = overlay_windows, + request_hud_window = self.hud_window.is_some(), + request_loupe_window = self.loupe_window.is_some(), + request_toolbar_window, + request_scroll_preview_window = self.scroll_preview_window.is_some(), + scroll_capture_active = self.scroll_capture.active, + alt_held = self.state.alt_held, + "Requested redraw fan-out for frozen selection drag." + ); + } + if let Some(hud) = self.hud_window.as_ref() { hud.window.request_redraw(); } @@ -834,12 +866,7 @@ impl OverlaySession { // toolbar redraw on the fullscreen overlay path disabled for this platform. // Future direction: if toolbar styling moves off native blur, add a dedicated capture // pass feeding a toolbar-local shader-blur texture. - if cfg!(target_os = "macos") - && matches!(self.state.mode, OverlayMode::Frozen) - && self.toolbar_state.visible - && self.state.monitor == Some(monitor) - && self.state.frozen_image.is_some() - { + if request_toolbar_window { self.request_redraw_toolbar_window(); } @@ -847,24 +874,40 @@ impl OverlaySession { } pub(super) fn request_redraw_hud_window(&self) { + if self.frozen_selection_drag_hides_auxiliary_windows() { + return; + } + if let Some(hud) = self.hud_window.as_ref() { hud.window.request_redraw(); } } pub(super) fn request_redraw_toolbar_window(&self) { + if self.frozen_selection_drag_hides_auxiliary_windows() { + return; + } + if let Some(toolbar) = self.toolbar_window.as_ref() { toolbar.window.request_redraw(); } } pub(super) fn request_redraw_loupe_window(&self) { + if self.frozen_selection_drag_hides_auxiliary_windows() { + return; + } + if let Some(loupe) = self.loupe_window.as_ref() { loupe.window.request_redraw(); } } pub(super) fn request_redraw_scroll_preview_window(&self) { + if self.frozen_selection_drag_hides_auxiliary_windows() { + return; + } + if let Some(preview) = self.scroll_preview_window.as_ref() { preview.window.request_redraw(); } From 17aaef1d6f7ac49d4ff12e31dc59dd4c1343f2eb Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Mon, 6 Apr 2026 21:33:24 +0800 Subject: [PATCH 2/6] {"schema":"maestro/commit/1","summary":"fix linux lint for frozen toolbar focus helper","authority":"manual","breaking":false} --- packages/rsnap-overlay/src/overlay/toolbar_runtime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs index 519cc093..eeb355de 100644 --- a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs @@ -167,6 +167,7 @@ impl OverlaySession { || self.state.monitor != Some(monitor) } + #[cfg(target_os = "macos")] pub(super) fn should_focus_frozen_toolbar_window_on_show(&self) -> bool { !self.toolbar_window_visible && !self.skip_toolbar_focus_on_next_show From fd6136072ed9005b4e91fea3db50f80cfc56e5d8 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Mon, 6 Apr 2026 21:36:19 +0800 Subject: [PATCH 3/6] {"schema":"maestro/commit/1","summary":"restore toolbar focus helper in test builds","authority":"manual","breaking":false} --- packages/rsnap-overlay/src/overlay/toolbar_runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs index eeb355de..33cb3f4d 100644 --- a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs @@ -167,7 +167,7 @@ impl OverlaySession { || self.state.monitor != Some(monitor) } - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", test))] pub(super) fn should_focus_frozen_toolbar_window_on_show(&self) -> bool { !self.toolbar_window_visible && !self.skip_toolbar_focus_on_next_show From 86c0369446a7cd773512f78f57902ac61bb4e1f4 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Mon, 6 Apr 2026 21:47:03 +0800 Subject: [PATCH 4/6] {"schema":"maestro/commit/1","summary":"fix rust checks for selection drag changes","authority":"manual","breaking":false} --- packages/rsnap-overlay/src/overlay.rs | 219 ++++++------------ .../src/overlay/aux_window_runtime.rs | 2 + .../rsnap-overlay/src/overlay/hud_runtime.rs | 1 - .../src/overlay/tests/rendering_behaviors.rs | 8 +- .../src/overlay/toolbar_runtime.rs | 6 +- 5 files changed, 88 insertions(+), 148 deletions(-) diff --git a/packages/rsnap-overlay/src/overlay.rs b/packages/rsnap-overlay/src/overlay.rs index 5d35df2b..8d81d797 100644 --- a/packages/rsnap-overlay/src/overlay.rs +++ b/packages/rsnap-overlay/src/overlay.rs @@ -1681,6 +1681,7 @@ impl OverlaySession { press_cursor_x: cursor_x, press_cursor_y: cursor_y, }; + self.hide_auxiliary_windows_for_frozen_selection_drag(); true @@ -1766,17 +1767,21 @@ impl OverlaySession { if let Some(hud_window) = self.hud_window.as_ref() { hud_window.window.set_visible(false); } + self.hud_window_visible = false; if let Some(loupe_window) = self.loupe_window.as_ref() { loupe_window.window.set_visible(false); } + self.loupe_window_visible = false; + self.reset_loupe_window_warmup_redraws(); if let Some(toolbar_window) = self.toolbar_window.as_ref() { toolbar_window.window.set_visible(false); } + self.skip_toolbar_focus_on_next_show = true; self.toolbar_window_visible = false; self.toolbar_window_warmup_redraws_remaining = 0; @@ -1984,12 +1989,12 @@ impl OverlaySession { None }; - let redraw_request_elapsed = if should_trace_frozen_selection_drag_timing { let redraw_request_started_at = Instant::now(); self.request_redraw_for_monitor(monitor); self.request_redraw_toolbar_window(); + if self.scroll_capture.active { self.request_redraw_scroll_preview_window(); } @@ -1998,6 +2003,7 @@ impl OverlaySession { } else { self.request_redraw_for_monitor(monitor); self.request_redraw_toolbar_window(); + if self.scroll_capture.active { self.request_redraw_scroll_preview_window(); } @@ -3095,8 +3101,7 @@ impl OverlaySession { ) -> OverlayControl { let should_trace_frozen_selection_drag_timing = self.should_trace_frozen_selection_drag_timing(); - let cursor_move_started_at = - should_trace_frozen_selection_drag_timing.then(Instant::now); + let cursor_move_started_at = should_trace_frozen_selection_drag_timing.then(Instant::now); let old_monitor = self.active_cursor_monitor(); let now = Instant::now(); let Some(overlay_window) = self.windows.get(&window_id) else { @@ -3135,91 +3140,18 @@ impl OverlaySession { }; self.trace_cursor_moved_with_mapping(trace); - let cursor_update_elapsed = if should_trace_frozen_selection_drag_timing { - let cursor_update_started_at = Instant::now(); - self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); - - Some(cursor_update_started_at.elapsed()) - } else { - self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); - - None - }; - - let previous_drag_rect = self.state.drag_rect; - - let live_drag_update_elapsed = if should_trace_frozen_selection_drag_timing { - let live_drag_update_started_at = Instant::now(); - self.update_live_drag_rect(monitor, global); - - Some(live_drag_update_started_at.elapsed()) - } else { - self.update_live_drag_rect(monitor, global); - - None - }; - let (frozen_rect_changed, frozen_drag_update_elapsed) = - if should_trace_frozen_selection_drag_timing { - let frozen_drag_update_started_at = Instant::now(); - let frozen_rect_changed = self.update_frozen_selection_drag_rect(global); - (frozen_rect_changed, Some(frozen_drag_update_started_at.elapsed())) - } else { - (self.update_frozen_selection_drag_rect(global), None) - }; - let sync_cursor_icons_elapsed = if should_trace_frozen_selection_drag_timing { - let sync_cursor_icons_started_at = Instant::now(); - self.sync_overlay_cursor_icons(); - - Some(sync_cursor_icons_started_at.elapsed()) - } else { - self.sync_overlay_cursor_icons(); - - None - }; - let request_samples_elapsed = if should_trace_frozen_selection_drag_timing { - let request_samples_started_at = Instant::now(); - self.request_cursor_move_samples(monitor, global); - - Some(request_samples_started_at.elapsed()) - } else { - self.request_cursor_move_samples(monitor, global); - - None - }; - - if let Some(old_monitor) = old_monitor - && old_monitor != monitor - { - self.request_redraw_for_monitor(old_monitor); - } - - if Self::live_overlay_redraw_needed_for_cursor_update( + let timing = self.run_cursor_move_updates( + should_trace_frozen_selection_drag_timing, + cursor_move_started_at, old_monitor, + old_cursor, monitor, - previous_drag_rect, - self.state.drag_rect, - ) { - self.request_redraw_for_monitor(monitor); - } + global, + ); if should_trace_frozen_selection_drag_timing { - self.trace_frozen_selection_drag_cursor_move( - monitor, - old_monitor, - old_cursor, - FrozenSelectionDragCursorMoveTiming { - cursor_update_elapsed: cursor_update_elapsed.unwrap_or_default(), - live_drag_update_elapsed: live_drag_update_elapsed.unwrap_or_default(), - frozen_drag_update_elapsed: frozen_drag_update_elapsed.unwrap_or_default(), - frozen_rect_changed, - sync_cursor_icons_elapsed: sync_cursor_icons_elapsed.unwrap_or_default(), - request_samples_elapsed: request_samples_elapsed.unwrap_or_default(), - total_elapsed: cursor_move_started_at.map_or(Duration::ZERO, |started_at| { - started_at.elapsed() - }), - }, - ); + self.trace_frozen_selection_drag_cursor_move(monitor, old_monitor, old_cursor, timing); } OverlayControl::Continue @@ -3232,8 +3164,8 @@ impl OverlaySession { ) -> OverlayControl { let should_trace_frozen_selection_drag_timing = self.should_trace_frozen_selection_drag_timing(); - let cursor_move_started_at = - should_trace_frozen_selection_drag_timing.then(Instant::now); + let cursor_move_started_at = should_trace_frozen_selection_drag_timing.then(Instant::now); + if self.should_ignore_live_auxiliary_cursor_event(window_id) { return OverlayControl::Continue; } @@ -3260,29 +3192,40 @@ impl OverlaySession { ); } - let cursor_update_elapsed = if should_trace_frozen_selection_drag_timing { - let cursor_update_started_at = Instant::now(); - self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + let timing = self.run_cursor_move_updates( + should_trace_frozen_selection_drag_timing, + cursor_move_started_at, + old_monitor, + old_cursor, + monitor, + global, + ); - Some(cursor_update_started_at.elapsed()) - } else { - self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global); + if should_trace_frozen_selection_drag_timing { + self.trace_frozen_selection_drag_cursor_move(monitor, old_monitor, old_cursor, timing); + } - None - }; + OverlayControl::Continue + } + fn run_cursor_move_updates( + &mut self, + should_trace_frozen_selection_drag_timing: bool, + cursor_move_started_at: Option, + old_monitor: Option, + old_cursor: Option, + monitor: MonitorRect, + global: GlobalPoint, + ) -> FrozenSelectionDragCursorMoveTiming { + let cursor_update_elapsed = + Self::measure_duration_if(should_trace_frozen_selection_drag_timing, || { + self.update_cursor_for_live_move(old_monitor, old_cursor, monitor, global) + }); let previous_drag_rect = self.state.drag_rect; - - let live_drag_update_elapsed = if should_trace_frozen_selection_drag_timing { - let live_drag_update_started_at = Instant::now(); - self.update_live_drag_rect(monitor, global); - - Some(live_drag_update_started_at.elapsed()) - } else { - self.update_live_drag_rect(monitor, global); - - None - }; + let live_drag_update_elapsed = + Self::measure_duration_if(should_trace_frozen_selection_drag_timing, || { + self.update_live_drag_rect(monitor, global); + }); let (frozen_rect_changed, frozen_drag_update_elapsed) = if should_trace_frozen_selection_drag_timing { let frozen_drag_update_started_at = Instant::now(); @@ -3292,26 +3235,14 @@ impl OverlaySession { } else { (self.update_frozen_selection_drag_rect(global), None) }; - let sync_cursor_icons_elapsed = if should_trace_frozen_selection_drag_timing { - let sync_cursor_icons_started_at = Instant::now(); - self.sync_overlay_cursor_icons(); - - Some(sync_cursor_icons_started_at.elapsed()) - } else { - self.sync_overlay_cursor_icons(); - - None - }; - let request_samples_elapsed = if should_trace_frozen_selection_drag_timing { - let request_samples_started_at = Instant::now(); - self.request_cursor_move_samples(monitor, global); - - Some(request_samples_started_at.elapsed()) - } else { - self.request_cursor_move_samples(monitor, global); - - None - }; + let sync_cursor_icons_elapsed = + Self::measure_duration_if(should_trace_frozen_selection_drag_timing, || { + self.sync_overlay_cursor_icons(); + }); + let request_samples_elapsed = + Self::measure_duration_if(should_trace_frozen_selection_drag_timing, || { + self.request_cursor_move_samples(monitor, global); + }); if let Some(old_monitor) = old_monitor && old_monitor != monitor @@ -3328,26 +3259,30 @@ impl OverlaySession { self.request_redraw_for_monitor(monitor); } - if should_trace_frozen_selection_drag_timing { - self.trace_frozen_selection_drag_cursor_move( - monitor, - old_monitor, - old_cursor, - FrozenSelectionDragCursorMoveTiming { - cursor_update_elapsed: cursor_update_elapsed.unwrap_or_default(), - live_drag_update_elapsed: live_drag_update_elapsed.unwrap_or_default(), - frozen_drag_update_elapsed: frozen_drag_update_elapsed.unwrap_or_default(), - frozen_rect_changed, - sync_cursor_icons_elapsed: sync_cursor_icons_elapsed.unwrap_or_default(), - request_samples_elapsed: request_samples_elapsed.unwrap_or_default(), - total_elapsed: cursor_move_started_at.map_or(Duration::ZERO, |started_at| { - started_at.elapsed() - }), - }, - ); + FrozenSelectionDragCursorMoveTiming { + cursor_update_elapsed: cursor_update_elapsed.unwrap_or_default(), + live_drag_update_elapsed: live_drag_update_elapsed.unwrap_or_default(), + frozen_drag_update_elapsed: frozen_drag_update_elapsed.unwrap_or_default(), + frozen_rect_changed, + sync_cursor_icons_elapsed: sync_cursor_icons_elapsed.unwrap_or_default(), + request_samples_elapsed: request_samples_elapsed.unwrap_or_default(), + total_elapsed: cursor_move_started_at + .map_or(Duration::ZERO, |started_at| started_at.elapsed()), } + } - OverlayControl::Continue + fn measure_duration_if(enabled: bool, operation: impl FnOnce()) -> Option { + if enabled { + let started_at = Instant::now(); + + operation(); + + Some(started_at.elapsed()) + } else { + operation(); + + None + } } fn should_ignore_live_auxiliary_cursor_event(&self, window_id: WindowId) -> bool { diff --git a/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs b/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs index a81a830b..1c1b886b 100644 --- a/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/aux_window_runtime.rs @@ -424,9 +424,11 @@ impl OverlaySession { let Some(desired) = self.pending_toolbar_outer_pos else { return; }; + if self.frozen_selection_drag_hides_auxiliary_windows() { return; } + let elapsed = now.duration_since(self.last_toolbar_window_move_at); let interval = self .repaint_interval_for_monitor(self.state.monitor.or(self.active_cursor_monitor())) diff --git a/packages/rsnap-overlay/src/overlay/hud_runtime.rs b/packages/rsnap-overlay/src/overlay/hud_runtime.rs index 650132fc..7795ea17 100644 --- a/packages/rsnap-overlay/src/overlay/hud_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/hud_runtime.rs @@ -45,7 +45,6 @@ impl OverlaySession { return Some(OverlayControl::Continue); } - if self.scroll_capture.active { if let Some(hud_window) = self.hud_window.as_ref() && self.hud_window_visible diff --git a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs index 92e18fbc..e2431029 100644 --- a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs +++ b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs @@ -229,6 +229,7 @@ fn frozen_selection_drag_hides_auxiliary_windows_while_active() { session.state.begin_freeze(monitor); session.state.finish_freeze(monitor, tests::test_frozen_image()); + session.state.frozen_capture_rect = Some(capture_rect); session.frozen_capture_source = FrozenCaptureSource::DragRegion; session.toolbar_state.visible = true; @@ -238,9 +239,7 @@ fn frozen_selection_drag_hides_auxiliary_windows_while_active() { assert!(!session.frozen_selection_drag_hides_auxiliary_windows()); assert!(!session.should_hide_toolbar_window(monitor)); - assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); - assert!(session.frozen_selection_drag_hides_auxiliary_windows()); assert!(!session.hud_window_visible); assert!(!session.loupe_window_visible); @@ -264,12 +263,14 @@ fn frozen_selection_drag_defers_pending_toolbar_window_move() { session.state.begin_freeze(monitor); session.state.finish_freeze(monitor, tests::test_frozen_image()); + session.state.frozen_capture_rect = Some(capture_rect); session.frozen_capture_source = FrozenCaptureSource::DragRegion; assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); let last_move_at = session.last_toolbar_window_move_at; + session.pending_toolbar_outer_pos = Some(GlobalPoint::new(220, 260)); session.maybe_apply_pending_toolbar_window_move(last_move_at + Duration::from_millis(32)); @@ -286,6 +287,7 @@ fn frozen_selection_drag_skips_toolbar_focus_even_before_first_show() { session.state.begin_freeze(monitor); session.state.finish_freeze(monitor, tests::test_frozen_image()); + session.state.frozen_capture_rect = Some(capture_rect); session.frozen_capture_source = FrozenCaptureSource::DragRegion; session.toolbar_state.visible = true; @@ -293,9 +295,7 @@ fn frozen_selection_drag_skips_toolbar_focus_even_before_first_show() { assert!(!session.toolbar_window_visible); assert!(!session.skip_toolbar_focus_on_next_show); assert!(session.should_focus_frozen_toolbar_window_on_show()); - assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); - assert!(session.skip_toolbar_focus_on_next_show); assert!(!session.should_focus_frozen_toolbar_window_on_show()); diff --git a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs index 33cb3f4d..a5c4a22e 100644 --- a/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/toolbar_runtime.rs @@ -300,6 +300,7 @@ impl OverlaySession { pub(super) fn handle_toolbar_window_redraw_requested(&mut self) -> OverlayControl { let redraw_started_at = Instant::now(); + self.event_loop_last_progress_window_id = self.toolbar_window.as_ref().map(|toolbar_window| toolbar_window.window.id()); self.event_loop_last_progress_monitor_id = self.state.monitor.map(|monitor| monitor.id); @@ -321,9 +322,11 @@ impl OverlaySession { } let draw_frame_started_at = Instant::now(); + if let Err(err) = self.draw_toolbar_window_frame(monitor, toolbar_input) { return self.exit(OverlayExit::Error(format!("{err:#}"))); } + let draw_frame_elapsed = draw_frame_started_at.elapsed(); self.update_scroll_toolbar_default_position(monitor); @@ -331,7 +334,9 @@ impl OverlaySession { if let Some(toolbar_pos) = self.toolbar_state.floating_position { let position_update_started_at = Instant::now(); let _ = self.update_toolbar_outer_position(monitor, toolbar_pos); + self.force_apply_pending_toolbar_window_move(); + position_update_elapsed = Some(position_update_started_at.elapsed()); } if let Some(action) = self.toolbar_state.pending_action.take() { @@ -349,7 +354,6 @@ impl OverlaySession { self.request_redraw_toolbar_window(); } - if tracing::enabled!(tracing::Level::TRACE) && matches!(self.state.mode, OverlayMode::Frozen) { From e11d0ac9f4669a6afde41106b2981ac59ff8a84f Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Mon, 6 Apr 2026 22:08:14 +0800 Subject: [PATCH 5/6] {"schema":"maestro/commit/1","summary":"restore scroll preview after drag overlap","authority":"manual","breaking":false} --- .../src/overlay/scroll_preview_runtime.rs | 9 +++++-- .../src/overlay/tests/rendering_behaviors.rs | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs b/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs index 52646f0b..79e3bb27 100644 --- a/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/scroll_preview_runtime.rs @@ -142,8 +142,7 @@ impl OverlaySession { } pub(super) fn handle_scroll_preview_redraw_requested(&mut self) -> OverlayControl { - let should_hide_preview = - self.frozen_selection_drag_hides_auxiliary_windows() || !self.scroll_capture.active; + let should_hide_preview = self.should_hide_scroll_preview_window(); let Some(preview_window) = self.scroll_preview_window.as_mut() else { return OverlayControl::Continue; }; @@ -154,6 +153,8 @@ impl OverlaySession { return OverlayControl::Continue; } + preview_window.window.set_visible(true); + let theme = hud_helpers::effective_hud_theme(self.config.theme_mode, preview_window.window.theme()); let view = ScrollPreviewView { paused: self.scroll_capture.paused, theme }; @@ -167,6 +168,10 @@ impl OverlaySession { } } + pub(super) fn should_hide_scroll_preview_window(&self) -> bool { + self.frozen_selection_drag_hides_auxiliary_windows() || !self.scroll_capture.active + } + #[cfg(target_os = "macos")] pub(super) fn position_scroll_preview_window(&self, monitor: MonitorRect) { let Some(preview_window) = self.scroll_preview_window.as_ref() else { diff --git a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs index e2431029..5ae47814 100644 --- a/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs +++ b/packages/rsnap-overlay/src/overlay/tests/rendering_behaviors.rs @@ -239,6 +239,7 @@ fn frozen_selection_drag_hides_auxiliary_windows_while_active() { assert!(!session.frozen_selection_drag_hides_auxiliary_windows()); assert!(!session.should_hide_toolbar_window(monitor)); + assert!(session.should_hide_scroll_preview_window()); assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); assert!(session.frozen_selection_drag_hides_auxiliary_windows()); assert!(!session.hud_window_visible); @@ -255,6 +256,29 @@ fn frozen_selection_drag_hides_auxiliary_windows_while_active() { assert!(!session.should_focus_frozen_toolbar_window_on_show()); } +#[test] +fn frozen_selection_drag_releases_scroll_preview_hide_after_drag_stops() { + let monitor = tests::test_monitor(); + let capture_rect = RectPoints::new(100, 120, 200, 240); + let mut session = OverlaySession::new(); + + session.state.begin_freeze(monitor); + session.state.finish_freeze(monitor, tests::test_frozen_image()); + + session.state.frozen_capture_rect = Some(capture_rect); + session.frozen_capture_source = FrozenCaptureSource::DragRegion; + + assert!(session.begin_frozen_selection_drag(GlobalPoint::new(150, 180))); + + session.scroll_capture.active = true; + + assert!(session.should_hide_scroll_preview_window()); + + session.stop_frozen_selection_drag(); + + assert!(!session.should_hide_scroll_preview_window()); +} + #[test] fn frozen_selection_drag_defers_pending_toolbar_window_move() { let monitor = tests::test_monitor(); From 65850a978c55b35bb6a0dd6e2121177f539d89fb Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Mon, 6 Apr 2026 22:23:14 +0800 Subject: [PATCH 6/6] {"schema":"maestro/commit/1","summary":"restore frozen drag redraw fanout tracing","authority":"manual","breaking":false} --- .../src/overlay/window_runtime.rs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/rsnap-overlay/src/overlay/window_runtime.rs b/packages/rsnap-overlay/src/overlay/window_runtime.rs index e4872077..46d24b50 100644 --- a/packages/rsnap-overlay/src/overlay/window_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/window_runtime.rs @@ -823,15 +823,17 @@ impl OverlaySession { } } - if self.frozen_selection_drag_hides_auxiliary_windows() { - return; - } - - let request_toolbar_window = cfg!(target_os = "macos") + let hide_auxiliary_windows = self.frozen_selection_drag_hides_auxiliary_windows(); + let request_hud_window = !hide_auxiliary_windows && self.hud_window.is_some(); + let request_loupe_window = !hide_auxiliary_windows && self.loupe_window.is_some(); + let request_toolbar_window = !hide_auxiliary_windows + && cfg!(target_os = "macos") && matches!(self.state.mode, OverlayMode::Frozen) && self.toolbar_state.visible && self.state.monitor == Some(monitor) && self.state.frozen_image.is_some(); + let request_scroll_preview_window = + !hide_auxiliary_windows && self.scroll_preview_window.is_some(); if tracing::enabled!(tracing::Level::TRACE) && matches!(self.state.mode, OverlayMode::Frozen) @@ -845,23 +847,25 @@ impl OverlaySession { op = "overlay.frozen_selection_drag.redraw_fanout", monitor_id = monitor.id, overlay_window_count = overlay_windows, - request_hud_window = self.hud_window.is_some(), - request_loupe_window = self.loupe_window.is_some(), + request_hud_window, + request_loupe_window, request_toolbar_window, - request_scroll_preview_window = self.scroll_preview_window.is_some(), + request_scroll_preview_window, + hide_auxiliary_windows, scroll_capture_active = self.scroll_capture.active, alt_held = self.state.alt_held, "Requested redraw fan-out for frozen selection drag." ); } - - if let Some(hud) = self.hud_window.as_ref() { + if hide_auxiliary_windows { + return; + } + if request_hud_window && let Some(hud) = self.hud_window.as_ref() { hud.window.request_redraw(); } - if let Some(loupe) = self.loupe_window.as_ref() { + if request_loupe_window && let Some(loupe) = self.loupe_window.as_ref() { loupe.window.request_redraw(); } - // macOS uses a native toolbar popup window with compositor blur; keep shader-viewport // toolbar redraw on the fullscreen overlay path disabled for this platform. // Future direction: if toolbar styling moves off native blur, add a dedicated capture @@ -869,8 +873,9 @@ impl OverlaySession { if request_toolbar_window { self.request_redraw_toolbar_window(); } - - self.request_redraw_scroll_preview_window(); + if request_scroll_preview_window { + self.request_redraw_scroll_preview_window(); + } } pub(super) fn request_redraw_hud_window(&self) {