1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//! Meter — HeroUI v3 `Meter` (value within a known range).
//!
//! A meter reports a static measurement inside a known range (disk usage,
//! password strength), where a `ProgressBar` reports advancing work. The two
//! share their track geometry, so this delegates the bar rendering to
//! [`ProgressBar`].
use gpui::{App, IntoElement, Pixels, RenderOnce, SharedString, Window};
use herogpui_core::{Color, Size};
use crate::progress::ProgressBar;
/// HeroUI Meter. Supports `value` in `0..max` with
/// optional label and fill color.
#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
#[derive(IntoElement)]
pub struct Meter {
id: gpui::ElementId,
value: f32,
min_value: f32,
max_value: f32,
size: Size,
color: Color,
label: Option<SharedString>,
value_label: Option<SharedString>,
/// `Meter.ValueLabel`'s render props (`percentage`, `valueText`), forwarded
/// to the bar that draws them.
value_content: Option<std::sync::Arc<dyn Fn(f32, &str) -> gpui::AnyElement + 'static>>,
show_value: bool,
/// `formatOptions` — forwarded to the bar, which writes the label.
format: Option<herogpui_core::NumberFormat>,
/// The corner radius, forwarded to the bar that paints the track and its
/// fill.
radius: Option<Pixels>,
/// The `sx` slot, refined over the root style at the end of render.
sx: Option<Box<gpui::StyleRefinement>>,
}
impl Meter {
/// `value` — also accepted positionally by [`Meter::new`].
pub fn value(mut self, value: f32) -> Self {
self.value = value;
self
}
/// `Meter.ValueLabel`'s render function — handed `percentage` (0-100) and
/// `valueText`, like the bar's.
pub fn value_content(
mut self,
render: impl Fn(f32, &str) -> gpui::AnyElement + 'static,
) -> Self {
self.value_content = Some(std::sync::Arc::new(render));
self
}
/// Creates a meter with the given id and value.
pub fn new(id: impl Into<gpui::ElementId>, value: f32) -> Self {
Self {
id: id.into(),
value_content: None,
value,
min_value: 0.0,
max_value: 100.0,
size: Size::Md,
color: Color::Accent,
label: None,
value_label: None,
show_value: false,
format: None,
radius: None,
sx: None,
}
}
/// Sets the minimum value of the range.
pub fn min_value(mut self, v: f32) -> Self {
self.min_value = v;
self
}
/// Sets the maximum value of the range.
pub fn max_value(mut self, v: f32) -> Self {
self.max_value = v;
self
}
/// `valueLabel` — replaces the generated percentage.
pub fn value_label(mut self, text: impl Into<SharedString>) -> Self {
self.value_label = Some(text.into());
self
}
/// `formatOptions` — v3 defaults to `{style: "percent"}`.
pub fn format_options(mut self, format: herogpui_core::NumberFormat) -> Self {
self.format = Some(format);
self
}
/// The corner radius, forwarded to the bar the meter delegates to: the bar
/// paints the track and its fill, so the override travels with the
/// delegation the same way the `sx` slot does. Not a v3 prop; the removed
/// v2 `radius` prop is prohibited and this is a per-component repository
/// extension.
pub fn radius(mut self, radius: impl Into<Pixels>) -> Self {
self.radius = Some(radius.into());
self
}
/// Sets the meter size.
pub fn size(mut self, s: Size) -> Self {
self.size = s;
self
}
/// Sets the color role.
pub fn color(mut self, c: Color) -> Self {
self.color = c;
self
}
/// The one slot for caller-owned low-level styling: GPUI's styling methods
/// (`bg`, `text_color`, `w`, `h`, `p`, `rounded`, `border_color`, …)
/// applied to the meter's root element after every value the size and the
/// active theme chose, so they win. The meter draws no root of its own —
/// the bar it delegates to is the meter's root — so the override travels
/// down with it.
pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
crate::util::refine_sx(&mut self.sx, style);
self
}
/// Sets the label text.
pub fn label(mut self, l: impl Into<SharedString>) -> Self {
self.label = Some(l.into());
self
}
/// Show `value/max` next to the label (like `Meter.Output`).
pub fn show_value(mut self, v: bool) -> Self {
self.show_value = v;
self
}
}
impl RenderOnce for Meter {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
// `useMeter` delegates every attribute to `useProgressBar` and
// changes only the role, so the delegation carries it.
let mut p = ProgressBar::new(self.id)
.as_meter()
.value(self.value)
.min_value(self.min_value)
.max_value(self.max_value)
.size(self.size)
.color(self.color)
.show_value_label(self.show_value);
if let Some(format) = self.format.clone() {
p = p.format_options(format);
}
if let Some(render) = self.value_content {
p = p.value_content(move |percentage, text, _| render(percentage, text));
}
if let Some(vl) = self.value_label {
p = p.value_label(vl);
}
if let Some(l) = self.label {
p = p.label(l.to_string());
}
// The bar owns the geometry: it paints the track and its fill, so the
// radius rides the delegation like the `sx` refinement below.
if let Some(radius) = self.radius {
p = p.radius(radius);
}
// The bar's root is this meter's root: hand the refinement down rather
// than box the bar in a wrapper of its own.
if let Some(sx) = self.sx {
p = p.sx_refinement(sx);
}
p.into_any_element()
}
}
crate::util::impl_component_styled!(Meter);