use crate::{ModifiedView, Rect, Renderer, View, ViewModifier};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Shadow {
pub offset_x: f32,
pub offset_y: f32,
pub blur_radius: f32,
pub spread: f32,
pub color: [f32; 4],
pub inset: bool,
}
impl Shadow {
pub fn new(offset_x: f32, offset_y: f32, blur: f32, color: [f32; 4]) -> Self {
Self {
offset_x,
offset_y,
blur_radius: blur,
spread: 0.0,
color,
inset: false,
}
}
pub fn inset(offset_x: f32, offset_y: f32, blur: f32, color: [f32; 4]) -> Self {
Self {
offset_x,
offset_y,
blur_radius: blur,
spread: 0.0,
color,
inset: true,
}
}
pub fn drop(offset_x: f32, offset_y: f32, blur: f32) -> Self {
Self {
offset_x,
offset_y,
blur_radius: blur,
spread: 0.0,
color: [0.0, 0.0, 0.0, 0.3],
inset: false,
}
}
pub fn spread(mut self, spread: f32) -> Self {
self.spread = spread;
self
}
pub fn color(mut self, color: [f32; 4]) -> Self {
self.color = color;
self
}
pub fn set_inset(mut self, inset: bool) -> Self {
self.inset = inset;
self
}
pub fn modifier(self) -> ShadowModifier {
ShadowModifier { layers: vec![self] }
}
}
impl Default for Shadow {
fn default() -> Self {
Self {
offset_x: 0.0,
offset_y: 2.0,
blur_radius: 8.0,
spread: 0.0,
color: [0.0, 0.0, 0.0, 0.25],
inset: false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShadowModifier {
layers: Vec<Shadow>,
}
impl ShadowModifier {
pub fn new(layers: Vec<Shadow>) -> Self {
Self {
layers: layers.into_iter().take(4).collect(),
}
}
pub fn single(shadow: Shadow) -> Self {
Self {
layers: vec![shadow],
}
}
pub fn layered(shadows: impl IntoIterator<Item = Shadow>) -> Self {
Self::new(shadows.into_iter().collect())
}
pub fn and(mut self, shadow: Shadow) -> Self {
if self.layers.len() < 4 {
self.layers.push(shadow);
}
self
}
pub fn layers(&self) -> &[Shadow] {
&self.layers
}
}
impl ViewModifier for ShadowModifier {
fn modify<V: View>(self, content: V) -> impl View {
ModifiedView::new(content, self)
}
fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
if self.layers.is_empty() {
view.render(renderer, rect);
return;
}
for layer in &self.layers {
let effective_blur = (layer.blur_radius + layer.spread).max(0.0);
let offset = if layer.inset {
[-layer.offset_x, -layer.offset_y]
} else {
[layer.offset_x, layer.offset_y]
};
renderer.push_shadow(effective_blur, layer.color, offset);
}
view.render(renderer, rect);
for _ in &self.layers {
renderer.pop_shadow();
}
}
}
#[macro_export]
macro_rules! shadow {
($x:expr, $y:expr, $blur:expr, $color:expr) => {
$crate::shadow::Shadow::new($x, $y, $blur, $color).modifier()
};
(inset $x:expr, $y:expr, $blur:expr, $color:expr) => {
$crate::shadow::Shadow::inset($x, $y, $blur, $color).modifier()
};
($($shadow:expr),+ $(,)?) => {
$crate::shadow::ShadowModifier::layered(vec![$($shadow),+])
};
}
pub trait ViewShadowExt: View + Sized {
fn shadow(
self,
offset_x: f32,
offset_y: f32,
blur: f32,
color: [f32; 4],
) -> ModifiedView<Self, ShadowModifier> {
self.modifier(Shadow::new(offset_x, offset_y, blur, color).modifier())
}
fn inset_shadow(
self,
offset_x: f32,
offset_y: f32,
blur: f32,
color: [f32; 4],
) -> ModifiedView<Self, ShadowModifier> {
self.modifier(Shadow::inset(offset_x, offset_y, blur, color).modifier())
}
fn shadows(self, layers: Vec<Shadow>) -> ModifiedView<Self, ShadowModifier> {
self.modifier(ShadowModifier::new(layers))
}
}
impl<V: View> ViewShadowExt for V {}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::MockRenderer;
use crate::{Color, Never, Rect, Renderer, View};
struct TestView;
impl View for TestView {
type Body = Never;
fn body(self) -> Self::Body {
unreachable!()
}
fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
renderer.fill_rect(rect, [1.0, 1.0, 1.0, 1.0]);
}
}
#[test]
fn shadow_renders_without_panic() {
let mut renderer = MockRenderer::new();
let rect = Rect::new(10.0, 10.0, 100.0, 50.0);
let shadow = Shadow::drop(0.0, 4.0, 8.0);
let view = TestView.shadow(0.0, 4.0, 8.0, [0.0, 0.0, 0.0, 0.3]);
view.render(&mut renderer, rect);
renderer.assert_draw_call_count(1);
}
#[test]
fn inset_shadow_renders_without_panic() {
let mut renderer = MockRenderer::new();
let rect = Rect::new(10.0, 10.0, 100.0, 50.0);
let view = TestView.inset_shadow(0.0, 2.0, 4.0, [0.0, 0.0, 0.0, 0.5]);
view.render(&mut renderer, rect);
renderer.assert_draw_call_count(1);
}
#[test]
fn layered_shadows_render_without_panic() {
let mut renderer = MockRenderer::new();
let rect = Rect::new(10.0, 10.0, 100.0, 50.0);
let layers = vec![Shadow::drop(0.0, 2.0, 4.0), Shadow::drop(0.0, 8.0, 16.0)];
let view = TestView.shadows(layers);
view.render(&mut renderer, rect);
renderer.assert_draw_call_count(1);
}
#[test]
fn shadow_with_spread() {
let mut renderer = MockRenderer::new();
let rect = Rect::new(0.0, 0.0, 50.0, 50.0);
let shadow = Shadow::new(0.0, 4.0, 8.0, [0.0, 0.0, 0.0, 0.25]).spread(2.0);
let view = TestView.modifier(shadow.modifier());
view.render(&mut renderer, rect);
renderer.assert_draw_call_count(1);
}
#[test]
fn shadow_macro_works() {
let mut renderer = MockRenderer::new();
let rect = Rect::new(0.0, 0.0, 50.0, 50.0);
let view = TestView.shadow(2.0, 4.0, 8.0, [0.0, 0.0, 0.0, 0.3]);
view.render(&mut renderer, rect);
renderer.assert_draw_call_count(1);
}
}