@@ -1,6 +1,6 @@
use crate::{
- Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, KeyMatch,
- Keymap, Keystroke, KeystrokeMatcher, WindowContext,
+ Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
+ KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext,
};
use collections::FxHashMap;
use parking_lot::Mutex;
@@ -276,8 +276,9 @@ impl DispatchTree {
&mut self,
keystroke: &Keystroke,
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
- ) -> SmallVec<[KeyBinding; 1]> {
+ ) -> KeymatchResult {
let mut actions = SmallVec::new();
+ let mut pending = false;
let mut context_stack: SmallVec<[KeyContext; 4]> = SmallVec::new();
for node_id in dispatch_path {
@@ -294,12 +295,13 @@ impl DispatchTree {
.entry(context_stack.clone())
.or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone()));
- let mut matches = keystroke_matcher.match_keystroke(keystroke, &context_stack);
- actions.append(&mut matches);
+ let mut result = keystroke_matcher.match_keystroke(keystroke, &context_stack);
+ pending = result.pending || pending;
+ actions.append(&mut result.actions);
context_stack.pop();
}
- actions
+ KeymatchResult { actions, pending }
}
pub fn has_pending_keystrokes(&self) -> bool {
@@ -1,4 +1,4 @@
-use crate::{Action, KeyBinding, KeyContext, Keymap, KeymapVersion, Keystroke};
+use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::sync::Arc;
@@ -9,6 +9,11 @@ pub struct KeystrokeMatcher {
keymap_version: KeymapVersion,
}
+pub struct KeymatchResult {
+ pub actions: SmallVec<[Box<dyn Action>; 1]>,
+ pub pending: bool,
+}
+
impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version();
@@ -40,7 +45,7 @@ impl KeystrokeMatcher {
&mut self,
keystroke: &Keystroke,
context_stack: &[KeyContext],
- ) -> SmallVec<[KeyBinding; 1]> {
+ ) -> KeymatchResult {
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
if keymap.version() != self.keymap_version {
@@ -49,7 +54,7 @@ impl KeystrokeMatcher {
}
let mut pending_key = None;
- let mut found = SmallVec::new();
+ let mut actions = SmallVec::new();
for binding in keymap.bindings().rev() {
if !keymap.binding_enabled(binding, context_stack) {
@@ -60,7 +65,7 @@ impl KeystrokeMatcher {
self.pending_keystrokes.push(candidate.clone());
match binding.match_keystrokes(&self.pending_keystrokes) {
KeyMatch::Matched => {
- found.push(binding.clone());
+ actions.push(binding.action.boxed_clone());
}
KeyMatch::Pending => {
pending_key.get_or_insert(candidate);
@@ -71,15 +76,15 @@ impl KeystrokeMatcher {
}
}
- if !found.is_empty() {
- self.pending_keystrokes.clear();
- } else if let Some(pending_key) = pending_key {
+ let pending = if let Some(pending_key) = pending_key {
self.pending_keystrokes.push(pending_key);
+ true
} else {
self.pending_keystrokes.clear();
+ false
};
- found
+ KeymatchResult { actions, pending }
}
}
@@ -1,7 +1,8 @@
use crate::{
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke,
- Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
- Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
+ Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
+ PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds,
+ WindowOptions,
};
use collections::HashMap;
use parking_lot::Mutex;
@@ -4,13 +4,13 @@ use crate::{
DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect,
Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla,
ImageData, InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent,
- KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
- MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay,
- PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel,
- Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
- Shadow, SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine,
- Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions,
- SUBPIXEL_VARIANTS,
+ KeymatchResult, KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite,
+ MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
+ PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
+ PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
+ Scene, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, Surface,
+ TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
+ WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::{FxHashMap, FxHashSet};
@@ -38,6 +38,7 @@ use std::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
+ time::Duration,
};
use util::{post_inc, ResultExt};
@@ -282,11 +283,20 @@ pub struct Window {
activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
+ pending_input: Option<PendingInput>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) focus_invalidated: bool,
}
+#[derive(Default)]
+struct PendingInput {
+ text: String,
+ actions: SmallVec<[Box<dyn Action>; 1]>,
+ focus: Option<FocusId>,
+ timer: Option<Task<()>>,
+}
+
pub(crate) struct ElementStateBox {
inner: Box<dyn Any>,
parent_view_id: EntityId,
@@ -506,6 +516,7 @@ impl Window {
activation_observers: SubscriberSet::new(),
focus: None,
focus_enabled: true,
+ pending_input: None,
#[cfg(any(test, feature = "test-support"))]
focus_invalidated: false,
@@ -1785,21 +1796,56 @@ impl<'a> WindowContext<'a> {
.dispatch_path(node_id);
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
- let bindings = self
+ let KeymatchResult { actions, pending } = self
.window
.rendered_frame
.dispatch_tree
.dispatch_key(&key_down_event.keystroke, &dispatch_path);
- if !bindings.is_empty() {
+ if pending {
+ let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
+ if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus
+ {
+ currently_pending = PendingInput::default();
+ }
+ currently_pending.focus = self.window.focus;
+ if let Some(new_text) = &key_down_event.keystroke.ime_key.as_ref() {
+ currently_pending.text += new_text
+ }
+ for action in actions {
+ currently_pending.actions.push(action);
+ }
+
+ currently_pending.timer = Some(self.spawn(|mut cx| async move {
+ cx.background_executor.timer(Duration::from_secs(1)).await;
+ cx.update(move |cx| {
+ cx.clear_pending_keystrokes();
+ let Some(currently_pending) = cx.window.pending_input.take() else {
+ return;
+ };
+ cx.replay_pending_input(currently_pending)
+ })
+ .log_err();
+ }));
+ self.window.pending_input = Some(currently_pending);
+
+ self.propagate_event = false;
+ return;
+ } else if let Some(currently_pending) = self.window.pending_input.take() {
+ if actions.is_empty() {
+ self.replay_pending_input(currently_pending)
+ }
+ }
+
+ if !actions.is_empty() {
self.clear_pending_keystrokes();
}
self.propagate_event = true;
- for binding in bindings {
- self.dispatch_action_on_node(node_id, binding.action.boxed_clone());
+ for action in actions {
+ self.dispatch_action_on_node(node_id, action.boxed_clone());
if !self.propagate_event {
- self.dispatch_keystroke_observers(event, Some(binding.action));
+ self.dispatch_keystroke_observers(event, Some(action));
return;
}
}
@@ -1840,6 +1886,38 @@ impl<'a> WindowContext<'a> {
.has_pending_keystrokes()
}
+ fn replay_pending_input(&mut self, currently_pending: PendingInput) {
+ let node_id = self
+ .window
+ .focus
+ .and_then(|focus_id| {
+ self.window
+ .rendered_frame
+ .dispatch_tree
+ .focusable_node_id(focus_id)
+ })
+ .unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
+
+ if self.window.focus != currently_pending.focus {
+ return;
+ }
+
+ self.propagate_event = true;
+ for action in currently_pending.actions {
+ self.dispatch_action_on_node(node_id, action);
+ if !self.propagate_event {
+ return;
+ }
+ }
+
+ if !currently_pending.text.is_empty() {
+ if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
+ input_handler.flush_pending_input(¤tly_pending.text, self);
+ self.window.platform_window.set_input_handler(input_handler)
+ }
+ }
+ }
+
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
let dispatch_path = self
.window