use frus_core::{BorderRadius, Color, Path, Point, Rect, Scene};
use frus_layout::{Dimension, Style};
use crate::interaction::Status;
use crate::theme::Theme;
use crate::widget::Widget;
pub const PLACEHOLDER_COLOR: Color = Color {
r: 0x45 as f32 / 255.0,
g: 0x5A as f32 / 255.0,
b: 0x64 as f32 / 255.0,
a: 1.0,
};
pub const PLACEHOLDER_STROKE: f32 = 2.0;
pub const PLACEHOLDER_FALLBACK: f32 = 400.0;
pub struct Placeholder {
color: Option<Color>,
stroke: f32,
width: f32,
height: f32,
}
impl Default for Placeholder {
fn default() -> Self {
Self::new()
}
}
impl Placeholder {
pub fn new() -> Self {
Self {
color: None,
stroke: PLACEHOLDER_STROKE,
width: PLACEHOLDER_FALLBACK,
height: PLACEHOLDER_FALLBACK,
}
}
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn stroke(mut self, stroke: f32) -> Self {
self.stroke = stroke;
self
}
pub fn fallback(mut self, width: f32, height: f32) -> Self {
self.width = width;
self.height = height;
self
}
}
impl<Msg> Widget<Msg> for Placeholder {
fn style(&self) -> Style {
Style {
width: Dimension::Length(self.width),
height: Dimension::Length(self.height),
flex_grow: 1.0,
min_width: Dimension::Length(0.0),
min_height: Dimension::Length(0.0),
..Default::default()
}
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&[]
}
fn paint(&self, bounds: Rect, status: Status, theme: &Theme, scene: &mut Scene) {
let color = self.color.unwrap_or(PLACEHOLDER_COLOR).fade(status.opacity);
let _ = theme;
scene.draw_rect(
bounds,
Color::TRANSPARENT,
BorderRadius::ZERO,
self.stroke,
color,
);
let half = self.stroke / 2.0;
let (right, bottom) = (bounds.x + bounds.width, bounds.y + bounds.height);
for (x0, y0, x1, y1) in [
(bounds.x, bounds.y, right, bottom),
(bounds.x, bottom, right, bounds.y),
] {
let (dx, dy) = (x1 - x0, y1 - y0);
let len = (dx * dx + dy * dy).sqrt().max(f32::EPSILON);
let (nx, ny) = (-dy / len * half, dx / len * half);
let path = Path::new()
.move_to(Point::new(x0 + nx, y0 + ny))
.line_to(Point::new(x1 + nx, y1 + ny))
.line_to(Point::new(x1 - nx, y1 - ny))
.line_to(Point::new(x0 - nx, y0 - ny))
.close();
scene.fill_path(&path, color);
}
}
fn on_click(&self) -> Option<Msg> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use frus_core::{Primitive, Size};
fn primitives(w: &dyn Widget<()>) -> Vec<Primitive> {
let mut scene = Scene::new();
w.paint(
Rect::new(0.0, 0.0, 100.0, 50.0),
Status::default(),
&Theme::default(),
&mut scene,
);
let _ = Size::new(0.0, 0.0);
scene.primitives().to_vec()
}
#[test]
fn it_draws_a_box_and_its_two_diagonals() {
let p = Placeholder::new();
let painted = primitives(&p);
let rects = painted
.iter()
.filter(|x| matches!(x, Primitive::Rect { .. }))
.count();
let paths = painted
.iter()
.filter(|x| matches!(x, Primitive::Path { .. }))
.count();
assert_eq!(rects, 1, "the outline");
assert_eq!(paths, 2, "the two diagonals");
}
#[test]
fn the_colour_is_overridable() {
let default_color = match &primitives(&Placeholder::new())[0] {
Primitive::Rect { border_color, .. } => *border_color,
other => panic!("{other:?}"),
};
assert_eq!(default_color, PLACEHOLDER_COLOR);
let asked = Color::rgb(1.0, 0.0, 0.0);
let mine = match &primitives(&Placeholder::new().color(asked))[0] {
Primitive::Rect { border_color, .. } => *border_color,
other => panic!("{other:?}"),
};
assert_eq!(mine, asked);
}
#[test]
fn it_gives_way_to_the_layout_around_it() {
let style = Widget::<()>::style(&Placeholder::new());
assert_eq!(style.flex_grow, 1.0);
assert_eq!(style.min_width, Dimension::Length(0.0));
assert_eq!(style.width, Dimension::Length(PLACEHOLDER_FALLBACK));
}
}