mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
Merge PR #905: Keytips: Alt, F opens the File view, and its F letters still run
crates/ui-egui/src/lib.rs: keep main's file_tab close guard and 905's keytips condition.
This commit is contained in:
@@ -6,6 +6,7 @@ use super::{Action, Entry};
|
||||
/// whenever a pressed letter starts a deeper sequence, so only terminal entries are
|
||||
/// listed here. See keytip_table.rs for the helpers that read this table.
|
||||
pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("F", Entry::Tab("File")), // Excel: File tab (Backstage view); Opens the File view on Home; its letters below still run their commands.
|
||||
("H", Entry::Tab("Home")),
|
||||
("N", Entry::Tab("Insert")),
|
||||
("JI", Entry::Tab("Draw")), // Two-letter unit. Deeper Draw letters differ between Excel and GridCraft; left blank for future contributors.
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
" - Keep entries grouped by tab and sorted by sequence within a group; the FIRST entry for a tab is its canonical badge (legacy aliases come after it)."
|
||||
],
|
||||
"sequences": [
|
||||
{
|
||||
"seq": "F",
|
||||
"kind": "tab",
|
||||
"target": "File",
|
||||
"excel": "File tab (Backstage view)",
|
||||
"note": "Opens the File view on Home; its letters below still run their commands."
|
||||
},
|
||||
{
|
||||
"seq": "H",
|
||||
"kind": "tab",
|
||||
|
||||
@@ -1191,7 +1191,8 @@ impl SheetApp {
|
||||
if !self.chrome.file_tab {
|
||||
self.backstage = None;
|
||||
}
|
||||
if self.backstage.is_none() {
|
||||
// Keytips opened the File view (Alt, F): its letters keep working there.
|
||||
if self.backstage.is_none() || self.keytips.prefix().is_some() {
|
||||
self.ribbon_keys(&ctx);
|
||||
}
|
||||
if self.chrome.title_bar {
|
||||
@@ -1270,12 +1271,18 @@ impl SheetApp {
|
||||
&& self.grid.renaming_tab.is_none()
|
||||
&& (before.is_some() || !egui::Popup::is_any_open(ctx));
|
||||
let actions = self.keytips.process(ctx, enabled);
|
||||
// Backing out of F to the tab letters, or finishing an F sequence, closes the File view
|
||||
// that Alt, F opened: the command runs on the sheet, as it did without the view.
|
||||
if self.backstage.is_some() && before.is_some() && (self.keytips.prefix() == Some("") || !actions.is_empty()) {
|
||||
self.backstage = None;
|
||||
}
|
||||
if before.is_some() && before.as_deref() != self.keytips.prefix() && !ctx.input(|i| i.pointer.any_pressed()) {
|
||||
// Leave pointer cancellation to the popup so clicking a menu item still works.
|
||||
egui::Popup::close_all(ctx);
|
||||
}
|
||||
for action in actions {
|
||||
match action {
|
||||
keytips::Action::Tab("File") => self.open_backstage(backstage::Page::Home),
|
||||
keytips::Action::Tab(tab) => {
|
||||
self.run_or_alert("ui.ribbonTab", json!({"tab": tab}));
|
||||
self.ui.ribbon_collapsed = false;
|
||||
|
||||
@@ -265,6 +265,11 @@ fn file_menu(app: &mut SheetApp, ui: &mut Ui) {
|
||||
if file.clicked() {
|
||||
app.open_backstage(crate::backstage::Page::Home);
|
||||
}
|
||||
if app.keytips.prefix() == Some("")
|
||||
&& let Some(sequence) = keytip_table::tab_sequence("File")
|
||||
{
|
||||
keytip(app, ui, sequence, pos2(file.rect.center().x, file.rect.bottom() - 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
/// System / Light / Dark, as in the View tab's Display theme box (`view.theme`).
|
||||
|
||||
@@ -390,6 +390,25 @@ fn tab_letters_switch_to_their_ribbon_tabs() {
|
||||
assert_eq!(h.state().keytips.prefix(), None, "terminal tab units finish cleanly");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f_opens_the_file_view_and_its_letters_still_run() {
|
||||
use gridcraft_ui_egui::backstage::Page;
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, &[Key::F], false);
|
||||
assert_eq!(h.state().backstage.as_ref().map(|b| b.page), Some(Page::Home), "F opens the File view on Home");
|
||||
assert_eq!(h.state().keytips.prefix(), Some("F"));
|
||||
// Esc backs out one level: the File view closes and the tab letters show again.
|
||||
key(&mut h, Key::Escape);
|
||||
assert!(h.state().backstage.is_none());
|
||||
assert_eq!(h.state().keytips.prefix(), Some(""));
|
||||
letter(&mut h, Key::F, false);
|
||||
assert!(h.state().backstage.is_some());
|
||||
letter(&mut h, Key::N, false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "file.new"), "F, N still runs New");
|
||||
assert!(h.state().backstage.is_none(), "the command runs on the sheet, as before the view opened");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_gridlines_sequence_runs_end_to_end() {
|
||||
let mut h = harness(blank());
|
||||
|
||||
@@ -59,6 +59,14 @@ fn sequence(h: &mut Harness, seq: &str, text_first: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_f_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "F", false);
|
||||
assert!(h.state().backstage.is_some(), "F must open the File view");
|
||||
assert_eq!(h.state().keytips.prefix(), Some("F"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_h_() {
|
||||
let mut h = harness(blank());
|
||||
@@ -2741,7 +2749,7 @@ fn keytip_q_() {
|
||||
|
||||
#[test]
|
||||
fn generated_test_count_matches_the_table() {
|
||||
const EXPECTED: usize = 364;
|
||||
const EXPECTED: usize = 365;
|
||||
assert_eq!(gridcraft_ui_egui::keytips::keytip_table::SEQUENCES.len(), EXPECTED);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
const TABS: &[&str] = &["Home", "Insert", "Draw", "Page Layout", "Formulas", "Data", "Review", "View", "Automate"];
|
||||
/// "File" is not a ribbon tab: its sequence opens the Backstage view.
|
||||
const TABS: &[&str] = &["File", "Home", "Insert", "Draw", "Page Layout", "Formulas", "Data", "Review", "View", "Automate"];
|
||||
|
||||
/// Commands whose empty-params behaviour depends on the host machine (e.g. the
|
||||
/// system dictionary). These are never probed; their tests assert "invoked
|
||||
@@ -260,6 +261,10 @@ fn render_tests(entries: &[Entry], probe: &Probe) -> String {
|
||||
let mut out = String::from(HEADER);
|
||||
for e in entries {
|
||||
let body = match e.kind.as_str() {
|
||||
"tab" if e.target == "File" => format!(
|
||||
"sequence(&mut h, {seq:?}, false);\n assert!(h.state().backstage.is_some(), \"{seq} must open the File view\");\n assert_eq!(h.state().keytips.prefix(), Some({seq:?}));",
|
||||
seq = e.seq,
|
||||
),
|
||||
"tab" => format!(
|
||||
"sequence(&mut h, {seq:?}, false);\n assert_eq!(h.state().ui.ribbon_tab, {tab:?}, \"{seq} must switch to the {tab} tab\");",
|
||||
seq = e.seq,
|
||||
|
||||
Reference in New Issue
Block a user