#[non_exhaustive]pub enum Curve<'a> {
Linear {
step: Option<&'a str>,
},
Logarithmic {
step: Option<&'a str>,
},
}Expand description
One field of a form.
Borrowed rather than owned: a description is built, read once by a renderer, and dropped. Nothing here outlives the screen it describes.
§What it carries, and what it does not
Stated here so the next renderer does not re-ask, which is what the first two both did. It carries everything a renderer needs to draw the field: its kind, what it is called, what it is asked for, its standing help, what is wrong with it now, whether it is compulsory, whether it hides behind a disclosure, its ghost text, and the options it offers.
It does not carry the current value, and it is not going to. That is the
one thing here that is genuinely renderer state: a webview reads it back out
of the DOM, an immediate-mode renderer holds a &mut to the app’s own field
and writes through it, and a terminal keeps an edit buffer. A description
that carried the value would have to carry a way to write it back, at which
point it is a form model and no longer a description.
Constraints are here and enforcement is not, which is one line rather
than two. required, max_length, min and max are facts about
the question, so a renderer can emit its host’s idiom for each — an HTML
attribute, a marked label, a clamped spinner — and the platform helps the
user before anything is submitted. Deciding that a value is wrong stays with
whoever validated, and error is that decision arriving back.
The set stops before pattern, and stops there on both tests at once. A
regex has an honest answer in a webview and none anywhere else: egui would
have to run it per keystroke and decide what a half-typed value means, which
is enforcement wearing description’s clothes. And it is one site in goingson
and none in Balanced Breakfast, against 8 and 1 for maxlength. Measured
2026-08-09, 2cbad3e2.
How a slider’s position becomes its value, and how finely it moves.
The data of a slider is a fraction and a function taking numbers to
numbers. Stated by Max 2026-08-21, and it corrects a reading this crate
had carried since FieldKind::Range arrived at 0.28.0:
min and max were never the control’s extent.
A slider’s extent is always 0 to 1 — a thumb at 40% of a track — and the
bounds are f(0) and f(1). Linear is the constant-slope case, which is
exactly why nobody noticed the function was there: when f is
min + t * (max - min) the extent and the bounds coincide numerically and
the mapping is invisible.
So this is not a scale flag bolted onto a range. Every range described before it had a mapping, and four renderers each hard-coded the same one.
§Why a closed family and not a function
fn(f64) -> f64 is the literal reading and it does not survive the
description boundary. A fn pointer cannot be emitted into a browser, and it
cannot be compared or hashed in a way that means anything, which this struct
needs. A named family is the same semantics with arbitrary closures given
up, and nothing measured wants one: the tree has a single non-linear shape
across five controls and no second shape at all.
§Why the step is here
Max, in the same breath: if the family is prescriptive anyway, the step
spacing belongs in it. On a slider the granularity and the mapping are one
decision — a curve chosen without saying how finely it moves is half an
answer — and holding them apart is what let a 0-to-1 threshold ship as a
two-position control, since the host default of 1 was applied to a mapping
nobody had named. It also un-overloads Field::step, which stays as it
was for a typed value, where there is no mapping and the granularity is a
plain fact about the number.
A future curve carrying a fact of its own — an exponent, an inflection — puts it in its own variant rather than on the struct, which is the second reason this shape is right.
The step is in the value’s own units under every curve. What a curve
changes is the mapping, not the units the granularity is measured in: a step
of 0.001 on an envelope time is three decimals whether the track is
logarithmic or not, and a renderer that reads the step for display precision
keeps reading it the same way.
Added 0.32.0, from audiofiles’ ADSR envelope and its storage cap picker.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Linear
Constant slope: f(t) = min + t * (max - min).
What every described range meant before this enum existed, and the default, so a site that says nothing is correct unchanged.
Logarithmic
Constant ratio: f(t) = min * (max / min).powf(t).
The mapping for a question whose extent spans orders of magnitude and whose interesting half is the small end. audiofiles’ envelope times run 0.001 to 5 seconds, where a 5 ms attack and a 50 ms attack are audibly different instruments and a linear track puts both inside its first one percent.
§It needs positive bounds
A constant ratio is undefined across zero, so this asks for min > 0.
A range that does not have that is mapped Linearly
instead — see value_at. Stated rather than enforced,
the way every other constraint in this crate is, and it is not a
hypothetical: an envelope’s sustain is a 0-to-1 level and is linear for
this reason rather than by oversight.
Implementations§
Source§impl<'a> Curve<'a>
impl<'a> Curve<'a>
Sourcepub const fn step(self) -> Option<&'a str>
pub const fn step(self) -> Option<&'a str>
The granularity this curve moves in, whichever curve it is.
Every variant carries one, so reading it does not need a match at each of the four renderers.
Sourcepub fn is_ratio(self, min: f64, max: f64) -> bool
pub fn is_ratio(self, min: f64, max: f64) -> bool
Whether this curve maps as a constant ratio given these bounds.
The bounds are the argument because Logarithmic
is a request rather than a guarantee: it needs 0 < min < max, and a
range that does not have that is drawn linearly. A renderer asks this
instead of matching on the variant, so the fallback is decided in one
place rather than four.
Sourcepub fn value_at(self, position: f64, min: f64, max: f64) -> f64
pub fn value_at(self, position: f64, min: f64, max: f64) -> f64
The value at a position along the track, where position is 0 to 1.
f. The whole point of the type, and it lives here rather than in each
renderer so that a terminal’s bar, an egui slider and a browser’s input
cannot disagree about where a value sits.
A position outside 0 to 1 is clamped, and bounds that are equal or
inverted give min back: a track with no extent has one value on it.