Feedback
Spinner
An indeterminate loading indicator.


Static preview
use iced::time::Instant;
use iced::widget::{column, row, text};
use iced::{Alignment, Element, Subscription, window};
use iced_cube::feedback::spinner::advance;
use iced_cube::primitives::button::Variant;
use iced_cube::{button, spinner};
#[derive(Debug, Clone)]
pub enum Message {
Toggle,
Frame(Instant),
}
#[derive(Debug)]
pub struct Example {
loading: bool,
phase: f32,
last_frame: Option<Instant>,
}
impl Default for Example {
fn default() -> Self {
Self {
loading: true,
phase: 0.15,
last_frame: None,
}
}
}
impl Example {
pub fn update(&mut self, message: Message) {
match message {
Message::Toggle => {
self.loading = !self.loading;
self.last_frame = None;
}
Message::Frame(now) => {
if let Some(last) = self.last_frame {
self.phase = advance(self.phase, now - last);
}
self.last_frame = Some(now);
}
}
}
pub fn view(&self) -> Element<'_, Message> {
let status: Element<'_, Message> = if self.loading {
row![spinner(self.phase), text("Fetching latest data").size(14)]
.spacing(12)
.align_y(Alignment::Center)
.into()
} else {
text("Up to date").size(14).into()
};
column![
status,
button(if self.loading { "Stop" } else { "Refresh" })
.variant(Variant::Outline)
.on_press(Message::Toggle),
]
.spacing(16)
.align_x(Alignment::Center)
.into()
}
/// Frames are only requested while the spinner is visible.
pub fn subscription(&self) -> Subscription<Message> {
if !self.loading {
return Subscription::none();
}
window::frames().map(Message::Frame)
}
}Usage
The spinner has no clock of its own. Your app keeps the phase in its state and moves it on with advance each frame, which keeps the spinner a pure function of state and makes it easy to test.
Subscribe to iced::window::frames() only while something is loading, and return Subscription::none() otherwise. That way an idle app does not redraw at all.
Several spinners can share one phase, as the rows of a job list do. The arc steps through FRAMES fixed positions per turn and is drawn as a cached image, so any number of spinners on screen stay cheap and look the same on every renderer, including WebGL in the browser.
Examples
Sizes
Small, medium and large spinners sharing one phase.


Static preview
use iced::time::Instant;
use iced::widget::row;
use iced::{Alignment, Element, Subscription, window};
use iced_cube::feedback::spinner::{Size, advance};
use iced_cube::spinner;
#[derive(Debug, Clone)]
pub enum Message {
Frame(Instant),
}
#[derive(Debug)]
pub struct Example {
phase: f32,
last_frame: Option<Instant>,
}
impl Default for Example {
fn default() -> Self {
Self {
phase: 0.15,
last_frame: None,
}
}
}
impl Example {
pub fn update(&mut self, message: Message) {
let Message::Frame(now) = message;
if let Some(last) = self.last_frame {
self.phase = advance(self.phase, now - last);
}
self.last_frame = Some(now);
}
pub fn view(&self) -> Element<'_, Message> {
row(Size::ALL.map(|size| spinner(self.phase).size(size).into()))
.spacing(24)
.align_y(Alignment::Center)
.into()
}
pub fn subscription(&self) -> Subscription<Message> {
window::frames().map(Message::Frame)
}
}API
| Item | Description |
|---|---|
spinner(phase) | Creates a spinner at a phase measured in turns, from 0.0 to 1.0. |
.size(Size) | Sm, Md or Lg. Defaults to Md. |
advance(phase, elapsed) | Returns the phase moved on by the elapsed time. One turn takes PERIOD. |