Skip to content

Commit

Permalink
objdiff-gui: Implement keyboard shortcuts (#139)
Browse files Browse the repository at this point in the history
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* Add enter and back hotkeys

* Add scroll hotkeys

* Add hotkeys to select the next symbol above/below the current one in the listing

* Do not clear highlighted symbol when backing out of diff view

* Do not clear highlighted symbol when hovering mouse over an unpaired symbol

* Auto-scroll the keyboard-selected symbols into view if offscreen

* Fix some hotkeys stealing input from focused widgets

e.g. The symbol list was stealing the W/S key presses when typing into the symbol filter text edit.

If the user actually wants to use these shortcuts while a widget is focused, they can simply press the escape key to unfocus all widgets and then press the shortcut.

* Add Ctrl+F/S shortcuts for focusing the object and symbol filter text edits

* Add space as alternative to enter hotkey

This is for consistency with egui's builtint enter/space hotkey for interacting with the focused widget.

* Add hotkeys to change target and base functions

* Split function diff view: Enable PageUp/PageDown/Home/End for scrolling

* Add escape as an alternative to back hotkey

* Fix auto-scrolling to highlighted symbol only working for the left side

The flag is cleared after one scroll to avoid doing it continuously, but this breaks when we need to scroll to both the left and the right symbol at the same time. So now each side has its own flag to keep track of this state independently.

* Simplify clearing of the autoscroll flag, remove &mut State

* Found a better place to clear the autoscroll flag

DiffViewState::post_update is where the flag gets set, so clearing it right before that at the start of the function seems to make the most sense, instead of doing it in App::update.
  • Loading branch information
LagoLunatic authored Dec 3, 2024
1 parent 304df96 commit abe68ef
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 51 deletions.
108 changes: 108 additions & 0 deletions objdiff-gui/src/hotkeys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use egui::{
style::ScrollAnimation, vec2, Context, Key, KeyboardShortcut, Modifiers, PointerButton,
};

fn any_widget_focused(ctx: &Context) -> bool { ctx.memory(|mem| mem.focused().is_some()) }

pub fn enter_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.key_pressed(Key::Enter)
|| i.key_pressed(Key::Space)
|| i.pointer.button_pressed(PointerButton::Extra2)
})
}

pub fn back_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.key_pressed(Key::Backspace)
|| i.key_pressed(Key::Escape)
|| i.pointer.button_pressed(PointerButton::Extra1)
})
}

pub fn up_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| i.key_pressed(Key::ArrowUp) || i.key_pressed(Key::W))
}

pub fn down_pressed(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| i.key_pressed(Key::ArrowDown) || i.key_pressed(Key::S))
}

pub fn page_up_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::PageUp)) }

pub fn page_down_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::PageDown)) }

pub fn home_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::Home)) }

pub fn end_pressed(ctx: &Context) -> bool { ctx.input_mut(|i| i.key_pressed(Key::End)) }

pub fn check_scroll_hotkeys(ui: &mut egui::Ui, include_small_increments: bool) {
let ui_height = ui.available_rect_before_wrap().height();
if up_pressed(ui.ctx()) && include_small_increments {
ui.scroll_with_delta_animation(vec2(0.0, ui_height / 10.0), ScrollAnimation::none());
} else if down_pressed(ui.ctx()) && include_small_increments {
ui.scroll_with_delta_animation(vec2(0.0, -ui_height / 10.0), ScrollAnimation::none());
} else if page_up_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, ui_height), ScrollAnimation::none());
} else if page_down_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, -ui_height), ScrollAnimation::none());
} else if home_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, f32::INFINITY), ScrollAnimation::none());
} else if end_pressed(ui.ctx()) {
ui.scroll_with_delta_animation(vec2(0.0, -f32::INFINITY), ScrollAnimation::none());
}
}

pub fn consume_up_key(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.consume_key(Modifiers::NONE, Key::ArrowUp) || i.consume_key(Modifiers::NONE, Key::W)
})
}

pub fn consume_down_key(ctx: &Context) -> bool {
if any_widget_focused(ctx) {
return false;
}
ctx.input_mut(|i| {
i.consume_key(Modifiers::NONE, Key::ArrowDown) || i.consume_key(Modifiers::NONE, Key::S)
})
}

const OBJECT_FILTER_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::F);

pub fn consume_object_filter_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&OBJECT_FILTER_SHORTCUT))
}

const SYMBOL_FILTER_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::S);

pub fn consume_symbol_filter_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&SYMBOL_FILTER_SHORTCUT))
}

const CHANGE_TARGET_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::T);

pub fn consume_change_target_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_TARGET_SHORTCUT))
}

const CHANGE_BASE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::B);

pub fn consume_change_base_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_BASE_SHORTCUT))
}
1 change: 1 addition & 0 deletions objdiff-gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod app;
mod app_config;
mod config;
mod fonts;
mod hotkeys;
mod jobs;
mod update;
mod views;
Expand Down
7 changes: 6 additions & 1 deletion objdiff-gui/src/views/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use strum::{EnumMessage, VariantArray};
use crate::{
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
config::ProjectObjectNode,
hotkeys,
jobs::{
check_update::{start_check_update, CheckUpdateResult},
update::start_update,
Expand Down Expand Up @@ -254,7 +255,11 @@ pub fn config_ui(
}
} else {
let had_search = !config_state.object_search.is_empty();
egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui);
let response =
egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui);
if hotkeys::consume_object_filter_shortcut(ui.ctx()) {
response.request_focus();
}

let mut root_open = None;
let mut node_open = NodeOpen::Default;
Expand Down
17 changes: 11 additions & 6 deletions objdiff-gui/src/views/data_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ use objdiff_core::{
};
use time::format_description;

use crate::views::{
appearance::Appearance,
column_layout::{render_header, render_table},
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
write_text,
use crate::{
hotkeys,
views::{
appearance::Appearance,
column_layout::{render_header, render_table},
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
write_text,
},
};

const BYTES_PER_ROW: usize = 16;
Expand Down Expand Up @@ -176,6 +179,8 @@ fn data_table_ui(
let left_diffs = left_section.map(|(_, section)| split_diffs(&section.data_diff));
let right_diffs = right_section.map(|(_, section)| split_diffs(&section.data_diff));

hotkeys::check_scroll_hotkeys(ui, true);

render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| {
let i = row.index();
let address = i * BYTES_PER_ROW;
Expand Down Expand Up @@ -224,7 +229,7 @@ pub fn data_diff_ui(
render_header(ui, available_width, 2, |ui, column| {
if column == 0 {
// Left column
if ui.button("⏴ Back").clicked() {
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
}

Expand Down
21 changes: 13 additions & 8 deletions objdiff-gui/src/views/extab_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ use objdiff_core::{
};
use time::format_description;

use crate::views::{
appearance::Appearance,
column_layout::{render_header, render_strips},
function_diff::FunctionDiffContext,
symbol_diff::{
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState, SymbolRefByName,
View,
use crate::{
hotkeys,
views::{
appearance::Appearance,
column_layout::{render_header, render_strips},
function_diff::FunctionDiffContext,
symbol_diff::{
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState,
SymbolRefByName, View,
},
},
};

Expand Down Expand Up @@ -136,7 +139,7 @@ pub fn extab_diff_ui(
if column == 0 {
// Left column
ui.horizontal(|ui| {
if ui.button("⏴ Back").clicked() {
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
}
ui.separator();
Expand Down Expand Up @@ -232,6 +235,8 @@ pub fn extab_diff_ui(
}
});

hotkeys::check_scroll_hotkeys(ui, true);

// Table
render_strips(ui, available_width, 2, |ui, column| {
if column == 0 {
Expand Down
30 changes: 16 additions & 14 deletions objdiff-gui/src/views/function_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ use objdiff_core::{
};
use time::format_description;

use crate::views::{
appearance::Appearance,
column_layout::{render_header, render_strips, render_table},
symbol_diff::{
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, DiffViewState,
SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
use crate::{
hotkeys,
views::{
appearance::Appearance,
column_layout::{render_header, render_strips, render_table},
symbol_diff::{
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation,
DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
},
},
};

Expand Down Expand Up @@ -436,6 +439,7 @@ fn asm_table_ui(
};
if left_len.is_some() && right_len.is_some() {
// Joint view
hotkeys::check_scroll_hotkeys(ui, true);
render_table(
ui,
available_width,
Expand Down Expand Up @@ -471,6 +475,7 @@ fn asm_table_ui(
if column == 0 {
if let Some(ctx) = left_ctx {
if ctx.has_symbol() {
hotkeys::check_scroll_hotkeys(ui, false);
render_table(
ui,
available_width / 2.0,
Expand Down Expand Up @@ -516,9 +521,6 @@ fn asm_table_ui(
SymbolRefByName::new(right_symbol, right_section),
));
}
DiffViewAction::SetSymbolHighlight(_, _) => {
// Ignore
}
_ => {
ret = Some(action);
}
Expand All @@ -531,6 +533,7 @@ fn asm_table_ui(
} else if column == 1 {
if let Some(ctx) = right_ctx {
if ctx.has_symbol() {
hotkeys::check_scroll_hotkeys(ui, false);
render_table(
ui,
available_width / 2.0,
Expand Down Expand Up @@ -576,9 +579,6 @@ fn asm_table_ui(
right_symbol_ref,
));
}
DiffViewAction::SetSymbolHighlight(_, _) => {
// Ignore
}
_ => {
ret = Some(action);
}
Expand Down Expand Up @@ -679,7 +679,7 @@ pub fn function_diff_ui(
if column == 0 {
// Left column
ui.horizontal(|ui| {
if ui.button("⏴ Back").clicked() {
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
}
ui.separator();
Expand Down Expand Up @@ -712,10 +712,11 @@ pub fn function_diff_ui(
.color(appearance.highlight_color),
);
if right_ctx.is_some_and(|m| m.has_symbol())
&& ui
&& (ui
.button("Change target")
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
.clicked()
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
{
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
Expand Down Expand Up @@ -789,6 +790,7 @@ pub fn function_diff_ui(
"Choose a different symbol to use as the base",
)
.clicked()
|| hotkeys::consume_change_base_shortcut(ui.ctx())
{
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
Expand Down
Loading

0 comments on commit abe68ef

Please sign in to comment.