| 382 |
382 |
|
//! that one page: audiofiles' library browser and goingson's filters are the
|
| 383 |
383 |
|
//! same shape.
|
| 384 |
384 |
|
//!
|
|
385 |
+ |
//! 0.33.0 gives a number its unit. [`Field::unit`] carries what the value is
|
|
386 |
+ |
//! measured in, and [`FieldKind::measurable`] says which kinds read it. Decided
|
|
387 |
+ |
//! by Max 2026-08-21 (`32215e21`) against eight sites across four audiofiles
|
|
388 |
+ |
//! files that had each independently put the unit in parentheses at the end of
|
|
389 |
+ |
//! the label -- three of them written while the gap was a known open question.
|
|
390 |
+ |
//!
|
|
391 |
+ |
//! - **A unit is a fact about the value, not part of the question's name.** The
|
|
392 |
+ |
//! two readings come apart the moment anything reads a field back rather than
|
|
393 |
+ |
//! drawing it, which is the argument that decided it.
|
|
394 |
+ |
//! - **The convention it replaces froze the worst placement.** A label is the
|
|
395 |
+ |
//! sentence above the control, so unit-in-label was the same answer on every
|
|
396 |
+ |
//! host -- including the host that had somewhere better, since egui's slider
|
|
397 |
+ |
//! already draws a suffix beside the readout, which is what these controls did
|
|
398 |
+ |
//! before they were described.
|
|
399 |
+ |
//! - A string rather than a closed family, which is [`Curve`]'s argument
|
|
400 |
+ |
//! inverted and correctly so: a curve is a mapping this crate computes, and a
|
|
401 |
+ |
//! unit is a symbol it only carries. The measured set is `GiB`, `dBFS`, `s`
|
|
402 |
+ |
//! and `ms`, and this crate does not know what the next consumer measures in.
|
|
403 |
+ |
//!
|
|
404 |
+ |
//! Additive: absent is what every field meant before.
|
|
405 |
+ |
//!
|
| 385 |
406 |
|
//! 0.32.0 is the slider's real shape. **The data of a slider is a fraction and
|
| 386 |
407 |
|
//! a function taking numbers to numbers** (Max, 2026-08-21), so [`Curve`]
|
| 387 |
408 |
|
//! arrives and [`Field::curve`] with it. [`Field::min`] and [`Field::max`] were
|
| 3509 |
3530 |
|
pub const fn takes_files(self) -> bool {
|
| 3510 |
3531 |
|
matches!(self, Self::File)
|
| 3511 |
3532 |
|
}
|
|
3533 |
+ |
|
|
3534 |
+ |
/// Whether the value is a quantity, so [`Field::unit`] means something.
|
|
3535 |
+ |
///
|
|
3536 |
+ |
/// The two numeric kinds and nothing else. A date is a quantity in the sense
|
|
3537 |
+ |
/// that it is ordered, and it is not one in the sense that matters here:
|
|
3538 |
+ |
/// its unit is fixed by the kind, so `Date` carrying `days` would be the
|
|
3539 |
+ |
/// description restating what [`kind`](Field::kind) already said.
|
|
3540 |
+ |
///
|
|
3541 |
+ |
/// [`takes_files`](Self::takes_files)'s footing, and for its reason: the
|
|
3542 |
+ |
/// renderers ask this before they decide where a unit goes, and a
|
|
3543 |
+ |
/// `matches!` per renderer is where the next measurable kind goes missing.
|
|
3544 |
+ |
///
|
|
3545 |
+ |
/// Added 0.33.0 with [`Field::unit`].
|
|
3546 |
+ |
#[must_use]
|
|
3547 |
+ |
pub const fn measurable(self) -> bool {
|
|
3548 |
+ |
matches!(self, Self::Number | Self::Range)
|
|
3549 |
+ |
}
|
| 3512 |
3550 |
|
}
|
| 3513 |
3551 |
|
|
| 3514 |
3552 |
|
/// A family of media a file can belong to.
|
| 4033 |
4071 |
|
///
|
| 4034 |
4072 |
|
/// Added 0.32.0.
|
| 4035 |
4073 |
|
pub curve: Curve<'a>,
|
|
4074 |
+ |
/// What the number is measured in: `s`, `ms`, `dB`, `GiB`.
|
|
4075 |
+ |
///
|
|
4076 |
+ |
/// A fact about the value, not part of the question's name, and that
|
|
4077 |
+ |
/// distinction is the whole reason it is a member. The two readings come
|
|
4078 |
+ |
/// apart the moment anything reads a field back rather than drawing it: a
|
|
4079 |
+ |
/// [`max`](Self::max) of `-96` and a bound of `-96 dBFS` are the same number
|
|
4080 |
+ |
/// and not the same answer, and under the convention this replaces the unit
|
|
4081 |
+ |
/// could only be recovered by parsing it back out of a label.
|
|
4082 |
+ |
///
|
|
4083 |
+ |
/// # Where a renderer draws it
|
|
4084 |
+ |
///
|
|
4085 |
+ |
/// Beside the value, wherever that host puts a value. Not in the label: the
|
|
4086 |
+ |
/// label is the sentence above the control and that is the one place the
|
|
4087 |
+ |
/// convention could put it, which is why it read the same on every host and
|
|
4088 |
+ |
/// was wrong on the one host that had somewhere better. egui puts it inside
|
|
4089 |
+ |
/// the slider where the readout already is, a terminal appends it to the
|
|
4090 |
+ |
/// value in the edit line, a webview sets it adjacent to the input.
|
|
4091 |
+ |
///
|
|
4092 |
+ |
/// # Which kinds read it
|
|
4093 |
+ |
///
|
|
4094 |
+ |
/// [`FieldKind::measurable`] answers, and it is
|
|
4095 |
+ |
/// [`takes_files`](FieldKind::takes_files)'s footing: three renderers ask
|
|
4096 |
+ |
/// before they can decide whether to draw this, and a `matches!` per
|
|
4097 |
+ |
/// renderer is where the next measurable kind goes missing. A unit on a kind
|
|
4098 |
+ |
/// that rejects it is sayable and ignored, the same way
|
|
4099 |
+ |
/// [`options`](Self::options) is on a kind that offers none.
|
|
4100 |
+ |
///
|
|
4101 |
+ |
/// # Why a string
|
|
4102 |
+ |
///
|
|
4103 |
+ |
/// The measured sites are `GiB`, `dBFS`, `s` and `ms`. An enum would have to
|
|
4104 |
+ |
/// grow a member for every unit any consumer ever wants, and this crate does
|
|
4105 |
+ |
/// not know them; it knows that a number has one.
|
|
4106 |
+ |
///
|
|
4107 |
+ |
/// Written as the symbol alone, with no brackets and no leading space. The
|
|
4108 |
+ |
/// spacing is the renderer's, because a slider's readout and a sentence want
|
|
4109 |
+ |
/// different answers.
|
|
4110 |
+ |
///
|
|
4111 |
+ |
/// Added 0.33.0, `32215e21`, on eight sites across four files that had each
|
|
4112 |
+ |
/// arrived at "Attack (s)" separately.
|
|
4113 |
+ |
pub unit: Option<&'a str>,
|
| 4036 |
4114 |
|
/// Whether the field lives behind a "more options" disclosure.
|
| 4037 |
4115 |
|
pub extended: bool,
|
| 4038 |
4116 |
|
}
|
| 4057 |
4135 |
|
max: None,
|
| 4058 |
4136 |
|
step: None,
|
| 4059 |
4137 |
|
curve: Curve::Linear { step: None },
|
|
4138 |
+ |
unit: None,
|
| 4060 |
4139 |
|
extended: false,
|
| 4061 |
4140 |
|
}
|
| 4062 |
4141 |
|
}
|
| 4849 |
4928 |
|
assert!(FieldKind::Rich.visible());
|
| 4850 |
4929 |
|
}
|
| 4851 |
4930 |
|
|
|
4931 |
+ |
#[test]
|
|
4932 |
+ |
fn only_the_two_numeric_kinds_are_measurable() {
|
|
4933 |
+ |
assert!(FieldKind::Number.measurable());
|
|
4934 |
+ |
assert!(FieldKind::Range.measurable());
|
|
4935 |
+ |
// A date is ordered and is not a quantity with a unit to choose: its
|
|
4936 |
+ |
// unit is fixed by the kind, so saying one would restate `kind`.
|
|
4937 |
+ |
for kind in [
|
|
4938 |
+ |
FieldKind::Text,
|
|
4939 |
+ |
FieldKind::Date,
|
|
4940 |
+ |
FieldKind::DateTime,
|
|
4941 |
+ |
FieldKind::Select,
|
|
4942 |
+ |
FieldKind::Checkbox,
|
|
4943 |
+ |
FieldKind::File,
|
|
4944 |
+ |
] {
|
|
4945 |
+ |
assert!(!kind.measurable(), "{kind:?}");
|
|
4946 |
+ |
}
|
|
4947 |
+ |
}
|
|
4948 |
+ |
|
|
4949 |
+ |
#[test]
|
|
4950 |
+ |
fn a_field_carries_no_unit_until_one_is_given() {
|
|
4951 |
+ |
// Additive: absent is what every field described before 0.33.0 meant.
|
|
4952 |
+ |
let plain = Field::new(FieldKind::Number, "attack", "Attack");
|
|
4953 |
+ |
assert_eq!(plain.unit, None);
|
|
4954 |
+ |
let measured = Field {
|
|
4955 |
+ |
unit: Some("s"),
|
|
4956 |
+ |
..Field::range("attack", "Attack", "0.001", "5")
|
|
4957 |
+ |
};
|
|
4958 |
+ |
assert_eq!(measured.unit, Some("s"));
|
|
4959 |
+ |
assert!(measured.kind.measurable());
|
|
4960 |
+ |
}
|
|
4961 |
+ |
|
| 4852 |
4962 |
|
#[test]
|
| 4853 |
4963 |
|
fn only_a_subtree_prunes_and_only_the_listing_modes_offer_values() {
|
| 4854 |
4964 |
|
// Excluding a value from a flat facet is the same fact as not picking
|