use tessera_ui::{
Color, ComputedData, Dp, MeasurementError, Modifier, PxPosition, PxSize,
layout::{LayoutInput, LayoutOutput, LayoutSpec, RenderInput},
tessera, use_context,
};
use crate::{
pipelines::shape::command::{ShadowLayers, ShapeCommand},
shape_def::{ResolvedShape, Shape},
surface::SurfaceDefaults,
theme::MaterialTheme,
};
use super::ModifierExt;
#[derive(Clone, Debug)]
pub struct ShadowArgs {
pub elevation: Dp,
pub shape: Shape,
pub clip: bool,
pub ambient_color: Option<Color>,
pub spot_color: Option<Color>,
}
impl Default for ShadowArgs {
fn default() -> Self {
Self {
elevation: Dp(0.0),
shape: Shape::RECTANGLE,
clip: false,
ambient_color: None,
spot_color: None,
}
}
}
impl ShadowArgs {
pub fn new(elevation: Dp) -> Self {
Self {
elevation,
..Default::default()
}
}
pub fn shape(mut self, shape: Shape) -> Self {
self.shape = shape;
self
}
pub fn clip(mut self, clip: bool) -> Self {
self.clip = clip;
self
}
pub fn ambient_color(mut self, color: Color) -> Self {
self.ambient_color = Some(color);
self
}
pub fn spot_color(mut self, color: Color) -> Self {
self.spot_color = Some(color);
self
}
}
impl From<Dp> for ShadowArgs {
fn from(elevation: Dp) -> Self {
Self::new(elevation)
}
}
pub(super) fn apply_shadow_modifier(base: Modifier, args: ShadowArgs) -> Modifier {
let scheme = use_context::<MaterialTheme>()
.expect("MaterialTheme must be provided")
.get()
.color_scheme;
let mut layers = SurfaceDefaults::synthesize_shadow_layers(args.elevation, &scheme);
if let Some(ambient) = args.ambient_color
&& let Some(ref mut layer) = layers.ambient
{
layer.color = ambient;
}
if let Some(spot) = args.spot_color
&& let Some(ref mut layer) = layers.spot
{
layer.color = spot;
}
let mut modifier = base.push_wrapper(move |child| {
let shape = args.shape;
move || {
modifier_shadow_layers(layers, shape, || {
child();
});
}
});
if args.clip {
modifier = modifier.clip_to_bounds();
}
modifier
}
#[derive(Clone, PartialEq)]
struct ShadowLayout {
shadow: ShadowLayers,
shape: Shape,
}
impl LayoutSpec for ShadowLayout {
fn measure(
&self,
input: &LayoutInput<'_>,
output: &mut LayoutOutput<'_>,
) -> Result<ComputedData, MeasurementError> {
let child_id = input
.children_ids()
.first()
.copied()
.expect("modifier_shadow expects exactly one child");
let child_measurement = input.measure_child_in_parent_constraint(child_id)?;
output.place_child(child_id, PxPosition::ZERO);
Ok(child_measurement)
}
fn record(&self, input: &RenderInput<'_>) {
let mut metadata = input.metadata_mut();
let Some(size) = metadata.computed_data else {
return;
};
metadata.push_draw_command(shape_shadow_command_layers(
self.shadow,
self.shape,
size.into(),
));
}
}
#[tessera]
pub(super) fn modifier_shadow_layers<F>(shadow: ShadowLayers, shape: Shape, child: F)
where
F: FnOnce(),
{
layout(ShadowLayout { shadow, shape });
child();
}
pub(super) fn shape_shadow_command_layers(
shadow: ShadowLayers,
shape: Shape,
size: PxSize,
) -> ShapeCommand {
let color = Color::TRANSPARENT;
match shape.resolve_for_size(size) {
ResolvedShape::Rounded {
corner_radii,
corner_g2,
} => ShapeCommand::Rect {
color,
corner_radii,
corner_g2,
shadow: Some(shadow),
},
ResolvedShape::Ellipse => ShapeCommand::Ellipse {
color,
shadow: Some(shadow),
},
}
}