309a152d58
Sixteen `Stroke::new(1.0, ...)` calls in the editor relied on a numeric
inference fallback that rustc is removing:
warning: falling back to `f32` as the trait bound `f32: From<f64>` is
not satisfied
= warning: this was previously accepted by the compiler but is being
phased out; it will become a hard error in a future release!
These were never clippy lints - a plain `cargo build` emitted all sixteen
- and they are not style. They are a dated compile break: the code stops
building on whichever release drops the fallback. Applied via `cargo fix`,
since rustc marks the suggestion machine-applicable; the diff is sixteen
literals gaining an `_f32` suffix and nothing else. The values were
already f32, just inferred.
Denying the future_incompatible group makes the next one a build failure
the day it appears rather than something that accumulates unnoticed until
a toolchain bump breaks the build on a day nobody chose. Verified by
reverting one literal: the build then fails with exit 101 instead of
warning.
Pinning rust-toolchain.toml was considered and rejected for this purpose.
It buys reproducibility, but it works against early warning - a pinned
toolchain finds out about the next deferred hard error later, not sooner.
Build warnings drop from 17 to 0. The 15 remaining clippy lints are
genuine style suggestions and are untouched, as is clippy's absence from
CI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
144 lines
5.5 KiB
Rust
144 lines
5.5 KiB
Rust
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//! Draggable crossover handles — replaces the two plain crossover sliders.
|
|
//!
|
|
//! A horizontal log-frequency strip split into LOW / MID / HIGH by two draggable handles, plus a
|
|
//! number box per crossover (double-click to type an exact value). The handles enforce a dynamic
|
|
//! limit: lo/mid can never exceed mid/hi (and vice-versa), on top of each param's own range.
|
|
|
|
use nih_plug::prelude::*;
|
|
use nih_plug_egui::egui::{
|
|
self, pos2, vec2, Align2, Color32, CornerRadius, DragValue, FontId, Painter, Rect, Sense, Stroke,
|
|
};
|
|
|
|
use crate::params::Bari206Params;
|
|
use super::palette;
|
|
|
|
/// Height of the handle strip.
|
|
const GRAPH_H: f32 = 54.0;
|
|
/// Displayed frequency axis (log), independent of the params' own ranges.
|
|
const DISP_MIN_HZ: f32 = 20.0;
|
|
const DISP_MAX_HZ: f32 = 20_000.0;
|
|
|
|
fn log_span() -> f32 {
|
|
DISP_MAX_HZ.ln() - DISP_MIN_HZ.ln()
|
|
}
|
|
|
|
fn fmt_hz(hz: f32) -> String {
|
|
if hz >= 1000.0 {
|
|
format!("{:.2} kHz", hz / 1000.0)
|
|
} else {
|
|
format!("{:.0} Hz", hz)
|
|
}
|
|
}
|
|
|
|
pub(super) fn draw(ui: &mut egui::Ui, params: &Bari206Params, setter: &ParamSetter) {
|
|
let lo = params.crossover_low_hz.value();
|
|
let hi = params.crossover_high_hz.value();
|
|
// Each param's own min/max (normalized 0/1 map to the range ends).
|
|
let lo_min = params.crossover_low_hz.preview_plain(0.0);
|
|
let lo_max = params.crossover_low_hz.preview_plain(1.0);
|
|
let hi_min = params.crossover_high_hz.preview_plain(0.0);
|
|
let hi_max = params.crossover_high_hz.preview_plain(1.0);
|
|
// Dynamic limits so the two never cross: lo <= hi.
|
|
let lo_upper = lo_max.min(hi);
|
|
let hi_lower = hi_min.max(lo);
|
|
|
|
ui.label("Crossover");
|
|
|
|
let (rect, _) =
|
|
ui.allocate_exact_size(vec2(ui.available_width(), GRAPH_H), Sense::hover());
|
|
let p = ui.painter_at(rect);
|
|
let (left, right, top, bottom) =
|
|
(rect.left() + 2.0, rect.right() - 2.0, rect.top() + 2.0, rect.bottom() - 2.0);
|
|
let width = right - left;
|
|
let x_for = |hz: f32| left + (hz.max(1.0).ln() - DISP_MIN_HZ.ln()) / log_span() * width;
|
|
let xlo = x_for(lo);
|
|
let xhi = x_for(hi);
|
|
|
|
// Three band regions.
|
|
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), 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, 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| {
|
|
ui.label("Lo/Mid");
|
|
let mut v = lo as f64;
|
|
if ui
|
|
.add(DragValue::new(&mut v).range(lo_min as f64..=lo_upper as f64).speed(0.5).suffix(" Hz"))
|
|
.changed()
|
|
{
|
|
set_clamped(setter, ¶ms.crossover_low_hz, v as f32, lo_min, lo_upper);
|
|
}
|
|
ui.add_space(16.0);
|
|
ui.label("Mid/Hi");
|
|
let mut v = hi as f64;
|
|
if ui
|
|
.add(DragValue::new(&mut v).range(hi_lower as f64..=hi_max as f64).speed(2.0).suffix(" Hz"))
|
|
.changed()
|
|
{
|
|
set_clamped(setter, ¶ms.crossover_high_hz, v as f32, hi_lower, hi_max);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// One draggable vertical handle. Drives `param` from the pointer's x (log-frequency), clamped to
|
|
/// `[min, max]` (which already encodes the dynamic lo<=hi limit), with proper begin/end gestures.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn handle(
|
|
ui: &egui::Ui,
|
|
p: &Painter,
|
|
setter: &ParamSetter,
|
|
id_salt: &str,
|
|
x: f32,
|
|
hz: f32,
|
|
param: &FloatParam,
|
|
min: f32,
|
|
max: f32,
|
|
left: f32,
|
|
width: f32,
|
|
top: f32,
|
|
bottom: f32,
|
|
color: Color32,
|
|
) {
|
|
let hit = Rect::from_min_max(pos2(x - 5.0, top), pos2(x + 5.0, bottom));
|
|
let resp = ui
|
|
.interact(hit, ui.id().with(id_salt), Sense::drag())
|
|
.on_hover_cursor(egui::CursorIcon::ResizeHorizontal);
|
|
|
|
if resp.drag_started() {
|
|
setter.begin_set_parameter(param);
|
|
}
|
|
if resp.dragged() {
|
|
if let Some(pos) = resp.interact_pointer_pos() {
|
|
let frac = ((pos.x - left) / width).clamp(0.0, 1.0);
|
|
let new = (DISP_MIN_HZ.ln() + frac * log_span()).exp().clamp(min, max);
|
|
setter.set_parameter(param, new);
|
|
}
|
|
}
|
|
if resp.drag_stopped() {
|
|
setter.end_set_parameter(param);
|
|
}
|
|
|
|
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_f32, col));
|
|
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).
|
|
fn set_clamped(setter: &ParamSetter, param: &FloatParam, value: f32, min: f32, max: f32) {
|
|
setter.begin_set_parameter(param);
|
|
setter.set_parameter(param, value.clamp(min, max));
|
|
setter.end_set_parameter(param);
|
|
}
|