Skip to main content

agg_gui/widgets/
container.rs

1//! `Container` — a rectangular box with optional background, border, and
2//! padding that holds zero or more child widgets.
3//!
4//! Phase 4 child layout is a simple top-down vertical stack (bottom-most child
5//! at `y = padding`, each subsequent child placed above the previous). Flex
6//! layout arrives in Phase 5.
7
8use crate::color::Color;
9use crate::device_scale::device_scale;
10use crate::event::{Event, EventResult};
11use crate::geometry::{Rect, Size};
12use crate::draw_ctx::DrawCtx;
13use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
14use crate::widget::Widget;
15
16/// A rectangular container widget.
17///
18/// Paints a background rounded-rect (optional border), then lets the framework
19/// recurse into its children. Children are stacked bottom-to-top inside the
20/// padding area.
21pub struct Container {
22    bounds: Rect,
23    children: Vec<Box<dyn Widget>>,
24    base: WidgetBase,
25    pub background: Color,
26    pub border_color: Option<Color>,
27    pub border_width: f64,
28    pub corner_radius: f64,
29    pub inner_padding: Insets,
30}
31
32impl Container {
33    /// Create a transparent container with no border and default padding.
34    pub fn new() -> Self {
35        Self {
36            bounds: Rect::default(),
37            children: Vec::new(),
38            base: WidgetBase::new(),
39            background: Color::rgba(0.0, 0.0, 0.0, 0.0),
40            border_color: None,
41            border_width: 1.0,
42            corner_radius: 0.0,
43            inner_padding: Insets::ZERO,
44        }
45    }
46
47    /// Append a child widget.
48    pub fn add(mut self, child: Box<dyn Widget>) -> Self {
49        self.children.push(child);
50        self
51    }
52
53    pub fn with_background(mut self, color: Color) -> Self {
54        self.background = color;
55        self
56    }
57
58    pub fn with_border(mut self, color: Color, width: f64) -> Self {
59        self.border_color = Some(color);
60        self.border_width = width;
61        self
62    }
63
64    pub fn with_corner_radius(mut self, r: f64) -> Self {
65        self.corner_radius = r;
66        self
67    }
68
69    pub fn with_padding(mut self, p: f64) -> Self {
70        self.inner_padding = Insets::all(p);
71        self
72    }
73
74    pub fn with_inner_padding(mut self, p: Insets) -> Self {
75        self.inner_padding = p;
76        self
77    }
78
79    pub fn with_margin(mut self, m: Insets)    -> Self { self.base.margin   = m; self }
80    pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
81    pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
82    pub fn with_min_size(mut self, s: Size)    -> Self { self.base.min_size = s; self }
83    pub fn with_max_size(mut self, s: Size)    -> Self { self.base.max_size = s; self }
84}
85
86impl Default for Container {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl Widget for Container {
93    fn type_name(&self) -> &'static str { "Container" }
94    fn bounds(&self) -> Rect { self.bounds }
95    fn set_bounds(&mut self, bounds: Rect) { self.bounds = bounds; }
96
97    fn children(&self) -> &[Box<dyn Widget>] { &self.children }
98    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
99
100    fn margin(&self)   -> Insets  { self.base.margin }
101    fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
102    fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
103    fn min_size(&self) -> Size    { self.base.min_size }
104    fn max_size(&self) -> Size    { self.base.max_size }
105
106    fn layout(&mut self, available: Size) -> Size {
107        let pad_l = self.inner_padding.left;
108        let pad_r = self.inner_padding.right;
109        let pad_t = self.inner_padding.top;
110        let pad_b = self.inner_padding.bottom;
111        let inner_w = (available.width - pad_l - pad_r).max(0.0);
112
113        // Stack children top-to-bottom (first child = visually highest).
114        // In Y-up coordinates, "top" = higher Y values.
115        // Start cursor at the top of the inner area; move it downward each step.
116        // Child margins are additive: top margin pushes the cursor down before
117        // placing the child; bottom margin is consumed after it.
118        let scale = device_scale();
119        let mut cursor_y = available.height - pad_t;
120
121        for child in self.children.iter_mut() {
122            let m = child.margin().scale(scale);
123            let avail_w = (inner_w - m.left - m.right).max(0.0);
124            let avail_h = (cursor_y - pad_b - m.top - m.bottom).max(0.0);
125            let desired = child.layout(Size::new(avail_w, avail_h));
126
127            // Top margin moves the cursor down before the child is placed.
128            cursor_y -= m.top;
129            let child_y = cursor_y - desired.height;
130            let child_bounds = Rect::new(
131                pad_l + m.left,
132                child_y,
133                desired.width.min(avail_w),
134                desired.height,
135            );
136            child.set_bounds(child_bounds);
137            // Bottom margin is consumed below the child.
138            cursor_y = child_y - m.bottom;
139        }
140
141        // Container fills all available space.
142        Size::new(available.width, available.height)
143    }
144
145    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
146        let w = self.bounds.width;
147        let h = self.bounds.height;
148        let r = self.corner_radius;
149
150        // Background
151        if self.background.a > 0.001 {
152            ctx.set_fill_color(self.background);
153            ctx.begin_path();
154            ctx.rounded_rect(0.0, 0.0, w, h, r);
155            ctx.fill();
156        }
157
158        // Border
159        if let Some(bc) = self.border_color {
160            ctx.set_stroke_color(bc);
161            ctx.set_line_width(self.border_width);
162            ctx.begin_path();
163            ctx.rounded_rect(0.0, 0.0, w, h, r);
164            ctx.stroke();
165        }
166    }
167
168    fn on_event(&mut self, _event: &Event) -> EventResult {
169        EventResult::Ignored
170    }
171}