//! The Container primitive: a styled box that lays out children via taffy. use guiduck_scene::Fragment; use guiduck_scene::geom::{Rect, Size, Stroke}; use guiduck_scene::paint::Brush; use super::Widget; use crate::dirty::Dirt; /// A box with an optional background, rounded corners, and border. All /// layout behavior (flex/grid, padding, gap, …) comes from the taffy style /// the container was inserted with. #[derive(Default)] pub struct Container { background: Option, corner_radius: f64, border: Option<(Brush, f64)>, dirt: Dirt, } impl Container { pub fn new() -> Self { Self::default() } pub fn background(mut self, brush: impl Into) -> Self { self.set_background(brush); self } pub fn corner_radius(mut self, radius: f64) -> Self { self.set_corner_radius(radius); self } pub fn border(mut self, brush: impl Into, width: f64) -> Self { self.border = Some((brush.into(), width)); self } /// The background currently in effect (the builder method of the same /// name sets it at construction). pub fn current_background(&self) -> Option<&Brush> { self.background.as_ref() } /// Give the box a background. "No background" is the state a fresh /// container is already in, so there is nothing here to unset: the /// property a `.gdc` writes is a brush, and this is the setter that takes /// one. pub fn set_background(&mut self, brush: impl Into) { let brush = Some(brush.into()); if self.background == brush { return; } self.background = brush; self.dirt.mark_paint(); } pub fn set_corner_radius(&mut self, radius: f64) { if self.corner_radius == radius { return; } self.corner_radius = radius; self.dirt.mark_paint(); } pub fn set_border(&mut self, border: Option<(Brush, f64)>) { if self.border == border { return; } self.border = border; self.dirt.mark_paint(); } } impl Widget for Container { fn paint(&mut self, fragment: &mut Fragment, size: Size) { let bounds = Rect::from_origin_size((0.0, 0.0), size); if let Some(background) = &self.background { fragment.fill( bounds.to_rounded_rect(self.corner_radius), background.clone(), ); } if let Some((brush, width)) = &self.border { // Stroke centered on an inset path so the border stays inside // the widget's bounds. let inset = bounds.inset(-width / 2.0); fragment.stroke( inset.to_rounded_rect((self.corner_radius - width / 2.0).max(0.0)), brush.clone(), Stroke::new(*width), ); } } fn role(&self) -> accesskit::Role { accesskit::Role::GenericContainer } fn take_dirt(&mut self) -> Dirt { std::mem::take(&mut self.dirt) } fn type_name(&self) -> &'static str { "container" } fn apply_style(&mut self, style: &crate::style::ComputedStyle) { if let Some(brush) = style.brush("background") { self.set_background(brush.clone()); } if let Some(radius) = style.number("corner-radius") { self.set_corner_radius(radius); } } }