From e42f580510ed833f42c18cd38b18ce8ea804d437 Mon Sep 17 00:00:00 2001 From: OmarGonD Date: Mon, 19 Oct 2026 15:09:43 -0500 Subject: [PATCH] Keep selected document text readable --- crates/ely-lok/src/view/render.rs | 4 +- crates/ely-lok/src/view/tests/theme.rs | 87 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/crates/ely-lok/src/view/render.rs b/crates/ely-lok/src/view/render.rs index 7e83263..42a76e1 100644 --- a/crates/ely-lok/src/view/render.rs +++ b/crates/ely-lok/src/view/render.rs @@ -46,7 +46,9 @@ impl Render for LokView { let focus = self.focus.clone(); let colors = &cx.theme().colors; let paper = super::paper(); - let (selection_color, caret_color) = (colors.selection, colors.focus); + // Opaque overlays hide glyphs already painted in the tiles. + let selection_color = colors.selection.alpha(colors.selection.a.min(0.3)); + let caret_color = colors.focus; let canvas_color = colors.sunken; let caret_width = cx.theme().caret_width().to_pixels(window.rem_size()); self.handle_size = cx.theme().handle_hit().to_pixels(window.rem_size()); diff --git a/crates/ely-lok/src/view/tests/theme.rs b/crates/ely-lok/src/view/tests/theme.rs index 2ca2413..16d5d6b 100644 --- a/crates/ely-lok/src/view/tests/theme.rs +++ b/crates/ely-lok/src/view/tests/theme.rs @@ -83,6 +83,93 @@ fn a_dark_app_paints_the_page_white() { assert!(light > 240, "the view paints the page white: {light}"); } +#[test] +fn selection_keeps_document_text_readable_across_themes() { + use ely_gpui_component::{ + devtools::{contrast, over}, + theme::{ActiveTheme, Mode, Palette, Theme}, + }; + + let Some(install) = crate::client::tests::install() else { + eprintln!("SKIP: no pinned LOK build"); + return; + }; + let mut cx = TestAppContext::single(); + cx.update(ely_gpui_component::init_for_tests); + let (view, worker) = cx.update(|app| live_view(app, &install)); + let doc = cx.update(|app| view.read(app).doc.clone()); + let shown = view.clone(); + let window = cx.add_window(move |_, _| Host(shown)); + worker + .lock() + .expect("the worker") + .run(&doc, "edit.select_all", &[]) + .expect("select all"); + assert!( + settle(&cx, |app| !view.read(app).selection.is_empty()), + "the selection arrives" + ); + + let mut themes = vec![ + ("dark".to_string(), Mode::Dark, None), + ("light".to_string(), Mode::Light, None), + ]; + for (name, mode, color) in [ + ("omarchy dark", Mode::Dark, 0x292e42), + ("omarchy light", Mode::Light, 0xccd0da), + ] { + let mut palette = match mode { + Mode::Dark => Palette::dark(false), + Mode::Light => Palette::light(false), + }; + palette.selection = gpui::rgb(color).into(); + themes.push((name.to_string(), mode, Some(palette))); + } + let mut translucent = Palette::dark(false); + translucent.selection = gpui::rgb(0x292e42).into(); + translucent.selection.a = 0.15; + themes.push(("translucent".to_string(), Mode::Dark, Some(translucent))); + + for (name, mode, palette) in themes { + cx.update(|app| { + Theme::set_palette(mode, palette, app); + Theme::set_mode_now(mode, app); + }); + cx.update_window(window.into(), |_, window, app| { + window.draw(app).clear(app); + let source = app.theme().colors.selection; + let selected: Vec<_> = window + .painted_quads() + .into_iter() + .filter_map(|quad| quad.background.as_solid()) + .filter(|color| color.alpha(1.0) == source.alpha(1.0)) + .collect(); + assert!(!selected.is_empty(), "{name}: the selection is painted"); + for color in selected { + for (text, background) in [(gpui::black(), paper()), (gpui::white(), gpui::black())] + { + let ratio = contrast(over(color, text), over(color, background)); + assert!( + ratio >= 4.5, + "{name}: selected text stays readable: {ratio}" + ); + } + assert!( + color.a > 0.0 && color.a <= source.a, + "{name}: the theme's opacity is respected" + ); + if source.a < 0.3 { + assert_eq!( + color.a, source.a, + "{name}: a translucent theme stays unchanged" + ); + } + } + }) + .expect("the window lives"); + } +} + #[test] fn a_failed_tile_reports_and_retries_on_a_move() { let Some(install) = crate::client::tests::install() else {