kas_core/event/cx/
key.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: key handling and selection focus
7
8use super::{EventCx, EventState, NavAdvance};
9#[allow(unused)] use crate::Events;
10use crate::event::{Command, Event, FocusSource};
11use crate::util::WidgetHierarchy;
12use crate::{Action, HasId};
13use crate::{Id, Node, geom::Rect, runner::WindowDataErased};
14use winit::event::{ElementState, Ime, KeyEvent};
15use winit::keyboard::{Key, ModifiersState, PhysicalKey};
16use winit::window::ImePurpose;
17
18#[derive(Debug)]
19pub(super) struct PendingSelFocus {
20    target: Option<Id>,
21    key_focus: bool,
22    ime: Option<ImePurpose>,
23    source: FocusSource,
24}
25
26impl EventState {
27    pub(crate) fn clear_access_key_bindings(&mut self) {
28        self.access_keys.clear();
29    }
30
31    pub(crate) fn add_access_key_binding(&mut self, id: &Id, key: &Key) -> bool {
32        if !self.modifiers.alt_key() && !self.config.alt_bypass {
33            return false;
34        }
35
36        if self.access_keys.contains_key(key) {
37            false
38        } else {
39            self.access_keys.insert(key.clone(), id.clone());
40            self.modifiers.alt_key()
41        }
42    }
43
44    /// Get the current modifier state
45    #[inline]
46    pub fn modifiers(&self) -> ModifiersState {
47        self.modifiers
48    }
49
50    /// Get whether this widget has `(key_focus, sel_focus)`
51    ///
52    /// -   `key_focus`: implies this widget receives keyboard input
53    /// -   `sel_focus`: implies this widget is allowed to select things
54    ///
55    /// Note that `key_focus` implies `sel_focus`.
56    #[inline]
57    pub fn has_key_focus(&self, w_id: &Id) -> (bool, bool) {
58        let sel_focus = *w_id == self.sel_focus;
59        (sel_focus && self.key_focus, sel_focus)
60    }
61
62    #[inline]
63    pub(super) fn key_focus(&self) -> Option<Id> {
64        if self.key_focus { self.sel_focus.clone() } else { None }
65    }
66
67    pub(super) fn clear_key_focus(&mut self) {
68        if self.key_focus {
69            if let Some(ref mut pending) = self.pending_sel_focus {
70                if pending.target == self.sel_focus {
71                    pending.key_focus = false;
72                }
73            } else {
74                self.pending_sel_focus = Some(PendingSelFocus {
75                    target: None,
76                    key_focus: false,
77                    ime: None,
78                    source: FocusSource::Synthetic,
79                });
80            }
81        }
82    }
83
84    pub(super) fn clear_sel_socus_on(&mut self, target: &Id) {
85        if let Some(id) = self.sel_focus.as_ref()
86            && target.is_ancestor_of(id)
87        {
88            if let Some(pending) = self.pending_sel_focus.as_mut() {
89                if pending.target.as_ref() == Some(id) {
90                    pending.target = None;
91                    pending.key_focus = false;
92                } else {
93                    // We have a new focus target, hence the old one will be cleared
94                }
95            } else {
96                self.pending_sel_focus = Some(PendingSelFocus {
97                    target: None,
98                    key_focus: false,
99                    ime: None,
100                    source: FocusSource::Synthetic,
101                });
102            }
103        }
104    }
105
106    /// Request keyboard input focus
107    ///
108    /// When granted, the widget will receive [`Event::KeyFocus`] followed by
109    /// [`Event::Key`] for each key press / release. Note that this disables
110    /// translation of key events to [`Event::Command`] while key focus is
111    /// active.
112    ///
113    /// Providing an [`ImePurpose`] enables Input Method Editor events
114    /// (see [`Event::ImeFocus`]). TODO: this feature is incomplete; winit does
115    /// not currently support setting surrounding text.
116    ///
117    /// The `source` parameter is used by [`Event::SelFocus`].
118    ///
119    /// Key focus implies sel focus (see [`Self::request_sel_focus`]) and
120    /// navigation focus.
121    #[inline]
122    pub fn request_key_focus(&mut self, target: Id, ime: Option<ImePurpose>, source: FocusSource) {
123        if self.nav_focus.as_ref() != Some(&target) {
124            self.set_nav_focus(target.clone(), source);
125        }
126
127        self.pending_sel_focus = Some(PendingSelFocus {
128            target: Some(target),
129            key_focus: true,
130            ime,
131            source,
132        });
133    }
134
135    /// Request selection focus
136    ///
137    /// To prevent multiple simultaneous selections (e.g. of text) in the UI,
138    /// only widgets with "selection focus" are allowed to select things.
139    /// Selection focus is implied by character focus. [`Event::LostSelFocus`]
140    /// is sent when selection focus is lost; in this case any existing
141    /// selection should be cleared.
142    ///
143    /// The `source` parameter is used by [`Event::SelFocus`].
144    ///
145    /// Selection focus implies navigation focus.
146    ///
147    /// When key focus is lost, [`Event::LostSelFocus`] is sent.
148    #[inline]
149    pub fn request_sel_focus(&mut self, target: Id, source: FocusSource) {
150        if self.nav_focus.as_ref() != Some(&target) {
151            self.set_nav_focus(target.clone(), source);
152        }
153
154        if let Some(ref pending) = self.pending_sel_focus
155            && pending.target.as_ref() == Some(&target)
156        {
157            return;
158        }
159
160        self.pending_sel_focus = Some(PendingSelFocus {
161            target: Some(target),
162            key_focus: false,
163            ime: None,
164            source,
165        });
166    }
167
168    /// Visually depress a widget via a key code
169    ///
170    /// When a button-like widget is activated by a key it may call this to
171    /// ensure the widget is visually depressed until the key is released.
172    /// The widget will not receive a notification of key-release but will be
173    /// redrawn automatically.
174    ///
175    /// Note that keyboard shortcuts and mnemonics should usually match against
176    /// the "logical key". [`PhysicalKey`] is used here since the the logical key
177    /// may be changed by modifier keys.
178    ///
179    /// Does nothing when `code` is `None`.
180    pub fn depress_with_key(&mut self, id: impl HasId, code: impl Into<Option<PhysicalKey>>) {
181        fn inner(state: &mut EventState, id: Id, code: PhysicalKey) {
182            if state
183                .key_depress
184                .get(&code)
185                .map(|target| *target == id)
186                .unwrap_or(false)
187            {
188                return;
189            }
190
191            state.key_depress.insert(code, id.clone());
192            state.redraw(id);
193        }
194
195        if let Some(code) = code.into() {
196            inner(self, id.has_id(), code);
197        }
198    }
199
200    /// End Input Method Editor focus on `target`, if present
201    #[inline]
202    pub fn cancel_ime_focus(&mut self, target: Id) {
203        if let Some(pending) = self.pending_sel_focus.as_mut() {
204            if pending.target.as_ref() == Some(&target) {
205                pending.ime = None;
206            }
207        } else if self.ime.is_some() && self.sel_focus.as_ref() == Some(&target) {
208            self.pending_sel_focus = Some(PendingSelFocus {
209                target: Some(target),
210                key_focus: self.key_focus,
211                ime: None,
212                source: FocusSource::Synthetic,
213            });
214        }
215    }
216
217    /// Set IME cursor area
218    ///
219    /// This should be called after receiving [`Event::ImeFocus`], and any time
220    /// that the `rect` parameter changes, until [`Event::LostImeFocus`] is
221    /// received.
222    ///
223    /// This sets the text cursor's area, `rect`, relative to the widget's own
224    /// coordinate space. If never called, then the widget's whole rect is used.
225    ///
226    /// This does nothing if `target` does not have IME-enabled input focus.
227    #[inline]
228    pub fn set_ime_cursor_area(&mut self, target: &Id, rect: Rect) {
229        if self.ime.is_some() && self.sel_focus.as_ref() == Some(target) {
230            self.ime_cursor_area = rect;
231        }
232    }
233}
234
235impl<'a> EventCx<'a> {
236    pub(super) fn keyboard_input(
237        &mut self,
238        mut widget: Node<'_>,
239        mut event: KeyEvent,
240        is_synthetic: bool,
241    ) {
242        if let Some(id) = self.key_focus() {
243            // TODO(winit): https://github.com/rust-windowing/winit/issues/3038
244            let mut mods = self.modifiers;
245            mods.remove(ModifiersState::SHIFT);
246            if !mods.is_empty()
247                || event
248                    .text
249                    .as_ref()
250                    .and_then(|t| t.chars().next())
251                    .map(|c| c.is_control())
252                    .unwrap_or(false)
253            {
254                event.text = None;
255            }
256
257            if self.send_event(widget.re(), id, Event::Key(&event, is_synthetic)) {
258                return;
259            }
260        }
261
262        if event.state == ElementState::Pressed && !is_synthetic {
263            self.start_key_event(widget, event.logical_key, event.physical_key);
264        } else if event.state == ElementState::Released
265            && let Some(id) = self.key_depress.remove(&event.physical_key)
266        {
267            self.redraw(id);
268        }
269    }
270
271    pub(super) fn start_key_event(&mut self, mut widget: Node<'_>, vkey: Key, code: PhysicalKey) {
272        let id = widget.id();
273        log::trace!("start_key_event: window={id}, vkey={vkey:?}, physical_key={code:?}");
274
275        let opt_cmd = self.config.shortcuts().try_match(self.modifiers, &vkey);
276
277        if Some(Command::Exit) == opt_cmd {
278            self.runner.exit();
279            return;
280        } else if Some(Command::Close) == opt_cmd {
281            self.handle_close();
282            return;
283        } else if let Some(cmd) = opt_cmd {
284            let mut targets = vec![];
285            let mut send = |_self: &mut Self, id: Id, cmd| -> bool {
286                if !targets.contains(&id) {
287                    let event = Event::Command(cmd, Some(code));
288                    let used = _self.send_event(widget.re(), id.clone(), event);
289                    targets.push(id);
290                    used
291                } else {
292                    false
293                }
294            };
295
296            if (self.key_focus || cmd.suitable_for_sel_focus())
297                && let Some(id) = self.sel_focus.clone()
298                && send(self, id, cmd)
299            {
300                return;
301            }
302
303            if !self.modifiers.alt_key()
304                && let Some(id) = self.nav_focus.clone()
305                && send(self, id, cmd)
306            {
307                return;
308            }
309
310            if let Some(id) = self
311                .popups
312                .last()
313                .filter(|popup| popup.is_sized)
314                .map(|popup| popup.desc.id.clone())
315                && send(self, id, cmd)
316            {
317                return;
318            }
319
320            let fallback = self.nav_fallback.clone().unwrap_or(id);
321            if send(self, fallback, cmd) {
322                return;
323            }
324
325            if matches!(cmd, Command::Debug) {
326                let over_id = self.mouse.over_id();
327                let hier = WidgetHierarchy::new(widget.as_tile(), over_id.clone());
328                log::debug!("Widget heirarchy (filter={over_id:?}): {hier}");
329                return;
330            }
331        }
332
333        // Next priority goes to access keys when Alt is held or alt_bypass is true
334        let target = self.access_keys.get(&vkey).cloned();
335
336        if let Some(id) = target {
337            self.close_non_ancestors_of(Some(&id));
338
339            if let Some(id) = self.nav_next(widget.re(), Some(&id), NavAdvance::None) {
340                self.request_nav_focus(id, FocusSource::Key);
341            }
342
343            let event = Event::Command(Command::Activate, Some(code));
344            self.send_event(widget, id, event);
345        } else if self.config.nav_focus && opt_cmd == Some(Command::Tab) {
346            let shift = self.modifiers.shift_key();
347            self.next_nav_focus(None, shift, FocusSource::Key);
348        }
349    }
350
351    pub(super) fn modifiers_changed(&mut self, state: ModifiersState) {
352        if state.alt_key() != self.modifiers.alt_key() {
353            // This controls drawing of access key indicators
354            self.window_action(Action::REDRAW);
355        }
356        self.modifiers = state;
357    }
358
359    pub(super) fn ime_event(&mut self, widget: Node<'_>, ime: Ime) {
360        match ime {
361            winit::event::Ime::Enabled => {
362                // We expect self.ime.is_some(), but it's possible that the request is outdated
363                if self.ime.is_some()
364                    && let Some(id) = self.sel_focus.clone()
365                {
366                    self.send_event(widget, id, Event::ImeFocus);
367                }
368            }
369            winit::event::Ime::Disabled => {
370                // We can only assume that this is received due to us disabling
371                // IME if self.old_ime_target is set, and is otherwise due to an
372                // external cause.
373                let mut target = self.old_ime_target.take();
374                if target.is_none() && self.ime.is_some() {
375                    target = self.sel_focus.clone();
376                    self.ime = None;
377                    self.ime_cursor_area = Rect::ZERO;
378                }
379                if let Some(id) = target {
380                    self.send_event(widget, id, Event::LostImeFocus);
381                }
382            }
383            winit::event::Ime::Preedit(text, cursor) => {
384                if self.ime.is_some()
385                    && let Some(id) = self.sel_focus.clone()
386                {
387                    self.send_event(widget, id, Event::ImePreedit(&text, cursor));
388                }
389            }
390            winit::event::Ime::Commit(text) => {
391                if self.ime.is_some()
392                    && let Some(id) = self.sel_focus.clone()
393                {
394                    self.send_event(widget, id, Event::ImeCommit(&text));
395                }
396            }
397        }
398    }
399
400    // Set selection focus to `wid` immediately; if `key_focus` also set that
401    pub(super) fn set_sel_focus(
402        &mut self,
403        window: &dyn WindowDataErased,
404        mut widget: Node<'_>,
405        pending: PendingSelFocus,
406    ) {
407        let PendingSelFocus {
408            target,
409            key_focus,
410            ime,
411            source,
412        } = pending;
413        let target_is_new = target != self.sel_focus;
414        let old_key_focus = self.key_focus;
415        self.key_focus = key_focus;
416
417        log::trace!("set_sel_focus: target={target:?}, key_focus={key_focus}");
418
419        if let Some(id) = self.sel_focus.clone() {
420            if self.ime.is_some() && (ime.is_none() || target_is_new) {
421                window.set_ime_allowed(None);
422                self.old_ime_target = Some(id.clone());
423                self.ime = None;
424                self.ime_cursor_area = Rect::ZERO;
425            }
426
427            if old_key_focus && (!key_focus || target_is_new) {
428                // If widget has key focus, this is lost
429                self.send_event(widget.re(), id.clone(), Event::LostKeyFocus);
430            }
431
432            if target.is_none() {
433                // Retain selection focus without a new target
434                return;
435            } else if target_is_new {
436                // Selection focus is lost if another widget receives key focus
437                self.send_event(widget.re(), id, Event::LostSelFocus);
438            }
439        }
440
441        if let Some(id) = target.clone() {
442            if target_is_new {
443                self.send_event(widget.re(), id.clone(), Event::SelFocus(source));
444            }
445
446            if key_focus && (!old_key_focus || target_is_new) {
447                self.send_event(widget.re(), id.clone(), Event::KeyFocus);
448            }
449
450            if ime.is_some() && (ime != self.ime || target_is_new) {
451                window.set_ime_allowed(ime);
452                self.ime = ime;
453            }
454        }
455
456        self.sel_focus = target;
457    }
458}