use frust_core::{
AnyView, BoxConstraints, BuildCtx, ChangeFlags, ChildPod, CornerRadii, DashPattern, EventCtx,
EventResult, InputEvent, LayoutCtx, PaintCtx, PaintScene, SemanticsCtx, View, Widget, any,
};
use kurbo::{Point, RoundedRect, Shape, Size};
use peniko::{Brush, Color};
const BORDER_TOLERANCE: f64 = 0.1;
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum BorderStyle {
#[default]
Solid,
Dashed(DashPattern),
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Sizing {
Hug,
Expand,
Fixed(f64, f64),
FixedCentered(f64, f64),
}
pub struct ContainerView<State: 'static> {
child: Option<AnyView<State>>,
fill: Option<Color>,
radius: CornerRadii,
border: Option<(Color, f64)>,
border_style: BorderStyle,
glow: Option<(Color, f64, f64)>,
sizing: Sizing,
}
pub fn container<State: 'static, V: View<State>>(child: V) -> ContainerView<State> {
ContainerView {
child: Some(any(child)),
fill: None,
radius: CornerRadii::default(),
border: None,
border_style: BorderStyle::default(),
glow: None,
sizing: Sizing::Hug,
}
}
pub fn colored_box<State: 'static>() -> ContainerView<State> {
ContainerView {
child: None,
fill: None,
radius: CornerRadii::default(),
border: None,
border_style: BorderStyle::default(),
glow: None,
sizing: Sizing::Hug,
}
}
impl<State: 'static> ContainerView<State> {
pub fn child<V: View<State>>(mut self, child: V) -> Self {
self.child = Some(any(child));
self
}
pub fn fill(mut self, color: Color) -> Self {
self.fill = Some(color);
self
}
pub fn radius(mut self, radius: impl Into<CornerRadii>) -> Self {
self.radius = radius.into();
self
}
pub fn border(mut self, color: Color, width: f64) -> Self {
self.border = Some((color, width));
self
}
pub fn border_style(mut self, style: BorderStyle) -> Self {
self.border_style = style;
self
}
pub fn glow(mut self, color: Color, std_dev: f64, spread: f64) -> Self {
self.glow = Some((color, std_dev, spread));
self
}
pub fn expand(mut self) -> Self {
self.sizing = Sizing::Expand;
self
}
pub fn size(mut self, width: f64, height: f64) -> Self {
self.sizing = Sizing::Fixed(width, height);
self
}
pub fn size_centered(mut self, width: f64, height: f64) -> Self {
self.sizing = Sizing::FixedCentered(width, height);
self
}
}
pub struct ContainerWidget {
child: Option<ChildPod>,
fill: Option<Color>,
radius: CornerRadii,
border: Option<(Color, f64)>,
border_style: BorderStyle,
glow: Option<(Color, f64, f64)>,
sizing: Sizing,
}
impl<State: 'static> View<State> for ContainerView<State> {
type Element = ContainerWidget;
fn build(&self, ctx: &mut BuildCtx<'_>) -> ContainerWidget {
ContainerWidget {
child: self
.child
.as_ref()
.map(|view| crate::authoring::build_child(view, ctx)),
fill: self.fill,
radius: self.radius,
border: self.border,
border_style: self.border_style,
glow: self.glow,
sizing: self.sizing,
}
}
fn rebuild(
&self,
prev: &Self,
element: &mut ContainerWidget,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
let mut flags = ChangeFlags::NONE;
if prev.fill != self.fill
|| prev.radius != self.radius
|| prev.border != self.border
|| prev.border_style != self.border_style
|| prev.glow != self.glow
{
element.fill = self.fill;
element.radius = self.radius;
element.border = self.border;
element.border_style = self.border_style;
element.glow = self.glow;
flags |= ChangeFlags::PAINT;
}
if prev.sizing != self.sizing {
element.sizing = self.sizing;
flags |= ChangeFlags::LAYOUT;
}
match (&prev.child, &self.child, &mut element.child) {
(Some(prev_view), Some(next_view), Some(pod)) => {
flags |= crate::authoring::rebuild_child(prev_view, next_view, pod, ctx);
}
(None, Some(next_view), _) => {
element.child = Some(crate::authoring::build_child(next_view, ctx));
flags |= ChangeFlags::LAYOUT | ChangeFlags::PAINT;
}
(Some(prev_view), None, Some(pod)) => {
crate::authoring::teardown_child(prev_view, pod, ctx);
element.child = None;
flags |= ChangeFlags::LAYOUT | ChangeFlags::PAINT;
}
_ => {}
}
flags
}
fn teardown(&self, element: &mut ContainerWidget, ctx: &mut BuildCtx<'_>) {
if let (Some(view), Some(pod)) = (&self.child, &mut element.child) {
crate::authoring::teardown_child(view, pod, ctx);
}
}
}
fn inset_radii(radii: CornerRadii, inset: f64) -> CornerRadii {
CornerRadii::new(
(radii.top_left - inset).max(0.0),
(radii.top_right - inset).max(0.0),
(radii.bottom_right - inset).max(0.0),
(radii.bottom_left - inset).max(0.0),
)
}
fn collapse_or_fill(max: f64, min: f64) -> f64 {
if max.is_finite() { max } else { min }
}
impl Widget for ContainerWidget {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
match &mut self.child {
Some(pod) => {
if let Sizing::FixedCentered(w, h) = self.sizing {
let own_size = bc.constrain(Size::new(w, h));
let child_size = pod.layout_child(ctx, &BoxConstraints::loose(own_size));
let x = ((own_size.width - child_size.width) / 2.0).max(0.0);
let y = ((own_size.height - child_size.height) / 2.0).max(0.0);
pod.set_origin(Point::new(x, y));
own_size
} else {
let child_size = pod.layout_child(ctx, bc);
pod.set_origin(Point::ZERO);
bc.constrain(child_size)
}
}
None => {
let intrinsic = match self.sizing {
Sizing::Hug => bc.min(),
Sizing::Expand => Size::new(
collapse_or_fill(bc.max().width, bc.min().width),
collapse_or_fill(bc.max().height, bc.min().height),
),
Sizing::Fixed(w, h) | Sizing::FixedCentered(w, h) => Size::new(w, h),
};
bc.constrain(intrinsic)
}
}
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
let origin = ctx.origin();
let size = ctx.size();
if let Some((color, std_dev, spread)) = self.glow {
let shadow_origin = Point::new(origin.x - spread, origin.y - spread);
let shadow_size = Size::new(
(size.width + 2.0 * spread).max(0.0),
(size.height + 2.0 * spread).max(0.0),
);
let clamped_fill_radius = self.radius.largest().min(size.width.min(size.height) / 2.0);
let shadow_radius = clamped_fill_radius + spread.max(0.0);
scene.draw_shadow(shadow_origin, shadow_size, shadow_radius, std_dev, color);
}
if let Some(fill) = self.fill {
scene.fill_rounded_rect_radii(origin, size, self.radius, fill);
}
if let Some((color, width)) = self.border {
let half = width / 2.0;
let radii = inset_radii(self.radius, half);
let rr = RoundedRect::new(
half,
half,
size.width - half,
size.height - half,
(
radii.top_left,
radii.top_right,
radii.bottom_right,
radii.bottom_left,
),
);
let path = rr.to_path(BORDER_TOLERANCE);
match self.border_style {
BorderStyle::Solid => scene.stroke_path(origin, &path, width, &Brush::Solid(color)),
BorderStyle::Dashed(dash) => {
scene.stroke_path_dashed(origin, &path, width, dash, &Brush::Solid(color))
}
}
}
if let Some(pod) = &mut self.child {
pod.paint_child(ctx, scene);
}
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
match &mut self.child {
Some(pod) => crate::authoring::route_event_single(pod, ctx, event),
None => EventResult::Ignored,
}
}
fn semantics(&self, ctx: &mut SemanticsCtx) {
if let Some(pod) = &self.child {
pod.semantics_child(ctx);
}
}
crate::authoring::visit_children!(child);
}