ltk/event_loop/drag.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
use super::app_data::AppData;
use super::surface::{ SurfaceFocus, SurfaceState };
use crate::app::App;
use crate::types::Point;
impl<A: App> AppData<A>
{
/// Fire long-press messages for any surface whose deadline has
/// elapsed. Idempotent — if a press is in-flight but not yet due this
/// does nothing. On fire, the message is pushed to `pending_msgs`,
/// `long_press_fired` is set, and the per-surface long-press slot is
/// cleared so the same press can't fire twice.
pub( crate ) fn check_long_press_deadlines( &mut self )
{
let dur = self.app.long_press_duration();
let now = std::time::Instant::now();
// Returns `(lp_msg_opt, ds_msg_opt, origin, text_idx_opt)`:
// - `lp_msg_opt = Some` → user-set on_long_press fires; push msg
// (opens the context menu in the app).
// - `ds_msg_opt = Some` → user-set on_drag_start fires; push
// msg + seed `on_drag_move(origin)` so the drag arms with
// the right anchor point. Also flips `long_press_fired` so
// subsequent motion / release route through drag / drop.
// - `text_idx_opt = Some` → press was on a TextEdit with
// neither user msg; the runtime opens the built-in Copy /
// Cut / Paste menu instead.
// Any combination can be present: an icon with both menu and
// drag fires both messages in order (menu first, then drag).
let fire = |ss: &mut SurfaceState<A::Message>|
-> Option<( Option<A::Message>, Option<A::Message>, Point, Option<usize> )>
{
let start = ss.gesture.long_press_start?;
if now.duration_since( start ) < dur { return None; }
ss.gesture.long_press_start = None;
let origin = ss.gesture.long_press_origin.unwrap_or_default();
let lp_msg = ss.gesture.long_press_msg.take();
let ds_msg = ss.gesture.drag_start_msg.take();
let text_idx = ss.gesture.long_press_text_idx.take();
if lp_msg.is_none() && ds_msg.is_none() && text_idx.is_none() { return None; }
// Only flip "fired" when the gesture actually transitions
// into drag mode. A menu-only widget (no on_drag_start)
// stays in tap mode, so a release after the menu fires
// still goes through `on_release` cleanly. The built-in
// text menu likewise does not switch to drag, so a
// subsequent motion can still extend a selection.
if ds_msg.is_some() { ss.gesture.long_press_fired = true; }
ss.request_redraw();
Some( ( lp_msg, ds_msg, origin, text_idx ) )
};
// Capture per-surface fires, then post-process so the borrow
// of `self.main` / `self.overlays` is released before we
// call menu helpers (which take &mut self again).
let main_fire = fire( &mut self.main ).map( |x| ( SurfaceFocus::Main, x ) );
let overlay_fires: Vec<_> = self.overlays.iter_mut()
.filter_map( |( id, ss )| fire( ss ).map( |x| ( SurfaceFocus::Overlay( *id ), x ) ) )
.collect();
let mut consumed_anything = false;
for ( focus, ( lp_msg, ds_msg, origin, text_idx ) ) in main_fire.into_iter().chain( overlay_fires )
{
// Push menu first so the app sets up `edit_menu` before
// the drag-arm message lands and starts consuming
// `on_drag_move` callbacks.
if let Some( m ) = lp_msg
{
self.pending_msgs.push( m );
consumed_anything = true;
}
if let Some( m ) = ds_msg
{
self.pending_msgs.push( m );
let ( ox, oy ) = self.surface_offset_for( focus );
let global = Point { x: origin.x + ox, y: origin.y + oy };
self.pending_drag_inits.push( global );
// Drag promotion cancels any held-button repeat — the
// gesture has switched semantics and the timer has
// nothing to fire against any more.
self.stop_button_repeat();
consumed_anything = true;
}
if let Some( idx ) = text_idx
{
// Built-in Copy / Cut / Paste menu near the press.
self.show_context_menu( focus, idx, origin );
}
}
if consumed_anything { self.dirty_caches(); }
}
pub( crate ) fn has_active_long_press_drag( &self ) -> bool
{
self.main.gesture.long_press_fired
|| self.overlays.values().any( |ss| ss.gesture.long_press_fired )
}
pub( crate ) fn clear_long_press_drag( &mut self )
{
let clear = |ss: &mut SurfaceState<A::Message>|
{
ss.gesture.long_press_start = None;
ss.gesture.long_press_origin = None;
ss.gesture.long_press_msg = None;
ss.gesture.drag_start_msg = None;
ss.gesture.long_press_text_idx = None;
ss.gesture.long_press_fired = false;
ss.gesture.mouse_press = false;
};
clear( &mut self.main );
for ss in self.overlays.values_mut()
{
clear( ss );
}
}
}