Skip to main content

kas_core/event/cx/
mod.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 context state
7
8use linear_map::{LinearMap, set::LinearSet};
9use smallvec::SmallVec;
10use std::any::TypeId;
11use std::collections::{HashMap, VecDeque};
12use std::fmt::Debug;
13use std::future::Future;
14use std::ops::{Deref, DerefMut};
15use std::pin::Pin;
16use std::time::Instant;
17
18use super::*;
19use crate::cast::{Cast, Conv};
20use crate::config::{ConfigMsg, WindowConfig};
21use crate::draw::DrawShared;
22use crate::geom::{Offset, Vec2};
23use crate::messages::Erased;
24use crate::runner::{Platform, RunnerT, WindowDataErased};
25#[allow(unused)] use crate::theme::SizeCx;
26use crate::theme::ThemeSize;
27use crate::window::{PopupDescriptor, WindowId};
28use crate::{ActionClose, ActionMoved, ActionRedraw, ActionResize, ConfigAction, HasId, Id, Node};
29use key::Input;
30use nav::NavFocus;
31
32#[cfg(feature = "accesskit")] mod accessibility;
33mod key;
34mod nav;
35mod press;
36mod send;
37mod timer;
38mod window;
39
40pub use nav::NavAdvance;
41pub use press::{GrabBuilder, GrabMode, Press, PressSource, PressStart};
42pub(crate) use press::{Mouse, Touch};
43pub use timer::TimerHandle;
44
45struct PopupState {
46    id: WindowId,
47    desc: PopupDescriptor,
48    old_nav_focus: Option<Id>,
49    is_sized: bool,
50}
51
52/// Event context state
53///
54/// This struct encapsulates window-specific event-handling state and handling.
55/// Most operations are only available via a [`EventCx`] handle, though some
56/// are available on this struct.
57///
58/// Besides event handling, this struct also configures widgets.
59///
60/// Some methods are intended only for usage by graphics and platform backends
61/// and are hidden from generated documentation unless the `internal_doc`
62/// feature is enabled. Event handling assumes [winit].
63///
64/// [winit]: https://github.com/rust-windowing/winit
65//
66// Note that the most frequent usage of fields is to check highlighting states
67// for each widget during drawing. Most fields contain only a few values, hence
68// `SmallVec` is used to keep contents in local memory.
69pub struct EventState {
70    pub(crate) window_id: WindowId,
71    pub(crate) config: WindowConfig,
72    platform: Platform,
73    disabled: Vec<Id>,
74    window_has_focus: bool,
75    #[cfg(feature = "accesskit")]
76    accesskit_is_enabled: bool,
77    modifiers: ModifiersState,
78    input: Input,
79    nav: NavFocus,
80    key_depress: LinearMap<PhysicalKey, Id>,
81    mouse: Mouse,
82    touch: Touch,
83    access_keys: HashMap<Key, Id>,
84    popups: SmallVec<[PopupState; 16]>,
85    popup_removed: SmallVec<[(Id, WindowId); 16]>,
86    time_updates: Vec<(Instant, Id, TimerHandle)>,
87    frame_updates: LinearSet<(Id, TimerHandle)>,
88    need_frame_update: bool,
89    // Set of messages awaiting sending
90    send_queue: VecDeque<(Id, Erased)>,
91    // Set of futures of messages together with id of sending widget
92    fut_messages: Vec<(Id, Pin<Box<dyn Future<Output = Erased>>>)>,
93    pub(super) pending_send_targets: Vec<(TypeId, Id)>,
94    action_moved: Option<ActionMoved>,
95    pub(crate) action_redraw: Option<ActionRedraw>,
96    action_close: Option<ActionClose>,
97}
98
99impl EventState {
100    /// Construct per-window event state
101    #[inline]
102    pub(crate) fn new(window_id: WindowId, config: WindowConfig, platform: Platform) -> Self {
103        EventState {
104            window_id,
105            config,
106            platform,
107            disabled: vec![],
108            window_has_focus: false,
109            #[cfg(feature = "accesskit")]
110            accesskit_is_enabled: false,
111            modifiers: ModifiersState::empty(),
112            input: Input::default(),
113            nav: NavFocus::default(),
114            key_depress: Default::default(),
115            mouse: Default::default(),
116            touch: Default::default(),
117            access_keys: Default::default(),
118            popups: Default::default(),
119            popup_removed: Default::default(),
120            time_updates: vec![],
121            frame_updates: Default::default(),
122            need_frame_update: false,
123            send_queue: Default::default(),
124            pending_send_targets: vec![],
125            fut_messages: vec![],
126            action_moved: None,
127            action_redraw: None,
128            action_close: None,
129        }
130    }
131
132    /// Update scale factor
133    pub(crate) fn update_config(&mut self, scale_factor: f32) {
134        self.config.update(scale_factor);
135    }
136
137    /// Configure a widget tree
138    ///
139    /// This should be called by the toolkit on the widget tree when the window
140    /// is created (before or after resizing).
141    ///
142    /// This method calls [`ConfigCx::configure`] in order to assign
143    /// [`Id`] identifiers and call widgets' [`Events::configure`]
144    /// method. Additionally, it updates the [`EventState`] to account for
145    /// renamed and removed widgets.
146    #[must_use]
147    pub(crate) fn full_configure(
148        &mut self,
149        sizer: &dyn ThemeSize,
150        node: Node,
151    ) -> Option<ActionResize> {
152        let id = Id::ROOT.make_child(self.window_id.get().cast());
153
154        log::debug!(target: "kas_core::event", "full_configure of Window{id}");
155
156        // These are recreated during configure:
157        self.nav.fallback = None;
158
159        let mut cx = ConfigCx::new(sizer, self);
160        cx.configure(node, id);
161        let resize = cx.resize;
162        // Ignore cx.redraw: we can assume a redraw will happen
163        self.action_moved = Some(ActionMoved);
164        resize
165    }
166
167    /// Construct a [`EventCx`] referring to this state
168    ///
169    /// Invokes the given closure on this [`EventCx`].
170    #[inline]
171    #[must_use]
172    pub(crate) fn with<'a, F: FnOnce(&mut EventCx)>(
173        &'a mut self,
174        runner: &'a mut dyn RunnerT,
175        theme: &'a dyn ThemeSize,
176        window: &'a dyn WindowDataErased,
177        f: F,
178    ) -> Option<ActionResize> {
179        let mut cx = EventCx {
180            runner,
181            window,
182            cx: ConfigCx::new(theme, self),
183            target_is_disabled: false,
184            last_child: None,
185            scroll: Scroll::None,
186            resize_window: None,
187        };
188        f(&mut cx);
189        let resize = cx.resize.or(cx.resize_window);
190        let redraw = cx.redraw;
191        self.action_redraw = self.action_redraw.or(redraw);
192        resize
193    }
194
195    /// Clear all focus and grabs on `target`
196    fn cancel_event_focus(&mut self, target: &Id) {
197        self.input.clear_sel_socus_on(target);
198        self.clear_nav_focus_on(target);
199        self.mouse.cancel_event_focus(target);
200        self.touch.cancel_event_focus(target);
201    }
202
203    /// Check whether a widget is disabled
204    ///
205    /// A widget is disabled if any ancestor is.
206    #[inline]
207    pub fn is_disabled(&self, w_id: &Id) -> bool {
208        // TODO(opt): we should be able to use binary search here
209        for id in &self.disabled {
210            if id.is_ancestor_of(w_id) {
211                return true;
212            }
213        }
214        false
215    }
216
217    /// Access configuration data
218    ///
219    /// All widgets will be reconfigured if configuration data changes.
220    #[inline]
221    pub fn config(&self) -> &WindowConfig {
222        &self.config
223    }
224
225    /// Is mouse panning enabled?
226    #[inline]
227    pub fn config_enable_pan(&self, source: PressSource) -> bool {
228        source.is_touch()
229            || source.is_primary()
230                && self
231                    .config
232                    .event()
233                    .mouse_pan()
234                    .is_enabled_with(self.modifiers())
235    }
236
237    /// Is mouse text panning enabled?
238    #[inline]
239    pub fn config_enable_mouse_text_pan(&self) -> bool {
240        self.config
241            .event()
242            .mouse_text_pan()
243            .is_enabled_with(self.modifiers())
244    }
245
246    /// Test pan threshold against config, adjusted for scale factor
247    ///
248    /// Returns true when `dist` is large enough to switch to pan mode.
249    #[inline]
250    pub fn config_test_pan_thresh(&self, dist: Offset) -> bool {
251        Vec2::conv(dist).abs().max_comp() >= self.config.event().pan_dist_thresh()
252    }
253
254    /// Notify that a widget must be redrawn
255    ///
256    /// This method is designed to support partial redraws though these are not
257    /// currently implemented. See also [`Self::action_redraw`].
258    #[inline]
259    pub fn redraw(&mut self, id: impl HasId) {
260        // NOTE: redraws are fast enough not to bother handling locally
261        let _ = id;
262
263        self.action_redraw = Some(ActionRedraw);
264    }
265
266    /// Redraw `id` if not `None`
267    #[inline]
268    pub(crate) fn opt_redraw(&mut self, id: Option<Id>) {
269        if let Some(id) = id {
270            self.redraw(id);
271        }
272    }
273
274    /// Notify that widgets under self may have moved
275    ///
276    /// This updates the widget(s) under mouse and touch events.
277    #[inline]
278    pub fn region_moved(&mut self) {
279        self.action_moved = Some(ActionMoved);
280    }
281
282    /// Request that the window be closed
283    #[inline]
284    pub fn close_own_window(&mut self) {
285        self.action_close = Some(ActionClose);
286    }
287
288    /// Notify of an [`ActionMoved`] or `Option<ActionMoved>`
289    #[inline]
290    pub fn action_moved(&mut self, action: impl Into<Option<ActionMoved>>) {
291        self.action_moved = self.action_moved.or(action.into());
292    }
293
294    /// Notify of an [`ActionRedraw`] or `Option<ActionRedraw>`
295    ///
296    /// This will force a redraw of the whole window. See also [`Self::redraw`].
297    #[inline]
298    pub fn action_redraw(&mut self, action: impl Into<Option<ActionRedraw>>) {
299        self.action_redraw = self.action_redraw.or(action.into());
300    }
301}
302
303/// Widget configuration and update context
304///
305/// This type supports access to [`EventState`] via [`Deref`] / [`DerefMut`]
306/// and to [`SizeCx`] via [`Self::size_cx`].
307#[must_use]
308pub struct ConfigCx<'a> {
309    theme: &'a dyn ThemeSize,
310    state: &'a mut EventState,
311    resize: Option<ActionResize>,
312    redraw: Option<ActionRedraw>,
313}
314
315impl<'a> Deref for ConfigCx<'a> {
316    type Target = EventState;
317    fn deref(&self) -> &EventState {
318        self.state
319    }
320}
321impl<'a> DerefMut for ConfigCx<'a> {
322    fn deref_mut(&mut self) -> &mut EventState {
323        self.state
324    }
325}
326
327impl<'a> ConfigCx<'a> {
328    /// Construct
329    #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
330    #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
331    pub fn new(sh: &'a dyn ThemeSize, ev: &'a mut EventState) -> Self {
332        ConfigCx {
333            theme: sh,
334            state: ev,
335            resize: None,
336            redraw: None,
337        }
338    }
339
340    /// Access a [`SizeCx`]
341    #[inline]
342    pub fn size_cx<'b>(&'b mut self) -> SizeCx<'b>
343    where
344        'a: 'b,
345    {
346        SizeCx::new(self.state, self.theme)
347    }
348
349    /// Configure a widget
350    ///
351    /// All widgets must be configured after construction; see
352    /// [widget lifecycle](crate::Widget#widget-lifecycle) and
353    /// [configuration](Events#configuration).
354    /// Widgets must always be sized after configuration.
355    ///
356    /// Assigns `id` to the widget. This must be valid and is usually
357    /// constructed with [`Events::make_child_id`].
358    #[inline]
359    pub fn configure(&mut self, mut widget: Node<'_>, id: Id) {
360        // This recurses; avoid passing existing state in
361        // (Except redraw: this doesn't matter.)
362        let start_resize = std::mem::take(&mut self.resize);
363        widget._configure(self, id);
364        self.resize = self.resize.or(start_resize);
365    }
366
367    /// Update a widget
368    ///
369    /// All widgets must be updated after input data changes; see
370    /// [update](Events#update).
371    #[inline]
372    pub fn update(&mut self, mut widget: Node<'_>) {
373        // This recurses; avoid passing existing state in
374        // (Except redraw: this doesn't matter.)
375        let start_resize = std::mem::take(&mut self.resize);
376        widget._update(self);
377        self.resize = self.resize.or(start_resize);
378    }
379
380    /// Set/unset a widget as disabled
381    ///
382    /// Disabled status applies to all descendants and blocks reception of
383    /// events ([`Unused`] is returned automatically when the
384    /// recipient or any ancestor is disabled).
385    ///
386    /// Disabling a widget clears navigation, selection and key focus when the
387    /// target is disabled, and also cancels press/pan grabs.
388    pub fn set_disabled(&mut self, target: Id, disable: bool) {
389        if disable {
390            self.cancel_event_focus(&target);
391        }
392
393        for (i, id) in self.disabled.iter().enumerate() {
394            if target == id {
395                if !disable {
396                    self.redraw();
397                    self.disabled.remove(i);
398                }
399                return;
400            }
401        }
402        if disable {
403            self.redraw();
404            self.disabled.push(target);
405        }
406    }
407
408    /// Set a target for messages of a specific type when sent to `Id::default()`
409    ///
410    /// Messages of this type sent to `Id::default()` from any window will be
411    /// sent to `id`.
412    pub fn set_send_target_for<M: Debug + 'static>(&mut self, id: Id) {
413        let type_id = TypeId::of::<M>();
414        self.pending_send_targets.push((type_id, id));
415    }
416
417    /// Notify that a widget must be redrawn
418    ///
419    /// "The current widget" is inferred from the widget tree traversal through
420    /// which the `EventCx` is made accessible. The resize is handled locally
421    /// during the traversal unwind if possible.
422    #[inline]
423    pub fn redraw(&mut self) {
424        self.redraw = Some(ActionRedraw);
425    }
426
427    /// Require that the current widget (and its descendants) be resized
428    ///
429    /// "The current widget" is inferred from the widget tree traversal through
430    /// which the `EventCx` is made accessible. The resize is handled locally
431    /// during the traversal unwind if possible.
432    #[inline]
433    pub fn resize(&mut self) {
434        self.resize = Some(ActionResize);
435    }
436
437    #[inline]
438    pub(crate) fn needs_redraw(&self) -> bool {
439        self.redraw.is_some()
440    }
441
442    #[inline]
443    pub(crate) fn needs_resize(&self) -> bool {
444        self.resize.is_some()
445    }
446
447    #[inline]
448    pub(crate) fn set_resize(&mut self, resize: Option<ActionResize>) {
449        self.resize = resize;
450    }
451}
452
453/// Event handling context
454///
455/// This type supports access to [`ConfigCx`] and [`EventState`] via (recursive)
456/// [`Deref`] / [`DerefMut`] and to [`SizeCx`] via [`ConfigCx::size_cx`].
457#[must_use]
458pub struct EventCx<'a> {
459    runner: &'a mut dyn RunnerT,
460    window: &'a dyn WindowDataErased,
461    cx: ConfigCx<'a>,
462    pub(crate) target_is_disabled: bool,
463    last_child: Option<usize>,
464    scroll: Scroll,
465    resize_window: Option<ActionResize>,
466}
467
468impl<'a> Deref for EventCx<'a> {
469    type Target = ConfigCx<'a>;
470    fn deref(&self) -> &Self::Target {
471        &self.cx
472    }
473}
474impl<'a> DerefMut for EventCx<'a> {
475    fn deref_mut(&mut self) -> &mut Self::Target {
476        &mut self.cx
477    }
478}
479
480impl<'a> EventCx<'a> {
481    /// Get a [`DrawShared`]
482    pub fn draw_shared(&mut self) -> &mut dyn DrawShared {
483        self.runner.draw_shared()
484    }
485
486    /// Update configuration data
487    #[inline]
488    pub fn change_config(&mut self, msg: ConfigMsg) {
489        let action = self.config.change_config(msg);
490        if !action.is_empty() {
491            self.runner.config_update(action);
492        }
493    }
494
495    /// Update configuration data using a closure
496    pub fn with_config(&mut self, f: impl FnOnce(&WindowConfig) -> ConfigAction) {
497        let action = f(&self.config);
498        if !action.is_empty() {
499            self.runner.config_update(action);
500        }
501    }
502
503    /// Terminate the GUI
504    #[inline]
505    pub fn exit(&mut self) {
506        self.runner.exit();
507    }
508
509    /// Check for clean flags pre-recursion
510    fn pre_recursion(&self) {
511        debug_assert!(self.resize.is_none());
512        debug_assert!(self.scroll == Scroll::None);
513        debug_assert!(self.last_child.is_none());
514    }
515
516    /// Clean up post-recursion
517    fn post_recursion(&mut self) {
518        self.last_child = None;
519        self.scroll = Scroll::None;
520        self.resize_window = self.resize_window.or(self.resize);
521        self.resize = None;
522    }
523}