| 245 |
245 |
|
Text(&'a str),
|
| 246 |
246 |
|
/// A checkbox, on or off.
|
| 247 |
247 |
|
On(bool),
|
|
248 |
+ |
/// Both ends of a [`FieldKind::Interval`], lower first.
|
|
249 |
+ |
///
|
|
250 |
+ |
/// Two values rather than one string with a separator, which is
|
|
251 |
+ |
/// [`makeover_layout::Field::upper_name`]'s reason one level down: an
|
|
252 |
+ |
/// interval is submitted under two names, so it is held as two values, and
|
|
253 |
+ |
/// a delimiter this crate owned could appear inside either of them.
|
|
254 |
+ |
///
|
|
255 |
+ |
/// Either end may be empty while the other stands. An open end is an
|
|
256 |
+ |
/// answer -- "over 120 BPM" -- rather than a half-filled box.
|
|
257 |
+ |
///
|
|
258 |
+ |
/// Added 0.33.0 with makeover-layout 0.34.0.
|
|
259 |
+ |
Between {
|
|
260 |
+ |
/// What the lower box holds now.
|
|
261 |
+ |
lower: &'a str,
|
|
262 |
+ |
/// What the upper box holds now.
|
|
263 |
+ |
upper: &'a str,
|
|
264 |
+ |
},
|
| 248 |
265 |
|
}
|
| 249 |
266 |
|
|
| 250 |
267 |
|
impl<'a> Held<'a> {
|
| 252 |
269 |
|
#[must_use]
|
| 253 |
270 |
|
pub const fn text(self) -> &'a str {
|
| 254 |
271 |
|
match self {
|
| 255 |
|
- |
Self::Text(text) => text,
|
|
272 |
+ |
Self::Text(text) | Self::Between { lower: text, .. } => text,
|
| 256 |
273 |
|
Self::Absent | Self::On(_) => "",
|
| 257 |
274 |
|
}
|
| 258 |
275 |
|
}
|
| 259 |
276 |
|
|
|
277 |
+ |
/// The upper end, for the one variant that has one.
|
|
278 |
+ |
#[must_use]
|
|
279 |
+ |
pub const fn upper(self) -> &'a str {
|
|
280 |
+ |
match self {
|
|
281 |
+ |
Self::Between { upper, .. } => upper,
|
|
282 |
+ |
Self::Absent | Self::Text(_) | Self::On(_) => "",
|
|
283 |
+ |
}
|
|
284 |
+ |
}
|
|
285 |
+ |
|
| 260 |
286 |
|
/// Whether a checkbox is ticked.
|
| 261 |
287 |
|
#[must_use]
|
| 262 |
288 |
|
pub const fn on(self) -> bool {
|
| 469 |
495 |
|
let line = range_line(style, field, held.text(), well);
|
| 470 |
496 |
|
text::draw_line(&line, below(area, used), buf)
|
| 471 |
497 |
|
}
|
|
498 |
+ |
// One question, so one line. The two ends read left to right with the
|
|
499 |
+ |
// word between them, which is what a terminal has instead of two boxes
|
|
500 |
+ |
// side by side: a second row would read as a second question, and that
|
|
501 |
+ |
// is the reading the kind exists to prevent.
|
|
502 |
+ |
FieldKind::Interval => {
|
|
503 |
+ |
let line = interval_line(style, field, held, well);
|
|
504 |
+ |
text::draw_line(&line, below(area, used), buf)
|
|
505 |
+ |
}
|
| 472 |
506 |
|
kind if kind.offers_options() => {
|
| 473 |
507 |
|
let mut rows = 0;
|
| 474 |
508 |
|
for choice in field.options {
|
| 589 |
623 |
|
])
|
| 590 |
624 |
|
}
|
| 591 |
625 |
|
|
|
626 |
+ |
/// An interval as one line: the low end, the word, the high end.
|
|
627 |
+ |
///
|
|
628 |
+ |
/// One line because it is one question. Two rows would read as two questions,
|
|
629 |
+ |
/// which is exactly what [`FieldKind::Interval`] exists to stop the description
|
|
630 |
+ |
/// saying, and a terminal has no side-by-side boxes to fall back on.
|
|
631 |
+ |
///
|
|
632 |
+ |
/// # An open end draws the bound it falls back to
|
|
633 |
+ |
///
|
|
634 |
+ |
/// Muted, because it is where the axis ends rather than a value anybody set.
|
|
635 |
+ |
/// With no bound to fall back on there is nothing honest to draw and the end
|
|
636 |
+ |
/// stays blank: a terminal inventing a number here would report a filter the
|
|
637 |
+ |
/// user never applied, which is [`range_line`]'s position on an unreadable
|
|
638 |
+ |
/// value.
|
|
639 |
+ |
///
|
|
640 |
+ |
/// # The word, not a dash
|
|
641 |
+ |
///
|
|
642 |
+ |
/// A dash between two numbers is a minus sign to anyone reading a signed axis,
|
|
643 |
+ |
/// and half the measured axes are signed -- audiofiles filters loudness in
|
|
644 |
+ |
/// dBFS. `to` costs two cells and cannot be misread.
|
|
645 |
+ |
fn interval_line(
|
|
646 |
+ |
style: &PieceStyle,
|
|
647 |
+ |
field: &Field<'_>,
|
|
648 |
+ |
held: Held<'_>,
|
|
649 |
+ |
well: Style,
|
|
650 |
+ |
) -> Line<'static> {
|
|
651 |
+ |
let end = |value: &str, fallback: Option<&str>| match (value.is_empty(), fallback) {
|
|
652 |
+ |
(false, _) => Span::styled(measured(field, value), well),
|
|
653 |
+ |
(true, Some(bound)) => Span::styled(measured(field, bound), style.muted),
|
|
654 |
+ |
(true, None) => Span::styled(String::new(), style.muted),
|
|
655 |
+ |
};
|
|
656 |
+ |
Line::from(vec![
|
|
657 |
+ |
end(held.text(), field.min),
|
|
658 |
+ |
Span::styled(" to ", style.secondary),
|
|
659 |
+ |
end(held.upper(), field.max),
|
|
660 |
+ |
])
|
|
661 |
+ |
}
|
|
662 |
+ |
|
| 592 |
663 |
|
/// The unit to draw beside this field's value, if there is one to draw.
|
| 593 |
664 |
|
///
|
| 594 |
665 |
|
/// Two conditions rather than one: the field has to carry a unit and its kind
|
| 1008 |
1079 |
|
assert_eq!(rows(&buf)[1].trim_end(), "kick");
|
| 1009 |
1080 |
|
}
|
| 1010 |
1081 |
|
|
|
1082 |
+ |
#[test]
|
|
1083 |
+ |
fn an_interval_is_one_line_with_both_ends_on_it() {
|
|
1084 |
+ |
// One question, one line. Two rows would read as two questions, which
|
|
1085 |
+ |
// is the reading the kind exists to prevent.
|
|
1086 |
+ |
let style = style();
|
|
1087 |
+ |
let field_ = Field {
|
|
1088 |
+ |
min: Some("0"),
|
|
1089 |
+ |
max: Some("300"),
|
|
1090 |
+ |
unit: Some("BPM"),
|
|
1091 |
+ |
..Field::interval("bpm_min", "bpm_max", "BPM range")
|
|
1092 |
+ |
};
|
|
1093 |
+ |
let mut buf = buffer(40, 3);
|
|
1094 |
+ |
field(
|
|
1095 |
+ |
&style,
|
|
1096 |
+ |
&field_,
|
|
1097 |
+ |
Held::Between {
|
|
1098 |
+ |
lower: "90",
|
|
1099 |
+ |
upper: "130",
|
|
1100 |
+ |
},
|
|
1101 |
+ |
false,
|
|
1102 |
+ |
buf.area,
|
|
1103 |
+ |
&mut buf,
|
|
1104 |
+ |
);
|
|
1105 |
+ |
assert_eq!(rows(&buf)[0].trim_end(), "BPM range");
|
|
1106 |
+ |
assert_eq!(rows(&buf)[1].trim_end(), "90 BPM to 130 BPM");
|
|
1107 |
+ |
assert_eq!(rows(&buf)[2].trim_end(), "");
|
|
1108 |
+ |
}
|
|
1109 |
+ |
|
|
1110 |
+ |
#[test]
|
|
1111 |
+ |
fn an_open_end_falls_back_to_the_bound_it_means() {
|
|
1112 |
+ |
// "Over 120" is an answer rather than a half-filled box, and where the
|
|
1113 |
+ |
// axis ends is what the empty end stands for.
|
|
1114 |
+ |
let style = style();
|
|
1115 |
+ |
let field_ = Field {
|
|
1116 |
+ |
min: Some("0"),
|
|
1117 |
+ |
max: Some("300"),
|
|
1118 |
+ |
..Field::interval("bpm_min", "bpm_max", "BPM range")
|
|
1119 |
+ |
};
|
|
1120 |
+ |
let mut buf = buffer(40, 3);
|
|
1121 |
+ |
field(
|
|
1122 |
+ |
&style,
|
|
1123 |
+ |
&field_,
|
|
1124 |
+ |
Held::Between {
|
|
1125 |
+ |
lower: "120",
|
|
1126 |
+ |
upper: "",
|
|
1127 |
+ |
},
|
|
1128 |
+ |
false,
|
|
1129 |
+ |
buf.area,
|
|
1130 |
+ |
&mut buf,
|
|
1131 |
+ |
);
|
|
1132 |
+ |
assert_eq!(rows(&buf)[1].trim_end(), "120 to 300");
|
|
1133 |
+ |
}
|
|
1134 |
+ |
|
|
1135 |
+ |
#[test]
|
|
1136 |
+ |
fn an_unbounded_open_end_draws_nothing_rather_than_a_number() {
|
|
1137 |
+ |
// A terminal inventing a bound here would report a filter nobody
|
|
1138 |
+ |
// applied, which is `range_line`'s position on an unreadable value.
|
|
1139 |
+ |
// What is left reads as the sentence it is: up to 130.
|
|
1140 |
+ |
let style = style();
|
|
1141 |
+ |
let field_ = Field::interval("bpm_min", "bpm_max", "BPM range");
|
|
1142 |
+ |
let mut buf = buffer(40, 3);
|
|
1143 |
+ |
field(
|
|
1144 |
+ |
&style,
|
|
1145 |
+ |
&field_,
|
|
1146 |
+ |
Held::Between {
|
|
1147 |
+ |
lower: "",
|
|
1148 |
+ |
upper: "130",
|
|
1149 |
+ |
},
|
|
1150 |
+ |
false,
|
|
1151 |
+ |
buf.area,
|
|
1152 |
+ |
&mut buf,
|
|
1153 |
+ |
);
|
|
1154 |
+ |
assert_eq!(rows(&buf)[1].trim_end(), "to 130");
|
|
1155 |
+ |
}
|
|
1156 |
+ |
|
| 1011 |
1157 |
|
#[test]
|
| 1012 |
1158 |
|
fn a_range_holding_something_unreadable_still_shows_it() {
|
| 1013 |
1159 |
|
// The app put the value there. A terminal that quietly rounded it to a
|