use crate::motion::spin_icon_with_duration;
use gpui::{App, Component, Hsla, IntoElement, Pixels, RenderOnce, Window, px};
use liora_core::{Config, stable_unique_id};
use liora_icons::Icon;
use liora_icons_lucide::IconName;
pub struct Spinner {
size: Pixels,
color: Option<Hsla>,
icon: IconName,
}
impl Spinner {
pub fn new() -> Self {
Self {
size: px(16.0),
color: None,
icon: IconName::LoaderCircle,
}
}
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = size.into().max(px(8.0));
self
}
pub fn color(mut self, color: Hsla) -> Self {
self.color = Some(color);
self
}
pub fn icon(mut self, icon: IconName) -> Self {
self.icon = icon;
self
}
pub fn small(self) -> Self {
self.size(px(12.0))
}
pub fn large(self) -> Self {
self.size(px(24.0))
}
}
impl RenderOnce for Spinner {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.global::<Config>().theme.clone();
let color = self.color.unwrap_or(theme.primary.base);
let motion_id = stable_unique_id(
format!(
"liora-spinner-motion:{:?}:{:?}:{:?}",
self.icon, self.size, color
),
"liora-spinner-motion",
_window,
cx,
);
spin_icon_with_duration(
motion_id,
Icon::new(self.icon).size(self.size).color(color),
std::time::Duration::from_millis(1350),
)
}
}
impl IntoElement for Spinner {
type Element = Component<Self>;
fn into_element(self) -> Self::Element {
Component::new(self)
}
}
impl Default for Spinner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spinner_builders_track_state() {
let spinner = Spinner::new().large().icon(IconName::RefreshCw);
assert_eq!(spinner.size, px(24.0));
assert_eq!(spinner.icon, IconName::RefreshCw);
}
#[test]
fn spinner_uses_stable_motion_ids_so_animation_can_continue() {
let source = include_str!("spinner.rs");
assert!(source.contains("stable_unique_id("));
assert!(source.contains("liora-spinner-motion:{:?}:{:?}:{:?}"));
assert!(source.contains("spin_icon_with_duration("));
assert!(source.contains("Duration::from_millis(1350)"));
let render_body = source
.split("impl RenderOnce for Spinner")
.nth(1)
.expect("Spinner should implement RenderOnce")
.split("impl IntoElement for Spinner")
.next()
.expect("RenderOnce block should end before IntoElement");
assert!(!render_body.contains(r#"liora_core::unique_id("liora-spinner-motion")"#));
assert!(render_body.contains("Icon::new(self.icon).size(self.size).color(color)"));
let stale_field = ["motion_id", ": &'static str"].join("");
assert!(!source.contains(&stale_field));
}
}