Tab strip: reveal the edge tab after a repeated Ctrl-click on an arrow

When the edge sheet was already active nothing changed, so the strip was not
scrolled back to it. A Ctrl-click on the arrow now asks for the reveal itself.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76
2026-11-07 17:22:54 +01:00
co-authored by Claude Sonnet 5.5
parent 7e828a982c
commit 87cb0f11d2
2 changed files with 68 additions and 3 deletions
+24 -3
View File
@@ -61,6 +61,18 @@ fn tab_drop(i: usize, steps: i64, copy: bool) -> Option<serde_json::Value> {
Some(json!({"sheet": i, "to": to}))
}
/// Where a request to scroll the active tab into view is kept until the tabs are drawn.
fn reveal_request_id(scope: egui::Id) -> egui::Id {
scope.with("worksheet_tabs_reveal_request")
}
/// Asks the tab strip to bring the active tab into view in this frame, even when the active sheet
/// did not change (a Ctrl-click on an arrow whose edge sheet is already active).
fn request_reveal(ui: &Ui) {
let id = reveal_request_id(ui.scope_id());
ui.ctx().data_mut(|data| data.insert_temp(id, true));
}
/// The New sheet (+) button and the gap before it.
const NEW_SHEET_WIDTH: f32 = 32.0;
/// The handle between the tabs and the horizontal scrollbar.
@@ -127,7 +139,10 @@ pub fn sheet_tabs(app: &mut SheetApp, ui: &mut Ui) {
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),
Some(i) => {
activate_sheet(app, i);
request_reveal(ui);
}
None => app.run_or_alert("sheet.previous", json!({})),
}
}
@@ -136,7 +151,10 @@ pub fn sheet_tabs(app: &mut SheetApp, ui: &mut Ui) {
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),
Some(i) => {
activate_sheet(app, i);
request_reveal(ui);
}
None => app.run_or_alert("sheet.next", json!({})),
}
}
@@ -181,10 +199,13 @@ pub fn sheet_tabs(app: &mut SheetApp, ui: &mut Ui) {
// allow browsing other tabs without snapping back on every frame.
let reveal_key = egui::Id::unique((d.uid, d.wb.active_sheet(), &sheets, tabs_width.to_bits()));
let reveal_id = ui.scope_id().with("worksheet_tabs_reveal");
let request_id = reveal_request_id(ui.scope_id());
let reveal_active = ui.ctx().data_mut(|data| {
let changed = data.get_temp::<egui::Id>(reveal_id) != Some(reveal_key);
data.insert_temp(reveal_id, reveal_key);
changed
// A Ctrl-click on an arrow whose edge sheet is already active changes nothing above,
// yet asks to bring that tab into view.
changed | data.remove_temp::<bool>(request_id).unwrap_or(false)
});
if view.show_sheet_tabs {
ui.allocate_ui_with_layout(vec2(tabs_width, 30.0), egui::Layout::left_to_right(egui::Align::Center), |ui| {
+44
View File
@@ -58,6 +58,20 @@ fn click(h: &mut Harness, at: egui::Pos2) {
h.run_steps(20);
}
fn ctrl_click(h: &mut Harness, at: egui::Pos2) {
let m = Modifiers::COMMAND;
h.input_mut().events.extend([
Event::ModifiersChanged(m),
Event::PointerMoved(at),
Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: true, modifiers: m },
]);
h.step();
h.input_mut().events.push(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: false, modifiers: m });
h.run_steps(20);
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
h.run_steps(2);
}
#[test]
fn opening_and_activating_late_sheets_reveals_their_tabs() {
for width in [500.0, 1000.0] {
@@ -85,6 +99,36 @@ fn navigation_arrows_reveal_the_selected_tab() {
assert_visible(&h, "Tab29");
}
/// A Ctrl/Cmd-click on the Next arrow jumps to the last sheet; repeated after the strip was scrolled
/// away from it, the already active edge tab is brought back into view.
#[test]
fn repeated_ctrl_click_on_the_arrow_reveals_the_active_edge_tab() {
let mut h = harness(700.0, 0);
let (_, clip) = tab(&h, "Tab00").unwrap();
let next = pos2(clip.left() - 44.0, clip.center().y);
ctrl_click(&mut h, next);
assert_eq!(h.state().session.doc().unwrap().wb.active_sheet(), 29);
assert_visible(&h, "Tab29");
// Browse the strip back to the start without changing the selection.
h.input_mut().events.extend([Event::PointerMoved(clip.center())]);
for _ in 0..3 {
h.input_mut().events.push(Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
source: egui::MouseWheelSource::Unknown,
phase: egui::TouchPhase::Move,
delta: vec2(0.0, 600.0),
modifiers: Modifiers::NONE,
});
h.run_steps(10);
}
h.run_steps(40);
assert!(!tab(&h, "Tab29").is_some_and(|(r, c)| c.contains_rect(r)), "the strip was scrolled away from the active tab");
assert_eq!(h.state().session.doc().unwrap().wb.active_sheet(), 29);
ctrl_click(&mut h, next);
assert_eq!(h.state().session.doc().unwrap().wb.active_sheet(), 29);
assert_visible(&h, "Tab29");
}
#[test]
/// Horizontal wheel/trackpad browsing leaves the workbook unchanged, does not snap back on idle frames, and activating a sheet still reveals its tab.
fn manual_wheel_and_trackpad_browsing_does_not_change_selection_or_snap_back() {