Compare commits

...

18 Commits

Author SHA1 Message Date
0899e6779c Add alt shortcut support to many elements 2024-12-01 22:36:15 -07:00
LagoLunatic
95528fa8d2 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.
2024-12-01 22:36:15 -07:00
LagoLunatic
517b84e766 Simplify clearing of the autoscroll flag, remove &mut State 2024-12-01 22:36:15 -07:00
LagoLunatic
45dd73f5a9 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.
2024-12-01 22:36:15 -07:00
LagoLunatic
d7d7a7f14a Add escape as an alternative to back hotkey 2024-12-01 22:36:15 -07:00
LagoLunatic
441b30070e Split function diff view: Enable PageUp/PageDown/Home/End for scrolling 2024-12-01 22:36:15 -07:00
LagoLunatic
28bd7182d1 Add hotkeys to change target and base functions 2024-12-01 22:36:13 -07:00
LagoLunatic
3f03a75825 Add space as alternative to enter hotkey
This is for consistency with egui's builtint enter/space hotkey for interacting with the focused widget.
2024-12-01 22:36:05 -07:00
LagoLunatic
4fb64a3ad4 Add Ctrl+F/S shortcuts for focusing the object and symbol filter text edits 2024-12-01 22:36:05 -07:00
LagoLunatic
77c104c67b 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.
2024-12-01 22:36:05 -07:00
LagoLunatic
046f3d6999 Auto-scroll the keyboard-selected symbols into view if offscreen 2024-12-01 22:36:05 -07:00
LagoLunatic
2427b06584 Do not clear highlighted symbol when hovering mouse over an unpaired symbol 2024-12-01 22:36:05 -07:00
LagoLunatic
157e99de6f Do not clear highlighted symbol when backing out of diff view 2024-12-01 22:36:05 -07:00
LagoLunatic
b571787732 Add hotkeys to select the next symbol above/below the current one in the listing 2024-12-01 22:36:05 -07:00
LagoLunatic
dbf86ec3cf Add scroll hotkeys 2024-12-01 22:36:05 -07:00
LagoLunatic
acc1189150 Add enter and back hotkeys 2024-12-01 22:36:05 -07:00
LagoLunatic
123253c322 Update .gitignore 2024-12-01 22:36:05 -07:00
LagoLunatic
ef680a5e7d Fix missing dependency feature for objdiff-gui 2024-12-01 22:36:05 -07:00
11 changed files with 414 additions and 76 deletions

2
.gitignore vendored
View File

@@ -18,4 +18,4 @@ android.keystore
*.frag
*.vert
*.metal
.vscode/launch.json
.vscode/

View File

@@ -95,7 +95,7 @@ exec = "0.3"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@@ -25,6 +25,7 @@ use time::UtcOffset;
use crate::{
app_config::{deserialize_config, AppConfigVersion},
config::{load_project_config, ProjectObjectNode},
hotkeys,
jobs::{
objdiff::{start_build, ObjDiffConfig},
Job, JobQueue, JobResult, JobStatus,
@@ -527,6 +528,10 @@ impl App {
}
fn post_update(&mut self, ctx: &egui::Context, action: Option<DiffViewAction>) {
if action.is_some() {
ctx.request_repaint();
}
self.appearance.post_update(ctx);
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
@@ -690,13 +695,13 @@ impl eframe::App for App {
*show_side_panel = !*show_side_panel;
}
ui.separator();
ui.menu_button("File", |ui| {
ui.menu_button(hotkeys::button_alt_text(ui, "_File"), |ui| {
#[cfg(debug_assertions)]
if ui.button("Debug…").clicked() {
if ui.button(hotkeys::button_alt_text(ui, "_Debug…")).clicked() {
*show_debug = !*show_debug;
ui.close_menu();
}
if ui.button("Project…").clicked() {
if ui.button(hotkeys::button_alt_text(ui, "_Project…")).clicked() {
*show_project_config = !*show_project_config;
ui.close_menu();
}
@@ -705,10 +710,11 @@ impl eframe::App for App {
} else {
vec![]
};
let recent_projects_text = hotkeys::button_alt_text(ui, "_Recent Projects…");
if recent_projects.is_empty() {
ui.add_enabled(false, egui::Button::new("Recent projects"));
ui.add_enabled(false, egui::Button::new(recent_projects_text));
} else {
ui.menu_button("Recent Projects", |ui| {
ui.menu_button(recent_projects_text, |ui| {
if ui.button("Clear").clicked() {
state.write().unwrap().config.recent_projects.clear();
};
@@ -721,36 +727,39 @@ impl eframe::App for App {
}
});
}
if ui.button("Appearance…").clicked() {
if ui.button(hotkeys::button_alt_text(ui, "_Appearance…")).clicked() {
*show_appearance_config = !*show_appearance_config;
ui.close_menu();
}
if ui.button("Graphics…").clicked() {
if ui.button(hotkeys::button_alt_text(ui, "_Graphics…")).clicked() {
*show_graphics = !*show_graphics;
ui.close_menu();
}
if ui.button("Quit").clicked() {
if ui.button(hotkeys::button_alt_text(ui, "_Quit")).clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
ui.menu_button("Tools", |ui| {
if ui.button("Demangle…").clicked() {
ui.menu_button(hotkeys::button_alt_text(ui, "_Tools"), |ui| {
if ui.button(hotkeys::button_alt_text(ui, "_Demangle…")).clicked() {
*show_demangle = !*show_demangle;
ui.close_menu();
}
if ui.button("Rlwinm Decoder…").clicked() {
if ui.button(hotkeys::button_alt_text(ui, "_Rlwinm Decoder…")).clicked() {
*show_rlwinm_decode = !*show_rlwinm_decode;
ui.close_menu();
}
});
ui.menu_button("Diff Options", |ui| {
if ui.button("Arch Settings…").clicked() {
ui.menu_button(hotkeys::button_alt_text(ui, "_Diff Options"), |ui| {
if ui.button(hotkeys::button_alt_text(ui, "_Arch Settings…")).clicked() {
*show_arch_config = !*show_arch_config;
ui.close_menu();
}
let mut state = state.write().unwrap();
let response = ui
.checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes")
.checkbox(
&mut state.config.rebuild_on_changes,
hotkeys::button_alt_text(ui, "_Rebuild on changes"),
)
.on_hover_text("Automatically re-run the build & diff when files change.");
if response.changed() {
state.watcher_change = true;
@@ -759,18 +768,21 @@ impl eframe::App for App {
!diff_state.symbol_state.disable_reverse_fn_order,
egui::Checkbox::new(
&mut diff_state.symbol_state.reverse_fn_order,
"Reverse function order (-inline deferred)",
hotkeys::button_alt_text(
ui,
"Reverse function order (-inline _deferred)",
),
),
)
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
ui.checkbox(
&mut diff_state.symbol_state.show_hidden_symbols,
"Show hidden symbols",
hotkeys::button_alt_text(ui, "Show _hidden symbols"),
);
if ui
.checkbox(
&mut state.config.diff_obj_config.relax_reloc_diffs,
"Relax relocation diffs",
hotkeys::button_alt_text(ui, "Rela_x relocation diffs"),
)
.on_hover_text(
"Ignores differences in relocation targets. (Address, name, etc)",
@@ -782,7 +794,7 @@ impl eframe::App for App {
if ui
.checkbox(
&mut state.config.diff_obj_config.space_between_args,
"Space between args",
hotkeys::button_alt_text(ui, "_Space between args"),
)
.changed()
{
@@ -791,14 +803,17 @@ impl eframe::App for App {
if ui
.checkbox(
&mut state.config.diff_obj_config.combine_data_sections,
"Combine data sections",
hotkeys::button_alt_text(ui, "Combine _data sections"),
)
.on_hover_text("Combines data sections with equal names.")
.changed()
{
state.queue_reload = true;
}
if ui.button("Clear custom symbol mappings").clicked() {
if ui
.button(hotkeys::button_alt_text(ui, "_Clear custom symbol mappings"))
.clicked()
{
state.clear_mappings();
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
state.queue_reload = true;

228
objdiff-gui/src/hotkeys.rs Normal file
View File

@@ -0,0 +1,228 @@
use egui::{
style::ScrollAnimation, text::LayoutJob, vec2, Align, Context, FontSelection, Key,
KeyboardShortcut, Modifiers, PointerButton, RichText, Ui, WidgetText,
};
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))
}
fn shortcut_key(text: &str) -> (usize, char, Key) {
let i = text.chars().position(|c| c == '_').expect("No underscore in text");
let key = text.chars().nth(i + 1).expect("No character after underscore");
let shortcut_key = match key {
'a' | 'A' => Key::A,
'b' | 'B' => Key::B,
'c' | 'C' => Key::C,
'd' | 'D' => Key::D,
'e' | 'E' => Key::E,
'f' | 'F' => Key::F,
'g' | 'G' => Key::G,
'h' | 'H' => Key::H,
'i' | 'I' => Key::I,
'j' | 'J' => Key::J,
'k' | 'K' => Key::K,
'l' | 'L' => Key::L,
'm' | 'M' => Key::M,
'n' | 'N' => Key::N,
'o' | 'O' => Key::O,
'p' | 'P' => Key::P,
'q' | 'Q' => Key::Q,
'r' | 'R' => Key::R,
's' | 'S' => Key::S,
't' | 'T' => Key::T,
'u' | 'U' => Key::U,
'v' | 'V' => Key::V,
'w' | 'W' => Key::W,
'x' | 'X' => Key::X,
'y' | 'Y' => Key::Y,
'z' | 'Z' => Key::Z,
_ => panic!("Invalid key {}", key),
};
(i, key, shortcut_key)
}
fn alt_text_ui(ui: &Ui, text: &str, i: usize, key: char, interactive: bool) -> WidgetText {
let color = if interactive {
ui.visuals().widgets.inactive.text_color()
} else {
ui.visuals().widgets.noninteractive.text_color()
};
let mut job = LayoutJob::default();
if i > 0 {
let text = &text[..i];
RichText::new(text).color(color).append_to(
&mut job,
ui.style(),
FontSelection::Default,
Align::Center,
);
}
let mut rt = RichText::new(key).color(color);
if ui.input(|i| i.modifiers.alt) {
rt = rt.underline();
}
rt.append_to(&mut job, ui.style(), FontSelection::Default, Align::Center);
if i < text.len() - 1 {
let text = &text[i + 2..];
RichText::new(text).color(color).append_to(
&mut job,
ui.style(),
FontSelection::Default,
Align::Center,
);
}
job.into()
}
pub fn button_alt_text(ui: &Ui, text: &str) -> WidgetText {
let (n, c, key) = shortcut_key(text);
let result = alt_text_ui(ui, text, n, c, true);
if ui.input_mut(|i| check_alt_key(i, c, key)) {
let btn_id = ui.next_auto_id();
ui.memory_mut(|m| m.request_focus(btn_id));
ui.input_mut(|i| {
i.events.push(egui::Event::Key {
key: Key::Enter,
physical_key: None,
pressed: true,
repeat: false,
modifiers: Default::default(),
})
});
}
result
}
pub fn alt_text(ui: &Ui, text: &str, enter: bool) -> WidgetText {
let (n, c, key) = shortcut_key(text);
let result = alt_text_ui(ui, text, n, c, false);
if ui.input_mut(|i| check_alt_key(i, c, key)) {
let btn_id = ui.next_auto_id();
ui.memory_mut(|m| m.request_focus(btn_id));
if enter {
ui.input_mut(|i| {
i.events.push(egui::Event::Key {
key: Key::Enter,
physical_key: None,
pressed: true,
repeat: false,
modifiers: Default::default(),
})
});
}
}
result
}
fn check_alt_key(i: &mut egui::InputState, c: char, key: Key) -> bool {
if i.consume_key(Modifiers::ALT, key) {
// Remove any text input events that match the key
let cs = c.to_string();
i.events.retain(|e| !matches!(e, egui::Event::Text(t) if *t == cs));
true
} else {
false
}
}

View File

@@ -4,6 +4,7 @@ mod app;
mod app_config;
mod config;
mod fonts;
mod hotkeys;
mod jobs;
mod update;
mod views;

View File

@@ -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,
@@ -218,7 +219,7 @@ pub fn config_ui(
ui.horizontal(|ui| {
ui.heading("Project");
if ui.button(RichText::new("Settings")).clicked() {
if ui.button("Settings").clicked() {
*show_config_window = true;
}
});
@@ -254,7 +255,12 @@ 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(hotkeys::alt_text(ui, "Filter _objects", false))
.ui(ui);
if hotkeys::consume_object_filter_shortcut(ui.ctx()) {
response.request_focus();
}
let mut root_open = None;
let mut node_open = NodeOpen::Default;

View File

@@ -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;
@@ -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;
@@ -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()));
}

View File

@@ -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,
},
},
};
@@ -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();
@@ -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 {

View File

@@ -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,
},
},
};
@@ -432,6 +435,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,
@@ -467,6 +471,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,
@@ -512,9 +517,6 @@ fn asm_table_ui(
SymbolRefByName::new(right_symbol, right_section),
));
}
DiffViewAction::SetSymbolHighlight(_, _) => {
// Ignore
}
_ => {
ret = Some(action);
}
@@ -527,6 +529,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,
@@ -572,9 +575,6 @@ fn asm_table_ui(
right_symbol_ref,
));
}
DiffViewAction::SetSymbolHighlight(_, _) => {
// Ignore
}
_ => {
ret = Some(action);
}
@@ -675,7 +675,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();
@@ -708,10 +708,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()));
@@ -785,6 +786,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()));

View File

@@ -3,6 +3,7 @@ use std::cmp::Ordering;
use egui::{ProgressBar, RichText, Widget};
use crate::{
hotkeys,
jobs::{JobQueue, JobStatus},
views::appearance::Appearance,
};
@@ -94,7 +95,14 @@ impl From<&JobStatus> for JobStatusDisplay {
}
pub fn jobs_menu_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance) -> bool {
ui.label("Jobs:");
let mut clicked = false;
if egui::Label::new(hotkeys::alt_text(ui, "_Jobs:", true))
.sense(egui::Sense::click())
.ui(ui)
.clicked()
{
clicked = true;
}
let mut statuses = Vec::new();
for job in jobs.iter_mut() {
let Ok(status) = job.context.status.read() else {
@@ -105,7 +113,6 @@ pub fn jobs_menu_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appeara
let running_jobs = statuses.iter().filter(|s| !s.error).count();
let error_jobs = statuses.iter().filter(|s| s.error).count();
let mut clicked = false;
let spinner =
egui::Spinner::new().size(appearance.ui_font.size * 0.9).color(appearance.text_color);
match running_jobs.cmp(&1) {

View File

@@ -1,8 +1,8 @@
use std::{collections::BTreeMap, mem::take};
use std::{collections::BTreeMap, mem::take, ops::Bound};
use egui::{
text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, SelectableLabel, TextEdit,
Ui, Widget,
style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea,
SelectableLabel, TextEdit, Ui, Widget,
};
use objdiff_core::{
arch::ObjArch,
@@ -15,6 +15,7 @@ use regex::{Regex, RegexBuilder};
use crate::{
app::AppStateRef,
hotkeys,
jobs::{
create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult},
objdiff::{BuildStatus, ObjDiffResult},
@@ -56,8 +57,8 @@ pub enum DiffViewAction {
Build,
/// Navigate to a new diff view
Navigate(DiffViewNavigation),
/// Set the highlighted symbols in the symbols view
SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>),
/// Set the highlighted symbols in the symbols view, optionally scrolling them into view.
SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>, bool),
/// Set the symbols view search filter
SetSearch(String),
/// Submit the current function to decomp.me
@@ -135,6 +136,7 @@ pub struct DiffViewState {
#[derive(Default)]
pub struct SymbolViewState {
pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>),
pub autoscroll_to_highlighted_symbols: bool,
pub left_symbol: Option<SymbolRefByName>,
pub right_symbol: Option<SymbolRefByName>,
pub reverse_fn_order: bool,
@@ -197,6 +199,9 @@ impl DiffViewState {
ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url)));
}
// Clear the autoscroll flag so that it doesn't scroll continuously.
self.symbol_state.autoscroll_to_highlighted_symbols = false;
let Some(action) = action else {
return;
};
@@ -211,7 +216,6 @@ impl DiffViewState {
// Ignore action if we're already navigating
return;
}
self.symbol_state.highlighted_symbol = (None, None);
let Ok(mut state) = state.write() else {
return;
};
@@ -247,8 +251,9 @@ impl DiffViewState {
self.post_build_nav = Some(nav);
}
}
DiffViewAction::SetSymbolHighlight(left, right) => {
DiffViewAction::SetSymbolHighlight(left, right, autoscroll) => {
self.symbol_state.highlighted_symbol = (left, right);
self.symbol_state.autoscroll_to_highlighted_symbols = autoscroll;
}
DiffViewAction::SetSearch(search) => {
self.search_regex = if search.is_empty() {
@@ -534,7 +539,15 @@ fn symbol_ui(
ret = Some(DiffViewAction::Navigate(result));
}
});
if response.clicked() {
if selected && state.autoscroll_to_highlighted_symbols {
// Automatically scroll the view to encompass the selected symbol in case the user selected
// an offscreen symbol by using a keyboard shortcut.
ui.scroll_to_rect_animation(response.rect, None, ScrollAnimation::none());
// This autoscroll state flag will be reset in DiffViewState::post_update at the end of
// every frame so that we don't continuously scroll the view back when the user is trying to
// manually scroll away.
}
if response.clicked() || (selected && hotkeys::enter_pressed(ui.ctx())) {
if let Some(section) = section {
match section.kind {
ObjSectionKind::Code => {
@@ -561,20 +574,18 @@ fn symbol_ui(
}
}
} else if response.hovered() {
ret = Some(if let Some(target_symbol) = symbol_diff.target_symbol {
if column == 0 {
DiffViewAction::SetSymbolHighlight(
Some(symbol_diff.symbol_ref),
Some(target_symbol),
)
} else {
DiffViewAction::SetSymbolHighlight(
Some(target_symbol),
Some(symbol_diff.symbol_ref),
)
}
ret = Some(if column == 0 {
DiffViewAction::SetSymbolHighlight(
Some(symbol_diff.symbol_ref),
symbol_diff.target_symbol,
false,
)
} else {
DiffViewAction::SetSymbolHighlight(None, None)
DiffViewAction::SetSymbolHighlight(
symbol_diff.target_symbol,
Some(symbol_diff.symbol_ref),
false,
)
});
}
ret
@@ -648,6 +659,58 @@ pub fn symbol_list_ui(
}
}
hotkeys::check_scroll_hotkeys(ui, false);
let mut new_key_value_to_highlight = None;
if let Some(sym_ref) =
if column == 0 { state.highlighted_symbol.0 } else { state.highlighted_symbol.1 }
{
let up = if hotkeys::consume_up_key(ui.ctx()) {
Some(true)
} else if hotkeys::consume_down_key(ui.ctx()) {
Some(false)
} else {
None
};
if let Some(mut up) = up {
if state.reverse_fn_order {
up = !up;
}
new_key_value_to_highlight = if up {
mapping.range(..sym_ref).next_back()
} else {
mapping.range((Bound::Excluded(sym_ref), Bound::Unbounded)).next()
};
};
} else {
// No symbol is highlighted in this column. Select the topmost symbol instead.
// Note that we intentionally do not consume the up/down key presses in this case, but
// we do when a symbol is highlighted. This is so that if only one column has a symbol
// highlighted, that one takes precedence over the one with nothing highlighted.
if hotkeys::up_pressed(ui.ctx()) || hotkeys::down_pressed(ui.ctx()) {
new_key_value_to_highlight = if state.reverse_fn_order {
mapping.last_key_value()
} else {
mapping.first_key_value()
};
}
}
if let Some((new_sym_ref, new_symbol_diff)) = new_key_value_to_highlight {
ret = Some(if column == 0 {
DiffViewAction::SetSymbolHighlight(
Some(*new_sym_ref),
new_symbol_diff.target_symbol,
true,
)
} else {
DiffViewAction::SetSymbolHighlight(
new_symbol_diff.target_symbol,
Some(*new_sym_ref),
true,
)
});
}
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
@@ -838,7 +901,13 @@ pub fn symbol_diff_ui(
});
let mut search = state.search.clone();
if TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui).changed() {
let response = TextEdit::singleline(&mut search)
.hint_text(hotkeys::alt_text(ui, "Filter _symbols", false))
.ui(ui);
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
response.request_focus();
}
if response.changed() {
ret = Some(DiffViewAction::SetSearch(search));
}
} else if column == 1 {