Skip to main content

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::*;
9#[allow(unused)] use crate::Events;
10use crate::HasId;
11use crate::event::{Command, Event, FocusSource};
12use crate::geom::{Rect, Size};
13use crate::util::WidgetHierarchy;
14use crate::{Id, Node, Tile, TileExt};
15use winit::dpi::{LogicalPosition, LogicalSize};
16use winit::event::{ElementState, Ime, KeyEvent};
17use winit::keyboard::{Key, ModifiersState, PhysicalKey};
18use winit::window::{
19    ImeCapabilities, ImeEnableRequest, ImeRequest, ImeRequestData, ImeRequestError,
20};
21
22#[derive(Debug)]
23struct LostFocus {
24    id: Id,
25    key: bool,
26    ime: bool,
27}
28
29/// Keyboard and IME focus tracking
30#[derive(Debug, Default)]
31pub(super) struct Input {
32    focus: Option<Id>,
33    key_focus: bool,
34    ime_focus: bool,
35    lost_focus: Option<LostFocus>,
36    new_sel_focus: Option<FocusSource>,
37    new_key_focus: bool,
38    old_ime_target: Option<Id>,
39    /// Rect is cursor area in sel_focus's coordinate space if size != ZERO
40    ime_cursor_area: Rect,
41    last_ime_rect: Rect,
42    has_reported_ime_not_supported: bool,
43}
44
45impl Input {
46    #[inline]
47    pub(super) fn sel_focus(&self) -> Option<&Id> {
48        self.focus.as_ref()
49    }
50
51    #[inline]
52    pub(super) fn key_focus(&self) -> Option<&Id> {
53        self.key_focus.then_some(self.focus.as_ref()).flatten()
54    }
55
56    #[inline]
57    pub(super) fn ime_focus(&self) -> Option<&Id> {
58        self.ime_focus.then_some(self.focus.as_ref()).flatten()
59    }
60
61    /// Clear sel, key and ime focus on target
62    pub(super) fn clear_sel_socus_on(&mut self, target: &Id) {
63        if *target != self.sel_focus() {
64            return; // nothing to do
65        }
66        let old_focus = self.focus.take().unwrap();
67        let key = std::mem::take(&mut self.key_focus);
68        let ime = std::mem::take(&mut self.ime_focus);
69
70        if let Some(ref mut focus) = self.lost_focus {
71            if focus.id == target {
72                // Upgrade partial-loss to full-loss
73                focus.key |= key;
74                focus.ime |= ime;
75            } else {
76                // Loss of focus on another target implies old_focus changed
77                // recently without sending Event::SelFocus yet. Do nothing.
78            }
79        } else {
80            self.lost_focus = Some(LostFocus {
81                id: old_focus,
82                key,
83                ime,
84            });
85        }
86    }
87
88    #[inline]
89    pub(super) fn has_pending_changes(&self) -> bool {
90        self.lost_focus.is_some() || self.new_sel_focus.is_some() || self.new_key_focus
91    }
92
93    pub(super) fn frame_update(&mut self, window: &dyn WindowDataErased, widget: &dyn Tile) {
94        // Set IME cursor area, if moved.
95        if let Some(target) = self.ime_focus()
96            && let Some((mut rect, translation)) = widget.find_tile_rect(target)
97        {
98            if self.ime_cursor_area.size != Size::ZERO {
99                rect = self.ime_cursor_area;
100            }
101            rect += translation;
102            if rect != self.last_ime_rect {
103                let data = ImeRequestData::default().with_cursor_area(
104                    rect.pos.as_physical().into(),
105                    rect.size.as_physical().into(),
106                );
107                let req = winit::window::ImeRequest::Update(data);
108                match window.ime_request(req) {
109                    Ok(()) => (),
110                    Err(e) => log::warn!("Unexpected IME error: {e}"),
111                }
112                self.last_ime_rect = rect;
113            }
114        }
115    }
116}
117
118impl EventState {
119    pub(crate) fn clear_access_key_bindings(&mut self) {
120        self.access_keys.clear();
121    }
122
123    pub(crate) fn add_access_key_binding(&mut self, id: &Id, key: &Key) -> bool {
124        if !self.modifiers.alt_key() && !self.config.alt_bypass {
125            return false;
126        }
127
128        if self.access_keys.contains_key(key) {
129            false
130        } else {
131            self.access_keys.insert(key.clone(), id.clone());
132            self.modifiers.alt_key()
133        }
134    }
135
136    /// Get whether this widget has input focus
137    ///
138    /// Return values:
139    ///
140    /// -   `None` if the widget does not have selection focus
141    /// -   `Some(false)` if the widget has selection focus but not keyboard or
142    ///     IME focus
143    /// -   `Some(true)` if the widget has keyboard and/or IME focus
144    #[inline]
145    pub fn has_input_focus(&self, w_id: &Id) -> Option<bool> {
146        if *w_id == self.input.focus {
147            Some(self.input.key_focus || self.input.ime_focus)
148        } else {
149            None
150        }
151    }
152
153    /// Get the current modifier state
154    #[inline]
155    pub fn modifiers(&self) -> ModifiersState {
156        self.modifiers
157    }
158
159    /// Set IME cursor area
160    ///
161    /// This should be called after receiving
162    /// <code>[Event::Ime][]([Ime::Enabled][crate::event::Ime::Enabled])</code>,
163    /// and any time that the `rect` parameter changes, until
164    /// <code>[Event::Ime][]([Ime::Disabled][crate::event::Ime::Disabled])</code>
165    /// is received.
166    ///
167    /// This sets the text cursor's area, `rect`, relative to the widget's own
168    /// coordinate space. If never called, then the widget's whole rect is used.
169    ///
170    /// This does nothing if `target` does not have IME-enabled input focus.
171    #[inline]
172    pub fn set_ime_cursor_area(&mut self, target: &Id, rect: Rect) {
173        if *target == self.input.ime_focus() {
174            self.input.ime_cursor_area = rect;
175        }
176    }
177
178    /// Explicitly clear Input Method Editor focus on `target`
179    ///
180    /// This method may be used to disable IME focus while retaining selection
181    /// focus. IME focus is lost automatically when selection focus is lost.
182    #[inline]
183    pub fn cancel_ime_focus(&mut self, target: &Id) {
184        if *target != self.input.ime_focus() {
185            return;
186        }
187        self.input.ime_focus = false;
188
189        if let Some(ref mut focus) = self.input.lost_focus {
190            if focus.id == target {
191                focus.ime = true;
192            } else {
193                // Loss of focus on another target implies sel focus changed
194                // recently without sending Event::SelFocus yet. Do nothing.
195            }
196        } else {
197            self.input.lost_focus = Some(LostFocus {
198                id: target.clone(),
199                key: false,
200                ime: true,
201            });
202        }
203    }
204}
205
206impl<'a> EventCx<'a> {
207    /// Request keyboard input focus
208    ///
209    /// When granted, the widget will receive [`Event::KeyFocus`] followed by
210    /// [`Event::Key`] for each key press / release. Note that this disables
211    /// translation of key events to [`Event::Command`] while key focus is
212    /// active.
213    ///
214    /// Since key focus requires [selection focus](Self::request_sel_focus),
215    /// this method requests that too.
216    ///
217    /// The `source` parameter is used by [`Event::SelFocus`].
218    #[inline]
219    pub fn request_key_focus(&mut self, target: Id, source: FocusSource) {
220        self.request_sel_focus(target, source);
221        if !self.input.key_focus {
222            self.input.key_focus = true;
223            self.input.new_key_focus = true;
224        }
225    }
226
227    /// Request selection focus
228    ///
229    /// To prevent multiple simultaneous selections (e.g. of text) in the UI,
230    /// only widgets with "selection focus" are allowed to select things.
231    /// Selection focus is implied by character focus. [`Event::LostSelFocus`]
232    /// is sent when selection focus is lost; in this case any existing
233    /// selection should be cleared.
234    ///
235    /// Selection focus is a pre-requisite for
236    /// [key focus](Self::request_key_focus) and
237    /// [IME focus](Self::replace_ime_focus).
238    ///
239    /// The `source` parameter is used by [`Event::SelFocus`].
240    ///
241    /// Selection focus implies navigation focus.
242    ///
243    /// When key focus is lost, [`Event::LostSelFocus`] is sent.
244    #[inline]
245    pub fn request_sel_focus(&mut self, target: Id, source: FocusSource) {
246        if target == self.input.sel_focus() {
247            return;
248        } else if let Some(id) = self.input.sel_focus().cloned() {
249            self.input.clear_sel_socus_on(&id);
250        }
251
252        self.set_nav_focus(target.clone(), source);
253
254        self.input.focus = Some(target);
255        self.input.new_sel_focus = Some(source);
256    }
257
258    /// Request Input Method Editor (IME) focus
259    ///
260    /// IME focus requires selection focus (see
261    /// [`EventCx::request_key_focus`], [`EventCx::request_sel_focus`]).
262    /// The request fails if this has not already been obtained (wait for
263    /// [`Event::SelFocus`] or [`Event::KeyFocus`] before calling this method).
264    ///
265    /// This method enables IME focus, replacing the existing focus. This should
266    /// be used both to initially enable IME input and to replace the `hint`,
267    /// `purpose` or `surrounding_text`.
268    ///
269    /// Once enabled, `target` should receive [`Event::Ime`] events.
270    pub fn replace_ime_focus(
271        &mut self,
272        target: Id,
273        hint: ImeHint,
274        purpose: ImePurpose,
275        mut surrounding_text: Option<ImeSurroundingText>,
276    ) {
277        if target != self.input.focus {
278            return;
279        }
280
281        if let Some(id) = self.input.ime_focus().cloned() {
282            self.clear_ime_focus(id);
283        }
284
285        let mut capabilities = ImeCapabilities::new()
286            .with_hint_and_purpose()
287            .with_cursor_area();
288        if surrounding_text.is_some() {
289            capabilities = capabilities.with_surrounding_text();
290        }
291
292        // NOTE: we provide bogus cursor area and update in `frame_update`;
293        // the API does not allow to only provide this later.
294        let position = LogicalPosition::new(0, 0);
295        let size = LogicalSize::new(0, 0);
296
297        let mut data = ImeRequestData::default()
298            .with_hint_and_purpose(hint, purpose)
299            .with_cursor_area(position.into(), size.into());
300        if let Some(surrounding) = surrounding_text.take() {
301            data = data.with_surrounding_text(surrounding);
302        }
303
304        let req = ImeEnableRequest::new(capabilities, data).unwrap();
305        match self.window.ime_request(ImeRequest::Enable(req)) {
306            Ok(()) => {
307                // NOTE: we could store (a clone of) data here, but we don't
308                // need to: we only need to pass changed properties on update.
309                self.input.ime_focus = true;
310            }
311            Err(ImeRequestError::NotSupported) => {
312                if !self.input.has_reported_ime_not_supported {
313                    log::warn!("Failed to start Input Method Editor: not supported");
314                    self.input.has_reported_ime_not_supported = true;
315                }
316            }
317            Err(e) => log::error!("Unexpected IME error: {e}"),
318        }
319    }
320
321    /// Visually depress a widget via a key code
322    ///
323    /// When a button-like widget is activated by a key it may call this to
324    /// ensure the widget is visually depressed until the key is released.
325    /// The widget will not receive a notification of key-release but will be
326    /// redrawn automatically.
327    ///
328    /// Note that keyboard shortcuts and mnemonics should usually match against
329    /// the "logical key". [`PhysicalKey`] is used here since the the logical key
330    /// may be changed by modifier keys.
331    ///
332    /// Does nothing when `code` is `None`.
333    pub fn depress_with_key(&mut self, id: impl HasId, code: impl Into<Option<PhysicalKey>>) {
334        fn inner(state: &mut EventState, id: Id, code: PhysicalKey) {
335            if state
336                .key_depress
337                .get(&code)
338                .map(|target| *target == id)
339                .unwrap_or(false)
340            {
341                return;
342            }
343
344            state.key_depress.insert(code, id.clone());
345            state.redraw(id);
346        }
347
348        if let Some(code) = code.into() {
349            inner(self, id.has_id(), code);
350        }
351    }
352
353    /// Update surrounding text used by Input Method Editor
354    pub fn update_ime_surrounding_text(&self, target: &Id, surrounding_text: ImeSurroundingText) {
355        if *target != self.input.ime_focus() {
356            return;
357        }
358
359        let data = ImeRequestData::default().with_surrounding_text(surrounding_text);
360        let req = ImeRequest::Update(data);
361        match self.window.ime_request(req) {
362            Ok(()) => (),
363            Err(e) => log::error!("Unexpected IME error: {e}"),
364        }
365    }
366
367    pub(super) fn clear_ime_focus(&mut self, id: Id) {
368        // NOTE: we assume that winit will send us Ime::Disabled
369        self.input.old_ime_target = Some(id);
370        self.window.ime_request(ImeRequest::Disable).unwrap();
371        self.input.ime_focus = false;
372        self.input.ime_cursor_area = Rect::ZERO;
373    }
374
375    pub(super) fn keyboard_input(
376        &mut self,
377        mut widget: Node<'_>,
378        mut event: KeyEvent,
379        is_synthetic: bool,
380    ) {
381        if let Some(id) = self.input.key_focus().cloned() {
382            // TODO(winit): https://github.com/rust-windowing/winit/issues/3038
383            let mut mods = self.modifiers;
384            mods.remove(ModifiersState::SHIFT);
385            if !mods.is_empty()
386                || event
387                    .text
388                    .as_ref()
389                    .and_then(|t| t.chars().next())
390                    .map(|c| c.is_control())
391                    .unwrap_or(false)
392            {
393                event.text = None;
394            }
395
396            if self.send_event(widget.re(), id, Event::Key(&event, is_synthetic)) {
397                return;
398            }
399        }
400
401        if event.state == ElementState::Pressed && !is_synthetic {
402            self.start_key_event(widget, event.key_without_modifiers, event.physical_key);
403        } else if event.state == ElementState::Released
404            && self.key_depress.remove(&event.physical_key).is_some()
405        {
406            self.redraw();
407        }
408    }
409
410    pub(super) fn start_key_event(&mut self, mut widget: Node<'_>, vkey: Key, code: PhysicalKey) {
411        let id = widget.id();
412        log::trace!("start_key_event: window={id}, vkey={vkey:?}, physical_key={code:?}");
413
414        let opt_cmd = self.config.shortcuts().try_match(self.modifiers, &vkey);
415
416        if Some(Command::Exit) == opt_cmd {
417            self.runner.exit();
418            return;
419        } else if Some(Command::Close) == opt_cmd {
420            self.handle_close();
421            return;
422        } else if let Some(cmd) = opt_cmd {
423            let mut targets = vec![];
424            let mut send = |_self: &mut Self, id: Id, cmd| -> bool {
425                if !targets.contains(&id) {
426                    let event = Event::Command(cmd, Some(code));
427                    let used = _self.send_event(widget.re(), id.clone(), event);
428                    targets.push(id);
429                    used
430                } else {
431                    false
432                }
433            };
434
435            if (self.input.key_focus || cmd.suitable_for_sel_focus())
436                && let Some(ref id) = self.input.focus
437                && send(self, id.clone(), cmd)
438            {
439                return;
440            }
441
442            if !self.modifiers.alt_key()
443                && let Some(id) = self.nav_focus().cloned()
444                && send(self, id, cmd)
445            {
446                return;
447            }
448
449            if let Some(id) = self
450                .popups
451                .last()
452                .filter(|popup| popup.is_sized)
453                .map(|popup| popup.desc.id.clone())
454                && send(self, id, cmd)
455            {
456                return;
457            }
458
459            let fallback = self.nav.fallback.clone().unwrap_or(id);
460            if send(self, fallback, cmd) {
461                return;
462            }
463
464            if matches!(cmd, Command::Debug) {
465                let over_id = self.mouse.over_id();
466                let hier = WidgetHierarchy::new(widget.as_tile(), over_id.clone());
467                log::debug!("Widget heirarchy (filter={over_id:?}): {hier}");
468                return;
469            }
470        }
471
472        // Next priority goes to access keys when Alt is held or alt_bypass is true
473        let target = self.access_keys.get(&vkey).cloned();
474
475        if let Some(id) = target {
476            self.close_non_ancestors_of(Some(&id));
477
478            if let Some(id) = self.nav_next(widget.as_tile(), Some(&id), NavAdvance::None) {
479                self.request_nav_focus(id, FocusSource::Key);
480            }
481
482            let event = Event::Command(Command::Activate, Some(code));
483            self.send_event(widget, id, event);
484        } else if self.config.nav_focus && opt_cmd == Some(Command::Tab) {
485            let shift = self.modifiers.shift_key();
486            self.next_nav_focus(None, shift, FocusSource::Key);
487        }
488    }
489
490    pub(super) fn modifiers_changed(&mut self, state: ModifiersState) {
491        if state.alt_key() != self.modifiers.alt_key() {
492            // This controls drawing of access key indicators
493            self.redraw();
494        }
495        self.modifiers = state;
496    }
497
498    pub(super) fn ime_event(&mut self, widget: Node<'_>, ime: Ime) {
499        if ime == Ime::Disabled
500            && let Some(target) = self.input.old_ime_target.take()
501        {
502            // Assume that we disabled IME when old_ime_target is set
503            self.send_event(widget, target, Event::Ime(super::Ime::Disabled));
504            return;
505        }
506
507        if let Some(id) = self.input.ime_focus().cloned() {
508            let event = match ime {
509                Ime::Enabled => super::Ime::Enabled,
510                Ime::Preedit(ref text, cursor) => super::Ime::Preedit { text, cursor },
511                Ime::Commit(ref text) => super::Ime::Commit { text },
512                Ime::DeleteSurrounding {
513                    before_bytes,
514                    after_bytes,
515                } => super::Ime::DeleteSurrounding {
516                    before_bytes,
517                    after_bytes,
518                },
519                Ime::Disabled => {
520                    // IME disabled by external cause
521                    self.input.ime_focus = false;
522                    self.input.ime_cursor_area = Rect::ZERO;
523
524                    super::Ime::Disabled
525                }
526            };
527
528            self.send_event(widget, id, Event::Ime(event));
529        }
530    }
531
532    pub(super) fn flush_pending_input_focus(&mut self, mut widget: Node<'_>) {
533        if let Some(focus) = self.input.lost_focus.take() {
534            if focus.ime {
535                self.clear_ime_focus(focus.id.clone());
536            }
537
538            if focus.key {
539                self.send_event(widget.re(), focus.id.clone(), Event::LostKeyFocus);
540            }
541
542            if focus.id != self.input.sel_focus() {
543                self.send_event(widget.re(), focus.id, Event::LostSelFocus);
544            }
545        }
546
547        if let Some(source) = self.input.new_sel_focus.take()
548            && let Some(id) = self.input.sel_focus().cloned()
549        {
550            self.send_event(widget.re(), id, Event::SelFocus(source));
551        }
552        if self.input.new_key_focus
553            && let Some(id) = self.input.key_focus().cloned()
554        {
555            self.input.new_key_focus = false;
556            self.send_event(widget.re(), id, Event::KeyFocus);
557        }
558    }
559}