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
//! `Spinner` primitive: an indeterminate activity indicator. Used for
//! ongoing operations whose duration isn't known ahead of time —
//! LSP boot, git clone, Mason installs, project search.
//!
//! The spinner carries a `frame_idx` (app-incremented per tick) and a
//! label. Backends pick the glyph from their own frame table using
//! `frame_idx % frame_count`. TUI commonly uses braille frames
//! (`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`), GUI backends may use a rotating arc or native
//! equivalent.
//!
//! # Backend contract
//!
//! **Declarative snapshot.** The app advances `frame_idx` on its own
//! animation ticker (~100 ms for braille, faster for arcs). The
//! primitive has no built-in timer — it's a paint description only.
//! Backends rasterise the current glyph + label.
//!
//! # Adoption status (#825)
//!
//! Kept public: [`crate::compose::chat_controller::ChatController`]
//! already constructs and renders a real `Spinner` in its status strip
//! (the "thinking" indicator) — not just an example. It's also the
//! prerequisite shape for #788's timer work, per that issue.
//!
//! # Timer work landed on top, not inside (#832)
//!
//! #832 added [`crate::runner::Reaction::RedrawAfter`] +
//! [`crate::backend::Backend::request_frame_in`] — a way for an app to
//! ask the runner to wake it again after a precise interval, replacing
//! the pre-#832 pattern of relying on a fixed-cadence poll to
//! incidentally re-check timer state. Per this module's own doc above
//! (checked, as #832 asked, before building on it): that timer work
//! landed *around* `Spinner`, not inside it. The primitive stays exactly
//! what #825 shaped it as — a declarative snapshot with an app-owned,
//! app-incremented `frame_idx` and no built-in timer of its own. What
//! changed is what drives the increment: `examples/common/chat_demo.rs`'s
//! `tick` now calls `request_frame_in` to re-arm itself at a precise
//! ~100ms cadence instead of assuming the runner will call `tick` again
//! soon regardless — see that example's doc for the full before/after.
//! A primitive-owned timer remains out of scope: it would need its own
//! per-backend clock/thread story this module deliberately doesn't have,
//! and every existing adopter already owns exactly the ticker cadence it
//! wants.
//!
//! # No [`crate::Style`] tokens
//!
//! `Toast`/`Tooltip`/`ContextMenu`/`Palette`/`Dialog` each read
//! [`crate::Style::corner_radius`]/`border_width`/`shadow_elevation`,
//! and `ProgressBar`'s track reads `corner_radius`. `Spinner` has no
//! bordered box at all — every backend paints a bare glyph-plus-label,
//! matching VS Code's own activity spinner (a rotating icon, no card)
//! — so there is no rectangle for a radius to round or a shadow to sit
//! behind. Each backend's `draw_spinner` stays untouched by that token
//! set.
use crateRect;
use crate;
use ;
/// Declarative description of an indeterminate spinner.
///
/// # Examples
///
/// ```
/// use quadraui::{Spinner, SpinnerHit, SpinnerMeasure, WidgetId};
///
/// let spinner = Spinner {
/// id: WidgetId::new("spinner:lsp_boot"),
/// label: "Indexing…".to_string(),
/// frame_idx: 3,
/// accent: None,
/// };
///
/// let layout = spinner.layout(0.0, 0.0, SpinnerMeasure::new(80.0, 1.0));
/// assert_eq!(layout.hit_test(5.0, 0.0, &spinner.id), SpinnerHit::Body(spinner.id.clone()));
/// ```
// ── D6 Layout API ───────────────────────────────────────────────────────────
/// Measurement for a `Spinner`. Backends report the inline width of
/// the glyph + label at current font metrics.
/// Classification of a hit-test result. Spinners are not interactive —
/// only `Body` / `Empty`. `Body` is returned so apps can still use a
/// spinner click as "focus the originating operation" if they want.
/// Fully-resolved spinner layout.