Edit text: white or pale text is edited on a dark box instead of vanishing on white (#913) (#921)

The inline Edit text box was always filled white and drew the text in its
own colour, so white text from a coloured banner disappeared while editing.
The box is now white or a dark grey, whichever contrasts more with the text
(WCAG contrast), and the caret follows the box.

Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DWAA1660 2026-10-10 15:39:39 -06:00 committed by GitHub
parent 82b1093b73
commit bfbeee9fa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 47 additions and 1 deletions

View File

@ -170,6 +170,30 @@ fn color32(color: [f64; 3]) -> Color32 {
)
}
/// The dark box behind text too pale to read on white.
const DARK_EDITOR_FILL: Color32 = Color32::from_gray(0x26);
/// WCAG relative luminance of an sRGB colour.
fn luminance(c: Color32) -> f32 {
let channel = |v: u8| {
let v = f32::from(v) / 255.0;
if v <= 0.040_45 { v / 12.92 } else { ((v + 0.055) / 1.055).powf(2.4) }
};
0.2126 * channel(c.r()) + 0.7152 * channel(c.g()) + 0.0722 * channel(c.b())
}
/// WCAG contrast ratio between two colours (1 to 21).
fn contrast(a: Color32, b: Color32) -> f32 {
let (la, lb) = (luminance(a), luminance(b));
(la.max(lb) + 0.05) / (la.min(lb) + 0.05)
}
/// The inline editor's background for text drawn in `text`: white, or a dark box when the text
/// reads better on that (white or pale text, which would vanish on white).
pub(crate) fn editor_fill(text: Color32) -> Color32 {
if contrast(text, Color32::WHITE) >= contrast(text, DARK_EDITOR_FILL) { Color32::WHITE } else { DARK_EDITOR_FILL }
}
/// A selected page image, and what the pointer is doing to it.
#[derive(Clone, Debug, PartialEq)]
pub struct ImageSelection {
@ -533,7 +557,11 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) -
}) + 8.0;
width = width.max(needed).min(ed.max_width);
}
egui::Frame::NONE.fill(Color32::WHITE).stroke(Stroke::new(1.5, ACCENT)).inner_margin(egui::Margin::symmetric(2, 0)).show(ui, |ui| {
// White, unless the text is too pale to read on white (white text from a coloured
// 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 };
let rows = ed.text.lines().count().max(1);
let r = ui.add(
egui::TextEdit::multiline(&mut ed.text)
@ -572,3 +600,21 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) -
None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pale_text_is_edited_on_a_dark_box() {
// #913: white text from a coloured banner was typed onto a white box and vanished.
for pale in [Color32::WHITE, Color32::from_rgb(0xFF, 0xFF, 0x00), Color32::from_gray(0xE0), Color32::from_rgb(0xFF, 0xA5, 0x00)] {
assert_eq!(editor_fill(pale), DARK_EDITOR_FILL, "{pale:?}");
assert!(contrast(pale, editor_fill(pale)) >= 4.5, "{pale:?} is readable");
}
// Ordinary dark or saturated text keeps the white box.
for dark in [Color32::BLACK, Color32::from_rgb(0x1F, 0x2A, 0x6B), Color32::from_rgb(0xC0, 0x00, 0x00), Color32::from_gray(0x60)] {
assert_eq!(editor_fill(dark), Color32::WHITE, "{dark:?}");
}
}
}