Skip to main content

guise/feedback/
ringprogress.rs

1//! `RingProgress` — a circular determinate progress indicator.
2//!
3//! gpui has no arc/conic primitive, so the fill is rendered as a clipped column
4//! rising from the bottom of a circle (a gauge), with the percentage centered on
5//! top. A true stroked ring would need a custom `canvas` paint pass.
6
7use gpui::prelude::*;
8use gpui::{div, px, relative, App, FontWeight, IntoElement, SharedString, Window};
9
10use crate::devtools::Probed;
11use crate::theme::{theme, ColorName};
12
13/// A circular progress gauge. `RingProgress::new(72.0).label("72%")`.
14#[derive(IntoElement)]
15pub struct RingProgress {
16  value: f32,
17  size: f32,
18  color: ColorName,
19  label: Option<SharedString>,
20}
21
22impl RingProgress {
23  pub fn new(value: f32) -> Self {
24    RingProgress {
25      value: value.clamp(0.0, 100.0),
26      size: 80.0,
27      color: ColorName::Blue,
28      label: None,
29    }
30  }
31
32  /// Diameter in px.
33  pub fn size(mut self, size: f32) -> Self {
34    self.size = size;
35    self
36  }
37
38  pub fn color(mut self, color: ColorName) -> Self {
39    self.color = color;
40    self
41  }
42
43  /// Centered label (defaults to the rounded percentage).
44  pub fn label(mut self, label: impl Into<SharedString>) -> Self {
45    self.label = Some(label.into());
46    self
47  }
48}
49
50impl RenderOnce for RingProgress {
51  fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
52    let t = theme(cx);
53    let accent = t.color(self.color, t.primary_shade()).alpha(0.85);
54    let track = if t.scheme.is_dark() {
55      t.color(ColorName::Dark, 4)
56    } else {
57      t.color(ColorName::Gray, 2)
58    }
59    .hsla();
60    let text = t.text().hsla();
61    let frac = (self.value / 100.0).clamp(0.0, 1.0);
62    let label = self
63      .label
64      .unwrap_or_else(|| SharedString::from(format!("{}%", self.value.round() as i64)));
65
66    div()
67      .relative()
68      .w(px(self.size))
69      .h(px(self.size))
70      .rounded(px(self.size / 2.0))
71      .overflow_hidden()
72      .bg(track)
73      .flex()
74      .items_center()
75      .justify_center()
76      // Fill rising from the bottom.
77      .child(
78        div()
79          .absolute()
80          .bottom(px(0.0))
81          .left(px(0.0))
82          .right(px(0.0))
83          .h(relative(frac))
84          .bg(accent),
85      )
86      // Centered label, painted over the fill.
87      .child(
88        div()
89          .font_weight(FontWeight::SEMIBOLD)
90          .text_size(px(self.size * 0.22))
91          .text_color(text)
92          .child(label),
93      )
94      .probe("RingProgress")
95  }
96}