Add initial support for search by keystroke to keybinding editor (#34274)
This PR adds preliminary support for searching keybindings by keystrokes in the keybinding editor. Release Notes: - N/A
This commit is contained in:
parent
b4cbea50bb
commit
7eb739d489
3 changed files with 219 additions and 42 deletions
|
@ -1112,7 +1112,10 @@
|
||||||
"context": "KeymapEditor",
|
"context": "KeymapEditor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-f": "search::FocusSearch"
|
"ctrl-f": "search::FocusSearch",
|
||||||
|
"alt-find": "keymap_editor::ToggleKeystrokeSearch",
|
||||||
|
"alt-ctrl-f": "keymap_editor::ToggleKeystrokeSearch",
|
||||||
|
"alt-c": "keymap_editor::ToggleConflictFilter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1211,7 +1211,8 @@
|
||||||
"context": "KeymapEditor",
|
"context": "KeymapEditor",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-f": "search::FocusSearch"
|
"cmd-alt-f": "keymap_editor::ToggleKeystrokeSearch",
|
||||||
|
"cmd-alt-c": "keymap_editor::ToggleConflictFilter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,7 +10,7 @@ use feature_flags::FeatureFlagViewExt;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
|
Action, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
|
||||||
FocusHandle, Focusable, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy,
|
FocusHandle, Focusable, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy,
|
||||||
StyledText, Subscription, WeakEntity, actions, div,
|
StyledText, Subscription, WeakEntity, actions, div,
|
||||||
};
|
};
|
||||||
|
@ -57,7 +57,11 @@ actions!(
|
||||||
/// Copies the action name to clipboard.
|
/// Copies the action name to clipboard.
|
||||||
CopyAction,
|
CopyAction,
|
||||||
/// Copies the context predicate to clipboard.
|
/// Copies the context predicate to clipboard.
|
||||||
CopyContext
|
CopyContext,
|
||||||
|
/// Toggles Conflict Filtering
|
||||||
|
ToggleConflictFilter,
|
||||||
|
/// Toggle Keystroke search
|
||||||
|
ToggleKeystrokeSearch,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -143,6 +147,22 @@ impl KeymapEventChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Default, PartialEq)]
|
||||||
|
enum SearchMode {
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
KeyStroke,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchMode {
|
||||||
|
fn invert(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
SearchMode::Normal => SearchMode::KeyStroke,
|
||||||
|
SearchMode::KeyStroke => SearchMode::Normal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Copy, Clone)]
|
||||||
enum FilterState {
|
enum FilterState {
|
||||||
#[default]
|
#[default]
|
||||||
All,
|
All,
|
||||||
|
@ -221,11 +241,13 @@ struct KeymapEditor {
|
||||||
keybindings: Vec<ProcessedKeybinding>,
|
keybindings: Vec<ProcessedKeybinding>,
|
||||||
keybinding_conflict_state: ConflictState,
|
keybinding_conflict_state: ConflictState,
|
||||||
filter_state: FilterState,
|
filter_state: FilterState,
|
||||||
|
search_mode: SearchMode,
|
||||||
// corresponds 1 to 1 with keybindings
|
// corresponds 1 to 1 with keybindings
|
||||||
string_match_candidates: Arc<Vec<StringMatchCandidate>>,
|
string_match_candidates: Arc<Vec<StringMatchCandidate>>,
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
table_interaction_state: Entity<TableInteractionState>,
|
table_interaction_state: Entity<TableInteractionState>,
|
||||||
filter_editor: Entity<Editor>,
|
filter_editor: Entity<Editor>,
|
||||||
|
keystroke_editor: Entity<KeystrokeInput>,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +267,12 @@ impl KeymapEditor {
|
||||||
cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
|
cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
|
||||||
let table_interaction_state = TableInteractionState::new(window, cx);
|
let table_interaction_state = TableInteractionState::new(window, cx);
|
||||||
|
|
||||||
|
let keystroke_editor = cx.new(|cx| {
|
||||||
|
let mut keystroke_editor = KeystrokeInput::new(window, cx);
|
||||||
|
keystroke_editor.highlight_on_focus = false;
|
||||||
|
keystroke_editor
|
||||||
|
});
|
||||||
|
|
||||||
let filter_editor = cx.new(|cx| {
|
let filter_editor = cx.new(|cx| {
|
||||||
let mut editor = Editor::single_line(window, cx);
|
let mut editor = Editor::single_line(window, cx);
|
||||||
editor.set_placeholder_text("Filter action names…", cx);
|
editor.set_placeholder_text("Filter action names…", cx);
|
||||||
|
@ -260,17 +288,28 @@ impl KeymapEditor {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
cx.subscribe(&keystroke_editor, |this, _, _, cx| {
|
||||||
|
if matches!(this.search_mode, SearchMode::Normal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update_matches(cx);
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
workspace,
|
workspace,
|
||||||
keybindings: vec![],
|
keybindings: vec![],
|
||||||
keybinding_conflict_state: ConflictState::default(),
|
keybinding_conflict_state: ConflictState::default(),
|
||||||
filter_state: FilterState::default(),
|
filter_state: FilterState::default(),
|
||||||
|
search_mode: SearchMode::default(),
|
||||||
string_match_candidates: Arc::new(vec![]),
|
string_match_candidates: Arc::new(vec![]),
|
||||||
matches: vec![],
|
matches: vec![],
|
||||||
focus_handle: focus_handle.clone(),
|
focus_handle: focus_handle.clone(),
|
||||||
_keymap_subscription,
|
_keymap_subscription,
|
||||||
table_interaction_state,
|
table_interaction_state,
|
||||||
filter_editor,
|
filter_editor,
|
||||||
|
keystroke_editor,
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -279,30 +318,47 @@ impl KeymapEditor {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_query(&self, cx: &mut Context<Self>) -> String {
|
fn current_action_query(&self, cx: &App) -> String {
|
||||||
self.filter_editor.read(cx).text(cx)
|
self.filter_editor.read(cx).text(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(&self, cx: &mut Context<Self>) {
|
fn current_keystroke_query(&self, cx: &App) -> Vec<Keystroke> {
|
||||||
let query = self.current_query(cx);
|
match self.search_mode {
|
||||||
|
SearchMode::KeyStroke => self
|
||||||
|
.keystroke_editor
|
||||||
|
.read(cx)
|
||||||
|
.keystrokes()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
SearchMode::Normal => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| Self::process_query(this, query, cx).await)
|
fn update_matches(&self, cx: &mut Context<Self>) {
|
||||||
.detach();
|
let action_query = self.current_action_query(cx);
|
||||||
|
let keystroke_query = self.current_keystroke_query(cx);
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
Self::process_query(this, action_query, keystroke_query, cx).await
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_query(
|
async fn process_query(
|
||||||
this: WeakEntity<Self>,
|
this: WeakEntity<Self>,
|
||||||
query: String,
|
action_query: String,
|
||||||
|
keystroke_query: Vec<Keystroke>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let query = command_palette::normalize_action_query(&query);
|
let action_query = command_palette::normalize_action_query(&action_query);
|
||||||
let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
|
let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| {
|
||||||
(this.string_match_candidates.clone(), this.keybindings.len())
|
(this.string_match_candidates.clone(), this.keybindings.len())
|
||||||
})?;
|
})?;
|
||||||
let executor = cx.background_executor().clone();
|
let executor = cx.background_executor().clone();
|
||||||
let mut matches = fuzzy::match_strings(
|
let mut matches = fuzzy::match_strings(
|
||||||
&string_match_candidates,
|
&string_match_candidates,
|
||||||
&query,
|
&action_query,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
keybind_count,
|
keybind_count,
|
||||||
|
@ -321,7 +377,26 @@ impl KeymapEditor {
|
||||||
FilterState::All => {}
|
FilterState::All => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.is_empty() {
|
match this.search_mode {
|
||||||
|
SearchMode::KeyStroke => {
|
||||||
|
matches.retain(|item| {
|
||||||
|
this.keybindings[item.candidate_id]
|
||||||
|
.ui_key_binding
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|binding| {
|
||||||
|
keystroke_query.iter().all(|key| {
|
||||||
|
binding.keystrokes.iter().any(|keystroke| {
|
||||||
|
keystroke.key == key.key
|
||||||
|
&& keystroke.modifiers == key.modifiers
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SearchMode::Normal => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if action_query.is_empty() {
|
||||||
// apply default sort
|
// apply default sort
|
||||||
// sorts by source precedence, and alphabetically by action name within each source
|
// sorts by source precedence, and alphabetically by action name within each source
|
||||||
matches.sort_by_key(|match_item| {
|
matches.sort_by_key(|match_item| {
|
||||||
|
@ -432,7 +507,7 @@ impl KeymapEditor {
|
||||||
let json_language = load_json_language(workspace.clone(), cx).await;
|
let json_language = load_json_language(workspace.clone(), cx).await;
|
||||||
let rust_language = load_rust_language(workspace.clone(), cx).await;
|
let rust_language = load_rust_language(workspace.clone(), cx).await;
|
||||||
|
|
||||||
let query = this.update(cx, |this, cx| {
|
let (action_query, keystroke_query) = this.update(cx, |this, cx| {
|
||||||
let (key_bindings, string_match_candidates) =
|
let (key_bindings, string_match_candidates) =
|
||||||
Self::process_bindings(json_language, rust_language, cx);
|
Self::process_bindings(json_language, rust_language, cx);
|
||||||
|
|
||||||
|
@ -455,10 +530,13 @@ impl KeymapEditor {
|
||||||
string: candidate.string.clone(),
|
string: candidate.string.clone(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
this.current_query(cx)
|
(
|
||||||
|
this.current_action_query(cx),
|
||||||
|
this.current_keystroke_query(cx),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
// calls cx.notify
|
// calls cx.notify
|
||||||
Self::process_query(this, query, cx).await
|
Self::process_query(this, action_query, keystroke_query, cx).await
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
@ -664,6 +742,33 @@ impl KeymapEditor {
|
||||||
};
|
};
|
||||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
|
cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_conflict_filter(
|
||||||
|
&mut self,
|
||||||
|
_: &ToggleConflictFilter,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.filter_state = self.filter_state.invert();
|
||||||
|
self.update_matches(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_keystroke_search(
|
||||||
|
&mut self,
|
||||||
|
_: &ToggleKeystrokeSearch,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.search_mode = self.search_mode.invert();
|
||||||
|
self.update_matches(cx);
|
||||||
|
|
||||||
|
match self.search_mode {
|
||||||
|
SearchMode::KeyStroke => {
|
||||||
|
window.focus(&self.keystroke_editor.focus_handle(cx));
|
||||||
|
}
|
||||||
|
SearchMode::Normal => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -763,41 +868,97 @@ impl Render for KeymapEditor {
|
||||||
.on_action(cx.listener(Self::delete_binding))
|
.on_action(cx.listener(Self::delete_binding))
|
||||||
.on_action(cx.listener(Self::copy_action_to_clipboard))
|
.on_action(cx.listener(Self::copy_action_to_clipboard))
|
||||||
.on_action(cx.listener(Self::copy_context_to_clipboard))
|
.on_action(cx.listener(Self::copy_context_to_clipboard))
|
||||||
|
.on_action(cx.listener(Self::toggle_conflict_filter))
|
||||||
|
.on_action(cx.listener(Self::toggle_keystroke_search))
|
||||||
.size_full()
|
.size_full()
|
||||||
.p_2()
|
.p_2()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.bg(theme.colors().editor_background)
|
.bg(theme.colors().editor_background)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.p_2()
|
||||||
|
.gap_1()
|
||||||
.key_context({
|
.key_context({
|
||||||
let mut context = KeyContext::new_with_defaults();
|
let mut context = KeyContext::new_with_defaults();
|
||||||
context.add("BufferSearchBar");
|
context.add("BufferSearchBar");
|
||||||
context
|
context
|
||||||
})
|
})
|
||||||
.h_8()
|
.child(
|
||||||
.pl_2()
|
div()
|
||||||
.pr_1()
|
.size_full()
|
||||||
.py_1()
|
.h_8()
|
||||||
.border_1()
|
.pl_2()
|
||||||
.border_color(theme.colors().border)
|
.pr_1()
|
||||||
.rounded_lg()
|
.py_1()
|
||||||
.child(self.filter_editor.clone())
|
.border_1()
|
||||||
.when(self.keybinding_conflict_state.any_conflicts(), |this| {
|
.border_color(theme.colors().border)
|
||||||
this.child(
|
.rounded_lg()
|
||||||
IconButton::new("KeymapEditorConflictIcon", IconName::Warning)
|
.child(self.filter_editor.clone()),
|
||||||
.tooltip(Tooltip::text(match self.filter_state {
|
)
|
||||||
FilterState::All => "Show conflicts",
|
.child(
|
||||||
FilterState::Conflicts => "Hide conflicts",
|
// TODO: Ask Mikyala if there's a way to get have items be aligned by horizontally
|
||||||
}))
|
// without embedding a h_flex in another h_flex
|
||||||
.selected_icon_color(Color::Error)
|
h_flex()
|
||||||
.toggle_state(matches!(self.filter_state, FilterState::Conflicts))
|
.when(self.keybinding_conflict_state.any_conflicts(), |this| {
|
||||||
.on_click(cx.listener(|this, _, _, cx| {
|
this.child(
|
||||||
this.filter_state = this.filter_state.invert();
|
IconButton::new("KeymapEditorConflictIcon", IconName::Warning)
|
||||||
this.update_matches(cx);
|
.tooltip({
|
||||||
})),
|
let filter_state = self.filter_state;
|
||||||
)
|
|
||||||
}),
|
move |window, cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
match filter_state {
|
||||||
|
FilterState::All => "Show conflicts",
|
||||||
|
FilterState::Conflicts => "Hide conflicts",
|
||||||
|
},
|
||||||
|
&ToggleConflictFilter,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.selected_icon_color(Color::Error)
|
||||||
|
.toggle_state(matches!(
|
||||||
|
self.filter_state,
|
||||||
|
FilterState::Conflicts
|
||||||
|
))
|
||||||
|
.on_click(|_, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
ToggleConflictFilter.boxed_clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
IconButton::new("KeymapEditorToggleFiltersIcon", IconName::Filter)
|
||||||
|
.tooltip(|window, cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Toggle Keystroke Search",
|
||||||
|
&ToggleKeystrokeSearch,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.toggle_state(matches!(self.search_mode, SearchMode::KeyStroke))
|
||||||
|
.on_click(|_, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
ToggleKeystrokeSearch.boxed_clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
.when(matches!(self.search_mode, SearchMode::KeyStroke), |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.child(self.keystroke_editor.clone())
|
||||||
|
.border_1()
|
||||||
|
.border_color(theme.colors().border)
|
||||||
|
.rounded_lg(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
Table::new()
|
Table::new()
|
||||||
.interactable(&self.table_interaction_state)
|
.interactable(&self.table_interaction_state)
|
||||||
|
@ -1522,6 +1683,7 @@ async fn remove_keybinding(
|
||||||
|
|
||||||
struct KeystrokeInput {
|
struct KeystrokeInput {
|
||||||
keystrokes: Vec<Keystroke>,
|
keystrokes: Vec<Keystroke>,
|
||||||
|
highlight_on_focus: bool,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
intercept_subscription: Option<Subscription>,
|
intercept_subscription: Option<Subscription>,
|
||||||
_focus_subscriptions: [Subscription; 2],
|
_focus_subscriptions: [Subscription; 2],
|
||||||
|
@ -1536,6 +1698,7 @@ impl KeystrokeInput {
|
||||||
];
|
];
|
||||||
Self {
|
Self {
|
||||||
keystrokes: Vec::new(),
|
keystrokes: Vec::new(),
|
||||||
|
highlight_on_focus: true,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
intercept_subscription: None,
|
intercept_subscription: None,
|
||||||
_focus_subscriptions,
|
_focus_subscriptions,
|
||||||
|
@ -1553,6 +1716,7 @@ impl KeystrokeInput {
|
||||||
{
|
{
|
||||||
if !event.modifiers.modified() {
|
if !event.modifiers.modified() {
|
||||||
self.keystrokes.pop();
|
self.keystrokes.pop();
|
||||||
|
cx.emit(());
|
||||||
} else {
|
} else {
|
||||||
last.modifiers = event.modifiers;
|
last.modifiers = event.modifiers;
|
||||||
}
|
}
|
||||||
|
@ -1562,6 +1726,7 @@ impl KeystrokeInput {
|
||||||
key: "".to_string(),
|
key: "".to_string(),
|
||||||
key_char: None,
|
key_char: None,
|
||||||
});
|
});
|
||||||
|
cx.emit(());
|
||||||
}
|
}
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -1575,6 +1740,7 @@ impl KeystrokeInput {
|
||||||
} else if Some(keystroke) != self.keystrokes.last() {
|
} else if Some(keystroke) != self.keystrokes.last() {
|
||||||
self.keystrokes.push(keystroke.clone());
|
self.keystrokes.push(keystroke.clone());
|
||||||
}
|
}
|
||||||
|
cx.emit(());
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -1589,6 +1755,7 @@ impl KeystrokeInput {
|
||||||
&& !last.key.is_empty()
|
&& !last.key.is_empty()
|
||||||
&& last.modifiers == event.keystroke.modifiers
|
&& last.modifiers == event.keystroke.modifiers
|
||||||
{
|
{
|
||||||
|
cx.emit(());
|
||||||
self.keystrokes.push(Keystroke {
|
self.keystrokes.push(Keystroke {
|
||||||
modifiers: event.keystroke.modifiers,
|
modifiers: event.keystroke.modifiers,
|
||||||
key: "".to_string(),
|
key: "".to_string(),
|
||||||
|
@ -1629,6 +1796,8 @@ impl KeystrokeInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<()> for KeystrokeInput {}
|
||||||
|
|
||||||
impl Focusable for KeystrokeInput {
|
impl Focusable for KeystrokeInput {
|
||||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
|
@ -1645,9 +1814,11 @@ impl Render for KeystrokeInput {
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
|
.on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
|
||||||
.on_key_up(cx.listener(Self::on_key_up))
|
.on_key_up(cx.listener(Self::on_key_up))
|
||||||
.focus(|mut style| {
|
.when(self.highlight_on_focus, |this| {
|
||||||
style.border_color = Some(colors.border_focused);
|
this.focus(|mut style| {
|
||||||
style
|
style.border_color = Some(colors.border_focused);
|
||||||
|
style
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.py_2()
|
.py_2()
|
||||||
.px_3()
|
.px_3()
|
||||||
|
@ -1688,6 +1859,7 @@ impl Render for KeystrokeInput {
|
||||||
.when(!is_focused, |this| this.icon_color(Color::Muted))
|
.when(!is_focused, |this| this.icon_color(Color::Muted))
|
||||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
this.keystrokes.pop();
|
this.keystrokes.pop();
|
||||||
|
cx.emit(());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
@ -1697,6 +1869,7 @@ impl Render for KeystrokeInput {
|
||||||
.when(!is_focused, |this| this.icon_color(Color::Muted))
|
.when(!is_focused, |this| this.icon_color(Color::Muted))
|
||||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||||
this.keystrokes.clear();
|
this.keystrokes.clear();
|
||||||
|
cx.emit(());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue