feat: extract a two-tier colour palette
Colour was 35 call sites and ~29 distinct literals spread across four widget files, with no egui Style or Visuals set at all - so the hand-painted widgets were hand-coloured while ParamSlider and the labels sat on stock egui defaults, which is most of why the UI did not read as one thing. The audit turned up more than duplication. Two different panel blacks ((16,16,20) and (20,20,24)) for the same intended surface; three reds, two greens and two blues close enough to look like mistakes; eight greys doing about four jobs. And a genuine collision: blue/green/orange meant LOW/MID/HIGH in the crossover and input/output/gain-reduction in the scope, so orange meant "the high band" in one widget and "gain reduction" in the one beside it. The rule that resolves it, and that the module documents: channel hue is chrome, functional colour is data. A pixel showing a value comes from the functional palette and means the same thing in every channel; a pixel saying which channel you are looking at comes from that channel's hue and never touches the data. The scope turns out not to need arbitration at all - it is tabbed, one channel at a time, so its three traces never compete with a hue. Two tiers. Seeds are a short hand-picked list: four channel hues, the functional colours, six neutrals. Everything else derives from a hue via `channel()`, so recolouring a channel is one constant. Derivation is const fn - including the HSV conversion, written out because ecolor's is not const - so the whole table is built at compile time and costs nothing at runtime. Chosen so the hues describe the colours already in use: the derived crossover strips land within 1-3 units of the literals they replace. The two drag handles shift by ~17 on one channel, being now consistently saturated with the rest. Also lands the one deliberate visual change: the meter's four cells were identical and distinguishable only by the label underneath, which is where channel identity was hardest to read. They now carry a dark channel wash and an accent rule, while the bars stay strictly functional. Visuals are wired from the same seeds in the editor's build closure, so egui's own widgets stop looking foreign. meter.rs's local level_color is gone; the palette owns the headroom ramp rather than there being two places to edit it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LU1NRL7fAaZTtFMTTPn2b1
This commit is contained in:
@@ -11,6 +11,7 @@ use nih_plug_egui::egui::{
|
||||
};
|
||||
|
||||
use crate::params::Bari206Params;
|
||||
use super::palette;
|
||||
|
||||
/// Height of the handle strip.
|
||||
const GRAPH_H: f32 = 54.0;
|
||||
@@ -55,19 +56,19 @@ pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, setter: &ParamSett
|
||||
let xhi = x_for(hi);
|
||||
|
||||
// Three band regions.
|
||||
p.rect_filled(Rect::from_min_max(pos2(left, top), pos2(xlo, bottom)), CornerRadius::ZERO, Color32::from_rgb(28, 38, 52));
|
||||
p.rect_filled(Rect::from_min_max(pos2(xlo, top), pos2(xhi, bottom)), CornerRadius::ZERO, Color32::from_rgb(30, 48, 36));
|
||||
p.rect_filled(Rect::from_min_max(pos2(xhi, top), pos2(right, bottom)), CornerRadius::ZERO, Color32::from_rgb(52, 38, 30));
|
||||
p.rect_filled(Rect::from_min_max(pos2(left, top), pos2(xlo, bottom)), CornerRadius::ZERO, palette::CHANNELS[0].strip);
|
||||
p.rect_filled(Rect::from_min_max(pos2(xlo, top), pos2(xhi, bottom)), CornerRadius::ZERO, palette::CHANNELS[1].strip);
|
||||
p.rect_filled(Rect::from_min_max(pos2(xhi, top), pos2(right, bottom)), CornerRadius::ZERO, palette::CHANNELS[2].strip);
|
||||
|
||||
let band_label = |cx: f32, t: &str| {
|
||||
p.text(pos2(cx, top + 2.0), Align2::CENTER_TOP, t, FontId::proportional(11.0), Color32::from_gray(160));
|
||||
p.text(pos2(cx, top + 2.0), Align2::CENTER_TOP, t, FontId::proportional(11.0), palette::TEXT);
|
||||
};
|
||||
band_label((left + xlo) * 0.5, "LOW");
|
||||
band_label((xlo + xhi) * 0.5, "MID");
|
||||
band_label((xhi + right) * 0.5, "HIGH");
|
||||
|
||||
handle(ui, &p, setter, "xover_lo", xlo, lo, ¶ms.crossover_low_hz, lo_min, lo_upper, left, width, top, bottom, Color32::from_rgb(120, 170, 230));
|
||||
handle(ui, &p, setter, "xover_hi", xhi, hi, ¶ms.crossover_high_hz, hi_lower, hi_max, left, width, top, bottom, Color32::from_rgb(230, 150, 120));
|
||||
handle(ui, &p, setter, "xover_lo", xlo, lo, ¶ms.crossover_low_hz, lo_min, lo_upper, left, width, top, bottom, palette::CHANNELS[0].handle);
|
||||
handle(ui, &p, setter, "xover_hi", xhi, hi, ¶ms.crossover_high_hz, hi_lower, hi_max, left, width, top, bottom, palette::CHANNELS[2].handle);
|
||||
|
||||
// Number boxes (double-click to type). Clamped to the same dynamic limits.
|
||||
ui.horizontal(|ui| {
|
||||
@@ -129,9 +130,9 @@ fn handle(
|
||||
setter.end_set_parameter(param);
|
||||
}
|
||||
|
||||
let col = if resp.dragged() || resp.hovered() { Color32::WHITE } else { color };
|
||||
let col = if resp.dragged() || resp.hovered() { palette::TEXT_HOVER } else { color };
|
||||
p.line_segment([pos2(x, top), pos2(x, bottom)], Stroke::new(2.0, col));
|
||||
p.text(pos2(x, bottom - 1.0), Align2::CENTER_BOTTOM, fmt_hz(hz), FontId::proportional(10.0), Color32::from_gray(220));
|
||||
p.text(pos2(x, bottom - 1.0), Align2::CENTER_BOTTOM, fmt_hz(hz), FontId::proportional(10.0), palette::TEXT_BRIGHT);
|
||||
}
|
||||
|
||||
/// Set a param to `value` clamped to `[min, max]`, wrapped in its own gesture (for the number box).
|
||||
|
||||
Reference in New Issue
Block a user