ltk/input/repeat/
button.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
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

use calloop::timer::{ Timer, TimeoutAction };

use crate::app::App;
use crate::event_loop::{ AppData, SurfaceFocus };
use crate::event_loop::repeat::ButtonRepeatState;

impl<A: App> AppData<A>
{
	/// Schedule a button-press repeat timer. The runtime fires the
	/// `on_press` message *immediately* on press too — this fn only
	/// arms the held-down repeat path; the first fire happens at
	/// the call site so a quick tap still registers as a single
	/// press.
	///
	/// Each timer tick re-reads the live `on_press` from the
	/// current widget tree (via the snapshotted `flat_idx`) rather
	/// than replaying a captured message. This is what makes
	/// stepper-style buttons work: a stepper builds an `on_press`
	/// like `"go to value + 5"` at view-build time, so replaying
	/// the press-time snapshot after the first fire would re-issue
	/// the same target and the value would freeze. By reading
	/// `on_press` afresh each tick we pick up the new target the
	/// view rebuilt with the updated value.
	///
	/// No-op when [`Self::effective_repeat_interval`] reports
	/// repeat disabled (zero rate, app override of
	/// `Duration::ZERO`). Self-cancels on the first tick where the
	/// widget at `idx` no longer exists or no longer carries a
	/// press message.
	pub( crate ) fn start_button_repeat( &mut self, focus: SurfaceFocus, idx: usize )
	{
		// Cancel any pre-existing repeat first — only one button can
		// be in repeat mode at a time, and a fresh press should
		// supersede a previous one.
		self.stop_button_repeat();
		// Button repeat ticks at a fixed 120 ms (~8 Hz). The keyboard
		// repeat interval is ~33 ms (30 Hz), which suits cursor /
		// character entry but is too aggressive for pointer steppers
		// — a date / time picker would walk a full minute in under
		// two seconds. 120 ms is fast enough to ramp through values,
		// slow enough that the user can still release on the value
		// they want.
		if matches!( self.effective_repeat_interval(), None ) { return; }
		let interval = std::time::Duration::from_millis( 120 );
		let delay = self.effective_repeat_delay();

		let timer = Timer::from_duration( delay );
		let token = self.loop_handle.insert_source( timer, move |_, _, data: &mut AppData<A>|
		{
			let live_msg = crate::tree::find_handlers(
				&data.surface( focus ).widget_rects,
				idx,
			)
			.and_then( |h| h.press_msg() );
			match live_msg
			{
				Some( m ) =>
				{
					data.pending_msgs.push( m );
					TimeoutAction::ToDuration( interval )
				}
				None =>
				{
					// Widget gone (view restructured) or no longer
					// has an on_press → drop ourselves so the timer
					// does not fire forever against a stale slot.
					data.button_repeat = None;
					TimeoutAction::Drop
				}
			}
		} );
		if let Ok( token ) = token
		{
			self.button_repeat = Some( ButtonRepeatState { token } );
		}
	}

	/// Cancel any active button-press repeat timer.
	pub( crate ) fn stop_button_repeat( &mut self )
	{
		if let Some( state ) = self.button_repeat.take()
		{
			self.loop_handle.remove( state.token );
		}
	}
}