use frust::authoring::{PaintCtx, PaintScene};
use kurbo::Rect;
use peniko::Color;
use crate::interaction::InteractionState;
pub use crate::interaction::DRAGGED_OPACITY;
pub use crate::interaction::FOCUS_OPACITY;
pub use crate::interaction::HOVER_OPACITY;
pub use crate::interaction::PRESSED_OPACITY;
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct StateLayer {
state: InteractionState,
}
impl StateLayer {
pub fn new() -> Self {
Self::default()
}
pub fn set_hovered(&mut self, hovered: bool) -> bool {
self.state.set_hovered(hovered)
}
pub fn set_focused(&mut self, focused: bool) -> bool {
self.state.set_focused(focused)
}
pub fn set_pressed(&mut self, pressed: bool) -> bool {
self.state.set_pressed(pressed)
}
pub fn set_dragged(&mut self, dragged: bool) -> bool {
self.state.set_dragged(dragged)
}
pub fn is_active(&self) -> bool {
self.state.is_active()
}
pub fn opacity(&self) -> f32 {
self.state.max_active_opacity()
}
pub fn paint(
&self,
_ctx: &mut PaintCtx<'_>,
scene: &mut dyn PaintScene,
shape_rect: Rect,
radius: f64,
content_color: Color,
) {
let opacity = self.opacity();
if opacity <= 0.0 {
return;
}
scene.fill_rounded_rect(
shape_rect.origin(),
shape_rect.size(),
radius,
with_alpha(content_color, opacity),
);
}
}
fn with_alpha(color: Color, alpha: f32) -> Color {
let c = color.components;
Color::new([c[0], c[1], c[2], alpha])
}
#[cfg(test)]
mod tests {
use super::*;
use frust::authoring::PaintCtx;
use kurbo::{Point, Size};
#[test]
fn no_active_state_has_zero_opacity() {
let layer = StateLayer::new();
assert_eq!(layer.opacity(), 0.0);
assert!(!layer.is_active());
}
#[test]
fn each_state_selects_its_own_opacity() {
let mut layer = StateLayer::new();
layer.set_hovered(true);
assert_eq!(layer.opacity(), HOVER_OPACITY);
let mut layer = StateLayer::new();
layer.set_focused(true);
assert_eq!(layer.opacity(), FOCUS_OPACITY);
let mut layer = StateLayer::new();
layer.set_pressed(true);
assert_eq!(layer.opacity(), PRESSED_OPACITY);
let mut layer = StateLayer::new();
layer.set_dragged(true);
assert_eq!(layer.opacity(), DRAGGED_OPACITY);
}
#[test]
fn combined_states_use_the_maximum_opacity() {
let mut layer = StateLayer::new();
layer.set_hovered(true);
layer.set_pressed(true);
assert_eq!(layer.opacity(), PRESSED_OPACITY);
layer.set_dragged(true);
assert_eq!(layer.opacity(), DRAGGED_OPACITY);
}
#[test]
fn setters_report_whether_the_flag_changed() {
let mut layer = StateLayer::new();
assert!(layer.set_hovered(true), "false -> true is a change");
assert!(!layer.set_hovered(true), "true -> true is not a change");
assert!(layer.set_hovered(false), "true -> false is a change");
}
#[derive(Default)]
struct RRectRecorder {
rrects: Vec<(Point, Size, f64, Color)>,
}
impl PaintScene for RRectRecorder {
fn fill_rect(&mut self, _o: Point, _s: Size, _c: Color) {}
fn draw_text(&mut self, _o: Point, _t: &str) {}
fn fill_rounded_rect(&mut self, o: Point, s: Size, radius: f64, color: Color) {
self.rrects.push((o, s, radius, color));
}
}
#[test]
fn inactive_layer_paints_nothing() {
let layer = StateLayer::new();
let mut rec = RRectRecorder::default();
let mut ctx = PaintCtx::new(Point::ZERO, Size::new(40.0, 40.0));
let shape = Rect::from_origin_size(Point::new(2.0, 2.0), Size::new(36.0, 36.0));
layer.paint(&mut ctx, &mut rec, shape, 8.0, Color::from_rgb8(0, 0, 0));
assert!(rec.rrects.is_empty());
}
#[test]
fn active_layer_paints_content_color_at_state_opacity() {
let mut layer = StateLayer::new();
layer.set_hovered(true);
let mut rec = RRectRecorder::default();
let mut ctx = PaintCtx::new(Point::ZERO, Size::new(40.0, 40.0));
let shape = Rect::from_origin_size(Point::new(2.0, 2.0), Size::new(36.0, 36.0));
let content_color = Color::from_rgb8(0x10, 0x20, 0x30);
layer.paint(&mut ctx, &mut rec, shape, 8.0, content_color);
let (origin, size, radius, color) = rec.rrects.first().copied().expect("paints one rect");
assert_eq!(origin, shape.origin());
assert_eq!(size, shape.size());
assert_eq!(radius, 8.0);
assert_eq!(color, with_alpha(content_color, HOVER_OPACITY));
}
}