This commit is contained in:
Brandon Thomas
2026-10-23 16:04:58 -04:00
5 changed files with 111 additions and 2 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ pub fn restore(app: &mut SheetApp, ui: Option<&str>, prefs: Option<&str>) {
{
// No usable saved language: keep the browser's, as the desktop keeps the system's.
let language = gridcraft_ui_egui::i18n::saved_language(&json).unwrap_or(app.ui.language);
app.ui = state;
app.ui = state.at_startup();
app.ui.language = language;
}
if let Some(s) = prefs.filter(readable)
+1 -1
View File
@@ -111,7 +111,7 @@ fn load_prefs(app: &mut SheetApp) {
{
let saved = serde_json::from_slice(&b).ok().and_then(|v| gridcraft_ui_egui::i18n::saved_language(&v));
let language = saved.unwrap_or(app.ui.language);
app.ui = ui;
app.ui = ui.at_startup();
app.ui.language = language;
}
if let Ok(b) = std::fs::read(dir.join("prefs.json"))
+8
View File
@@ -117,6 +117,14 @@ impl Default for UiState {
}
impl UiState {
/// The state saved by an earlier run, as the app starts with it. The ribbon tab is written to
/// the preferences but not restored: Excel opens on Home every launch, so a contextual tab
/// (Table Design) cannot come back with nothing selected to justify it.
pub fn at_startup(mut self) -> Self {
self.ribbon_tab = "Home".into();
self
}
pub fn theme_preference(&self) -> egui::ThemePreference {
if self.system_theme {
egui::ThemePreference::System
+7
View File
@@ -204,6 +204,11 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) {
let t = Tokens::get(ui.ctx());
let collapsed = app.ui.ribbon_collapsed;
let h = if collapsed { 34.0 } else { 34.0 + 92.0 };
// A contextual tab exists only while its object is selected (Excel hides Table Design once the
// cell leaves the table); a remembered one that is gone would draw an empty ribbon.
if CONTEXTUAL_TABS.contains(&app.ui.ribbon_tab.as_str()) && !contextual_tabs(app).contains(&app.ui.ribbon_tab.as_str()) {
app.ui.ribbon_tab = "Home".to_string();
}
egui::Panel::top("ribbon")
.exact_size(h)
.frame(egui::Frame::NONE.fill(t.window).inner_margin(egui::Margin { left: 10, right: 10, top: 0, bottom: 6 }))
@@ -322,6 +327,8 @@ fn theme_choices(app: &mut SheetApp, ui: &mut Ui) {
}
}
const CONTEXTUAL_TABS: [&str; 3] = ["Table Design", "Chart Design", crate::paper_view::TAB];
fn contextual_tabs(app: &SheetApp) -> Vec<&'static str> {
let mut v = Vec::new();
if let Some(d) = app.session.active()
@@ -0,0 +1,94 @@
//! A contextual ribbon tab (Table Design, Chart Design) only exists while its object is selected.
//! The tab remembered in the saved UI state, or left behind when the selection moves away, must
//! not leave the ribbon blank: it falls back to Home.
use egui::vec2;
use gridcraft_engine::Session;
use gridcraft_ui_egui::SheetApp;
use serde_json::json;
type Harness = egui_kittest::Harness<'static, SheetApp>;
fn harness(session: Session) -> Harness {
let mut h = egui_kittest::Harness::builder().with_size(vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SheetApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
SheetApp::new(session, Default::default()),
);
h.run_steps(4);
h
}
fn session_with_table() -> Session {
let mut session = Session::new();
session.new_workbook();
session.execute("range.setValues", json!({"range": "A1", "values": [["Name", "Qty"], ["a", 1], ["b", 2]]})).unwrap();
session.execute("insert.table", json!({"range": "A1:B3", "header": true})).unwrap();
session.execute("selection.set", json!({"range": "A2"})).unwrap();
session
}
fn app_restored_on(tab: &str, session: Session) -> Harness {
let mut app = SheetApp::new(session, Default::default());
app.ui.ribbon_tab = tab.into();
let mut h = egui_kittest::Harness::builder().with_size(vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SheetApp| {
let ctx = ui.ctx().clone();
app.logic(&ctx);
app.ui(ui);
},
app,
);
h.run_steps(4);
h
}
#[test]
fn a_saved_table_design_tab_without_a_table_opens_on_home() {
let mut session = Session::new();
session.new_workbook();
let h = app_restored_on("Table Design", session);
assert_eq!(h.state().ui.ribbon_tab, "Home");
}
#[test]
fn a_saved_chart_design_tab_without_a_chart_opens_on_home() {
let mut session = Session::new();
session.new_workbook();
let h = app_restored_on("Chart Design", session);
assert_eq!(h.state().ui.ribbon_tab, "Home");
}
#[test]
fn leaving_the_table_while_on_table_design_returns_to_home() {
let mut h = harness(session_with_table());
h.state_mut().ui.ribbon_tab = "Table Design".into();
h.run_steps(2);
assert_eq!(h.state().ui.ribbon_tab, "Table Design", "the tab stays while the cell is in the table");
h.state_mut().session.execute("selection.set", json!({"range": "D10"})).unwrap();
h.run_steps(2);
assert_eq!(h.state().ui.ribbon_tab, "Home");
}
#[test]
fn startup_opens_on_home_not_the_saved_tab() {
let saved = gridcraft_ui_egui::UiState { ribbon_tab: "Table Design".into(), dark: true, ..Default::default() };
let json = serde_json::to_string(&saved).unwrap();
assert!(json.contains(r#""ribbonTab":"Table Design""#), "{json}");
let restored = serde_json::from_str::<gridcraft_ui_egui::UiState>(&json).unwrap().at_startup();
assert_eq!(restored.ribbon_tab, "Home", "the saved tab is not restored");
assert!(restored.dark, "the other preferences are");
}
#[test]
fn an_ordinary_tab_stays_put_in_a_session() {
let mut session = Session::new();
session.new_workbook();
let mut h = harness(session);
h.state_mut().ui.ribbon_tab = "Data".into();
h.run_steps(3);
assert_eq!(h.state().ui.ribbon_tab, "Data");
}