container.rs
raw
//! 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<Brush>,
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<Brush>) -> 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<Brush>, 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<Brush>) {
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);
}
}
}