Panics · P12 Data Display: featured columns, gauges, usage bars and stars meet readings past their scale
This commit is contained in:
1 parent
c9f492d0b4
commit
4a05c12889
5 files changed
+61
-33
No files matched your search
@@ -35,12 +35,13 @@ impl Comparison {
|
|||||||
|
|
||||||
/// Tints column `ix`, such as the plan you suggest.
|
/// Tints column `ix`, such as the plan you suggest.
|
||||||
pub fn featured(mut self, ix: usize) -> Self {
|
pub fn featured(mut self, ix: usize) -> Self {
|
||||||
assert!(
|
self.featured = (ix < self.columns.len()).then_some(ix);
|
||||||
ix < self.columns.len(),
|
if self.featured.is_none() {
|
||||||
"column {ix} is past the last of {}",
|
log::error!(
|
||||||
self.columns.len()
|
"comparison: column {ix} of {}; none featured",
|
||||||
);
|
self.columns.len()
|
||||||
self.featured = Some(ix);
|
);
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +157,17 @@ impl RenderOnce for Comparison {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::Comparison;
|
use super::Comparison;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_featured_column_past_the_last_features_none() {
|
||||||
|
assert_eq!(Comparison::new(["Free", "Pro"]).featured(5).featured, None);
|
||||||
|
assert_eq!(
|
||||||
|
Comparison::new(["Free", "Pro"]).featured(1).featured,
|
||||||
|
Some(1)
|
||||||
|
);
|
||||||
|
let later = Comparison::new(["Free", "Pro"]).featured(1).featured(5);
|
||||||
|
assert_eq!(later.featured, None, "a later bad index clears the first");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "row Storage needs a cell per option")]
|
#[should_panic(expected = "row Storage needs a cell per option")]
|
||||||
fn a_row_needs_a_cell_per_option() {
|
fn a_row_needs_a_cell_per_option() {
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ pub struct Gauge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Gauge {
|
impl Gauge {
|
||||||
/// `value` lies within `min..=max`.
|
/// A `value` past `min..=max` pegs the dial and shows as it is.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: impl Into<ElementId>,
|
id: impl Into<ElementId>,
|
||||||
label: impl Into<SharedString>,
|
label: impl Into<SharedString>,
|
||||||
@@ -200,10 +200,6 @@ impl Gauge {
|
|||||||
min < max,
|
min < max,
|
||||||
"a gauge needs min below max, got {min} and {max}"
|
"a gauge needs min below max, got {min} and {max}"
|
||||||
);
|
);
|
||||||
assert!(
|
|
||||||
(min..=max).contains(&value),
|
|
||||||
"{value} is outside {min}..={max}"
|
|
||||||
);
|
|
||||||
Self {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
@@ -236,7 +232,7 @@ impl Gauge {
|
|||||||
impl RenderOnce for Gauge {
|
impl RenderOnce for Gauge {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (min, max) = self.range;
|
let (min, max) = self.range;
|
||||||
let share = ((self.value - min) / (max - min)) as f32;
|
let share = ((self.value - min) / (max - min)).clamp(0.0, 1.0) as f32;
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let (limits, track, tones) = (self.thresholds, theme.colors.border, tones(&theme.colors));
|
let (limits, track, tones) = (self.thresholds, theme.colors.border, tones(&theme.colors));
|
||||||
let dial = gliding(self.id.clone(), share, window, cx, move |at| {
|
let dial = gliding(self.id.clone(), share, window, cx, move |at| {
|
||||||
@@ -340,14 +336,9 @@ impl UsageBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UsageBar {
|
impl UsageBar {
|
||||||
/// What the parts use and whether they fill the total, within float error.
|
/// What the parts use and whether they fill the total, within float error; over it they fill it.
|
||||||
fn used(&self) -> (f64, bool) {
|
fn used(&self) -> (f64, bool) {
|
||||||
let used: f64 = self.parts.iter().map(|(_, amount)| amount).sum();
|
let used: f64 = self.parts.iter().map(|(_, amount)| amount).sum();
|
||||||
assert!(
|
|
||||||
used <= self.total * (1.0 + 1e-9),
|
|
||||||
"the parts ({used}) pass the total ({})",
|
|
||||||
self.total
|
|
||||||
);
|
|
||||||
(used, used >= self.total * (1.0 - 1e-9))
|
(used, used >= self.total * (1.0 - 1e-9))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,11 +356,7 @@ impl RenderOnce for UsageBar {
|
|||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = &theme.colors;
|
let colors = &theme.colors;
|
||||||
let (used, full) = self.used();
|
let (used, full) = self.used();
|
||||||
assert!(
|
let hue = |ix: usize| colors.chart[ix % colors.chart.len()];
|
||||||
self.parts.len() <= colors.chart.len(),
|
|
||||||
"a usage bar has colors for {} parts",
|
|
||||||
colors.chart.len()
|
|
||||||
);
|
|
||||||
let total = self.total;
|
let total = self.total;
|
||||||
let grown = |share: f64| {
|
let grown = |share: f64| {
|
||||||
let mut part = div().h_full().flex_basis(relative(0.0));
|
let mut part = div().h_full().flex_basis(relative(0.0));
|
||||||
@@ -383,7 +370,7 @@ impl RenderOnce for UsageBar {
|
|||||||
.h(theme.meter_track())
|
.h(theme.meter_track())
|
||||||
.children(self.parts.iter().enumerate().map(|(ix, (_, amount))| {
|
.children(self.parts.iter().enumerate().map(|(ix, (_, amount))| {
|
||||||
grown(amount / total)
|
grown(amount / total)
|
||||||
.bg(colors.chart[ix])
|
.bg(hue(ix))
|
||||||
.when(ix == 0, |part| part.rounded_l_full())
|
.when(ix == 0, |part| part.rounded_l_full())
|
||||||
.when(ix == last && full, |part| part.rounded_r_full())
|
.when(ix == last && full, |part| part.rounded_r_full())
|
||||||
}))
|
}))
|
||||||
@@ -400,12 +387,7 @@ impl RenderOnce for UsageBar {
|
|||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.child(
|
.child(div().size(theme.status_dot()).rounded_full().bg(hue(ix)))
|
||||||
div()
|
|
||||||
.size(theme.status_dot())
|
|
||||||
.rounded_full()
|
|
||||||
.bg(colors.chart[ix]),
|
|
||||||
)
|
|
||||||
.child(div().text_color(colors.fg).child(name.clone()))
|
.child(div().text_color(colors.fg).child(name.clone()))
|
||||||
.child(
|
.child(
|
||||||
tabular(div())
|
tabular(div())
|
||||||
@@ -440,6 +422,12 @@ mod tests {
|
|||||||
assert_eq!(tone(0.97, limits, &colors), colors.danger);
|
assert_eq!(tone(0.97, limits, &colors), colors.danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parts_past_the_total_fill_it() {
|
||||||
|
let over = UsageBar::new(10.0).part("a", 8.0).part("b", 5.0);
|
||||||
|
assert_eq!(over.used(), (13.0, true));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parts_that_sum_to_the_total_fill_it_despite_float_error() {
|
fn parts_that_sum_to_the_total_fill_it_despite_float_error() {
|
||||||
assert!(UsageBar::new(0.3).part("a", 0.1).part("b", 0.2).used().1);
|
assert!(UsageBar::new(0.3).part("a", 0.1).part("b", 0.2).used().1);
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ impl Stars {
|
|||||||
impl RenderOnce for Stars {
|
impl RenderOnce for Stars {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (score, max) = (self.score, self.max);
|
let (score, max) = (self.score, self.max);
|
||||||
assert!(score <= max as f32, "a score of {score} is above {max}");
|
if score > max as f32 {
|
||||||
|
log::error!("stars: a score of {score} is above {max}; all filled");
|
||||||
|
}
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let (on, off) = (theme.colors.accent, theme.colors.border_strong);
|
let (on, off) = (theme.colors.accent, theme.colors.border_strong);
|
||||||
let side = theme.icon_size(self.size);
|
let side = theme.icon_size(self.size);
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ use gpui::{
|
|||||||
TestAppContext, VisualTestContext, Window, div, point, px,
|
TestAppContext, VisualTestContext, Window, div, point, px,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Badge, Carousel, DescriptionList, PropertyGrid, PropertyGroup, Statistic, Tone};
|
use super::{
|
||||||
|
Badge, Carousel, DescriptionList, Gauge, PropertyGrid, PropertyGroup, Stars, Statistic, Tone,
|
||||||
|
UsageBar,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
primitives::Measure,
|
primitives::Measure,
|
||||||
theme::{TextSize, Theme},
|
theme::{TextSize, Theme},
|
||||||
@@ -283,3 +286,25 @@ fn left_from_the_first_slide_wraps_to_the_last(cx: &mut TestAppContext) {
|
|||||||
settle(cx);
|
settle(cx);
|
||||||
assert_eq!(front(&places), Some(0));
|
assert_eq!(front(&places), Some(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Readings past their scale: a gauge over its max, stars over theirs, more parts than hues.
|
||||||
|
struct Over;
|
||||||
|
|
||||||
|
impl Render for Over {
|
||||||
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let parts = (0..10).fold(UsageBar::new(100.0), |bar, ix| {
|
||||||
|
bar.part(format!("p{ix}"), 12.0)
|
||||||
|
});
|
||||||
|
div()
|
||||||
|
.child(Gauge::new("cpu", "CPU", 140.0, 0.0, 100.0))
|
||||||
|
.child(Stars::new(6.5))
|
||||||
|
.child(parts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn readings_past_their_scale_draw(cx: &mut TestAppContext) {
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let (_, cx) = cx.add_window_view(|_, _| Over);
|
||||||
|
cx.run_until_parked();
|
||||||
|
}
|
||||||
+2
-1
@@ -26,7 +26,7 @@ Each item reads every site in its folders, fixes what the rules name, updates ca
|
|||||||
- [x] P09 Overlays — `src/overlays`
|
- [x] P09 Overlays — `src/overlays`
|
||||||
- [x] P10 Feedback — `src/feedback`
|
- [x] P10 Feedback — `src/feedback`
|
||||||
- [x] P11 Loading & Motion — `src/motion`
|
- [x] P11 Loading & Motion — `src/motion`
|
||||||
- [ ] P12 Data Display — `src/data_display`
|
- [x] P12 Data Display — `src/data_display`
|
||||||
- [ ] P13 Lists & Trees — `src/lists`
|
- [ ] P13 Lists & Trees — `src/lists`
|
||||||
- [ ] P14 Tables — `src/tables`
|
- [ ] P14 Tables — `src/tables`
|
||||||
- [ ] P15 Charts — `src/charts`
|
- [ ] P15 Charts — `src/charts`
|
||||||
@@ -76,3 +76,4 @@ One line per item: what changed, what stayed and why, review rounds.
|
|||||||
- P09: a `Lightbox` or `Tour` past its last item logs and closes, handing focus back. Kept: dialog Enter without `focus_first` and popover face builders on an owner's opener (static builder misuse). Review: 1 round.
|
- P09: a `Lightbox` or `Tour` past its last item logs and closes, handing focus back. Kept: dialog Enter without `focus_first` and popover face builders on an owner's opener (static builder misuse). Review: 1 round.
|
||||||
- P10: no change. Kept: a toast's stay above zero (one value). Review: 1 round.
|
- P10: no change. Kept: a toast's stay above zero (one value). Review: 1 round.
|
||||||
- P11: a buffer at or behind the value draws none; `slide` was fixed in P05. Kept: values in 0..=1, segment and skeleton counts (one value each), builder misuse, reorder's permutation. Review: 1 round.
|
- P11: a buffer at or behind the value draws none; `slide` was fixed in P05. Kept: values in 0..=1, segment and skeleton counts (one value each), builder misuse, reorder's permutation. Review: 1 round.
|
||||||
|
- P12: a featured column past the columns features none; a gauge past its scale pegs; usage parts past the total fill it and repeat hues past eight; stars above max log and fill. Kept: one-value contracts, a comparison row's shape, construction. Review: 3 rounds (round 2 met an edit that had not applied).
|
||||||
Reference in new issue
Block a user