| 228 |
228 |
|
//! in the list, inert, with its precondition beside it instead of behind a
|
| 229 |
229 |
|
//! hover — a greyed row with no reason reads as a dead end, which is the
|
| 230 |
230 |
|
//! whole finding.
|
|
231 |
+ |
//! - **`Choice::detail` (makeover-layout 0.39.0) is drawn under the option in a
|
|
232 |
+ |
//! radio group and inside the row in a combo.** A closed chooser hides its
|
|
233 |
+ |
//! list, so everything an option carries has to travel with its row; a group
|
|
234 |
+ |
//! has a line to spare and putting a sentence beside the control instead
|
|
235 |
+ |
//! would push every option's radio out of line with its neighbours.
|
| 231 |
236 |
|
//!
|
| 232 |
237 |
|
//! The value still arrives as a `&mut String` and a slider is a number, so the
|
| 233 |
238 |
|
//! parse and the write-back are this renderer's, and the write happens only on
|
| 728 |
733 |
|
/// belong wherever that read happens rather than here. What the app gets is the
|
| 729 |
734 |
|
/// local value in `DATETIME_FORMAT`, which is what it got before the member
|
| 730 |
735 |
|
/// existed. No described site on this host asks for it today.
|
|
736 |
+ |
/// One row of a closed chooser, as the single string it has room for.
|
|
737 |
+ |
///
|
|
738 |
+ |
/// A combo hides its list, so everything an option carries has to travel with
|
|
739 |
+ |
/// the row it belongs to: there is no second line to put a detail on and no
|
|
740 |
+ |
/// space beside the row to put a reason in. That is the same constraint a
|
|
741 |
+ |
/// `<select>`'s option has in the webview, and it takes the same answer.
|
|
742 |
+ |
///
|
|
743 |
+ |
/// The order is what the option *is* before why it cannot be picked, which is
|
|
744 |
+ |
/// the order the two read in and the order the radio group draws them in.
|
|
745 |
+ |
fn combo_row(opt: &Choice<'_>) -> String {
|
|
746 |
+ |
let mut text = opt.label.to_owned();
|
|
747 |
+ |
for extra in [opt.detail, opt.unavailable].into_iter().flatten() {
|
|
748 |
+ |
text.push_str(" ");
|
|
749 |
+ |
text.push_str(extra);
|
|
750 |
+ |
}
|
|
751 |
+ |
text
|
|
752 |
+ |
}
|
|
753 |
+ |
|
|
754 |
+ |
/// How far an option's second line is inset, in points.
|
|
755 |
+ |
///
|
|
756 |
+ |
/// makeover-layout 0.39.0. The width of egui's radio button plus the gap after
|
|
757 |
+ |
/// it, so the line starts under the label rather than under the control. A
|
|
758 |
+ |
/// magnitude, which is `makeover-geometry`'s subject and not this crate's --
|
|
759 |
+ |
/// but this one is measured off a widget egui draws and sizes, so there is
|
|
760 |
+ |
/// nothing for a spacing scale to say about it.
|
|
761 |
+ |
const OPTION_DETAIL_INDENT: f32 = 22.0;
|
|
762 |
+ |
|
| 731 |
763 |
|
const fn control_shape(kind: FieldKind) -> Control {
|
| 732 |
764 |
|
match kind {
|
| 733 |
765 |
|
FieldKind::Select => Control::Chosen,
|
| 1141 |
1173 |
|
RichText::new(opt.label).color(option_color(value, opt.value, palette)),
|
| 1142 |
1174 |
|
)
|
| 1143 |
1175 |
|
};
|
|
1176 |
+ |
// What picking it means, under the option rather than
|
|
1177 |
+ |
// beside it. makeover-layout 0.39.0, and the placement is
|
|
1178 |
+ |
// the difference from the reason above: a precondition is
|
|
1179 |
+ |
// short enough to sit on the line, and a sentence saying
|
|
1180 |
+ |
// what a tier is would push every option's control out of
|
|
1181 |
+ |
// line with its neighbours.
|
|
1182 |
+ |
//
|
|
1183 |
+ |
// Indented past the radio, so it reads as belonging to the
|
|
1184 |
+ |
// option above rather than as a label of its own. Muted,
|
|
1185 |
+ |
// the same reading `.form-option-detail` takes in the
|
|
1186 |
+ |
// webview: the line orients the label, it does not compete
|
|
1187 |
+ |
// with it.
|
|
1188 |
+ |
if let Some(detail) = opt.detail {
|
|
1189 |
+ |
ui.horizontal(|ui| {
|
|
1190 |
+ |
ui.add_space(OPTION_DETAIL_INDENT);
|
|
1191 |
+ |
ui.label(RichText::new(detail).color(palette.content_muted));
|
|
1192 |
+ |
});
|
|
1193 |
+ |
}
|
| 1144 |
1194 |
|
answered = Some(match answered {
|
| 1145 |
1195 |
|
Some(prev) => prev.union(picked),
|
| 1146 |
1196 |
|
None => picked,
|
| 1167 |
1217 |
|
// Same rule as the radio group: shown, inert, and
|
| 1168 |
1218 |
|
// saying why. A closed control hides its list, so the
|
| 1169 |
1219 |
|
// reason has to travel with the row it belongs to.
|
| 1170 |
|
- |
if let Some(reason) = opt.unavailable {
|
|
1220 |
+ |
// A closed control hides its list, so everything an
|
|
1221 |
+ |
// option carries has to travel with the row it belongs
|
|
1222 |
+ |
// to. That is why both extra strings run into the text
|
|
1223 |
+ |
// here and neither does in the group above: a combo
|
|
1224 |
+ |
// row is a row, the way a `<select>`'s option is.
|
|
1225 |
+ |
let text = combo_row(opt);
|
|
1226 |
+ |
if opt.unavailable.is_some() {
|
| 1171 |
1227 |
|
ui.add_enabled_ui(false, |ui| {
|
| 1172 |
1228 |
|
ui.selectable_value(
|
| 1173 |
1229 |
|
value,
|
| 1174 |
1230 |
|
opt.value.to_owned(),
|
| 1175 |
|
- |
RichText::new(format!("{} {reason}", opt.label))
|
| 1176 |
|
- |
.color(palette.content_muted),
|
|
1231 |
+ |
RichText::new(text).color(palette.content_muted),
|
| 1177 |
1232 |
|
);
|
| 1178 |
1233 |
|
});
|
| 1179 |
1234 |
|
continue;
|
| 1181 |
1236 |
|
ui.selectable_value(
|
| 1182 |
1237 |
|
value,
|
| 1183 |
1238 |
|
opt.value.to_owned(),
|
| 1184 |
|
- |
RichText::new(opt.label).color(option_color(value, opt.value, palette)),
|
|
1239 |
+ |
RichText::new(text).color(option_color(value, opt.value, palette)),
|
| 1185 |
1240 |
|
);
|
| 1186 |
1241 |
|
}
|
| 1187 |
1242 |
|
})
|
| 1708 |
1763 |
|
);
|
| 1709 |
1764 |
|
}
|
| 1710 |
1765 |
|
|
|
1766 |
+ |
#[test]
|
|
1767 |
+ |
fn a_closed_chooser_carries_both_of_an_options_extra_lines_in_its_row() {
|
|
1768 |
+ |
// makeover-layout 0.39.0. The radio group has room for a second line
|
|
1769 |
+ |
// and this control does not, so the two draw the same description
|
|
1770 |
+ |
// differently and neither is a preference.
|
|
1771 |
+ |
let plain = Choice::new("24", "Small Files");
|
|
1772 |
+ |
assert_eq!(combo_row(&plain), "Small Files");
|
|
1773 |
+ |
|
|
1774 |
+ |
let detailed = plain.detailing("$24/mo. Fits audio, plugins, binaries.");
|
|
1775 |
+ |
assert_eq!(
|
|
1776 |
+ |
combo_row(&detailed),
|
|
1777 |
+ |
"Small Files $24/mo. Fits audio, plugins, binaries."
|
|
1778 |
+ |
);
|
|
1779 |
+ |
|
|
1780 |
+ |
// What it is, then why it cannot be picked. An option carrying both has
|
|
1781 |
+ |
// said two things, and the order is the one the group draws.
|
|
1782 |
+ |
let both = detailed.unless("Sold out.");
|
|
1783 |
+ |
assert_eq!(
|
|
1784 |
+ |
combo_row(&both),
|
|
1785 |
+ |
"Small Files $24/mo. Fits audio, plugins, binaries. Sold out."
|
|
1786 |
+ |
);
|
|
1787 |
+ |
assert_eq!(
|
|
1788 |
+ |
combo_row(&plain.unless("Sold out.")),
|
|
1789 |
+ |
"Small Files Sold out.",
|
|
1790 |
+ |
"the reason still reads the way it did before the detail existed"
|
|
1791 |
+ |
);
|
|
1792 |
+ |
}
|
|
1793 |
+ |
|
| 1711 |
1794 |
|
#[test]
|
| 1712 |
1795 |
|
fn only_a_required_field_is_marked() {
|
| 1713 |
1796 |
|
let style = FieldStyle::default();
|