bevy_ui_builders/scroll_view/
builder.rs1use bevy::prelude::*;
4use super::types::*;
5
6#[derive(Component, Debug, Clone, Default)]
8pub struct ScrollbarThumb;
9
10pub struct ScrollViewBuilder {
12 width: Val,
13 height: Val,
14 max_width: Val,
15 max_height: Val,
16 padding: UiRect,
17 margin: UiRect,
18 gap: Val,
19 direction: ScrollDirection,
20 config: ScrollConfig,
21 background_color: Color,
22}
23
24impl ScrollViewBuilder {
25 pub fn new() -> Self {
27 Self {
28 width: Val::Percent(100.0),
29 height: Val::Auto,
30 max_width: Val::Percent(100.0),
31 max_height: Val::Vh(90.0), padding: UiRect::all(Val::Vw(2.0)), margin: UiRect::ZERO,
34 gap: Val::Vh(2.0), direction: ScrollDirection::Vertical,
36 config: ScrollConfig::default(),
37 background_color: Color::NONE,
38 }
39 }
40
41 pub fn width(mut self, width: Val) -> Self {
43 self.width = width;
44 self
45 }
46
47 pub fn height(mut self, height: Val) -> Self {
49 self.height = height;
50 self
51 }
52
53 pub fn max_width(mut self, max_width: Val) -> Self {
55 self.max_width = max_width;
56 self
57 }
58
59 pub fn max_height(mut self, max_height: Val) -> Self {
61 self.max_height = max_height;
62 self
63 }
64
65 pub fn padding_vh(mut self, vh: f32) -> Self {
67 self.padding = UiRect::all(Val::Vh(vh));
68 self
69 }
70
71 pub fn padding_vw(mut self, vw: f32) -> Self {
73 self.padding = UiRect::all(Val::Vw(vw));
74 self
75 }
76
77 pub fn padding(mut self, padding: UiRect) -> Self {
79 self.padding = padding;
80 self
81 }
82
83 pub fn margin(mut self, margin: UiRect) -> Self {
85 self.margin = margin;
86 self
87 }
88
89 pub fn gap(mut self, gap: Val) -> Self {
91 self.gap = gap;
92 self
93 }
94
95 pub fn direction(mut self, direction: ScrollDirection) -> Self {
97 self.direction = direction;
98 self
99 }
100
101 pub fn auto_scroll(mut self, enabled: bool) -> Self {
103 self.config.auto_scroll_to_focus = enabled;
104 self
105 }
106
107 pub fn scrollbar_visibility(mut self, visibility: ScrollbarVisibility) -> Self {
109 self.config.scrollbar_visibility = visibility;
110 self
111 }
112
113 pub fn enable_drag_scroll(mut self, enabled: bool) -> Self {
115 self.config.enable_drag_scroll = enabled;
116 self
117 }
118
119 pub fn enable_kinetic_scroll(mut self, enabled: bool) -> Self {
121 self.config.enable_kinetic_scroll = enabled;
122 self
123 }
124
125 pub fn scroll_sensitivity(mut self, sensitivity: f32) -> Self {
127 self.config.scroll_sensitivity = sensitivity;
128 self
129 }
130
131 pub fn scrollbar_width(mut self, width: f32) -> Self {
133 self.config.scrollbar_width = width;
134 self
135 }
136
137 pub fn min_thumb_length(mut self, length: f32) -> Self {
139 self.config.min_thumb_length = length;
140 self
141 }
142
143 pub fn show_indicators(mut self, show: bool) -> Self {
145 self.config.scrollbar_visibility = if show {
146 ScrollbarVisibility::AutoHide { timeout_secs: 2.0 }
147 } else {
148 ScrollbarVisibility::Never
149 };
150 self
151 }
152
153 pub fn background_color(mut self, color: Color) -> Self {
155 self.background_color = color;
156 self
157 }
158
159 pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
161 let overflow = match self.direction {
162 ScrollDirection::Vertical => Overflow::scroll_y(),
163 ScrollDirection::Horizontal => Overflow::scroll_x(),
164 ScrollDirection::Both => Overflow::scroll(),
165 };
166
167 let (flex_direction, row_gap, column_gap) = match self.direction {
168 ScrollDirection::Vertical => (FlexDirection::Column, self.gap, Val::ZERO),
169 ScrollDirection::Horizontal => (FlexDirection::Row, Val::ZERO, self.gap),
170 ScrollDirection::Both => (FlexDirection::Column, self.gap, self.gap),
171 };
172
173 let show_scrollbar = self.config.scrollbar_visibility != ScrollbarVisibility::Never;
174 let enable_drag = self.config.enable_drag_scroll;
175 let enable_kinetic = self.config.enable_kinetic_scroll;
176 let scrollbar_width = self.config.scrollbar_width;
177 let min_thumb_length = self.config.min_thumb_length;
178
179 let mut container_bundle = (
180 Node {
181 width: self.width,
182 height: self.height,
183 max_width: self.max_width,
184 max_height: self.max_height,
185 padding: self.padding,
186 margin: self.margin,
187 flex_direction,
188 row_gap,
189 column_gap,
190 align_items: AlignItems::Stretch,
191 overflow,
192 ..default()
193 },
194 BackgroundColor(self.background_color),
195 ScrollView,
196 ScrollPosition::default(),
197 self.config,
198 Interaction::default(), );
200
201 let container = parent.spawn(container_bundle).id();
202
203 if enable_drag {
205 parent.commands().entity(container).insert(DragScrollTarget);
206 }
207
208 if enable_kinetic {
210 parent.commands().entity(container).insert(KineticScrollState::default());
211 }
212
213 if show_scrollbar && self.direction != ScrollDirection::Horizontal {
215 parent.spawn((
216 Node {
217 position_type: PositionType::Absolute,
218 right: Val::Px(4.0),
219 top: Val::Px(4.0),
220 bottom: Val::Px(4.0),
221 width: Val::Px(scrollbar_width),
222 ..default()
223 },
224 BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.1)),
225 ScrollbarState::new(container),
226 )).with_children(|scrollbar| {
227 scrollbar.spawn((
228 Node {
229 width: Val::Percent(100.0),
230 height: Val::Percent(20.0), ..default()
232 },
233 BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.3)),
234 BorderRadius::all(Val::Px(scrollbar_width / 2.0)),
235 ScrollbarThumb,
236 Interaction::default(), ));
238 });
239 }
240
241 container
242 }
243
244 pub fn build_with_children<F>(self, parent: &mut ChildSpawnerCommands, children_fn: F) -> Entity
246 where
247 F: FnOnce(&mut ChildSpawnerCommands),
248 {
249 let overflow = match self.direction {
250 ScrollDirection::Vertical => Overflow::scroll_y(),
251 ScrollDirection::Horizontal => Overflow::scroll_x(),
252 ScrollDirection::Both => Overflow::scroll(),
253 };
254
255 let (flex_direction, row_gap, column_gap) = match self.direction {
256 ScrollDirection::Vertical => (FlexDirection::Column, self.gap, Val::ZERO),
257 ScrollDirection::Horizontal => (FlexDirection::Row, Val::ZERO, self.gap),
258 ScrollDirection::Both => (FlexDirection::Column, self.gap, self.gap),
259 };
260
261 let show_scrollbar = self.config.scrollbar_visibility != ScrollbarVisibility::Never;
262 let enable_drag = self.config.enable_drag_scroll;
263 let enable_kinetic = self.config.enable_kinetic_scroll;
264 let scrollbar_width = self.config.scrollbar_width;
265 let min_thumb_length = self.config.min_thumb_length;
266
267 let container = parent
268 .spawn((
269 Node {
270 width: self.width,
271 height: self.height,
272 max_width: self.max_width,
273 max_height: self.max_height,
274 padding: self.padding,
275 margin: self.margin,
276 flex_direction,
277 row_gap,
278 column_gap,
279 align_items: AlignItems::Stretch,
280 overflow,
281 ..default()
282 },
283 BackgroundColor(self.background_color),
284 ScrollView,
285 ScrollPosition::default(),
286 self.config,
287 Interaction::default(), ))
289 .with_children(children_fn)
290 .id();
291
292 if enable_drag {
294 parent.commands().entity(container).insert(DragScrollTarget);
295 }
296
297 if enable_kinetic {
299 parent.commands().entity(container).insert(KineticScrollState::default());
300 }
301
302 if show_scrollbar && self.direction != ScrollDirection::Horizontal {
304 parent.spawn((
305 Node {
306 position_type: PositionType::Absolute,
307 right: Val::Px(4.0),
308 top: Val::Px(4.0),
309 bottom: Val::Px(4.0),
310 width: Val::Px(scrollbar_width),
311 ..default()
312 },
313 BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.1)),
314 ScrollbarState::new(container),
315 )).with_children(|scrollbar| {
316 scrollbar.spawn((
317 Node {
318 width: Val::Percent(100.0),
319 height: Val::Percent(20.0), ..default()
321 },
322 BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.3)),
323 BorderRadius::all(Val::Px(scrollbar_width / 2.0)),
324 ScrollbarThumb,
325 Interaction::default(), ));
327 });
328 }
329
330 container
331 }
332}
333
334impl Default for ScrollViewBuilder {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340pub fn scroll_view() -> ScrollViewBuilder {
342 ScrollViewBuilder::new()
343}