diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 7fc2e07087..c78a370f12 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -10,9 +10,10 @@ use feature_flags::FeatureFlagViewExt; use fs::Fs; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - Action, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter, - FocusHandle, Focusable, Global, KeyContext, Keystroke, ModifiersChangedEvent, MouseButton, - Point, ScrollStrategy, StyledText, Subscription, WeakEntity, actions, anchored, deferred, div, + Action, AnimationExt, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, + EventEmitter, FocusHandle, Focusable, Global, KeyContext, KeyDownEvent, Keystroke, + ModifiersChangedEvent, MouseButton, Point, ScrollStrategy, StyledText, Subscription, + WeakEntity, actions, anchored, deferred, div, }; use language::{Language, LanguageConfig, ToOffset as _}; use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets}; @@ -1839,7 +1840,8 @@ struct KeystrokeInput { keystrokes: Vec, placeholder_keystrokes: Option>, highlight_on_focus: bool, - focus_handle: FocusHandle, + outer_focus_handle: FocusHandle, + inner_focus_handle: FocusHandle, intercept_subscription: Option, _focus_subscriptions: [Subscription; 2], } @@ -1850,16 +1852,18 @@ impl KeystrokeInput { window: &mut Window, cx: &mut Context, ) -> Self { - let focus_handle = cx.focus_handle(); + let outer_focus_handle = cx.focus_handle(); + let inner_focus_handle = cx.focus_handle(); let _focus_subscriptions = [ - cx.on_focus_in(&focus_handle, window, Self::on_focus_in), - cx.on_focus_out(&focus_handle, window, Self::on_focus_out), + cx.on_focus_in(&inner_focus_handle, window, Self::on_inner_focus_in), + cx.on_focus_out(&inner_focus_handle, window, Self::on_inner_focus_out), ]; Self { keystrokes: Vec::new(), placeholder_keystrokes, highlight_on_focus: true, - focus_handle, + inner_focus_handle, + outer_focus_handle, intercept_subscription: None, _focus_subscriptions, } @@ -1926,7 +1930,7 @@ impl KeystrokeInput { cx.notify(); } - fn on_focus_in(&mut self, _window: &mut Window, cx: &mut Context) { + fn on_inner_focus_in(&mut self, _window: &mut Window, cx: &mut Context) { if self.intercept_subscription.is_none() { let listener = cx.listener(|this, event: &gpui::KeystrokeEvent, _window, cx| { this.handle_keystroke(&event.keystroke, cx); @@ -1935,13 +1939,14 @@ impl KeystrokeInput { } } - fn on_focus_out( + fn on_inner_focus_out( &mut self, _event: gpui::FocusOutEvent, _window: &mut Window, - _cx: &mut Context, + cx: &mut Context, ) { self.intercept_subscription.take(); + cx.notify(); } fn keystrokes(&self) -> &[Keystroke] { @@ -1984,26 +1989,18 @@ impl EventEmitter<()> for KeystrokeInput {} impl Focusable for KeystrokeInput { fn focus_handle(&self, _cx: &App) -> FocusHandle { - self.focus_handle.clone() + self.outer_focus_handle.clone() } } impl Render for KeystrokeInput { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors(); - let is_focused = self.focus_handle.is_focused(window); + let is_inner_focused = self.inner_focus_handle.is_focused(window); return h_flex() - .id("keybinding_input") - .track_focus(&self.focus_handle) - .on_modifiers_changed(cx.listener(Self::on_modifiers_changed)) - .on_key_up(cx.listener(Self::on_key_up)) - .when(self.highlight_on_focus, |this| { - this.focus(|mut style| { - style.border_color = Some(colors.border_focused); - style - }) - }) + .id("keystroke-input") + .track_focus(&self.outer_focus_handle) .py_2() .px_3() .gap_2() @@ -2014,10 +2011,31 @@ impl Render for KeystrokeInput { .rounded_md() .overflow_hidden() .bg(colors.editor_background) - .border_1() + .border_2() .border_color(colors.border_variant) + .focus(|mut s| { + s.border_color = Some(colors.border_focused); + s + }) + .on_key_down(cx.listener(|this, event: &KeyDownEvent, window, cx| { + // TODO: replace with action + if !event.keystroke.modifiers.modified() && event.keystroke.key == "enter" { + window.focus(&this.inner_focus_handle); + cx.notify(); + } + })) .child( h_flex() + .id("keystroke-input-inner") + .track_focus(&self.inner_focus_handle) + .on_modifiers_changed(cx.listener(Self::on_modifiers_changed)) + .on_key_up(cx.listener(Self::on_key_up)) + .when(self.highlight_on_focus, |this| { + this.focus(|mut style| { + style.border_color = Some(colors.border_focused); + style + }) + }) .w_full() .min_w_0() .justify_center() @@ -2029,10 +2047,28 @@ impl Render for KeystrokeInput { h_flex() .gap_0p5() .flex_none() + .when(is_inner_focused, |this| { + this.child( + Icon::new(IconName::Circle) + .color(Color::Error) + .with_animation( + "recording-pulse", + gpui::Animation::new(std::time::Duration::from_secs(1)) + .repeat() + .with_easing(gpui::pulsating_between(0.8, 1.0)), + { + let color = Color::Error.color(cx); + move |this, delta| { + this.color(Color::Custom(color.opacity(delta))) + } + }, + ), + ) + }) .child( IconButton::new("backspace-btn", IconName::Delete) .tooltip(Tooltip::text("Delete Keystroke")) - .when(!is_focused, |this| this.icon_color(Color::Muted)) + .when(!is_inner_focused, |this| this.icon_color(Color::Muted)) .on_click(cx.listener(|this, _event, _window, cx| { this.keystrokes.pop(); cx.emit(()); @@ -2042,7 +2078,7 @@ impl Render for KeystrokeInput { .child( IconButton::new("clear-btn", IconName::Eraser) .tooltip(Tooltip::text("Clear Keystrokes")) - .when(!is_focused, |this| this.icon_color(Color::Muted)) + .when(!is_inner_focused, |this| this.icon_color(Color::Muted)) .on_click(cx.listener(|this, _event, _window, cx| { this.keystrokes.clear(); cx.emit(());