From 14f2ea9f57f98d7c7f4f77b2cc9e97d0f21c618f Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Fri, 16 Oct 2026 14:53:20 +0200 Subject: [PATCH] Cell editor: the caret contrasts with the cell's fill (fixes #1408) --- crates/ui-egui/src/grid.rs | 9 ++++ crates/ui-egui/tests/cell_editor_caret.rs | 59 +++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 crates/ui-egui/tests/cell_editor_caret.rs diff --git a/crates/ui-egui/src/grid.rs b/crates/ui-egui/src/grid.rs index 00e0c18be..73d912907 100644 --- a/crates/ui-egui/src/grid.rs +++ b/crates/ui-egui/src/grid.rs @@ -2725,6 +2725,9 @@ fn in_cell_editor(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo, sh: &Sheet, let id = egui::Id::new("gridcraft.cell_editor"); let mut child = ui.new_child(egui::UiBuilder::new().max_rect(erect.shrink2(vec2(2.0, 1.0)))); child.set_clip_rect(clip); + // The theme's caret is meant for the UI chrome and vanishes on some fills (and, in the dark + // theme, on the white paper): pick black or white against the fill, like Excel. + child.visuals_mut().text_cursor.stroke.color = ink_on(fill); crate::formula_bar::editor_widget(app, &mut child, id, font, false, erect.width() - 4.0); } @@ -2734,6 +2737,12 @@ fn popup_dismissed(response: &egui::Response, was_open: bool) -> bool { response.ctx.input(|i| !i.focused || i.key_pressed(egui::Key::Escape)) || (was_open && response.clicked_elsewhere()) } +/// Black or white, whichever reads on `fill` (the threshold chart labels use). +fn ink_on(fill: Color32) -> Color32 { + let l = 0.299 * fill.r() as f32 + 0.587 * fill.g() as f32 + 0.114 * fill.b() as f32; + if l < 150.0 { Color32::WHITE } else { Color32::BLACK } +} + /// The table whose header cell (`row`, `col`) was clicked, not just the first table in the column. fn filter_table_range(sh: &Sheet, col: u32, row: u32) -> Option { sh.tables.iter().find(|t| t.range.start.row == row && col >= t.range.start.col && col <= t.range.end.col).map(|t| t.range) diff --git a/crates/ui-egui/tests/cell_editor_caret.rs b/crates/ui-egui/tests/cell_editor_caret.rs new file mode 100644 index 000000000..95b0ff6f3 --- /dev/null +++ b/crates/ui-egui/tests/cell_editor_caret.rs @@ -0,0 +1,59 @@ +//! The caret of the in-cell editor stays visible on the cell's fill, whatever the theme. +use egui::{Color32, Context, RawInput, Rect, Shape, pos2, vec2}; +use gridcraft_engine::Session; +use gridcraft_ui_egui::{SheetApp, theme}; +use serde_json::json; + +fn frame(ctx: &Context, app: &mut SheetApp) -> egui::FullOutput { + let mut output = + ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(1200.0, 800.0))), ..Default::default() }, |ui| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }); + output.textures_delta.clear(); + output +} + +/// The caret painted while editing A1 over `fill` (`None`: no fill), with the app in `dark` mode. +fn caret(fill: Option<&str>, dark: bool) -> Color32 { + let ctx = Context::default(); + theme::apply(&ctx, dark); + let mut session = Session::new(); + session.new_workbook(); + let mut app = SheetApp::new(session, Default::default()); + if let Some(color) = fill { + app.run("home.fillColor", json!({"range": "A1", "color": color})).unwrap(); + } + for _ in 0..4 { + frame(&ctx, &mut app); + } + app.begin_edit(Some("abc".into()), false); + for _ in 0..4 { + frame(&ctx, &mut app); + } + let output = frame(&ctx, &mut app); + // The editor paints its backdrop first; the caret is the vertical segment after it. + let paper = fill.map_or(Color32::WHITE, |c| Color32::from_hex(c).unwrap()); + let backdrop = output + .shapes + .iter() + .rposition(|s| matches!(&s.shape, Shape::Rect(r) if r.fill == paper && r.rect.width() > 20.0)) + .expect("editor backdrop painted"); + output.shapes[backdrop..] + .iter() + .find_map(|s| match &s.shape { + Shape::LineSegment { points, stroke } if points[0].x == points[1].x => Some(stroke.color), + _ => None, + }) + .expect("caret painted") +} + +#[test] +fn caret_contrasts_with_the_cell_fill() { + assert_eq!(caret(Some("#1F3864"), false), Color32::WHITE, "dark fill, light theme"); + assert_eq!(caret(Some("#1F3864"), true), Color32::WHITE, "dark fill, dark theme"); + assert_eq!(caret(Some("#FFFF00"), true), Color32::BLACK, "light fill, dark theme"); + assert_eq!(caret(None, true), Color32::BLACK, "no fill, dark theme: the cell is still white paper"); + assert_eq!(caret(None, false), Color32::BLACK, "no fill, light theme"); +}