bevy_ui_builders/scroll_view/
builder.rs

1//! ScrollView builder for creating scrollable containers with dynamic sizing
2
3use bevy::prelude::*;
4use super::types::*;
5
6/// Builder for creating scrollable containers with responsive sizing
7pub struct ScrollViewBuilder {
8    width: Val,
9    height: Val,
10    max_width: Val,
11    max_height: Val,
12    padding: UiRect,
13    margin: UiRect,
14    gap: Val,
15    direction: ScrollDirection,
16    config: ScrollConfig,
17    background_color: Color,
18}
19
20impl ScrollViewBuilder {
21    /// Create a new ScrollViewBuilder with sensible defaults
22    pub fn new() -> Self {
23        Self {
24            width: Val::Percent(100.0),
25            height: Val::Auto,
26            max_width: Val::Percent(100.0),
27            max_height: Val::Vh(90.0), // 90% viewport height by default
28            padding: UiRect::all(Val::Vw(2.0)), // 2% viewport width padding
29            margin: UiRect::ZERO,
30            gap: Val::Vh(2.0), // 2% viewport height gap
31            direction: ScrollDirection::Vertical,
32            config: ScrollConfig::default(),
33            background_color: Color::NONE,
34        }
35    }
36
37    /// Set the width of the scroll container
38    pub fn width(mut self, width: Val) -> Self {
39        self.width = width;
40        self
41    }
42
43    /// Set the height of the scroll container
44    pub fn height(mut self, height: Val) -> Self {
45        self.height = height;
46        self
47    }
48
49    /// Set the maximum width (useful for responsive design)
50    pub fn max_width(mut self, max_width: Val) -> Self {
51        self.max_width = max_width;
52        self
53    }
54
55    /// Set the maximum height (prevents infinite scrolling)
56    pub fn max_height(mut self, max_height: Val) -> Self {
57        self.max_height = max_height;
58        self
59    }
60
61    /// Set padding using viewport height percentage
62    pub fn padding_vh(mut self, vh: f32) -> Self {
63        self.padding = UiRect::all(Val::Vh(vh));
64        self
65    }
66
67    /// Set padding using viewport width percentage
68    pub fn padding_vw(mut self, vw: f32) -> Self {
69        self.padding = UiRect::all(Val::Vw(vw));
70        self
71    }
72
73    /// Set custom padding
74    pub fn padding(mut self, padding: UiRect) -> Self {
75        self.padding = padding;
76        self
77    }
78
79    /// Set margin
80    pub fn margin(mut self, margin: UiRect) -> Self {
81        self.margin = margin;
82        self
83    }
84
85    /// Set the gap between child elements (row_gap for vertical, column_gap for horizontal)
86    pub fn gap(mut self, gap: Val) -> Self {
87        self.gap = gap;
88        self
89    }
90
91    /// Set the scroll direction
92    pub fn direction(mut self, direction: ScrollDirection) -> Self {
93        self.direction = direction;
94        self
95    }
96
97    /// Enable/disable auto-scroll to focused elements
98    pub fn auto_scroll(mut self, enabled: bool) -> Self {
99        self.config.auto_scroll_to_focus = enabled;
100        self
101    }
102
103    /// Show/hide scroll indicators
104    pub fn show_indicators(mut self, show: bool) -> Self {
105        self.config.show_indicators = show;
106        self
107    }
108
109    /// Set the background color
110    pub fn background_color(mut self, color: Color) -> Self {
111        self.background_color = color;
112        self
113    }
114
115    /// Build the scroll view container and return its entity
116    pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
117        let overflow = match self.direction {
118            ScrollDirection::Vertical => Overflow::scroll_y(),
119            ScrollDirection::Horizontal => Overflow::scroll_x(),
120            ScrollDirection::Both => Overflow::scroll(),
121        };
122
123        let (flex_direction, row_gap, column_gap) = match self.direction {
124            ScrollDirection::Vertical => (FlexDirection::Column, self.gap, Val::ZERO),
125            ScrollDirection::Horizontal => (FlexDirection::Row, Val::ZERO, self.gap),
126            ScrollDirection::Both => (FlexDirection::Column, self.gap, self.gap),
127        };
128
129        let show_indicators = self.config.show_indicators;
130        let container = parent.spawn((
131            Node {
132                width: self.width,
133                height: self.height,
134                max_width: self.max_width,
135                max_height: self.max_height,
136                padding: self.padding,
137                margin: self.margin,
138                flex_direction,
139                row_gap,
140                column_gap,
141                align_items: AlignItems::Stretch,
142                overflow,
143                ..default()
144            },
145            BackgroundColor(self.background_color),
146            ScrollView,
147            ScrollState::default(),
148            self.config,
149        )).id();
150
151        // Add visual scrollbar if indicators are enabled
152        if show_indicators && self.direction != ScrollDirection::Horizontal {
153            parent.spawn((
154                Node {
155                    position_type: PositionType::Absolute,
156                    right: Val::Px(4.0),
157                    top: Val::Px(4.0),
158                    bottom: Val::Px(4.0),
159                    width: Val::Px(8.0),
160                    ..default()
161                },
162                BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.1)),
163                ScrollBarTrack,
164            )).with_children(|track| {
165                track.spawn((
166                    Node {
167                        width: Val::Percent(100.0),
168                        height: Val::Percent(20.0), // Will be dynamically sized
169                        ..default()
170                    },
171                    BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.3)),
172                    ScrollBarThumb { scroll_container: container },
173                    Visibility::Hidden, // Hidden initially
174                ));
175            });
176        }
177
178        container
179    }
180
181    /// Build the scroll view and add children using a closure
182    pub fn build_with_children<F>(self, parent: &mut ChildSpawnerCommands, children_fn: F) -> Entity
183    where
184        F: FnOnce(&mut ChildSpawnerCommands),
185    {
186        let overflow = match self.direction {
187            ScrollDirection::Vertical => Overflow::scroll_y(),
188            ScrollDirection::Horizontal => Overflow::scroll_x(),
189            ScrollDirection::Both => Overflow::scroll(),
190        };
191
192        let (flex_direction, row_gap, column_gap) = match self.direction {
193            ScrollDirection::Vertical => (FlexDirection::Column, self.gap, Val::ZERO),
194            ScrollDirection::Horizontal => (FlexDirection::Row, Val::ZERO, self.gap),
195            ScrollDirection::Both => (FlexDirection::Column, self.gap, self.gap),
196        };
197
198        let show_indicators = self.config.show_indicators;
199        let container = parent
200            .spawn((
201                Node {
202                    width: self.width,
203                    height: self.height,
204                    max_width: self.max_width,
205                    max_height: self.max_height,
206                    padding: self.padding,
207                    margin: self.margin,
208                    flex_direction,
209                    row_gap,
210                    column_gap,
211                    align_items: AlignItems::Stretch,
212                    overflow,
213                    ..default()
214                },
215                BackgroundColor(self.background_color),
216                ScrollView,
217                ScrollState::default(),
218                self.config,
219            ))
220            .with_children(children_fn)
221            .id();
222
223        // Add visual scrollbar if indicators are enabled
224        if show_indicators && self.direction != ScrollDirection::Horizontal {
225            parent.spawn((
226                Node {
227                    position_type: PositionType::Absolute,
228                    right: Val::Px(4.0),
229                    top: Val::Px(4.0),
230                    bottom: Val::Px(4.0),
231                    width: Val::Px(8.0),
232                    ..default()
233                },
234                BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.1)),
235                ScrollBarTrack,
236            )).with_children(|track| {
237                track.spawn((
238                    Node {
239                        width: Val::Percent(100.0),
240                        height: Val::Percent(20.0), // Will be dynamically sized
241                        ..default()
242                    },
243                    BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.3)),
244                    ScrollBarThumb { scroll_container: container },
245                    Visibility::Hidden, // Hidden initially
246                ));
247            });
248        }
249
250        container
251    }
252}
253
254impl Default for ScrollViewBuilder {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260/// Convenience function to create a ScrollViewBuilder
261pub fn scroll_view() -> ScrollViewBuilder {
262    ScrollViewBuilder::new()
263}