//! A visual container widget. use std::ops::Div; use kludgine::figures::units::{Lp, Px, UPx}; use kludgine::figures::{ Abs, Angle, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size, Zero, }; use kludgine::shapes::{CornerRadii, PathBuilder, Shape}; use kludgine::Color; use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::styles::components::{CornerRadius, IntrinsicPadding, Opacity, SurfaceColor}; use crate::styles::{Component, ContainerLevel, Dimension, Edges, RequireInvalidation, Styles}; use crate::value::{Dynamic, IntoValue, Value}; use crate::widget::{MakeWidget, RootBehavior, Widget, WidgetInstance, WidgetRef}; use crate::ConstraintLimit; /// A visual container widget, optionally applying padding and a background /// color. /// /// # Background Color Selection /// /// This widget has three different modes for coloring its background: /// /// - [`ContainerBackground::Auto`]: The background color is automatically /// selected by using the [next](ContainerLevel::next) level from the next /// parent container in the hierarchy. /// /// If the previous container is [`ContainerLevel::Highest`] or the previous /// parent container uses a color instead of a level, /// [`ContainerLevel::Lowest`] will be used. /// - [`ContainerBackground::Color`]: The specified color will be drawn. /// - [`ContainerBackground::Level`]: The /// [`SurfaceTheme`](crate::styles::SurfaceTheme) container color associated /// with the given level will be used. #[derive(Debug)] pub struct Container { /// The configured background selection. pub background: Value, /// Padding to surround the contained widget. /// /// If this is None, a uniform surround of [`IntrinsicPadding`] will be /// applied. pub padding: Option>>, /// The shadow to apply behind the container's background. pub shadow: Value, child: WidgetRef, applied_background: Option, } /// A strategy of applying a background to a [`Container`]. #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] pub enum ContainerBackground { /// Automatically select a [`ContainerLevel`] by picking the /// [next](ContainerLevel::next) level after the previous parent /// [`Container`]. /// /// If no parent container is found or a parent container is found with a /// [color](Self::Color) background, [`ContainerLevel::Lowest`] will be /// used. See [`Self::Level`] for more information. #[default] Auto, /// Fills the background with the specified color. Color(Color), /// Applies the [`SurfaceTheme`][st] color /// corresponding with the given level. /// /// | [`ContainerLevel`] | [`SurfaceTheme`][st] property | /// |--------------------|-------------------------------| /// | [`Lowest`][ll] | [`lowest_container`][llc] | /// | [`Low`][lo] | [`low_container`][loc] | /// | [`Low`][mi] | [`container`][mic] | /// | [`High`][hi] | [`high_container`][hic] | /// | [`Highest`][hh] | [`highest_container`][hhc] | /// /// [st]: crate::styles::SurfaceTheme /// [ll]: ContainerLevel::Lowest /// [llc]: crate::styles::SurfaceTheme::lowest_container /// [lo]: ContainerLevel::Low /// [loc]: crate::styles::SurfaceTheme::low_container /// [mi]: ContainerLevel::Mid /// [mic]: crate::styles::SurfaceTheme::container /// [hi]: ContainerLevel::High /// [hic]: crate::styles::SurfaceTheme::high_container /// [hh]: ContainerLevel::Highest /// [hhc]: crate::styles::SurfaceTheme::highest_container Level(ContainerLevel), } impl From for ContainerBackground { fn from(value: ContainerLevel) -> Self { Self::Level(value) } } impl From for ContainerBackground { fn from(value: Color) -> Self { Self::Color(value) } } impl Container { /// Returns a new container wrapping `child` with default padding and a /// background color automatically selected by the theme. /// /// See [`ContainerBackground::Auto`] for more information about automatic /// coloring. #[must_use] pub fn new(child: impl MakeWidget) -> Self { Self { padding: None, applied_background: None, background: Value::default(), shadow: Value::default(), child: WidgetRef::new(child), } } /// Pads the contained widget with `padding`, returning the updated /// container. #[must_use] pub fn pad_by(mut self, padding: impl IntoValue>) -> Self { self.padding = Some(padding.into_value()); self } /// Sets this container to render no background color, and then returns the /// updated container. #[must_use] pub fn transparent(mut self) -> Self { self.background = Value::Constant(ContainerBackground::Color(Color::CLEAR_WHITE)); self } /// Sets this container to use the specific container level, and then /// returns the updated container. #[must_use] pub fn contain_level(mut self, level: impl IntoValue) -> Container { self.background = level .into_value() .map_each(|level| ContainerBackground::from(*level)); self } /// Sets this container to render the specified `color` background, and then /// returns the updated container. #[must_use] pub fn background_color(mut self, color: impl IntoValue) -> Self { self.background = color .into_value() .map_each(|color| ContainerBackground::from(*color)); self } /// Renders `shadow` behind the container's background. #[must_use] pub fn shadow(mut self, shadow: impl IntoValue) -> Self { self.shadow = shadow.into_value(); self } fn padding(&self, context: &GraphicsContext<'_, '_, '_, '_, '_>) -> Edges { match &self.padding { Some(padding) => padding.get(), None => Edges::from(context.get(&IntrinsicPadding)), } .map(|dim| dim.into_px(context.gfx.scale())) } fn effective_background_color(&mut self, context: &WidgetContext<'_, '_>) -> kludgine::Color { let background = match self.background.get() { ContainerBackground::Color(color) => EffectiveBackground::Color(color), ContainerBackground::Level(level) => EffectiveBackground::Level(level), ContainerBackground::Auto => { EffectiveBackground::Level(match context.get(&CurrentContainerBackground) { EffectiveBackground::Color(_) => ContainerLevel::default(), EffectiveBackground::Level(level) => level.next().unwrap_or_default(), }) } }; if self.applied_background != Some(background) { context.attach_styles(Styles::new().with(&CurrentContainerBackground, background)); self.applied_background = Some(background); } match background { EffectiveBackground::Color(color) => color, EffectiveBackground::Level(level) => match level { ContainerLevel::Lowest => context.theme().surface.lowest_container, ContainerLevel::Low => context.theme().surface.low_container, ContainerLevel::Mid => context.theme().surface.container, ContainerLevel::High => context.theme().surface.high_container, ContainerLevel::Highest => context.theme().surface.highest_container, }, } } } impl Widget for Container { fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt.debug_struct("Container") .field("background", &self.background) .field("padding", &self.padding) .field("shadow", &self.shadow) .field("child", &self.child) .finish() } fn full_control_redraw(&self) -> bool { true } #[allow(clippy::too_many_lines)] fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { let opacity = context.get(&Opacity); let background = self.effective_background_color(context); let background = background.with_alpha_f32(background.alpha_f32() * *opacity); if background.alpha() > 0 { let shadow = self .shadow .get_tracking_invalidate(context) .into_px(context.gfx.scale()); let child_shadow_offset = shadow.offset.min(Point::ZERO).abs(); let child_size = context.gfx.region().size - shadow.spread * 2 - shadow.offset.abs(); let child_area = Rect::new(child_shadow_offset + shadow.spread, child_size); let corner_radii = context.get(&CornerRadius).into_px(context.gfx.scale()); // check if the shadow would be obscured before we try to draw it. if child_area.origin != Point::ZERO || child_size != context.gfx.region().size { render_shadow(&child_area, corner_radii, &shadow, background, context); } context.gfx.draw_shape(&Shape::filled_round_rect( child_area, corner_radii, background, )); } let child = self.child.mounted(context); context.for_other(&child).redraw(); } fn layout( &mut self, available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { let child = self.child.mounted(context); let corner_radii = context.get(&CornerRadius).into_upx(context.gfx.scale()); let max_space = available_space.map(ConstraintLimit::max); let min_dimension = max_space.width.min(max_space.height); let max_corner_radii = min_dimension / 2; let corner_radii = corner_radii.map(|r| r.min(max_corner_radii)); let mut padding = self.padding(context).into_upx(context.gfx.scale()); padding.left = padding .left .max(corner_radii.top_left / std::f32::consts::PI) .max(corner_radii.bottom_left / std::f32::consts::PI); padding.right = padding .right .max(corner_radii.top_right / std::f32::consts::PI) .max(corner_radii.bottom_right / std::f32::consts::PI); padding.top = padding .top .max(corner_radii.top_right / std::f32::consts::PI) .max(corner_radii.top_left / std::f32::consts::PI); padding.bottom = padding .bottom .max(corner_radii.bottom_right / std::f32::consts::PI) .max(corner_radii.bottom_left / std::f32::consts::PI); let padding_amount = padding.size(); let shadow = self .shadow .get_tracking_invalidate(context) .into_px(context.gfx.scale()); let shadow_spread = shadow.spread.into_unsigned(); let child_shadow_offset_amount = shadow.offset.abs().into_unsigned(); let child_size = context.for_other(&child).layout( available_space - padding_amount - child_shadow_offset_amount - shadow_spread * 2, ); let child_shadow_offset = shadow.offset.min(Point::ZERO).abs().into_unsigned(); context.set_child_layout( &child, Rect::new( Point::new(padding.left, padding.top) + child_shadow_offset + shadow_spread, child_size, ) .into_signed(), ); child_size + padding_amount + child_shadow_offset_amount + shadow_spread * 2 } fn root_behavior( &mut self, context: &mut EventContext<'_, '_>, ) -> Option<(RootBehavior, WidgetInstance)> { let mut padding = self .padding .as_ref() .map(|padding| padding.get().into_px(context.kludgine.scale())) .unwrap_or_default(); let shadow = self .shadow .get_tracking_invalidate(context) .into_px(context.kludgine.scale()); if shadow.offset.x >= 0 { padding.right += shadow.offset.x; } else { padding.left += shadow.offset.x.abs(); } if shadow.spread > 0 { padding += Edges::from(shadow.spread); } let behavior = if padding.is_zero() { RootBehavior::PassThrough } else { RootBehavior::Pad(padding.map(Dimension::from)) }; Some((behavior, self.child.widget().clone())) } } #[allow(clippy::too_many_lines)] fn render_shadow( child_area: &Rect, mut corner_radii: CornerRadii, shadow: &ContainerShadow, background: Color, context: &mut GraphicsContext<'_, '_, '_, '_, '_>, ) { let shadow_color = shadow.color.unwrap_or_else(|| context.theme_pair().shadow); let shadow_color = shadow_color.with_alpha_f32(shadow_color.alpha_f32() * background.alpha_f32()); let min_dimension = child_area.size.width.min(child_area.size.height); let max_corner_radii = min_dimension / 2; corner_radii = corner_radii.map(|r| r.min(max_corner_radii)); let max_corner = corner_radii .top_left .max(corner_radii.top_right) .max(corner_radii.bottom_left) .max(corner_radii.bottom_right); let max_blur = min_dimension / 2 - max_corner; let blur = shadow.blur_radius.min(max_blur).max(Px::ZERO); let gradient_size = shadow.spread + blur; if gradient_size > 0 { let mut solid_area = Rect::new(Point::squared(gradient_size), child_area.size - blur * 2); solid_area.origin += shadow.offset.max(Point::ZERO); let transparent = shadow_color.with_alpha(0); let solid_left = solid_area.origin.x; let solid_right = solid_area.origin.x + solid_area.size.width; let solid_top = solid_area.origin.y; let solid_bottom = solid_area.origin.y + solid_area.size.height; let solid_left_at_top = solid_area.origin.x + corner_radii.top_left; let solid_left_at_bottom = solid_area.origin.x + corner_radii.bottom_left; let solid_right_at_top = solid_area.origin.x + solid_area.size.width - corner_radii.top_right; let solid_right_at_bottom = solid_area.origin.x + solid_area.size.width - corner_radii.bottom_right; let solid_top_at_left = solid_area.origin.y + corner_radii.top_left; let solid_bottom_at_left = solid_area.origin.y + solid_area.size.height - corner_radii.bottom_left; let solid_top_at_right = solid_area.origin.y + corner_radii.top_right; let solid_bottom_at_right = solid_area.origin.y + solid_area.size.height - corner_radii.bottom_right; // Top if solid_left_at_top < solid_right_at_top { context.gfx.draw_shape( &PathBuilder::new(( Point::new(solid_left_at_top, solid_top_at_left), shadow_color, )) .line_to((Point::new(solid_left_at_top, solid_top), shadow_color)) .line_to(( Point::new(solid_left_at_top, solid_top - gradient_size), transparent, )) .line_to(( Point::new(solid_right_at_top, solid_top - gradient_size), transparent, )) .line_to((Point::new(solid_right_at_top, solid_top), shadow_color)) .line_to(( Point::new(solid_right_at_top, solid_top_at_right), shadow_color, )) .close() .filled(), ); } // Right context.gfx.draw_shape( &PathBuilder::new((Point::new(solid_right, solid_top_at_right), shadow_color)) .line_to(( Point::new(solid_right + gradient_size, solid_top_at_right), transparent, )) .line_to(( Point::new(solid_right + gradient_size, solid_bottom_at_right), transparent, )) .line_to((Point::new(solid_right, solid_bottom_at_right), shadow_color)) .close() .filled(), ); context.gfx.draw_shape( &PathBuilder::new(( Point::new(solid_right_at_top, solid_top_at_right), shadow_color, )) .line_to((Point::new(solid_right, solid_top_at_right), shadow_color)) .line_to((Point::new(solid_right, solid_bottom_at_right), shadow_color)) .line_to(( Point::new(solid_right_at_bottom, solid_bottom_at_right), shadow_color, )) .close() .filled(), ); // Bottom context.gfx.draw_shape( &PathBuilder::new(( Point::new(solid_left_at_bottom, solid_bottom_at_left), shadow_color, )) .line_to((Point::new(solid_left_at_bottom, solid_bottom), shadow_color)) .line_to(( Point::new(solid_left_at_bottom, solid_bottom + gradient_size), transparent, )) .line_to(( Point::new(solid_right_at_bottom, solid_bottom + gradient_size), transparent, )) .line_to(( Point::new(solid_right_at_bottom, solid_bottom), shadow_color, )) .line_to(( Point::new(solid_right_at_bottom, solid_bottom_at_right), shadow_color, )) .close() .filled(), ); // Left context.gfx.draw_shape( &PathBuilder::new(( Point::new(solid_left - gradient_size, solid_top_at_left), transparent, )) .line_to((Point::new(solid_left, solid_top_at_left), shadow_color)) .line_to((Point::new(solid_left, solid_bottom_at_left), shadow_color)) .line_to(( Point::new(solid_left - gradient_size, solid_bottom_at_left), transparent, )) .close() .filled(), ); context.gfx.draw_shape( &PathBuilder::new((Point::new(solid_left, solid_top_at_left), shadow_color)) .line_to(( Point::new(solid_left_at_top, solid_top_at_left), shadow_color, )) .line_to(( Point::new(solid_left_at_bottom, solid_bottom_at_left), shadow_color, )) .line_to((Point::new(solid_left, solid_bottom_at_left), shadow_color)) .close() .filled(), ); // Top Right shadow_arc( Point::new(solid_right_at_top, solid_top_at_right), corner_radii.top_right, gradient_size, shadow_color, transparent, Angle::degrees(270), context, ); // Bottom Right shadow_arc( Point::new(solid_right_at_bottom, solid_bottom_at_right), corner_radii.bottom_right, gradient_size, shadow_color, transparent, Angle::degrees(0), context, ); // Bottom Left shadow_arc( Point::new(solid_left_at_bottom, solid_bottom_at_left), corner_radii.bottom_left, gradient_size, shadow_color, transparent, Angle::degrees(90), context, ); // Top Left shadow_arc( Point::new(solid_left_at_top, solid_top_at_left), corner_radii.top_left, gradient_size, shadow_color, transparent, Angle::degrees(180), context, ); // Center context.gfx.draw_shape( &PathBuilder::new(( Point::new(solid_left_at_top, solid_top_at_left), shadow_color, )) .line_to(( Point::new(solid_right_at_top, solid_top_at_right), shadow_color, )) .line_to(( Point::new(solid_right_at_bottom, solid_bottom_at_right), shadow_color, )) .line_to(( Point::new(solid_left_at_bottom, solid_bottom_at_left), shadow_color, )) .close() .filled(), ); } else { context.gfx.draw_shape(&Shape::filled_round_rect( Rect::new(shadow.offset.max(Point::ZERO), child_area.size), corner_radii, shadow_color, )); } } /// Draws a gradiented arc, quantized into sections to compensate for /// `lyon_geom` having directional fill tesselator. If a single pair of arcs /// joined by line segements is tesselated, the gradient "leans" in the /// orientation of `FillOptions::sweep_orientation` and doesn't look properly /// circular. fn shadow_arc( origin: Point, radius: Px, gradient: Px, solid_color: Color, transparent_color: Color, start_angle: Angle, context: &mut GraphicsContext<'_, '_, '_, '_, '_>, ) { let full_radius = radius + gradient; let mut current_outer_arc = rotate_point( origin, Point::new(origin.x + full_radius, origin.y), start_angle, ); let mut current_inner_arc = rotate_point(origin, Point::new(origin.x + radius, origin.y), start_angle); let mut angle = Angle::degrees(0); while angle < Angle::degrees(90) { angle += Angle::degrees(5); let outer_arc = rotate_point( origin, Point::new(origin.x + full_radius, origin.y), start_angle + angle, ); if outer_arc == current_outer_arc { continue; } let inner_arc = rotate_point( origin, Point::new(origin.x + radius, origin.y), start_angle + angle, ); let mut path = PathBuilder::new((current_inner_arc, solid_color)); path = path .line_to((current_outer_arc, transparent_color)) .line_to((outer_arc, transparent_color)) .line_to((inner_arc, solid_color)); if inner_arc != current_inner_arc { path = path.line_to((current_inner_arc, solid_color)); } context.gfx.draw_shape(&path.close().filled()); if inner_arc != current_inner_arc { let mut path = PathBuilder::new((origin, solid_color)); path = path .line_to((current_inner_arc, solid_color)) .line_to((inner_arc, solid_color)) .line_to((origin, solid_color)); context.gfx.draw_shape(&path.close().filled()); } current_outer_arc = outer_arc; current_inner_arc = inner_arc; } } fn rotate_point(origin: Point, point: Point, angle: Angle) -> Point { let cos = angle.into_raidans_f().cos(); let sin = angle.into_raidans_f().sin(); let d = point - origin; origin + Point::new(d.x * cos - d.y * sin, d.y * cos + d.x * sin) } /// The selected background configuration of a [`Container`]. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum EffectiveBackground { /// The container rendered using the specified level's theme color. Level(ContainerLevel), /// The container rendered using the specified color. Color(Color), } impl TryFrom for EffectiveBackground { type Error = Component; fn try_from(value: Component) -> Result { match value { Component::Color(color) => Ok(EffectiveBackground::Color(color)), Component::ContainerLevel(level) => Ok(EffectiveBackground::Level(level)), other => Err(other), } } } impl From for Component { fn from(value: EffectiveBackground) -> Self { match value { EffectiveBackground::Level(level) => Self::ContainerLevel(level), EffectiveBackground::Color(color) => Self::Color(color), } } } impl RequireInvalidation for EffectiveBackground { fn requires_invalidation(&self) -> bool { false } } define_components! { Container { /// The container background behind the current widget. CurrentContainerBackground(EffectiveBackground, "background", |context| EffectiveBackground::Color(context.get(&SurfaceColor))) } } /// A shadow for a [`Container`]. #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] pub struct ContainerShadow { /// The color of the shadow to use for the solid area. /// /// This color will be faded to transparent if there is any blur on the /// shadow. pub color: Option, /// The offset of the shadow. pub offset: Point, /// The radius of the blur. pub blur_radius: Unit, /// An additional amount of space the blur should be expanded across in all /// directions. This increases the physical space of the shadow. pub spread: Unit, } impl ContainerShadow { /// Returns a new shadow that is offset underneath its contents. pub fn new(offset: Point) -> Self where Unit: Default, { Self { color: None, offset, blur_radius: Unit::default(), spread: Unit::default(), } } /// Returns a drop shadow placed `distance` below with a combined /// blur/spread radius of `blur`. pub fn drop(distance: Unit, blur: Unit) -> Self where Unit: Zero + Div + Default + Copy, { let half_blur = blur / 2; Self::new(Point::new(Unit::ZERO, distance)) .blur_radius(half_blur) .spread(half_blur) } /// Sets the shadow color and returns self. #[must_use] pub fn color(mut self, color: Color) -> Self { self.color = Some(color); self } /// Sets the blur radius and returns self. #[must_use] pub fn blur_radius(mut self, radius: Unit) -> Self { self.blur_radius = radius; self } /// Sets the spread radius and returns self. #[must_use] pub fn spread(mut self, spread: Unit) -> Self { self.spread = spread; self } } impl ScreenScale for ContainerShadow where Unit: ScreenScale, { type Lp = ContainerShadow; type Px = ContainerShadow; type UPx = ContainerShadow; fn into_px(self, scale: kludgine::figures::Fraction) -> Self::Px { ContainerShadow { color: self.color, offset: self.offset.into_px(scale), blur_radius: self.blur_radius.into_px(scale), spread: self.spread.into_px(scale), } } fn from_px(px: Self::Px, scale: kludgine::figures::Fraction) -> Self { Self { color: px.color, offset: Point::from_px(px.offset, scale), blur_radius: Unit::from_px(px.blur_radius, scale), spread: Unit::from_px(px.spread, scale), } } fn into_upx(self, scale: kludgine::figures::Fraction) -> Self::UPx { ContainerShadow { color: self.color, offset: self.offset.into_upx(scale), blur_radius: self.blur_radius.into_upx(scale), spread: self.spread.into_upx(scale), } } fn from_upx(px: Self::UPx, scale: kludgine::figures::Fraction) -> Self { Self { color: px.color, offset: Point::from_upx(px.offset, scale), blur_radius: Unit::from_upx(px.blur_radius, scale), spread: Unit::from_upx(px.spread, scale), } } fn into_lp(self, scale: kludgine::figures::Fraction) -> Self::Lp { ContainerShadow { color: self.color, offset: self.offset.into_lp(scale), blur_radius: self.blur_radius.into_lp(scale), spread: self.spread.into_lp(scale), } } fn from_lp(lp: Self::Lp, scale: kludgine::figures::Fraction) -> Self { Self { color: lp.color, offset: Point::from_lp(lp.offset, scale), blur_radius: Unit::from_lp(lp.blur_radius, scale), spread: Unit::from_lp(lp.spread, scale), } } } impl From for ContainerShadow { fn from(value: Px) -> Self { Self::from(Dimension::from(value)) } } impl From for ContainerShadow { fn from(value: Lp) -> Self { Self::from(Dimension::from(value)) } } impl From for ContainerShadow { fn from(spread: Dimension) -> Self { Self::default().spread(spread) } } impl From> for ContainerShadow { fn from(offset: Point) -> Self { Self::from(offset.map(Dimension::from)) } } impl From> for ContainerShadow { fn from(offset: Point) -> Self { Self::from(offset.map(Dimension::from)) } } impl From> for ContainerShadow { fn from(size: Point) -> Self { Self::new(size) } } impl IntoValue for Dimension { fn into_value(self) -> Value { ContainerShadow::from(self).into_value() } } impl IntoValue for Point { fn into_value(self) -> Value { ContainerShadow::from(self).into_value() } } impl IntoValue for Point { fn into_value(self) -> Value { ContainerShadow::from(self).into_value() } } impl IntoValue for Point { fn into_value(self) -> Value { ContainerShadow::from(self).into_value() } } impl IntoValue for ContainerShadow { fn into_value(self) -> Value { ContainerShadow { color: self.color, offset: self.offset.map(Dimension::from), blur_radius: Dimension::from(self.blur_radius), spread: Dimension::from(self.spread), } .into_value() } } impl From> for ContainerShadow { fn from(value: ContainerShadow) -> Self { ContainerShadow { color: value.color, offset: value.offset.map(Dimension::from), blur_radius: Dimension::from(value.blur_radius), spread: Dimension::from(value.spread), } } } impl From> for ContainerShadow { fn from(value: ContainerShadow) -> Self { ContainerShadow { color: value.color, offset: value.offset.map(Dimension::from), blur_radius: Dimension::from(value.blur_radius), spread: Dimension::from(value.spread), } } } impl IntoValue for ContainerShadow { fn into_value(self) -> Value { ContainerShadow::::from(self).into_value() } } impl IntoValue for Dynamic> { fn into_value(self) -> Value { Value::Dynamic(self.map_each_cloned(ContainerShadow::::from)) } } impl IntoValue for Dynamic> { fn into_value(self) -> Value { Value::Dynamic(self.map_each_cloned(ContainerShadow::::from)) } }