agg_gui/widgets/
container.rs1use crate::color::Color;
9use crate::device_scale::device_scale;
10use crate::draw_ctx::DrawCtx;
11use crate::event::{Event, EventResult};
12use crate::geometry::{Rect, Size};
13use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
14use crate::widget::Widget;
15
16#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
18#[derive(Clone, Debug)]
19pub struct ContainerProps {
20 pub background: Color,
21 pub border_color: Option<Color>,
22 pub border_width: f64,
23 pub corner_radius: f64,
24 pub inner_padding: Insets,
25 pub fit_height: bool,
30}
31
32impl Default for ContainerProps {
33 fn default() -> Self {
34 Self {
35 background: Color::rgba(0.0, 0.0, 0.0, 0.0),
36 border_color: None,
37 border_width: 1.0,
38 corner_radius: 0.0,
39 inner_padding: Insets::ZERO,
40 fit_height: false,
41 }
42 }
43}
44
45pub struct Container {
51 bounds: Rect,
52 children: Vec<Box<dyn Widget>>,
53 base: WidgetBase,
54 pub props: ContainerProps,
55}
56
57impl Container {
58 pub fn new() -> Self {
60 Self {
61 bounds: Rect::default(),
62 children: Vec::new(),
63 base: WidgetBase::new(),
64 props: ContainerProps::default(),
65 }
66 }
67
68 pub fn with_fit_height(mut self, fit: bool) -> Self {
75 self.props.fit_height = fit;
76 self
77 }
78
79 pub fn add(mut self, child: Box<dyn Widget>) -> Self {
81 self.children.push(child);
82 self
83 }
84
85 pub fn with_background(mut self, color: Color) -> Self {
86 self.props.background = color;
87 self
88 }
89
90 pub fn with_border(mut self, color: Color, width: f64) -> Self {
91 self.props.border_color = Some(color);
92 self.props.border_width = width;
93 self
94 }
95
96 pub fn with_corner_radius(mut self, r: f64) -> Self {
97 self.props.corner_radius = r;
98 self
99 }
100
101 pub fn with_padding(mut self, p: f64) -> Self {
102 self.props.inner_padding = Insets::all(p);
103 self
104 }
105
106 pub fn with_inner_padding(mut self, p: Insets) -> Self {
107 self.props.inner_padding = p;
108 self
109 }
110
111 pub fn with_margin(mut self, m: Insets) -> Self {
112 self.base.margin = m;
113 self
114 }
115 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
116 self.base.h_anchor = h;
117 self
118 }
119 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
120 self.base.v_anchor = v;
121 self
122 }
123 pub fn with_min_size(mut self, s: Size) -> Self {
124 self.base.min_size = s;
125 self
126 }
127 pub fn with_max_size(mut self, s: Size) -> Self {
128 self.base.max_size = s;
129 self
130 }
131}
132
133impl Default for Container {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl Widget for Container {
140 fn type_name(&self) -> &'static str {
141 "Container"
142 }
143 fn bounds(&self) -> Rect {
144 self.bounds
145 }
146 fn set_bounds(&mut self, bounds: Rect) {
147 self.bounds = bounds;
148 }
149
150 fn children(&self) -> &[Box<dyn Widget>] {
151 &self.children
152 }
153 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
154 &mut self.children
155 }
156
157 #[cfg(feature = "reflect")]
158 fn as_reflect(&self) -> Option<&dyn bevy_reflect::Reflect> {
159 Some(&self.props)
160 }
161 #[cfg(feature = "reflect")]
162 fn as_reflect_mut(&mut self) -> Option<&mut dyn bevy_reflect::Reflect> {
163 Some(&mut self.props)
164 }
165
166 fn margin(&self) -> Insets {
167 self.base.margin
168 }
169 fn widget_base(&self) -> Option<&WidgetBase> {
170 Some(&self.base)
171 }
172 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
173 Some(&mut self.base)
174 }
175 fn padding(&self) -> Insets {
176 self.props.inner_padding
177 }
178 fn h_anchor(&self) -> HAnchor {
179 self.base.h_anchor
180 }
181 fn v_anchor(&self) -> VAnchor {
182 self.base.v_anchor
183 }
184 fn min_size(&self) -> Size {
185 self.base.min_size
186 }
187 fn max_size(&self) -> Size {
188 self.base.max_size
189 }
190
191 fn layout(&mut self, available: Size) -> Size {
192 let pad_l = self.props.inner_padding.left;
193 let pad_r = self.props.inner_padding.right;
194 let pad_t = self.props.inner_padding.top;
195 let pad_b = self.props.inner_padding.bottom;
196 let inner_w = (available.width - pad_l - pad_r).max(0.0);
197
198 let scale = device_scale();
204 let start_cursor = available.height - pad_t;
205 let mut cursor_y = start_cursor;
206
207 for child in self.children.iter_mut() {
208 let m = child.margin().scale(scale);
209 let avail_w = (inner_w - m.left - m.right).max(0.0);
210 let avail_h = (cursor_y - pad_b - m.top - m.bottom).max(0.0);
211 let desired = child.layout(Size::new(avail_w, avail_h));
212
213 cursor_y -= m.top;
215 let child_y = cursor_y - desired.height;
216 let child_bounds = Rect::new(
217 pad_l + m.left,
218 child_y,
219 desired.width.min(avail_w),
220 desired.height,
221 );
222 child.set_bounds(child_bounds);
223 cursor_y = child_y - m.bottom;
225 }
226
227 if self.props.fit_height {
232 let consumed_h = (start_cursor - cursor_y).max(0.0);
233 let natural_h = (consumed_h + pad_t + pad_b).min(available.height);
234 Size::new(available.width, natural_h)
235 } else {
236 Size::new(available.width, available.height)
237 }
238 }
239
240 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
241 let w = self.bounds.width;
242 let h = self.bounds.height;
243 let r = self.props.corner_radius;
244
245 if self.props.background.a > 0.001 {
247 ctx.set_fill_color(self.props.background);
248 ctx.begin_path();
249 ctx.rounded_rect(0.0, 0.0, w, h, r);
250 ctx.fill();
251 }
252
253 if let Some(bc) = self.props.border_color {
255 ctx.set_stroke_color(bc);
256 ctx.set_line_width(self.props.border_width);
257 ctx.begin_path();
258 let inset = self.props.border_width * 0.5;
259 ctx.rounded_rect(
260 inset,
261 inset,
262 (w - self.props.border_width).max(0.0),
263 (h - self.props.border_width).max(0.0),
264 r,
265 );
266 ctx.stroke();
267 }
268 }
269
270 fn on_event(&mut self, _event: &Event) -> EventResult {
271 EventResult::Ignored
272 }
273}