1use bevy::prelude::*;
4use bevy::ui::RelativeCursorPosition;
5use crate::button::{ButtonBuilder, ButtonSize, ButtonStyle};
6use crate::styles::{colors, dimensions};
7use crate::systems::hover::{HoverScale, HoverColors};
8use super::types::*;
9
10pub struct SliderBuilder {
12 value: f32,
13 min: f32,
14 max: f32,
15 step: Option<f32>,
16 width: Val,
17 format: ValueFormat,
18 with_preview: bool,
19 with_buttons: bool,
20 label: Option<String>,
21}
22
23impl SliderBuilder {
24 pub fn new(range: std::ops::Range<f32>) -> Self {
26 Self {
27 value: range.start,
28 min: range.start,
29 max: range.end,
30 step: None,
31 width: Val::Px(200.0),
32 format: ValueFormat::Decimal(1),
33 with_preview: true,
34 with_buttons: false,
35 label: None,
36 }
37 }
38
39 pub fn value(mut self, value: f32) -> Self {
41 self.value = value.clamp(self.min, self.max);
42 self
43 }
44
45 pub fn step(mut self, step: f32) -> Self {
47 self.step = Some(step);
48 self
49 }
50
51 pub fn width(mut self, width: Val) -> Self {
53 self.width = width;
54 self
55 }
56
57 pub fn format(mut self, format: ValueFormat) -> Self {
59 self.format = format;
60 self
61 }
62
63 pub fn with_format(mut self, format: ValueFormat) -> Self {
65 self.format(format)
66 }
67
68 pub fn with_preview(mut self, show: bool) -> Self {
70 self.with_preview = show;
71 self
72 }
73
74 pub fn with_buttons(mut self) -> Self {
76 self.with_buttons = true;
77 self
78 }
79
80 pub fn label(mut self, label: impl Into<String>) -> Self {
82 self.label = Some(label.into());
83 self
84 }
85
86 pub fn with_marker<M: Component>(self, marker: M) -> SliderBuilderWithMarker<M> {
88 SliderBuilderWithMarker {
89 builder: self,
90 marker,
91 }
92 }
93
94 pub fn build_in(self, parent: &mut ChildSpawnerCommands) -> Entity {
96 self.build(parent)
97 }
98
99 pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
101 let container = parent.spawn((
102 Node {
103 width: self.width,
104 flex_direction: FlexDirection::Column,
105 row_gap: Val::Px(dimensions::SPACING_SMALL),
106 margin: UiRect::bottom(Val::Px(dimensions::SPACING_MEDIUM)),
107 ..default()
108 },
109 BackgroundColor(Color::NONE),
110 )).id();
111
112 let mut value_text_id = None;
113
114 parent.commands().entity(container).with_children(|container| {
115 if self.label.is_some() || self.with_preview {
117 container.spawn((
118 Node {
119 width: Val::Percent(100.0),
120 flex_direction: FlexDirection::Row,
121 justify_content: JustifyContent::SpaceBetween,
122 ..default()
123 },
124 BackgroundColor(Color::NONE),
125 )).with_children(|row| {
126 if let Some(label) = self.label {
128 row.spawn((
129 Text::new(label),
130 TextFont {
131 font_size: dimensions::FONT_SIZE_MEDIUM,
132 ..default()
133 },
134 TextColor(colors::TEXT_SECONDARY),
135 SliderLabel,
136 ));
137 }
138
139 if self.with_preview {
141 let entity = row.spawn((
142 Text::new(self.format.format(self.value)),
143 TextFont {
144 font_size: dimensions::FONT_SIZE_MEDIUM,
145 ..default()
146 },
147 TextColor(colors::TEXT_PRIMARY),
148 SliderValueText,
149 )).id();
150 value_text_id = Some(entity);
151 }
152 });
153 }
154
155 let mut slider_entity = container.spawn((
157 Button,
158 Node {
159 width: Val::Percent(100.0),
160 height: Val::Px(dimensions::SLIDER_TRACK_HEIGHT + dimensions::SLIDER_HANDLE_SIZE),
161 padding: UiRect::vertical(Val::Px(
162 (dimensions::SLIDER_HANDLE_SIZE - dimensions::SLIDER_TRACK_HEIGHT) / 2.0
163 )),
164 justify_content: JustifyContent::Start,
165 align_items: AlignItems::Center,
166 position_type: PositionType::Relative,
167 ..default()
168 },
169 BackgroundColor(Color::NONE),
170 Interaction::default(),
171 RelativeCursorPosition::default(),
172 SliderTrack,
173 ));
175
176 let track_entity = slider_entity.id();
177
178 let mut slider = Slider::new(self.min, self.max, self.value);
179 slider.step = self.step;
180 slider.value_text_entity = value_text_id;
181
182 slider_entity.insert(slider.clone());
183 slider_entity.insert(SliderConfig {
184 show_value: self.with_preview,
185 value_format: self.format.clone(),
186 track_height: dimensions::SLIDER_TRACK_HEIGHT,
187 handle_size: dimensions::SLIDER_HANDLE_SIZE,
188 track_color: colors::BACKGROUND_TERTIARY,
189 fill_color: colors::PRIMARY.with_alpha(0.3),
190 handle_color: colors::PRIMARY,
191 });
192
193 slider_entity.with_children(|track| {
194 track.spawn((
196 Node {
197 width: Val::Percent(100.0),
198 height: Val::Px(dimensions::SLIDER_TRACK_HEIGHT),
199 position_type: PositionType::Absolute,
200 ..default()
201 },
202 BackgroundColor(colors::BACKGROUND_TERTIARY),
203 BorderRadius::all(Val::Px(dimensions::SLIDER_TRACK_HEIGHT / 2.0)),
204 ));
205
206 let fill_width = slider.normalized() * 100.0;
208 track.spawn((
209 Node {
210 width: Val::Percent(fill_width),
211 height: Val::Px(dimensions::SLIDER_TRACK_HEIGHT),
212 position_type: PositionType::Absolute,
213 ..default()
214 },
215 BackgroundColor(colors::PRIMARY.with_alpha(0.3)),
216 BorderRadius::all(Val::Px(dimensions::SLIDER_TRACK_HEIGHT / 2.0)),
217 SliderFill,
218 ));
219
220 let handle_offset = slider.normalized() * 100.0;
222 track.spawn((
223 Node {
224 width: Val::Px(dimensions::SLIDER_HANDLE_SIZE),
225 height: Val::Px(dimensions::SLIDER_HANDLE_SIZE),
226 position_type: PositionType::Absolute,
227 left: Val::Percent(handle_offset.min(95.0)),
228 top: Val::Px(0.0),
229 border: UiRect::all(Val::Px(dimensions::BORDER_WIDTH_MEDIUM)),
230 ..default()
231 },
232 BackgroundColor(colors::PRIMARY),
233 BorderColor(colors::BORDER_LIGHT),
234 BorderRadius::all(Val::Px(dimensions::SLIDER_HANDLE_SIZE / 2.0)),
235 SliderHandle,
236 HoverScale(1.15), HoverColors {
239 normal_bg: colors::PRIMARY,
240 hover_bg: colors::PRIMARY_HOVER,
241 normal_border: colors::BORDER_LIGHT,
242 hover_border: colors::BORDER_FOCUS,
243 },
244 Interaction::default(),
245 ));
246 });
247
248 if self.with_buttons {
250 container.spawn((
251 Node {
252 width: Val::Percent(100.0),
253 flex_direction: FlexDirection::Row,
254 justify_content: JustifyContent::Center,
255 column_gap: Val::Px(dimensions::SPACING_MEDIUM),
256 margin: UiRect::top(Val::Px(dimensions::SPACING_SMALL)),
257 ..default()
258 },
259 BackgroundColor(Color::NONE),
260 )).with_children(|button_row| {
261 let dec_button = ButtonBuilder::new("-")
263 .style(ButtonStyle::Secondary)
264 .size(ButtonSize::Small)
265 .build(button_row);
266
267 button_row.commands()
268 .entity(dec_button)
269 .insert(SliderButtonAction {
270 slider_entity: track_entity,
271 delta: -self.step.unwrap_or((self.max - self.min) / 100.0),
272 });
273
274 let inc_button = ButtonBuilder::new("+")
276 .style(ButtonStyle::Secondary)
277 .size(ButtonSize::Small)
278 .build(button_row);
279
280 button_row.commands()
281 .entity(inc_button)
282 .insert(SliderButtonAction {
283 slider_entity: track_entity,
284 delta: self.step.unwrap_or((self.max - self.min) / 100.0),
285 });
286 });
287 }
288 });
289
290 container
291 }
292}
293
294pub struct SliderBuilderWithMarker<M: Component> {
296 builder: SliderBuilder,
297 marker: M,
298}
299
300impl<M: Component> SliderBuilderWithMarker<M> {
301 pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
303 let entity = self.builder.build(parent);
304 parent.commands().entity(entity).insert(self.marker);
305 entity
306 }
307
308 pub fn build_in(self, parent: &mut ChildSpawnerCommands) -> Entity {
310 self.build(parent)
311 }
312}