//! Global egui styling: font scaling, list/editor contrast tuning and the //! colour helpers they use. use super::*; /// Fraction each base font size is enlarged, applied once at startup so every /// panel's text is slightly bigger without changing layout maths elsewhere. pub(super) const FONT_SCALE: f32 = 1.12; /// Configure the shared context style once at startup: raise the contrast of /// *all* UI text (menus, top bar, buttons, panels, headings — everything, not /// just the file list and editor) and enlarge every base font size slightly. /// /// The default dark theme uses fairly dim greys (~140) for body text, the main /// legibility complaint; we pull each widget state's foreground toward white in /// dark mode / black in light mode. Explicitly coloured spans (grammar /// underlines, the cheatsheet's syntax colour) set their own colours and are /// unaffected. The per-pane boosts (`boost_list_contrast`, the editor contrast /// slider) still layer on top for their specific widgets. pub(super) fn apply_global_style(ctx: &egui::Context) { let mut style = (*ctx.style()).clone(); // Enlarge every text style (Body, Button, Heading, Monospace, Small). for font in style.text_styles.values_mut() { font.size = (font.size * FONT_SCALE).round(); } let v = &mut style.visuals; let target = if v.dark_mode { egui::Color32::WHITE } else { egui::Color32::BLACK }; let w = &mut v.widgets; w.noninteractive.fg_stroke.color = lerp_color(w.noninteractive.fg_stroke.color, target, 0.55); w.inactive.fg_stroke.color = lerp_color(w.inactive.fg_stroke.color, target, 0.45); w.hovered.fg_stroke.color = lerp_color(w.hovered.fg_stroke.color, target, 0.35); w.active.fg_stroke.color = lerp_color(w.active.fg_stroke.color, target, 0.35); w.open.fg_stroke.color = lerp_color(w.open.fg_stroke.color, target, 0.45); ctx.set_style(style); } /// Raise the text contrast of list-style widgets in the current `ui`, so file /// names in the left pane stay legible against the panel background. Only the /// widget foreground colours are touched; layout and the rest of the app are /// unaffected (the change is scoped to the panel that calls this). pub(super) fn boost_list_contrast(visuals: &mut egui::Visuals) { if visuals.dark_mode { // Unselected rows, hover, and the pressed state → toward white. visuals.widgets.inactive.fg_stroke.color = egui::Color32::from_gray(235); visuals.widgets.hovered.fg_stroke.color = egui::Color32::WHITE; visuals.widgets.active.fg_stroke.color = egui::Color32::WHITE; // Selected-row text (SelectableLabel uses selection.stroke when selected). visuals.selection.stroke.color = egui::Color32::WHITE; } else { visuals.widgets.inactive.fg_stroke.color = egui::Color32::from_gray(20); visuals.widgets.hovered.fg_stroke.color = egui::Color32::BLACK; visuals.widgets.active.fg_stroke.color = egui::Color32::BLACK; visuals.selection.stroke.color = egui::Color32::WHITE; } } /// The editor's text colour, interpolated from the theme's default toward the /// maximum-contrast colour (white in dark mode, black in light mode) by /// `contrast` percent (0 = theme default, 100 = full contrast). pub(super) fn editor_text_color(visuals: &egui::Visuals, contrast: f32) -> egui::Color32 { let base = visuals.text_color(); let target = if visuals.dark_mode { egui::Color32::WHITE } else { egui::Color32::BLACK }; lerp_color(base, target, contrast / 100.0) } /// Linearly interpolate between two colours in gamma (sRGB byte) space. /// `t` is clamped to `[0, 1]`; `t = 0` yields `a`, `t = 1` yields `b`. pub(super) fn lerp_color(a: egui::Color32, b: egui::Color32, t: f32) -> egui::Color32 { let t = t.clamp(0.0, 1.0); let mix = |x: u8, y: u8| (x as f32 + (y as f32 - x as f32) * t).round() as u8; egui::Color32::from_rgb( mix(a.r(), b.r()), mix(a.g(), b.g()), mix(a.b(), b.b()), ) } #[cfg(test)] mod tests { use super::*; #[test] fn lerp_color_hits_endpoints_and_midpoint() { let a = egui::Color32::from_rgb(0, 0, 0); let b = egui::Color32::from_rgb(200, 100, 50); assert_eq!(lerp_color(a, b, 0.0), a); assert_eq!(lerp_color(a, b, 1.0), b); assert_eq!(lerp_color(a, b, 0.5), egui::Color32::from_rgb(100, 50, 25)); // t is clamped to [0, 1]. assert_eq!(lerp_color(a, b, -1.0), a); assert_eq!(lerp_color(a, b, 2.0), b); } #[test] fn editor_contrast_moves_toward_white_in_dark_mode() { let dark = egui::Visuals::dark(); // 0% keeps the theme's text colour; 100% is fully white. assert_eq!(editor_text_color(&dark, 0.0), dark.text_color()); assert_eq!(editor_text_color(&dark, 100.0), egui::Color32::WHITE); // In light mode, full contrast is black instead. let light = egui::Visuals::light(); assert_eq!(editor_text_color(&light, 100.0), egui::Color32::BLACK); } }