bevy_ui_builders/dropdown/
builder.rs

1//! DropdownBuilder for creating dropdown select components
2
3use bevy::prelude::*;
4use super::types::*;
5use crate::styles::{colors, dimensions};
6use crate::relationships::BelongsToDropdown;
7
8/// Builder for creating dropdown select components
9///
10/// # Examples
11///
12/// ```ignore
13/// use bevy_ui_builders::prelude::*;
14///
15/// fn build_dropdown(parent: &mut ChildSpawnerCommands) {
16///     DropdownBuilder::new(vec!["Option 1".to_string(), "Option 2".to_string()])
17///         .placeholder("Select an option")
18///         .build(parent);
19/// }
20/// ```
21pub struct DropdownBuilder {
22    options: Vec<String>,
23    selected_index: Option<usize>,
24    placeholder: String,
25    width: Val,
26}
27
28impl DropdownBuilder {
29    /// Create a new dropdown with the given options
30    pub fn new(options: Vec<String>) -> Self {
31        Self {
32            options,
33            selected_index: None,
34            placeholder: "Select an option".to_string(),
35            width: Val::Px(200.0),
36        }
37    }
38
39    /// Set the placeholder text
40    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
41        self.placeholder = placeholder.into();
42        self
43    }
44
45    /// Set the initially selected index
46    pub fn selected_index(mut self, index: Option<usize>) -> Self {
47        self.selected_index = index;
48        self
49    }
50
51    /// Set the width of the dropdown
52    pub fn width(mut self, width: Val) -> Self {
53        self.width = width;
54        self
55    }
56
57    /// Build the dropdown and spawn it
58    pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
59        let data = DropdownData {
60            options: self.options.clone(),
61            selected_index: self.selected_index,
62            placeholder: self.placeholder.clone(),
63        };
64
65        let display_text = data.display_text().to_string();
66
67        // Temporary variable to hold dropdown entity ID
68        let mut dropdown_entity = Entity::PLACEHOLDER;
69
70        // Main dropdown container
71        let container_id = parent.spawn((
72            Node {
73                width: self.width,
74                flex_direction: FlexDirection::Column,
75                position_type: PositionType::Relative,
76                ..default()
77            },
78            Dropdown,
79            DropdownState::Closed,
80            data,
81        )).with_children(|dropdown| {
82            dropdown_entity = dropdown.target_entity();
83            // Dropdown button
84            dropdown.spawn((
85                Node {
86                    width: Val::Percent(100.0),
87                    height: Val::Px(dimensions::INPUT_HEIGHT),
88                    padding: UiRect::all(Val::Px(dimensions::PADDING_SMALL)),
89                    border: UiRect::all(Val::Px(dimensions::BORDER_WIDTH_THIN)),
90                    justify_content: JustifyContent::SpaceBetween,
91                    align_items: AlignItems::Center,
92                    flex_direction: FlexDirection::Row,
93                    ..default()
94                },
95                BackgroundColor(colors::BACKGROUND_TERTIARY),
96                BorderColor::all(colors::BORDER_DEFAULT),
97                BorderRadius::all(Val::Px(dimensions::BORDER_RADIUS_SMALL)),
98                DropdownButton,
99                Interaction::default(),
100            )).with_children(|button| {
101                // Selected value text
102                button.spawn((
103                    Text::new(display_text),
104                    TextFont {
105                        font_size: dimensions::FONT_SIZE_NORMAL,
106                        ..default()
107                    },
108                    TextColor(colors::TEXT_PRIMARY),
109                ));
110
111                // Down arrow indicator (ASCII for maximum compatibility)
112                button.spawn((
113                    Text::new("v"),
114                    TextFont {
115                        font_size: dimensions::FONT_SIZE_SMALL,
116                        ..default()
117                    },
118                    TextColor(colors::TEXT_SECONDARY),
119                ));
120            });
121
122            // Dropdown menu (initially hidden)
123            let menu_id = dropdown.spawn((
124                Node {
125                    width: Val::Percent(100.0),
126                    max_height: Val::Px(200.0),
127                    position_type: PositionType::Absolute,
128                    top: Val::Px(dimensions::INPUT_HEIGHT + 4.0),
129                    left: Val::Px(0.0),
130                    flex_direction: FlexDirection::Column,
131                    display: Display::None, // Hidden by default
132                    overflow: Overflow::scroll_y(),
133                    border: UiRect::all(Val::Px(2.0)),
134                    ..default()
135                },
136                BackgroundColor(Color::srgb(0.08, 0.08, 0.1)), // Fully opaque dark background
137                BorderColor::all(colors::BORDER_DEFAULT),
138                BorderRadius::all(Val::Px(dimensions::BORDER_RADIUS_SMALL)),
139                GlobalZIndex(dimensions::Z_INDEX_MODAL), // Use GlobalZIndex to appear above ALL UI elements
140                DropdownMenu,
141                BelongsToDropdown(dropdown_entity),
142            )).with_children(|menu| {
143                // Spawn options
144                for (index, option) in self.options.iter().enumerate() {
145                    menu.spawn((
146                        Node {
147                            width: Val::Percent(100.0),
148                            padding: UiRect::all(Val::Px(dimensions::PADDING_SMALL)),
149                            ..default()
150                        },
151                        BackgroundColor(if Some(index) == self.selected_index {
152                            Color::srgba(0.3, 0.5, 0.8, 0.3)
153                        } else {
154                            Color::NONE
155                        }),
156                        DropdownOption { index },
157                        Interaction::default(),
158                        BelongsToDropdown(dropdown_entity),
159                    )).with_children(|option_container| {
160                        option_container.spawn((
161                            Text::new(option.clone()),
162                            TextFont {
163                                font_size: dimensions::FONT_SIZE_NORMAL,
164                                ..default()
165                            },
166                            TextColor(colors::TEXT_PRIMARY),
167                        ));
168                    });
169                }
170            }).id();
171        }).id();
172
173        container_id
174    }
175}