#[cfg(target_os = "windows")]
use windows::Win32::Graphics::Direct2D::ID2D1RenderTarget;
#[cfg(target_os = "windows")]
use super::text::DWrite;
use crate::event::Rect;
use crate::primitives::spinner::{Spinner, SpinnerLayout, SpinnerMeasure};
#[cfg(target_os = "windows")]
use crate::theme::Theme;
const FRAMES: &[char] = &['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
fn frame_text(spinner: &Spinner) -> String {
let glyph = FRAMES[spinner.frame_idx % FRAMES.len()];
if spinner.label.is_empty() {
glyph.to_string()
} else {
format!("{glyph} {}", spinner.label)
}
}
pub trait SpinnerMeasureSource {
fn measure(&self, text: &str) -> (f32, f32);
}
#[cfg(target_os = "windows")]
impl SpinnerMeasureSource for DWrite {
fn measure(&self, text: &str) -> (f32, f32) {
self.measure_text(text).unwrap_or((0.0, 0.0))
}
}
pub fn win_spinner_layout(
measure: &dyn SpinnerMeasureSource,
rect: Rect,
spinner: &Spinner,
) -> SpinnerLayout {
let text = frame_text(spinner);
let (w, h) = measure.measure(&text);
spinner.layout(rect.x, rect.y, SpinnerMeasure::new(w, h))
}
#[cfg(target_os = "windows")]
pub fn draw_spinner(
target: &ID2D1RenderTarget,
dwrite: &DWrite,
rect: Rect,
spinner: &Spinner,
) -> SpinnerLayout {
let layout = win_spinner_layout(dwrite, rect, spinner);
let theme = Theme::default();
let fg = spinner.accent.unwrap_or(theme.foreground);
let text = frame_text(spinner);
let _ = dwrite.draw_text(target, &text, layout.bounds, fg);
layout
}
#[cfg(all(test, target_os = "windows"))]
mod tests {
use super::*;
use crate::primitives::spinner::SpinnerHit;
use crate::types::{Color, WidgetId};
use crate::win::testing::HeadlessSurface;
fn spinner(frame_idx: usize) -> Spinner {
Spinner {
id: WidgetId::new("sp"),
label: "Indexing…".into(),
frame_idx,
accent: None,
}
}
#[test]
fn layout_hit_test_round_trip() {
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let spinner = spinner(0);
let rect = Rect::new(10.0, 10.0, 0.0, 0.0);
let layout = win_spinner_layout(&dwrite, rect, &spinner);
assert!(layout.bounds.width > 0.0);
assert!(layout.bounds.height > 0.0);
let inside = layout.hit_test(
layout.bounds.x + 1.0,
layout.bounds.y + 1.0,
&WidgetId::new("sp"),
);
assert_eq!(inside, SpinnerHit::Body(WidgetId::new("sp")));
let outside = layout.hit_test(0.0, 0.0, &WidgetId::new("sp"));
assert_eq!(outside, SpinnerHit::Empty);
}
#[test]
fn paints_without_panicking() {
let surface = HeadlessSurface::new(200, 20).expect("create surface");
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let spinner = spinner(3);
let rect = Rect::new(0.0, 0.0, 0.0, 0.0);
let layout = surface
.paint(|target| {
draw_spinner(target, &dwrite, rect, &spinner);
})
.map(|_| win_spinner_layout(&dwrite, rect, &spinner))
.expect("paint spinner");
assert!(layout.bounds.width > 0.0);
}
#[test]
fn accent_colour_paints_without_panicking() {
let (dwrite, _, _) = DWrite::new("Segoe UI", 10.0, None).expect("create DWrite");
let mut spinner = spinner(9);
spinner.accent = Some(Color::rgb(255, 0, 0));
let rect = Rect::new(0.0, 0.0, 0.0, 0.0);
let surface = HeadlessSurface::new(200, 20).expect("create surface");
surface
.paint(|target| {
draw_spinner(target, &dwrite, rect, &spinner);
})
.expect("paint spinner with accent");
}
}