fix(ui): canvas-aware selection colors and paper space detection

- Add canvas_is_light helper based on WCAG luminance (> 0.5)
- Ensure crosshair_background always returns sheet white in paper space
- Update resolve_selection_base_color to resolve user ACI -> light-canvas palette -> theme palette
- Add light_canvas_color with curated deep-green/deep-blue pairs for light surfaces
- Extract selection_fill_alpha with 1.35x contrast compensation on light surfaces
- Thread canvas_bg through draw_marquee and poly selection
- Note deliberate limitation for desk-spanning marquee edge case
- Add test coverage for paper space, ClassicDark, custom light bg, and fill alpha clamping
This commit is contained in:
Karim Jerbi committed 2026-09-04 15:28:25 +01:00
1 parent 886d5777d8
commit 23a9884f90
3 files changed
+457 -86

No files matched your search

+48 -1
View File
@@ -10,7 +10,7 @@
//! `plotstyle` does not. Unifying them would silently change the plotstyle
//! disabled appearance. See `cargo test --lib ui::style::common_tests`.
use iced::Theme;
use iced::{Color, Theme};
/// Muted secondary text style — `background.base.text` at 68% opacity.
pub(crate) fn muted_style(theme: &Theme) -> iced::widget::text::Style {
@@ -26,3 +26,50 @@ pub(crate) fn muted_style(theme: &Theme) -> iced::widget::text::Style {
pub(crate) fn muted_text_style(theme: &Theme) -> iced::widget::text::Style {
muted_style(theme)
}
/// Convert sRGB channel [0.0, 1.0] to linear light for WCAG luminance calculations.
pub(crate) fn to_linear(c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
/// Calculate standard WCAG 2.1 relative luminance for an opaque color.
pub(crate) fn wcag_luminance(color: Color) -> f32 {
0.2126 * to_linear(color.r) + 0.7152 * to_linear(color.g) + 0.0722 * to_linear(color.b)
}
/// Calculate standard WCAG contrast ratio between two colors (range: 1.0 to 21.0).
pub(crate) fn wcag_contrast(c1: Color, c2: Color) -> f32 {
let l1 = wcag_luminance(c1);
let l2 = wcag_luminance(c2);
(l1.max(l2) + 0.05) / (l1.min(l2) + 0.05)
}
/// Returns `accent` if it achieves at least `min_contrast` against `bg`,
/// otherwise safely falls back to `fallback`.
pub(crate) fn accessible_accent_threshold(
accent: Color,
bg: Color,
fallback: Color,
min_contrast: f32,
) -> Color {
if wcag_contrast(accent, bg) >= min_contrast {
accent
} else {
fallback
}
}
/// Returns `accent` if it achieves at least 2.0:1 contrast against `bg`,
/// otherwise safely falls back to `fallback`.
pub(crate) fn accessible_accent(accent: Color, bg: Color, fallback: Color) -> Color {
accessible_accent_threshold(accent, bg, fallback, 2.0)
}
/// Returns true if the given canvas background color has a WCAG relative luminance > 0.5 (light surface).
pub(crate) fn canvas_is_light(bg: [f32; 4]) -> bool {
wcag_luminance(Color::from_rgb(bg[0], bg[1], bg[2])) > 0.5
}