Implement key bind reset

This commit is contained in:
Jeremy Soller 2026-02-05 12:57:56 -07:00
parent 6752b9a434
commit 1b980df309
5 changed files with 93 additions and 53 deletions

View file

@ -63,33 +63,33 @@ show-headerbar = Show header
show-header-description = Reveal the header from the right-click menu. show-header-description = Reveal the header from the right-click menu.
### Keyboard shortcuts ### Keyboard shortcuts
type-to-search = Type to search... add-another-keybinding = Add another keybinding
keyboard-shortcuts = Keyboard shortcuts
menu-keyboard-shortcuts = Keyboard shortcuts...
customize-shortcuts = Customize shortcuts
shortcut-capture-hint = Press new shortcut, or Esc to cancel
cancel = Cancel cancel = Cancel
replace = Replace close-window = Close window
shortcut-replace-title = Replace shortcut?
shortcut-replace-body = { $binding } is already assigned to { $existing }. Replace it with { $new_action }?
no-shortcuts = No shortcuts
add-shortcut = + Add
shortcut-group-clipboard = Clipboard
shortcut-group-tabs = Tabs
shortcut-group-window = Window
shortcut-group-zoom = Zoom
shortcut-group-other = Other
unbind = Unbind
copy-or-sigint = Copy or SIGINT copy-or-sigint = Copy or SIGINT
paste-primary = Paste primary focus-pane-down = Focus pane down
focus-pane-left = Focus pane left focus-pane-left = Focus pane left
focus-pane-right = Focus pane right focus-pane-right = Focus pane right
focus-pane-up = Focus pane up focus-pane-up = Focus pane up
focus-pane-down = Focus pane down keyboard-shortcuts = Keyboard shortcuts
toggle-fullscreen = Toggle fullscreen menu-keyboard-shortcuts = Keyboard shortcuts...
close-window = Close window no-shortcuts = No shortcuts
password-manager = Password manager password-manager = Password manager
paste-primary = Paste primary
replace = Replace
reset-to-default = Reset to default
shortcut-capture-hint = Press new shortcut, or Esc to cancel
shortcut-group-clipboard = Clipboard
shortcut-group-other = Other
shortcut-group-tabs = Tabs
shortcut-group-window = Window
shortcut-group-zoom = Zoom
shortcut-replace-body = { $binding } is already assigned to { $existing }. Replace it with { $new_action }?
shortcut-replace-title = Replace shortcut?
tab-activate = Activate tab { $number } tab-activate = Activate tab { $number }
toggle-fullscreen = Toggle fullscreen
type-to-search = Type to search...
unbind = Unbind
# Find # Find
find-placeholder = Find... find-placeholder = Find...

View file

@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_409_3702)">
<path d="M7 2L2 5L7 8V6H10C11.68 6 13 7.32 13 9C13 10.68 11.68 12 10 12L6 12.004C5.73478 12.004 5.48043 12.1094 5.29289 12.2969C5.10536 12.4844 5 12.7388 5 13.004C5 13.2692 5.10536 13.5236 5.29289 13.7111C5.48043 13.8987 5.73478 14.004 6 14.004L10 14C12.753 13.997 15 11.753 15 9C15 6.247 12.753 4 10 4H7V2Z" fill="#232323"/>
</g>
<defs>
<clipPath id="clip0_409_3702">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 573 B

View file

@ -33,6 +33,7 @@ impl IconCache {
bundle!("dialog-error-symbolic", 16); bundle!("dialog-error-symbolic", 16);
bundle!("edit-clear-symbolic", 16); bundle!("edit-clear-symbolic", 16);
bundle!("edit-delete-symbolic", 16); bundle!("edit-delete-symbolic", 16);
bundle!("edit-undo-symbolic", 16);
bundle!("list-add-symbolic", 16); bundle!("list-add-symbolic", 16);
bundle!("go-down-symbolic", 16); bundle!("go-down-symbolic", 16);
bundle!("go-up-symbolic", 16); bundle!("go-up-symbolic", 16);

View file

@ -163,10 +163,7 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
}; };
let shortcuts_config = shortcuts::ShortcutsConfig { let shortcuts_config = shortcuts::ShortcutsConfig::new(config.shortcuts_custom.clone());
defaults: shortcuts::Shortcuts::default(),
custom: config.shortcuts_custom.clone(),
};
let startup_options = if let Some(shell_program) = shell_program_opt { let startup_options = if let Some(shell_program) = shell_program_opt {
let options = tty::Options { let options = tty::Options {
@ -382,6 +379,7 @@ pub enum Message {
ShortcutConflictCancel, ShortcutConflictCancel,
ShortcutConflictReplace, ShortcutConflictReplace,
ShortcutRemove(shortcuts::Binding, shortcuts::BindingSource), ShortcutRemove(shortcuts::Binding, shortcuts::BindingSource),
ShortcutReset(shortcuts::KeyBindAction),
ShortcutSearch(String), ShortcutSearch(String),
MouseEnter(pane_grid::Pane), MouseEnter(pane_grid::Pane),
Opacity(u8), Opacity(u8),
@ -1003,16 +1001,28 @@ impl App {
} }
found_actions = true; found_actions = true;
let bindings = self.shortcuts_config.bindings_for_action(action); let (bindings, changed) = self.shortcuts_config.bindings_for_action(action);
let mut buttons = widget::row::with_capacity(2);
if changed {
buttons = buttons.push(widget::tooltip(
widget::button::custom(icon_cache_get("edit-undo-symbolic", 16))
.class(style::Button::Icon)
.on_press(Message::ShortcutReset(action)),
widget::text::body(fl!("reset-to-default")),
widget::tooltip::Position::Top,
));
}
buttons = buttons.push(widget::tooltip(
widget::button::custom(icon_cache_get("list-add-symbolic", 16))
.class(style::Button::Icon)
.on_press(Message::ShortcutCaptureStart(action)),
widget::text::body(fl!("add-another-keybinding")),
widget::tooltip::Position::Top,
));
list = list.list_item_padding(pad_m); list = list.list_item_padding(pad_m);
list = list.add( list = list.add(widget::settings::item::builder(action_label).control(buttons));
widget::settings::item::builder(action_label).control(
widget::button::custom(icon_cache_get("list-add-symbolic", 16))
.class(style::Button::Icon)
.on_press(Message::ShortcutCaptureStart(action)),
),
);
list = list.divider_padding(div_m); list = list.divider_padding(div_m);
if bindings.is_empty() { if bindings.is_empty() {
@ -2090,10 +2100,8 @@ impl Application for App {
//TODO: update syntax theme by clearing tabs, only if needed //TODO: update syntax theme by clearing tabs, only if needed
self.config = config; self.config = config;
if shortcuts_changed { if shortcuts_changed {
self.shortcuts_config = shortcuts::ShortcutsConfig { self.shortcuts_config =
defaults: shortcuts::Shortcuts::default(), shortcuts::ShortcutsConfig::new(self.config.shortcuts_custom.clone());
custom: self.config.shortcuts_custom.clone(),
};
self.key_binds = key_binds(&self.shortcuts_config); self.key_binds = key_binds(&self.shortcuts_config);
} }
return self.update_config(); return self.update_config();
@ -2431,6 +2439,10 @@ impl Application for App {
} }
self.save_shortcuts_custom(); self.save_shortcuts_custom();
} }
Message::ShortcutReset(reset_action) => {
self.shortcuts_config.reset_action(reset_action);
self.save_shortcuts_custom();
}
Message::ShortcutSearch(search) => { Message::ShortcutSearch(search) => {
self.shortcut_search_focus.set(true); self.shortcut_search_focus.set(true);
self.shortcut_search_regex = None; self.shortcut_search_regex = None;

View file

@ -163,38 +163,47 @@ pub struct ResolvedBinding {
pub source: BindingSource, pub source: BindingSource,
} }
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ShortcutsConfig { pub struct ShortcutsConfig {
pub defaults: Shortcuts, defaults: Shortcuts,
pub custom: Shortcuts, pub custom: Shortcuts,
} }
impl ShortcutsConfig { impl ShortcutsConfig {
pub fn new(custom: Shortcuts) -> Self {
Self {
defaults: fallback_shortcuts(),
custom,
}
}
pub fn key_binds(&self) -> HashMap<KeyBind, Action> { pub fn key_binds(&self) -> HashMap<KeyBind, Action> {
let mut binds = HashMap::new(); let mut binds = HashMap::new();
let defaults = self.defaults_or_fallback(); insert_shortcuts(&self.defaults, &mut binds, false);
insert_shortcuts(&defaults, &mut binds, false);
insert_shortcuts(&self.custom, &mut binds, true); insert_shortcuts(&self.custom, &mut binds, true);
binds binds
} }
pub fn bindings_for_action(&self, action: KeyBindAction) -> Vec<ResolvedBinding> { pub fn bindings_for_action(&self, action: KeyBindAction) -> (Vec<ResolvedBinding>, bool) {
let mut bindings = Vec::new(); let mut bindings = Vec::new();
let defaults = self.defaults_or_fallback();
for (binding, default_action) in &defaults.0 { let mut changed = false;
for (binding, default_action) in &self.defaults.0 {
if *default_action != action { if *default_action != action {
continue; continue;
} }
match self.custom.0.get(binding) { match self.custom.0.get(binding) {
Some(KeyBindAction::Unbind) => (), Some(KeyBindAction::Unbind) => {
changed = true;
}
Some(custom_action) => { Some(custom_action) => {
if *custom_action == action { if *custom_action == action {
bindings.push(ResolvedBinding { bindings.push(ResolvedBinding {
binding: binding.clone(), binding: binding.clone(),
source: BindingSource::Custom, source: BindingSource::Custom,
}); });
changed = true;
} }
} }
None => bindings.push(ResolvedBinding { None => bindings.push(ResolvedBinding {
@ -212,10 +221,11 @@ impl ShortcutsConfig {
binding: binding.clone(), binding: binding.clone(),
source: BindingSource::Custom, source: BindingSource::Custom,
}); });
changed = true;
} }
} }
bindings (bindings, changed)
} }
pub fn action_for_binding(&self, binding: &Binding) -> Option<KeyBindAction> { pub fn action_for_binding(&self, binding: &Binding) -> Option<KeyBindAction> {
@ -226,16 +236,23 @@ impl ShortcutsConfig {
return Some(*action); return Some(*action);
} }
let defaults = self.defaults_or_fallback(); self.defaults.0.get(binding).copied()
defaults.0.get(binding).copied()
} }
fn defaults_or_fallback(&self) -> Shortcuts { pub fn reset_action(&mut self, reset_action: KeyBindAction) {
if self.defaults.0.is_empty() { self.custom.0.retain(|binding, action| {
fallback_shortcuts() if *action == reset_action {
} else { // Remove any matching bindings
self.defaults.clone() return false;
} }
if let Some(default_action) = self.defaults.0.get(binding) {
if *default_action == reset_action {
// Remove binding that overrode a default
return false;
}
}
true
});
} }
} }