1use bevy::prelude::*;
4use bevy::ui::RelativeCursorPosition;
5use crate::button::{ButtonBuilder, ButtonSize, ButtonStyle};
6use crate::styles::{colors, dimensions};
7use super::types::*;
8
9pub struct SliderBuilder {
11 value: f32,
12 min: f32,
13 max: f32,
14 step: Option<f32>,
15 width: Val,
16 format: ValueFormat,
17 with_preview: bool,
18 with_buttons: bool,
19 label: Option<String>,
20}
21
22impl SliderBuilder {
23 pub fn new(range: std::ops::Range<f32>) -> Self {
25 Self {
26 value: range.start,
27 min: range.start,
28 max: range.end,
29 step: None,
30 width: Val::Px(200.0),
31 format: ValueFormat::Decimal(1),
32 with_preview: true,
33 with_buttons: false,
34 label: None,
35 }
36 }
37
38 pub fn value(mut self, value: f32) -> Self {
40 self.value = value.clamp(self.min, self.max);
41 self
42 }
43
44 pub fn step(mut self, step: f32) -> Self {
46 self.step = Some(step);
47 self
48 }
49
50 pub fn width(mut self, width: Val) -> Self {
52 self.width = width;
53 self
54 }
55
56 pub fn format(mut self, format: ValueFormat) -> Self {
58 self.format = format;
59 self
60 }
61
62 pub fn with_preview(mut self, show: bool) -> Self {
64 self.with_preview = show;
65 self
66 }
67
68 pub fn with_buttons(mut self) -> Self {
70 self.with_buttons = true;
71 self
72 }
73
74 pub fn label(mut self, label: impl Into<String>) -> Self {
76 self.label = Some(label.into());
77 self
78 }
79
80 pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
82 let container = parent.spawn((
83 Node {
84 width: self.width,
85 flex_direction: FlexDirection::Column,
86 row_gap: Val::Px(dimensions::SPACING_SMALL),
87 margin: UiRect::bottom(Val::Px(dimensions::SPACING_MEDIUM)),
88 ..default()
89 },
90 BackgroundColor(Color::NONE),
91 )).id();
92
93 let mut value_text_id = None;
94
95 parent.commands().entity(container).with_children(|container| {
96 if self.label.is_some() || self.with_preview {
98 container.spawn((
99 Node {
100 width: Val::Percent(100.0),
101 flex_direction: FlexDirection::Row,
102 justify_content: JustifyContent::SpaceBetween,
103 ..default()
104 },
105 BackgroundColor(Color::NONE),
106 )).with_children(|row| {
107 if let Some(label) = self.label {
109 row.spawn((
110 Text::new(label),
111 TextFont {
112 font_size: dimensions::FONT_SIZE_MEDIUM,
113 ..default()
114 },
115 TextColor(colors::TEXT_SECONDARY),
116 SliderLabel,
117 ));
118 }
119
120 if self.with_preview {
122 let entity = row.spawn((
123 Text::new(self.format.format(self.value)),
124 TextFont {
125 font_size: dimensions::FONT_SIZE_MEDIUM,
126 ..default()
127 },
128 TextColor(colors::TEXT_PRIMARY),
129 SliderValueText,
130 )).id();
131 value_text_id = Some(entity);
132 }
133 });
134 }
135
136 let mut slider_entity = container.spawn((
138 Button,
139 Node {
140 width: Val::Percent(100.0),
141 height: Val::Px(dimensions::SLIDER_TRACK_HEIGHT + dimensions::SLIDER_HANDLE_SIZE),
142 padding: UiRect::vertical(Val::Px(
143 (dimensions::SLIDER_HANDLE_SIZE - dimensions::SLIDER_TRACK_HEIGHT) / 2.0
144 )),
145 justify_content: JustifyContent::Start,
146 align_items: AlignItems::Center,
147 position_type: PositionType::Relative,
148 ..default()
149 },
150 BackgroundColor(Color::NONE),
151 Interaction::default(),
152 RelativeCursorPosition::default(),
153 SliderTrack,
154 ));
155
156 let track_entity = slider_entity.id();
157
158 let mut slider = Slider::new(self.min, self.max, self.value);
159 slider.step = self.step;
160 slider.value_text_entity = value_text_id;
161
162 slider_entity.insert(slider.clone());
163 slider_entity.insert(SliderConfig {
164 show_value: self.with_preview,
165 value_format: self.format.clone(),
166 track_height: dimensions::SLIDER_TRACK_HEIGHT,
167 handle_size: dimensions::SLIDER_HANDLE_SIZE,
168 track_color: colors::BACKGROUND_TERTIARY,
169 fill_color: colors::PRIMARY.with_alpha(0.3),
170 handle_color: colors::PRIMARY,
171 });
172
173 slider_entity.with_children(|track| {
174 track.spawn((
176 Node {
177 width: Val::Percent(100.0),
178 height: Val::Px(dimensions::SLIDER_TRACK_HEIGHT),
179 position_type: PositionType::Absolute,
180 ..default()
181 },
182 BackgroundColor(colors::BACKGROUND_TERTIARY),
183 BorderRadius::all(Val::Px(dimensions::SLIDER_TRACK_HEIGHT / 2.0)),
184 ));
185
186 let fill_width = slider.normalized() * 100.0;
188 track.spawn((
189 Node {
190 width: Val::Percent(fill_width),
191 height: Val::Px(dimensions::SLIDER_TRACK_HEIGHT),
192 position_type: PositionType::Absolute,
193 ..default()
194 },
195 BackgroundColor(colors::PRIMARY.with_alpha(0.3)),
196 BorderRadius::all(Val::Px(dimensions::SLIDER_TRACK_HEIGHT / 2.0)),
197 SliderFill,
198 ));
199
200 let handle_offset = slider.normalized() * 100.0;
202 track.spawn((
203 Node {
204 width: Val::Px(dimensions::SLIDER_HANDLE_SIZE),
205 height: Val::Px(dimensions::SLIDER_HANDLE_SIZE),
206 position_type: PositionType::Absolute,
207 left: Val::Percent(handle_offset.min(95.0)),
208 top: Val::Px(0.0),
209 border: UiRect::all(Val::Px(dimensions::BORDER_WIDTH_MEDIUM)),
210 ..default()
211 },
212 BackgroundColor(colors::PRIMARY),
213 BorderColor(colors::BORDER_LIGHT),
214 BorderRadius::all(Val::Px(dimensions::SLIDER_HANDLE_SIZE / 2.0)),
215 SliderHandle,
216 ));
217 });
218
219 if self.with_buttons {
221 container.spawn((
222 Node {
223 width: Val::Percent(100.0),
224 flex_direction: FlexDirection::Row,
225 justify_content: JustifyContent::Center,
226 column_gap: Val::Px(dimensions::SPACING_MEDIUM),
227 margin: UiRect::top(Val::Px(dimensions::SPACING_SMALL)),
228 ..default()
229 },
230 BackgroundColor(Color::NONE),
231 )).with_children(|button_row| {
232 let dec_button = ButtonBuilder::new("-")
234 .style(ButtonStyle::Secondary)
235 .size(ButtonSize::Small)
236 .build(button_row);
237
238 button_row.commands()
239 .entity(dec_button)
240 .insert(SliderButtonAction {
241 slider_entity: track_entity,
242 delta: -self.step.unwrap_or((self.max - self.min) / 100.0),
243 });
244
245 let inc_button = ButtonBuilder::new("+")
247 .style(ButtonStyle::Secondary)
248 .size(ButtonSize::Small)
249 .build(button_row);
250
251 button_row.commands()
252 .entity(inc_button)
253 .insert(SliderButtonAction {
254 slider_entity: track_entity,
255 delta: self.step.unwrap_or((self.max - self.min) / 100.0),
256 });
257 });
258 }
259 });
260
261 container
262 }
263}