From 8983f05c78e2106e8cd91016d45b7647ba0caedf Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:42:14 +0200 Subject: [PATCH] On-page text boxes keep a dark caret on their white backgrounds in the dark theme (#655) (#734) * Text editor on the page keeps a dark caret on its white box in the dark theme (#655) * Form fields, typed fill text and added text keep a dark caret on their white boxes in the dark theme (#655) --------- Co-authored-by: Brandon Thomas --- crates/ui-egui/src/content_ui.rs | 2 ++ crates/ui-egui/src/edit_text_ui.rs | 16 +++++++++++++++- crates/ui-egui/src/fill_sign.rs | 2 ++ crates/ui-egui/src/forms_ui.rs | 6 +++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/content_ui.rs b/crates/ui-egui/src/content_ui.rs index 16276791..da57fab9 100644 --- a/crates/ui-egui/src/content_ui.rs +++ b/crates/ui-egui/src/content_ui.rs @@ -337,6 +337,8 @@ pub(crate) fn editor(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, ad let memo = &view.content.undrawable; egui::Area::new(egui::Id::new(("added-text", view.id.0))).order(egui::Order::Foreground).fixed_pos(r.min).show(ctx, |ui| { let Some(t) = view.content.draft.as_mut() else { return }; + // The box is white in every theme, so the caret must not follow the theme (pale in dark mode) (#655). + ui.visuals_mut().text_cursor.stroke.color = crate::edit_text_ui::caret_color_on(Color32::WHITE); let [cr, cg, cb] = t.style.color.map(|v| (v.clamp(0.0, 1.0) * 255.0) as u8); ui.horizontal_top(|ui| { ui.spacing_mut().item_spacing.x = 4.0; diff --git a/crates/ui-egui/src/edit_text_ui.rs b/crates/ui-egui/src/edit_text_ui.rs index 556d0b38..9cf38e40 100644 --- a/crates/ui-egui/src/edit_text_ui.rs +++ b/crates/ui-egui/src/edit_text_ui.rs @@ -561,7 +561,7 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) - // banner, #913): then a dark box, with a caret that shows on it. let fill = editor_fill(text_color); egui::Frame::NONE.fill(fill).stroke(Stroke::new(1.5, ACCENT)).inner_margin(egui::Margin::symmetric(2, 0)).show(ui, |ui| { - ui.visuals_mut().text_cursor.stroke.color = if fill == Color32::WHITE { Color32::BLACK } else { Color32::WHITE }; + ui.visuals_mut().text_cursor.stroke.color = caret_color_on(fill); let rows = ed.text.lines().count().max(1); let r = ui.add( egui::TextEdit::multiline(&mut ed.text) @@ -601,6 +601,12 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) - } } +/// Caret colour that stays visible on a field filled with `fill`: black on light fills, white on dark ones. +pub(crate) fn caret_color_on(fill: Color32) -> Color32 { + let luma = 299 * u32::from(fill.r()) + 587 * u32::from(fill.g()) + 114 * u32::from(fill.b()); + if luma > 127_500 { Color32::BLACK } else { Color32::WHITE } +} + #[cfg(test)] mod tests { use super::*; @@ -617,4 +623,12 @@ mod tests { assert_eq!(editor_fill(dark), Color32::WHITE, "{dark:?}"); } } + + #[test] + fn caret_contrasts_with_its_fill() { + assert_eq!(caret_color_on(Color32::WHITE), Color32::BLACK); + assert_eq!(caret_color_on(Color32::from_rgb(0xFF, 0xFF, 0xF4)), Color32::BLACK); + assert_eq!(caret_color_on(Color32::BLACK), Color32::WHITE); + assert_eq!(caret_color_on(DARK_EDITOR_FILL), Color32::WHITE); + } } diff --git a/crates/ui-egui/src/fill_sign.rs b/crates/ui-egui/src/fill_sign.rs index f8614efb..974d344c 100644 --- a/crates/ui-egui/src/fill_sign.rs +++ b/crates/ui-egui/src/fill_sign.rs @@ -471,6 +471,8 @@ pub(crate) fn type_box(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, let mut cancel = false; egui::Area::new(egui::Id::new(("fill-text", view.id.0))).order(egui::Order::Foreground).fixed_pos(pos).show(ctx, |ui| { let Some(t) = view.fill_text.as_mut() else { return }; + // The box is white in every theme, so the caret must not follow the theme (pale in dark mode) (#655). + ui.visuals_mut().text_cursor.stroke.color = crate::edit_text_ui::caret_color_on(Color32::WHITE); let width = ((t.text.chars().count().max(8) as f32) * TEXT_SIZE as f32 * 0.6 * zoom).clamp(60.0, 600.0); // The area's max rect is last frame's size, which caps the edit's width; widen it so the // box grows as text is typed. diff --git a/crates/ui-egui/src/forms_ui.rs b/crates/ui-egui/src/forms_ui.rs index 3f07f9bd..38197232 100644 --- a/crates/ui-egui/src/forms_ui.rs +++ b/crates/ui-egui/src/forms_ui.rs @@ -12,6 +12,8 @@ use crate::canvas::{DocView, PageXform}; use crate::theme::Tokens; const FOCUS_BLUE: Color32 = Color32::from_rgb(0x14, 0x73, 0xE6); +/// Background of the in-place text field editor, the same in every theme. +const FIELD_FILL: Color32 = Color32::from_rgb(0xFF, 0xFF, 0xF4); /// The field being edited. #[derive(Clone, Debug, PartialEq)] @@ -295,11 +297,13 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, f egui::Area::new(egui::Id::new(("form-editor", view.id.0))).order(egui::Order::Foreground).fixed_pos(rect.min).show(ctx, |ui| { ui.set_min_size(rect.size()); let Some(fx) = view.forms.focus.as_mut() else { return }; + // The field is pale yellow in every theme, so the caret must not follow the theme (pale in dark mode) (#655). + ui.visuals_mut().text_cursor.stroke.color = crate::edit_text_ui::caret_color_on(FIELD_FILL); let mut te = if multiline { egui::TextEdit::multiline(&mut fx.text) } else { egui::TextEdit::singleline(&mut fx.text) }; te = te .desired_width(rect.width() - 6.0) .font(egui::FontId::proportional(font)) - .background_color(Color32::from_rgb(0xFF, 0xFF, 0xF4)) + .background_color(FIELD_FILL) .text_color(Color32::BLACK) .margin(egui::Margin::symmetric(3, 1)) .password(f.has(field_flags::PASSWORD))