bevy_ui_builders/slider/
builder.rs

1//! SliderBuilder implementation
2
3use bevy::prelude::*;
4use bevy::ui::RelativeCursorPosition;
5use crate::button::{ButtonBuilder, ButtonSize, ButtonStyle};
6use crate::styles::{colors, dimensions};
7use super::types::*;
8
9/// Builder for creating sliders
10pub 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    /// Create a new slider builder with range
24    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    /// Set the initial value
39    pub fn value(mut self, value: f32) -> Self {
40        self.value = value.clamp(self.min, self.max);
41        self
42    }
43
44    /// Set the step size for snapping
45    pub fn step(mut self, step: f32) -> Self {
46        self.step = Some(step);
47        self
48    }
49
50    /// Set the width
51    pub fn width(mut self, width: Val) -> Self {
52        self.width = width;
53        self
54    }
55
56    /// Set the value format
57    pub fn format(mut self, format: ValueFormat) -> Self {
58        self.format = format;
59        self
60    }
61
62    /// Show/hide value preview
63    pub fn with_preview(mut self, show: bool) -> Self {
64        self.with_preview = show;
65        self
66    }
67
68    /// Add increment/decrement buttons
69    pub fn with_buttons(mut self) -> Self {
70        self.with_buttons = true;
71        self
72    }
73
74    /// Add a label
75    pub fn label(mut self, label: impl Into<String>) -> Self {
76        self.label = Some(label.into());
77        self
78    }
79
80    /// Build the slider
81    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            // Label and value row
97            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                    // Label
108                    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                    // Value text
121                    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            // Slider track and handle
137            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 background
175                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                // Filled portion
187                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                // Handle
201                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            // Add increment/decrement buttons if requested
220            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                    // Decrement button
233                    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                    // Increment button
246                    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}