Tabs: Ctrl-click on the sheet arrows jumps to the first or last visible sheet (#3350)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76andClaude Sonnet 5.5 committed 2026-11-05 01:02:21 +01:00
1 parent 919a6b58fe
commit 7e828a982c
1 file changed
+32 -3
+32 -3
View File
@@ -22,6 +22,12 @@ fn navigator_items(sheets: &[Arc<Sheet>]) -> Vec<(String, String)> {
sheets.iter().enumerate().filter(|(_, s)| s.visibility == Visibility::Visible).map(|(i, s)| (i.to_string(), s.name.clone())).collect()
}
/// Workbook index of the first (or, with `last`, the last) visible sheet: where Ctrl-clicking a tab arrow lands.
fn edge_sheet(sheets: &[Arc<Sheet>], last: bool) -> Option<usize> {
let mut visible = sheets.iter().enumerate().filter(|(_, s)| s.visibility == Visibility::Visible).map(|(i, _)| i);
if last { visible.next_back() } else { visible.next() }
}
/// Switch to sheet `i` from the tab strip or the navigator. In point mode the formula being typed keeps going on the
/// other sheet; otherwise the edit is committed first.
fn activate_sheet(app: &mut SheetApp, i: usize) {
@@ -117,10 +123,22 @@ pub fn sheet_tabs(app: &mut SheetApp, ui: &mut Ui) {
ui.add_space(8.0);
if view.show_sheet_tabs {
if crate::widgets::icon_button(ui, Icon::Left, t.text_dim, app.ui.language.tr("Previous sheet"), vec2(24.0, 24.0)).clicked() {
app.run_or_alert("sheet.previous", json!({}));
// Ctrl-click jumps to the first visible sheet, like Excel's tab scroll buttons.
let edge =
if ui.input(|i| i.modifiers.command) { app.session.active().and_then(|d| edge_sheet(&d.wb.sheets, false)) } else { None };
match edge {
Some(i) => activate_sheet(app, i),
None => app.run_or_alert("sheet.previous", json!({})),
}
}
if crate::widgets::icon_button(ui, Icon::Right, t.text_dim, app.ui.language.tr("Next sheet"), vec2(24.0, 24.0)).clicked() {
app.run_or_alert("sheet.next", json!({}));
// Ctrl-click jumps to the last visible sheet.
let edge =
if ui.input(|i| i.modifiers.command) { app.session.active().and_then(|d| edge_sheet(&d.wb.sheets, true)) } else { None };
match edge {
Some(i) => activate_sheet(app, i),
None => app.run_or_alert("sheet.next", json!({})),
}
}
let all = crate::widgets::icon_button(ui, Icon::SheetList, t.text_dim, app.ui.language.tr("All sheets"), vec2(24.0, 24.0));
all.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, app.ui.language.tr("All sheets")));
@@ -522,7 +540,8 @@ fn status_bar_menu(app: &mut SheetApp, ui: &mut Ui) {
#[cfg(test)]
mod tests {
use super::{
MIN_TRACK, NEW_SHEET_WIDTH, SPLIT_WIDTH, activate_sheet, dragged_share, drop_copies_sheet, navigator_items, rename_needs_focus, tab_drop,
MIN_TRACK, NEW_SHEET_WIDTH, SPLIT_WIDTH, activate_sheet, dragged_share, drop_copies_sheet, edge_sheet, navigator_items, rename_needs_focus,
tab_drop,
};
use crate::SheetApp;
use egui::Modifiers;
@@ -614,6 +633,16 @@ mod tests {
assert_eq!(navigator_items(&sheets), vec![("0".to_string(), "Data".to_string()), ("3".to_string(), "Summary".to_string())]);
}
#[test]
fn ctrl_click_on_the_tab_arrows_lands_on_the_first_and_last_visible_sheet() {
let mut hidden = Sheet::new("Hidden");
hidden.visibility = Visibility::Hidden;
let sheets = [hidden.clone(), Sheet::new("A"), Sheet::new("B"), Sheet::new("C"), hidden].map(Arc::new);
assert_eq!(edge_sheet(&sheets, false), Some(1));
assert_eq!(edge_sheet(&sheets, true), Some(3));
assert_eq!(edge_sheet(&[], true), None);
}
#[test]
fn a_rejected_commit_keeps_the_sheet_and_the_edit() {
let mut session = gridcraft_engine::Session::new();