1use std::f64::consts::PI;
18use tracing::{instrument, trace};
19
20use druid::kurbo::Line;
21use druid::widget::prelude::*;
22use druid::{theme, Color, Data, KeyOrValue, Point, Vec2};
23
24pub struct Spinner {
31 t: f64,
32 color: KeyOrValue<Color>,
33}
34
35impl Spinner {
36 pub fn new() -> Spinner {
38 Spinner::default()
39 }
40
41 pub fn with_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
47 self.color = color.into();
48 self
49 }
50
51 pub fn set_color(&mut self, color: impl Into<KeyOrValue<Color>>) {
57 self.color = color.into();
58 }
59}
60
61impl Default for Spinner {
62 fn default() -> Self {
63 Spinner {
64 t: 0.0,
65 color: theme::TEXT_COLOR.into(),
66 }
67 }
68}
69
70impl<T: Data> Widget<T> for Spinner {
71 #[instrument(name = "Spinner", level = "trace", skip(self, ctx, event, _data, _env))]
72 fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut T, _env: &Env) {
73 if let Event::AnimFrame(interval) = event {
74 self.t += (*interval as f64) * 1e-9;
75 if self.t >= 1.0 {
76 self.t = 0.0;
77 }
78 ctx.request_anim_frame();
79 ctx.request_paint();
80 }
81 }
82
83 #[instrument(name = "Spinner", level = "trace", skip(self, ctx, event, _data, _env))]
84 fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, _data: &T, _env: &Env) {
85 if let LifeCycle::WidgetAdded = event {
86 ctx.request_anim_frame();
87 ctx.request_paint();
88 }
89 }
90
91 #[instrument(
92 name = "Spinner",
93 level = "trace",
94 skip(self, _ctx, _old_data, _data, _env)
95 )]
96 fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &T, _data: &T, _env: &Env) {}
97
98 #[instrument(
99 name = "Spinner",
100 level = "trace",
101 skip(self, _layout_ctx, bc, _data, env)
102 )]
103 fn layout(
104 &mut self,
105 _layout_ctx: &mut LayoutCtx,
106 bc: &BoxConstraints,
107 _data: &T,
108 env: &Env,
109 ) -> Size {
110 bc.debug_check("Spinner");
111
112 let size = if bc.is_width_bounded() && bc.is_height_bounded() {
113 bc.max()
114 } else {
115 bc.constrain(Size::new(
116 env.get(theme::BASIC_WIDGET_HEIGHT),
117 env.get(theme::BASIC_WIDGET_HEIGHT),
118 ))
119 };
120
121 trace!("Computed size: {}", size);
122 size
123 }
124
125 #[instrument(name = "Spinner", level = "trace", skip(self, ctx, _data, env))]
126 fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, env: &Env) {
127 let t = self.t;
128 let (width, height) = (ctx.size().width, ctx.size().height);
129 let center = Point::new(width / 2.0, height / 2.0);
130 let (r, g, b, original_alpha) = Color::as_rgba(self.color.resolve(env));
131 let scale_factor = width.min(height) / 40.0;
132
133 for step in 1..=12 {
134 let step = f64::from(step);
135 let fade_t = (t * 12.0 + 1.0).trunc();
136 let fade = ((fade_t + step).rem_euclid(12.0) / 12.0) + 1.0 / 12.0;
137 let angle = Vec2::from_angle((step / 12.0) * -2.0 * PI);
138 let ambit_start = center + (10.0 * scale_factor * angle);
139 let ambit_end = center + (20.0 * scale_factor * angle);
140 let color = Color::rgba(r, g, b, fade * original_alpha);
141
142 ctx.stroke(
143 Line::new(ambit_start, ambit_end),
144 &color,
145 3.0 * scale_factor,
146 );
147 }
148 }
149}