Skip to main content

kas_core/config/
event.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Event handling configuration
7
8use crate::ConfigAction;
9use crate::cast::Cast;
10#[allow(unused)] use crate::event::Event;
11use crate::event::ModifiersState;
12use crate::geom::Vec2;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15use std::cell::Ref;
16use std::time::Duration;
17
18/// A message which may be used to update [`EventConfig`]
19#[derive(Clone, Debug)]
20#[non_exhaustive]
21pub enum EventConfigMsg {
22    HoverDelay(u32),
23    MenuDelay(u32),
24    TouchSelectDelay(u32),
25    KineticTimeout(u32),
26    KineticDecayMul(f32),
27    KineticDecaySub(f32),
28    KineticGrabSub(f32),
29    ScrollDistEm(f32),
30    PanDistThresh(f32),
31    DoubleClickDistThresh(f32),
32    MousePan(MousePan),
33    MouseTextPan(MousePan),
34    MouseWheelActions(bool),
35    MouseNavFocus(bool),
36    TouchNavFocus(bool),
37    /// Reset all config values to default (not saved) values
38    ResetToDefault,
39}
40
41/// Event handling configuration
42///
43/// This is serializable (using `feature = "serde"`) with the following fields:
44///
45/// > `menu_delay_ms`: `u32` (milliseconds) \
46/// > `touch_select_delay_ms`: `u32` (milliseconds) \
47/// > `kinetic_timeout_ms`: `u32` (milliseconds) \
48/// > `kinetic_decay_mul`: `f32` (unitless, applied each second) \
49/// > `kinetic_decay_sub`: `f32` (pixels per second) \
50/// > `kinetic_grab_sub`: `f32` (pixels per second) \
51/// > `pan_dist_thresh`: `f32` (logical pixels) \
52/// > `double_click_dist_thresh`: `f32` (logical pixels) \
53/// > `mouse_pan`: [`MousePan`] \
54/// > `mouse_text_pan`: [`MousePan`] \
55/// > `mouse_nav_focus`: `bool` \
56/// > `touch_nav_focus`: `bool`
57///
58/// For descriptions of configuration effects, see [`EventWindowConfig`] methods.
59#[derive(Clone, Debug, PartialEq)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61pub struct EventConfig {
62    #[cfg_attr(feature = "serde", serde(default = "defaults::hover_delay_ms"))]
63    pub hover_delay_ms: u32,
64
65    #[cfg_attr(feature = "serde", serde(default = "defaults::menu_delay_ms"))]
66    pub menu_delay_ms: u32,
67
68    #[cfg_attr(feature = "serde", serde(default = "defaults::touch_select_delay_ms"))]
69    pub touch_select_delay_ms: u32,
70
71    #[cfg_attr(feature = "serde", serde(default = "defaults::kinetic_timeout_ms"))]
72    pub kinetic_timeout_ms: u32,
73
74    #[cfg_attr(feature = "serde", serde(default = "defaults::kinetic_decay_mul"))]
75    pub kinetic_decay_mul: f32,
76
77    #[cfg_attr(feature = "serde", serde(default = "defaults::kinetic_decay_sub"))]
78    pub kinetic_decay_sub: f32,
79
80    #[cfg_attr(feature = "serde", serde(default = "defaults::kinetic_grab_sub"))]
81    pub kinetic_grab_sub: f32,
82
83    #[cfg_attr(feature = "serde", serde(default = "defaults::scroll_dist_em"))]
84    pub scroll_dist_em: f32,
85
86    #[cfg_attr(feature = "serde", serde(default = "defaults::pan_dist_thresh"))]
87    pub pan_dist_thresh: f32,
88
89    #[cfg_attr(
90        feature = "serde",
91        serde(default = "defaults::double_click_dist_thresh")
92    )]
93    pub double_click_dist_thresh: f32,
94
95    #[cfg_attr(feature = "serde", serde(default = "defaults::mouse_pan"))]
96    pub mouse_pan: MousePan,
97    #[cfg_attr(feature = "serde", serde(default = "defaults::mouse_text_pan"))]
98    pub mouse_text_pan: MousePan,
99
100    #[cfg_attr(feature = "serde", serde(default = "defaults::mouse_wheel_actions"))]
101    pub mouse_wheel_actions: bool,
102
103    #[cfg_attr(feature = "serde", serde(default = "defaults::mouse_nav_focus"))]
104    pub mouse_nav_focus: bool,
105    #[cfg_attr(feature = "serde", serde(default = "defaults::touch_nav_focus"))]
106    pub touch_nav_focus: bool,
107}
108
109impl Default for EventConfig {
110    fn default() -> Self {
111        EventConfig {
112            hover_delay_ms: defaults::hover_delay_ms(),
113            menu_delay_ms: defaults::menu_delay_ms(),
114            touch_select_delay_ms: defaults::touch_select_delay_ms(),
115            kinetic_timeout_ms: defaults::kinetic_timeout_ms(),
116            kinetic_decay_mul: defaults::kinetic_decay_mul(),
117            kinetic_decay_sub: defaults::kinetic_decay_sub(),
118            kinetic_grab_sub: defaults::kinetic_grab_sub(),
119            scroll_dist_em: defaults::scroll_dist_em(),
120            pan_dist_thresh: defaults::pan_dist_thresh(),
121            double_click_dist_thresh: defaults::double_click_dist_thresh(),
122            mouse_pan: defaults::mouse_pan(),
123            mouse_text_pan: defaults::mouse_text_pan(),
124            mouse_wheel_actions: defaults::mouse_wheel_actions(),
125            mouse_nav_focus: defaults::mouse_nav_focus(),
126            touch_nav_focus: defaults::touch_nav_focus(),
127        }
128    }
129}
130
131impl EventConfig {
132    pub(super) fn change_config(&mut self, msg: EventConfigMsg) -> ConfigAction {
133        match msg {
134            EventConfigMsg::HoverDelay(v) => self.hover_delay_ms = v,
135            EventConfigMsg::MenuDelay(v) => self.menu_delay_ms = v,
136            EventConfigMsg::TouchSelectDelay(v) => self.touch_select_delay_ms = v,
137            EventConfigMsg::KineticTimeout(v) => self.kinetic_timeout_ms = v,
138            EventConfigMsg::KineticDecayMul(v) => self.kinetic_decay_mul = v,
139            EventConfigMsg::KineticDecaySub(v) => self.kinetic_decay_sub = v,
140            EventConfigMsg::KineticGrabSub(v) => self.kinetic_grab_sub = v,
141            EventConfigMsg::ScrollDistEm(v) => self.scroll_dist_em = v,
142            EventConfigMsg::PanDistThresh(v) => self.pan_dist_thresh = v,
143            EventConfigMsg::DoubleClickDistThresh(v) => self.double_click_dist_thresh = v,
144            EventConfigMsg::MousePan(v) => self.mouse_pan = v,
145            EventConfigMsg::MouseTextPan(v) => self.mouse_text_pan = v,
146            EventConfigMsg::MouseWheelActions(v) => self.mouse_wheel_actions = v,
147            EventConfigMsg::MouseNavFocus(v) => self.mouse_nav_focus = v,
148            EventConfigMsg::TouchNavFocus(v) => self.touch_nav_focus = v,
149            EventConfigMsg::ResetToDefault => *self = EventConfig::default(),
150        }
151
152        ConfigAction::EVENT
153    }
154}
155
156/// Accessor to event configuration
157///
158/// This is a helper to read event configuration, adapted for the current
159/// application and window scale.
160#[derive(Clone, Debug)]
161pub struct EventWindowConfig<'a>(pub(super) &'a super::WindowConfig);
162
163impl<'a> EventWindowConfig<'a> {
164    /// Access base (unscaled) event [`EventConfig`]
165    #[inline]
166    pub fn base(&self) -> Ref<'_, EventConfig> {
167        Ref::map(self.0.config.borrow(), |c| &c.event)
168    }
169
170    /// Delay before mouse hover action (show tooltip)
171    #[inline]
172    pub fn hover_delay(&self) -> Duration {
173        Duration::from_millis(self.base().hover_delay_ms.cast())
174    }
175
176    /// Delay before opening/closing menus on mouse over
177    #[inline]
178    pub fn menu_delay(&self) -> Duration {
179        Duration::from_millis(self.base().menu_delay_ms.cast())
180    }
181
182    /// Delay before switching from panning to (text) selection mode
183    #[inline]
184    pub fn touch_select_delay(&self) -> Duration {
185        Duration::from_millis(self.base().touch_select_delay_ms.cast())
186    }
187
188    /// Controls activation of kinetic scrolling
189    ///
190    /// This is the maximum time between the last press-movement and final
191    /// release to activate kinetic scrolling mode. The last few `PressMove`
192    /// events within this time window are used to calculate the initial speed.
193    #[inline]
194    pub fn kinetic_timeout(&self) -> Duration {
195        Duration::from_millis(self.base().kinetic_timeout_ms.cast())
196    }
197
198    /// Kinetic scrolling decay: `(mul, sub)`
199    ///
200    /// The `mul` factor describes exponential decay: effectively, velocity is
201    /// multiplied by `mul` every second. This is the dominant decay factor at
202    /// high speeds; `mul = 1.0` implies no decay while `mul = 0.0` implies an
203    /// instant stop.
204    ///
205    /// The `sub` factor describes linear decay: effectively, speed is reduced
206    /// by `sub` every second. This is the dominant decay factor at low speeds.
207    /// Units are pixels/second (output is adjusted for the window's scale factor).
208    #[inline]
209    pub fn kinetic_decay(&self) -> (f32, f32) {
210        (self.base().kinetic_decay_mul, self.0.kinetic_decay_sub)
211    }
212
213    /// Kinetic scrolling decay while grabbed: `sub`
214    ///
215    /// When a kinetic-scrolling element is grabbed while in motion, speed
216    /// relative to the grab velocity is reduced by this value each second.
217    /// This is applied in addition to the usual velocity decay (see
218    /// [`Self::kinetic_decay`]).
219    #[inline]
220    pub fn kinetic_grab_sub(&self) -> f32 {
221        self.base().kinetic_grab_sub
222    }
223
224    /// Get distance in pixels to scroll due to mouse wheel
225    ///
226    /// Calculates scroll distance from `(horiz, vert)` lines.
227    pub fn scroll_distance(&self, lines: (f32, f32)) -> Vec2 {
228        let x = self.0.scroll_dist * lines.0;
229        let y = self.0.scroll_dist * lines.1;
230        Vec2(x, y)
231    }
232
233    /// Drag distance threshold before panning (scrolling) starts
234    ///
235    /// When the distance moved is greater than this threshold, panning should
236    /// start; otherwise the system should wait for the text-selection timer.
237    /// We currently recommend the L-inf distance metric (max of abs of values).
238    ///
239    /// Units: physical pixels
240    #[inline]
241    pub fn pan_dist_thresh(&self) -> f32 {
242        self.0.pan_dist_thresh
243    }
244
245    /// Distance threshold to interrupt a double-click
246    ///
247    /// When the distance moved by the pointer since the last button-down event
248    /// is greater than this threshold, the event is considered a fresh click;
249    /// up to this threshold the click may be considered part of a chain
250    /// (double-click, triple-click etc.).
251    ///
252    /// Units: physical pixels
253    #[inline]
254    pub(crate) fn double_click_dist_thresh(&self) -> f32 {
255        self.0.double_click_dist_thresh
256    }
257
258    /// Whether the mouse wheel may trigger actions such as switching to the next item in a list
259    #[inline]
260    pub fn mouse_wheel_actions(&self) -> bool {
261        self.base().mouse_wheel_actions
262    }
263
264    /// When to pan general widgets (unhandled events) with the mouse
265    #[inline]
266    pub fn mouse_pan(&self) -> MousePan {
267        self.base().mouse_pan
268    }
269
270    /// When to pan text fields with the mouse
271    #[inline]
272    pub fn mouse_text_pan(&self) -> MousePan {
273        self.base().mouse_text_pan
274    }
275
276    /// Whether mouse clicks set keyboard navigation focus
277    #[inline]
278    pub fn mouse_nav_focus(&self) -> bool {
279        self.0.nav_focus && self.base().mouse_nav_focus
280    }
281
282    /// Whether touchscreen events set keyboard navigation focus
283    #[inline]
284    pub fn touch_nav_focus(&self) -> bool {
285        self.0.nav_focus && self.base().touch_nav_focus
286    }
287}
288
289/// When mouse-panning is enabled (click+drag to scroll)
290///
291/// For *text* objects, this may conflict with text selection, hence it is
292/// recommended to require a modifier or disable this feature.
293///
294/// For non-text cases, this does not conflict with other event handlers since
295/// panning is only possible when events are otherwise unused, thus `Always` is
296/// acceptable (equivalent to touch scrolling).
297#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
298#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
299#[repr(u8)]
300pub enum MousePan {
301    /// Disable
302    Never,
303    /// Only enable when the Alt key is held
304    WithAlt,
305    /// Only enable when the Ctrl key is held
306    WithCtrl,
307    /// Always enabled
308    Always,
309}
310
311impl MousePan {
312    /// Is this enabled with the current modifiers?
313    pub fn is_enabled_with(self, modifiers: ModifiersState) -> bool {
314        match self {
315            MousePan::Never => false,
316            MousePan::WithAlt => modifiers.alt_key(),
317            MousePan::WithCtrl => modifiers.control_key(),
318            MousePan::Always => true,
319        }
320    }
321}
322
323mod defaults {
324    use super::MousePan;
325
326    pub fn hover_delay_ms() -> u32 {
327        1000
328    }
329    pub fn menu_delay_ms() -> u32 {
330        250
331    }
332    pub fn touch_select_delay_ms() -> u32 {
333        1000
334    }
335    pub fn kinetic_timeout_ms() -> u32 {
336        50
337    }
338    pub fn kinetic_decay_mul() -> f32 {
339        0.625
340    }
341    pub fn kinetic_decay_sub() -> f32 {
342        200.0
343    }
344    pub fn kinetic_grab_sub() -> f32 {
345        10000.0
346    }
347    pub fn scroll_dist_em() -> f32 {
348        4.5
349    }
350    pub fn pan_dist_thresh() -> f32 {
351        5.0
352    }
353    pub fn double_click_dist_thresh() -> f32 {
354        4.0
355    }
356    pub fn mouse_wheel_actions() -> bool {
357        true
358    }
359    pub fn mouse_pan() -> MousePan {
360        MousePan::Always
361    }
362    pub fn mouse_text_pan() -> MousePan {
363        #[cfg(target_os = "windows")]
364        {
365            MousePan::WithAlt
366        }
367        #[cfg(not(target_os = "windows"))]
368        {
369            MousePan::WithCtrl
370        }
371    }
372    pub fn mouse_nav_focus() -> bool {
373        true
374    }
375    pub fn touch_nav_focus() -> bool {
376        true
377    }
378}