Skip to content

Commit 0899e67

Browse files
committed
Add alt shortcut support to many elements
1 parent 95528fa commit 0899e67

File tree

5 files changed

+172
-27
lines changed

5 files changed

+172
-27
lines changed

objdiff-gui/src/app.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use time::UtcOffset;
2525
use crate::{
2626
app_config::{deserialize_config, AppConfigVersion},
2727
config::{load_project_config, ProjectObjectNode},
28+
hotkeys,
2829
jobs::{
2930
objdiff::{start_build, ObjDiffConfig},
3031
Job, JobQueue, JobResult, JobStatus,
@@ -527,6 +528,10 @@ impl App {
527528
}
528529

529530
fn post_update(&mut self, ctx: &egui::Context, action: Option<DiffViewAction>) {
531+
if action.is_some() {
532+
ctx.request_repaint();
533+
}
534+
530535
self.appearance.post_update(ctx);
531536

532537
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
@@ -690,13 +695,13 @@ impl eframe::App for App {
690695
*show_side_panel = !*show_side_panel;
691696
}
692697
ui.separator();
693-
ui.menu_button("File", |ui| {
698+
ui.menu_button(hotkeys::button_alt_text(ui, "_File"), |ui| {
694699
#[cfg(debug_assertions)]
695-
if ui.button("Debug…").clicked() {
700+
if ui.button(hotkeys::button_alt_text(ui, "_Debug…")).clicked() {
696701
*show_debug = !*show_debug;
697702
ui.close_menu();
698703
}
699-
if ui.button("Project…").clicked() {
704+
if ui.button(hotkeys::button_alt_text(ui, "_Project…")).clicked() {
700705
*show_project_config = !*show_project_config;
701706
ui.close_menu();
702707
}
@@ -705,10 +710,11 @@ impl eframe::App for App {
705710
} else {
706711
vec![]
707712
};
713+
let recent_projects_text = hotkeys::button_alt_text(ui, "_Recent Projects…");
708714
if recent_projects.is_empty() {
709-
ui.add_enabled(false, egui::Button::new("Recent projects…"));
715+
ui.add_enabled(false, egui::Button::new(recent_projects_text));
710716
} else {
711-
ui.menu_button("Recent Projects…", |ui| {
717+
ui.menu_button(recent_projects_text, |ui| {
712718
if ui.button("Clear").clicked() {
713719
state.write().unwrap().config.recent_projects.clear();
714720
};
@@ -721,36 +727,39 @@ impl eframe::App for App {
721727
}
722728
});
723729
}
724-
if ui.button("Appearance…").clicked() {
730+
if ui.button(hotkeys::button_alt_text(ui, "_Appearance…")).clicked() {
725731
*show_appearance_config = !*show_appearance_config;
726732
ui.close_menu();
727733
}
728-
if ui.button("Graphics…").clicked() {
734+
if ui.button(hotkeys::button_alt_text(ui, "_Graphics…")).clicked() {
729735
*show_graphics = !*show_graphics;
730736
ui.close_menu();
731737
}
732-
if ui.button("Quit").clicked() {
738+
if ui.button(hotkeys::button_alt_text(ui, "_Quit")).clicked() {
733739
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
734740
}
735741
});
736-
ui.menu_button("Tools", |ui| {
737-
if ui.button("Demangle…").clicked() {
742+
ui.menu_button(hotkeys::button_alt_text(ui, "_Tools"), |ui| {
743+
if ui.button(hotkeys::button_alt_text(ui, "_Demangle…")).clicked() {
738744
*show_demangle = !*show_demangle;
739745
ui.close_menu();
740746
}
741-
if ui.button("Rlwinm Decoder…").clicked() {
747+
if ui.button(hotkeys::button_alt_text(ui, "_Rlwinm Decoder…")).clicked() {
742748
*show_rlwinm_decode = !*show_rlwinm_decode;
743749
ui.close_menu();
744750
}
745751
});
746-
ui.menu_button("Diff Options", |ui| {
747-
if ui.button("Arch Settings…").clicked() {
752+
ui.menu_button(hotkeys::button_alt_text(ui, "_Diff Options"), |ui| {
753+
if ui.button(hotkeys::button_alt_text(ui, "_Arch Settings…")).clicked() {
748754
*show_arch_config = !*show_arch_config;
749755
ui.close_menu();
750756
}
751757
let mut state = state.write().unwrap();
752758
let response = ui
753-
.checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes")
759+
.checkbox(
760+
&mut state.config.rebuild_on_changes,
761+
hotkeys::button_alt_text(ui, "_Rebuild on changes"),
762+
)
754763
.on_hover_text("Automatically re-run the build & diff when files change.");
755764
if response.changed() {
756765
state.watcher_change = true;
@@ -759,18 +768,21 @@ impl eframe::App for App {
759768
!diff_state.symbol_state.disable_reverse_fn_order,
760769
egui::Checkbox::new(
761770
&mut diff_state.symbol_state.reverse_fn_order,
762-
"Reverse function order (-inline deferred)",
771+
hotkeys::button_alt_text(
772+
ui,
773+
"Reverse function order (-inline _deferred)",
774+
),
763775
),
764776
)
765777
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
766778
ui.checkbox(
767779
&mut diff_state.symbol_state.show_hidden_symbols,
768-
"Show hidden symbols",
780+
hotkeys::button_alt_text(ui, "Show _hidden symbols"),
769781
);
770782
if ui
771783
.checkbox(
772784
&mut state.config.diff_obj_config.relax_reloc_diffs,
773-
"Relax relocation diffs",
785+
hotkeys::button_alt_text(ui, "Rela_x relocation diffs"),
774786
)
775787
.on_hover_text(
776788
"Ignores differences in relocation targets. (Address, name, etc)",
@@ -782,7 +794,7 @@ impl eframe::App for App {
782794
if ui
783795
.checkbox(
784796
&mut state.config.diff_obj_config.space_between_args,
785-
"Space between args",
797+
hotkeys::button_alt_text(ui, "_Space between args"),
786798
)
787799
.changed()
788800
{
@@ -791,14 +803,17 @@ impl eframe::App for App {
791803
if ui
792804
.checkbox(
793805
&mut state.config.diff_obj_config.combine_data_sections,
794-
"Combine data sections",
806+
hotkeys::button_alt_text(ui, "Combine _data sections"),
795807
)
796808
.on_hover_text("Combines data sections with equal names.")
797809
.changed()
798810
{
799811
state.queue_reload = true;
800812
}
801-
if ui.button("Clear custom symbol mappings").clicked() {
813+
if ui
814+
.button(hotkeys::button_alt_text(ui, "_Clear custom symbol mappings"))
815+
.clicked()
816+
{
802817
state.clear_mappings();
803818
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
804819
state.queue_reload = true;

objdiff-gui/src/hotkeys.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use egui::{
2-
style::ScrollAnimation, vec2, Context, Key, KeyboardShortcut, Modifiers, PointerButton,
2+
style::ScrollAnimation, text::LayoutJob, vec2, Align, Context, FontSelection, Key,
3+
KeyboardShortcut, Modifiers, PointerButton, RichText, Ui, WidgetText,
34
};
45

56
fn any_widget_focused(ctx: &Context) -> bool { ctx.memory(|mem| mem.focused().is_some()) }
@@ -106,3 +107,122 @@ const CHANGE_BASE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::
106107
pub fn consume_change_base_shortcut(ctx: &Context) -> bool {
107108
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_BASE_SHORTCUT))
108109
}
110+
111+
fn shortcut_key(text: &str) -> (usize, char, Key) {
112+
let i = text.chars().position(|c| c == '_').expect("No underscore in text");
113+
let key = text.chars().nth(i + 1).expect("No character after underscore");
114+
let shortcut_key = match key {
115+
'a' | 'A' => Key::A,
116+
'b' | 'B' => Key::B,
117+
'c' | 'C' => Key::C,
118+
'd' | 'D' => Key::D,
119+
'e' | 'E' => Key::E,
120+
'f' | 'F' => Key::F,
121+
'g' | 'G' => Key::G,
122+
'h' | 'H' => Key::H,
123+
'i' | 'I' => Key::I,
124+
'j' | 'J' => Key::J,
125+
'k' | 'K' => Key::K,
126+
'l' | 'L' => Key::L,
127+
'm' | 'M' => Key::M,
128+
'n' | 'N' => Key::N,
129+
'o' | 'O' => Key::O,
130+
'p' | 'P' => Key::P,
131+
'q' | 'Q' => Key::Q,
132+
'r' | 'R' => Key::R,
133+
's' | 'S' => Key::S,
134+
't' | 'T' => Key::T,
135+
'u' | 'U' => Key::U,
136+
'v' | 'V' => Key::V,
137+
'w' | 'W' => Key::W,
138+
'x' | 'X' => Key::X,
139+
'y' | 'Y' => Key::Y,
140+
'z' | 'Z' => Key::Z,
141+
_ => panic!("Invalid key {}", key),
142+
};
143+
(i, key, shortcut_key)
144+
}
145+
146+
fn alt_text_ui(ui: &Ui, text: &str, i: usize, key: char, interactive: bool) -> WidgetText {
147+
let color = if interactive {
148+
ui.visuals().widgets.inactive.text_color()
149+
} else {
150+
ui.visuals().widgets.noninteractive.text_color()
151+
};
152+
let mut job = LayoutJob::default();
153+
if i > 0 {
154+
let text = &text[..i];
155+
RichText::new(text).color(color).append_to(
156+
&mut job,
157+
ui.style(),
158+
FontSelection::Default,
159+
Align::Center,
160+
);
161+
}
162+
let mut rt = RichText::new(key).color(color);
163+
if ui.input(|i| i.modifiers.alt) {
164+
rt = rt.underline();
165+
}
166+
rt.append_to(&mut job, ui.style(), FontSelection::Default, Align::Center);
167+
if i < text.len() - 1 {
168+
let text = &text[i + 2..];
169+
RichText::new(text).color(color).append_to(
170+
&mut job,
171+
ui.style(),
172+
FontSelection::Default,
173+
Align::Center,
174+
);
175+
}
176+
job.into()
177+
}
178+
179+
pub fn button_alt_text(ui: &Ui, text: &str) -> WidgetText {
180+
let (n, c, key) = shortcut_key(text);
181+
let result = alt_text_ui(ui, text, n, c, true);
182+
if ui.input_mut(|i| check_alt_key(i, c, key)) {
183+
let btn_id = ui.next_auto_id();
184+
ui.memory_mut(|m| m.request_focus(btn_id));
185+
ui.input_mut(|i| {
186+
i.events.push(egui::Event::Key {
187+
key: Key::Enter,
188+
physical_key: None,
189+
pressed: true,
190+
repeat: false,
191+
modifiers: Default::default(),
192+
})
193+
});
194+
}
195+
result
196+
}
197+
198+
pub fn alt_text(ui: &Ui, text: &str, enter: bool) -> WidgetText {
199+
let (n, c, key) = shortcut_key(text);
200+
let result = alt_text_ui(ui, text, n, c, false);
201+
if ui.input_mut(|i| check_alt_key(i, c, key)) {
202+
let btn_id = ui.next_auto_id();
203+
ui.memory_mut(|m| m.request_focus(btn_id));
204+
if enter {
205+
ui.input_mut(|i| {
206+
i.events.push(egui::Event::Key {
207+
key: Key::Enter,
208+
physical_key: None,
209+
pressed: true,
210+
repeat: false,
211+
modifiers: Default::default(),
212+
})
213+
});
214+
}
215+
}
216+
result
217+
}
218+
219+
fn check_alt_key(i: &mut egui::InputState, c: char, key: Key) -> bool {
220+
if i.consume_key(Modifiers::ALT, key) {
221+
// Remove any text input events that match the key
222+
let cs = c.to_string();
223+
i.events.retain(|e| !matches!(e, egui::Event::Text(t) if *t == cs));
224+
true
225+
} else {
226+
false
227+
}
228+
}

objdiff-gui/src/views/config.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ pub fn config_ui(
219219

220220
ui.horizontal(|ui| {
221221
ui.heading("Project");
222-
if ui.button(RichText::new("Settings")).clicked() {
222+
if ui.button("Settings").clicked() {
223223
*show_config_window = true;
224224
}
225225
});
@@ -255,8 +255,9 @@ pub fn config_ui(
255255
}
256256
} else {
257257
let had_search = !config_state.object_search.is_empty();
258-
let response =
259-
egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui);
258+
let response = egui::TextEdit::singleline(&mut config_state.object_search)
259+
.hint_text(hotkeys::alt_text(ui, "Filter _objects", false))
260+
.ui(ui);
260261
if hotkeys::consume_object_filter_shortcut(ui.ctx()) {
261262
response.request_focus();
262263
}

objdiff-gui/src/views/jobs.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::cmp::Ordering;
33
use egui::{ProgressBar, RichText, Widget};
44

55
use crate::{
6+
hotkeys,
67
jobs::{JobQueue, JobStatus},
78
views::appearance::Appearance,
89
};
@@ -94,7 +95,14 @@ impl From<&JobStatus> for JobStatusDisplay {
9495
}
9596

9697
pub fn jobs_menu_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance) -> bool {
97-
ui.label("Jobs:");
98+
let mut clicked = false;
99+
if egui::Label::new(hotkeys::alt_text(ui, "_Jobs:", true))
100+
.sense(egui::Sense::click())
101+
.ui(ui)
102+
.clicked()
103+
{
104+
clicked = true;
105+
}
98106
let mut statuses = Vec::new();
99107
for job in jobs.iter_mut() {
100108
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
105113
let running_jobs = statuses.iter().filter(|s| !s.error).count();
106114
let error_jobs = statuses.iter().filter(|s| s.error).count();
107115

108-
let mut clicked = false;
109116
let spinner =
110117
egui::Spinner::new().size(appearance.ui_font.size * 0.9).color(appearance.text_color);
111118
match running_jobs.cmp(&1) {

objdiff-gui/src/views/symbol_diff.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,9 @@ pub fn symbol_diff_ui(
901901
});
902902

903903
let mut search = state.search.clone();
904-
let response = TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
904+
let response = TextEdit::singleline(&mut search)
905+
.hint_text(hotkeys::alt_text(ui, "Filter _symbols", false))
906+
.ui(ui);
905907
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
906908
response.request_focus();
907909
}

0 commit comments

Comments
 (0)