423e93c894
src/app.rs had grown to 3,700 lines, ~2,400 of them a single impl App block. Move it to src/app/mod.rs and spread the behaviour across eleven child modules grouped by feature: workspace, grammar, spelling, beats, find, editor, autocomplete, file_list, ui, style and util. The new modules are children of app rather than siblings, so they still reach App's private fields without widening its interface; methods and free helpers that are now used across module boundaries are marked pub(super). mod.rs keeps the state types, App::new and the eframe::App update loop. This is pure code motion - every non-blank line of the original file reappears exactly once, and the only edits are the pub(super) markers, the module scaffolding, and rewrapping five signatures that the added prefix pushed past 100 columns. Largest file is now editor.rs at 520 lines. Tests still 56/56, and cargo clippy --release reports the same five warnings as before the split. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBWj9TphFMCoh7VHaSRnvQ
117 lines
5.0 KiB
Rust
117 lines
5.0 KiB
Rust
//! 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);
|
|
}
|
|
}
|