bevy_ui_builders/panel/
builder.rs

1//! PanelBuilder implementation
2
3use bevy::prelude::*;
4use crate::{dimensions, label::{LabelBuilder, LabelStyle}};
5use super::types::*;
6
7/// Builder for creating panels with consistent styling
8pub struct PanelBuilder {
9    style: PanelStyle,
10    width: Val,
11    height: Val,
12    min_width: Val,
13    min_height: Val,
14    max_width: Val,
15    max_height: Val,
16    padding: UiRect,
17    margin: UiRect,
18    flex_direction: FlexDirection,
19    justify_content: JustifyContent,
20    align_items: AlignItems,
21    position_type: PositionType,
22    display: Display,
23    overflow: Overflow,
24    custom_background: Option<Color>,
25    title: Option<String>,
26    column_gap: Val,
27    row_gap: Val,
28    flex_basis: Val,
29    flex_grow: f32,
30    custom_border: Option<UiRect>,
31    border_color: Option<Color>,
32}
33
34impl PanelBuilder {
35    pub fn new() -> Self {
36        Self {
37            style: PanelStyle::Default,
38            width: Val::Auto,
39            height: Val::Auto,
40            min_width: Val::Auto,
41            min_height: Val::Auto,
42            max_width: Val::Auto,
43            max_height: Val::Auto,
44            padding: UiRect::all(Val::Px(dimensions::PANEL_PADDING)),
45            margin: UiRect::all(Val::Px(0.0)),
46            flex_direction: FlexDirection::Column,
47            justify_content: JustifyContent::Start,
48            align_items: AlignItems::Stretch,
49            position_type: PositionType::Relative,
50            display: Display::Flex,
51            overflow: Overflow::visible(),
52            custom_background: None,
53            title: None,
54            column_gap: Val::Px(0.0),
55            row_gap: Val::Px(0.0),
56            flex_basis: Val::Auto,
57            flex_grow: 0.0,
58            custom_border: None,
59            border_color: None,
60        }
61    }
62
63    pub fn style(mut self, style: PanelStyle) -> Self {
64        self.style = style;
65        self
66    }
67
68    pub fn width(mut self, width: Val) -> Self {
69        self.width = width;
70        self
71    }
72
73    pub fn height(mut self, height: Val) -> Self {
74        self.height = height;
75        self
76    }
77
78    pub fn min_width(mut self, min_width: Val) -> Self {
79        self.min_width = min_width;
80        self
81    }
82
83    pub fn min_height(mut self, min_height: Val) -> Self {
84        self.min_height = min_height;
85        self
86    }
87
88    pub fn max_width(mut self, max_width: Val) -> Self {
89        self.max_width = max_width;
90        self
91    }
92
93    pub fn max_height(mut self, max_height: Val) -> Self {
94        self.max_height = max_height;
95        self
96    }
97
98    pub fn overflow(mut self, overflow: Overflow) -> Self {
99        self.overflow = overflow;
100        self
101    }
102
103    /// Make this panel vertically scrollable
104    pub fn scrollable(mut self) -> Self {
105        self.overflow = Overflow::scroll_y();
106        self
107    }
108
109    /// Make this panel scrollable in both directions
110    pub fn scrollable_both(mut self) -> Self {
111        self.overflow = Overflow::scroll();
112        self
113    }
114
115    /// Use responsive padding based on viewport dimensions
116    pub fn responsive_padding(mut self) -> Self {
117        self.padding = UiRect::all(Val::Vw(2.0)); // 2% of viewport width
118        self
119    }
120
121    pub fn padding(mut self, padding: UiRect) -> Self {
122        self.padding = padding;
123        self
124    }
125
126    pub fn margin(mut self, margin: UiRect) -> Self {
127        self.margin = margin;
128        self
129    }
130
131    pub fn flex_direction(mut self, direction: FlexDirection) -> Self {
132        self.flex_direction = direction;
133        self
134    }
135
136    pub fn justify_content(mut self, justify: JustifyContent) -> Self {
137        self.justify_content = justify;
138        self
139    }
140
141    pub fn align_items(mut self, align: AlignItems) -> Self {
142        self.align_items = align;
143        self
144    }
145
146    pub fn position_type(mut self, position: PositionType) -> Self {
147        self.position_type = position;
148        self
149    }
150
151    pub fn display(mut self, display: Display) -> Self {
152        self.display = display;
153        self
154    }
155
156    /// Set a custom background color
157    pub fn custom_background(mut self, color: Color) -> Self {
158        self.custom_background = Some(color);
159        self
160    }
161
162    /// Set a title for the panel
163    pub fn with_title(mut self, title: impl Into<String>) -> Self {
164        self.title = Some(title.into());
165        self
166    }
167
168    /// Set column gap for flex layout
169    pub fn column_gap(mut self, gap: Val) -> Self {
170        self.column_gap = gap;
171        self
172    }
173
174    /// Set row gap for flex layout
175    pub fn row_gap(mut self, gap: Val) -> Self {
176        self.row_gap = gap;
177        self
178    }
179
180    /// Set flex basis
181    pub fn flex_basis(mut self, basis: Val) -> Self {
182        self.flex_basis = basis;
183        self
184    }
185
186    /// Set flex grow
187    pub fn flex_grow(mut self, grow: f32) -> Self {
188        self.flex_grow = grow;
189        self
190    }
191
192    /// Set background color (alias for custom_background)
193    pub fn background_color(mut self, color: Color) -> Self {
194        self.custom_background = Some(color);
195        self
196    }
197
198    /// Set custom border
199    pub fn border(mut self, border: UiRect) -> Self {
200        self.custom_border = Some(border);
201        self
202    }
203
204    /// Set border color
205    pub fn border_color(mut self, color: Color) -> Self {
206        self.border_color = Some(color);
207        self
208    }
209
210    pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
211        let background_color = self
212            .custom_background
213            .unwrap_or_else(|| self.style.background_color());
214
215        let border = self
216            .custom_border
217            .unwrap_or_else(|| UiRect::all(self.style.border_width()));
218
219        let border_color = self
220            .border_color
221            .unwrap_or_else(|| self.style.border_color());
222
223        let mut panel_entity = parent.spawn((
224            Node {
225                width: self.width,
226                height: self.height,
227                min_width: self.min_width,
228                min_height: self.min_height,
229                max_width: self.max_width,
230                max_height: self.max_height,
231                padding: self.padding,
232                margin: self.margin,
233                border,
234                flex_direction: self.flex_direction,
235                justify_content: self.justify_content,
236                align_items: self.align_items,
237                position_type: self.position_type,
238                display: self.display,
239                overflow: self.overflow,
240                column_gap: self.column_gap,
241                row_gap: self.row_gap,
242                flex_basis: self.flex_basis,
243                flex_grow: self.flex_grow,
244                ..default()
245            },
246            BackgroundColor(background_color),
247            BorderColor(border_color),
248            Panel { style: self.style },
249        ));
250
251        // Add title if provided
252        if let Some(title) = self.title {
253            panel_entity.with_children(|parent| {
254                LabelBuilder::new(title)
255                    .style(LabelStyle::Title)
256                    .margin(UiRect::bottom(Val::Px(dimensions::MARGIN_SMALL)))
257                    .build(parent);
258            });
259        }
260
261        panel_entity.id()
262    }
263
264    pub fn build_with_children<F>(
265        self,
266        parent: &mut ChildSpawnerCommands,
267        children: F,
268    ) -> Entity
269    where
270        F: FnOnce(&mut ChildSpawnerCommands),
271    {
272        let background_color = self
273            .custom_background
274            .unwrap_or_else(|| self.style.background_color());
275
276        let border = self
277            .custom_border
278            .unwrap_or_else(|| UiRect::all(self.style.border_width()));
279        let title = self.title.clone(); // Clone title for use in closure
280
281        parent
282            .spawn((
283                Node {
284                    width: self.width,
285                    height: self.height,
286                    min_width: self.min_width,
287                    min_height: self.min_height,
288                    max_width: self.max_width,
289                    max_height: self.max_height,
290                    padding: self.padding,
291                    margin: self.margin,
292                    border,
293                    flex_direction: self.flex_direction,
294                    justify_content: self.justify_content,
295                    align_items: self.align_items,
296                    position_type: self.position_type,
297                    display: self.display,
298                    overflow: self.overflow,
299                    column_gap: self.column_gap,
300                    row_gap: self.row_gap,
301                    flex_basis: self.flex_basis,
302                    flex_grow: self.flex_grow,
303                    ..default()
304                },
305                BackgroundColor(background_color),
306                BorderColor(self.style.border_color()),
307                Panel { style: self.style },
308            ))
309            .with_children(|parent| {
310                // Add title first if provided
311                if let Some(title_text) = title {
312                    LabelBuilder::new(title_text)
313                        .style(LabelStyle::Title)
314                        .margin(UiRect::bottom(Val::Px(dimensions::MARGIN_SMALL)))
315                        .build(parent);
316                }
317
318                // Then add user-provided children
319                children(parent);
320            })
321            .id()
322    }
323}
324
325/// Convenience function to create a panel builder
326pub fn panel() -> PanelBuilder {
327    PanelBuilder::new()
328}