1use std::cmp::Ordering;
2
3use bevy::{
4 ecs::{
5 component::Component,
6 entity::Entity,
7 event::EntityEvent,
8 hierarchy::{ChildOf, Children},
9 observer::On,
10 query::With,
11 system::{Commands, Query, ResMut, Single},
12 },
13 feathers::{
14 constants::icons,
15 containers::{
16 flex_spacer, group, group_body, group_header, pane, pane_body, pane_header, subpane,
17 subpane_body, subpane_header,
18 },
19 controls::{
20 ButtonVariant, FeathersButton, FeathersNumberInput, FeathersSelect, FeathersToolButton,
21 NumberInputPrecision, NumberInputValue, OptionIndex, list_rows_from_strings,
22 },
23 display::{icon, label},
24 theme::ThemedText,
25 },
26 log::tracing,
27 picking::events::{PointerEnter, PointerLeave},
28 scene::{CommandsSceneExt, Scene, bsn, on},
29 state::{state::NextState, state_scoped::DespawnOnExit},
30 ui::{
31 AlignItems, Display, FlexDirection, JustifyContent, Node, PositionType, percent, px,
32 widget::Text,
33 },
34 ui_widgets::{Activate, ValueChange},
35};
36use strum::IntoEnumIterator;
37
38use crate::{
39 editor::{
40 EditorState,
41 train::{
42 TrainEditorState,
43 placing::{
44 CurrentPlacingTrain,
45 templates::{CarriageBogiesTemplate, CarriageTemplate},
46 },
47 },
48 },
49 ui::{EditingWindow, IsHoveringUi, UiState},
50};
51
52#[derive(Clone, Component, Default)]
54struct CarriageListItem(pub usize);
55
56#[derive(Clone, Component, Default)]
57struct CarriagesList;
58
59#[derive(Component)]
60pub struct AddingTrainWindow;
61
62fn get_list_item_idx(
64 curr_entity: Entity,
65 target: &Query<&CarriageListItem>,
66 children: &Query<&ChildOf>,
67) -> Option<usize> {
68 if let Ok(t) = target.get(curr_entity) {
69 return Some(t.0);
70 }
71
72 if let Ok(child) = children.get(curr_entity) {
73 return get_list_item_idx(child.parent(), target, children);
74 }
75
76 None
77}
78
79fn remove_carriage(
80 e: On<Activate>,
81 mut cmd: Commands,
82 children: Query<&ChildOf>,
83 carriages_list: Single<(Entity, &Children), With<CarriagesList>>,
84 mut carriage_list_items: Query<&mut CarriageListItem>,
85 mut current_placing_train: ResMut<CurrentPlacingTrain>,
86) {
87 if let Some(target_carriage_idx) = get_list_item_idx(
89 e.event_target(),
90 &carriage_list_items
91 .transmute_lens::<&CarriageListItem>()
92 .query(),
93 &children,
94 ) {
95 current_placing_train.carriages.remove(target_carriage_idx);
96
97 for carriage_entity in carriages_list.1.iter() {
98 let mut curr_carriage_item = carriage_list_items.get_mut(*carriage_entity).unwrap();
99 match curr_carriage_item.0.cmp(&target_carriage_idx) {
100 Ordering::Greater => {
101 curr_carriage_item.0 -= 1;
102 }
103 Ordering::Equal => {
104 cmd.entity(*carriage_entity).despawn();
105 }
106 _ => {}
107 }
108 }
109 } else {
110 tracing::warn!("Failed to remove carriage, failed to find CarriageListItem!");
111 }
112}
113
114pub fn carriage_list_item(idx: usize, carriage_template: &CarriageTemplate) -> impl Scene {
115 let selected_bogie_idx = CarriageBogiesTemplate::iter()
116 .enumerate()
117 .find_map(|(i, v)| {
118 if v == carriage_template.bogies {
119 return Some(i);
120 }
121 None
122 });
123
124 bsn! {
125 @group()
126 CarriageListItem(idx)
127 Children [
128 @group_header() Children [
129 Text("Carriage") ThemedText
130 --
131 @flex_spacer()
132 --
133 @FeathersToolButton {
134 @variant: ButtonVariant::Plain,
135 }
136 Children [
137 @icon(icons::X)
138 ]
139 on(remove_carriage)
140 ]
141 --
142 @group_body() Children [
143 @label("Length (m):")
144 --
145 @FeathersNumberInput
146 NumberInputPrecision(3)
147 NumberInputValue::F32({carriage_template.length})
148 on(|e: On<ValueChange<f32>>, mut cmd: Commands, target: Query<&CarriageListItem>, children: Query<&ChildOf>, mut res: ResMut<CurrentPlacingTrain>| {
149 if let Some(carriage_idx) = get_list_item_idx(e.event_target(), &target, &children) {
150 cmd.entity(e.event_target()).insert(NumberInputValue::F32(e.value));
151 res.carriages[carriage_idx].length = e.value;
152 } else {
153 tracing::warn!("Failed to update carriage in adding train window, failed to find CarriageListItem!");
154 }
155 })
156 --
157 @label("Width (m):")
158 --
159 @FeathersNumberInput
160 NumberInputPrecision(3)
161 NumberInputValue::F32({carriage_template.width})
162 on(|e: On<ValueChange<f32>>, mut cmd: Commands, target: Query<&CarriageListItem>, children: Query<&ChildOf>, mut res: ResMut<CurrentPlacingTrain>| {
163 if let Some(carriage_idx) = get_list_item_idx(e.event_target(), &target, &children) {
164 cmd.entity(e.event_target()).insert(NumberInputValue::F32(e.value));
165 res.carriages[carriage_idx].width = e.value;
166 } else {
167 tracing::warn!("Failed to update carriage in adding train window, failed to find CarriageListItem!");
168 }
169 })
170 --
171 @label("Mass (kg):")
172 --
173 @FeathersNumberInput
174 NumberInputPrecision(3)
175 NumberInputValue::F32({carriage_template.mass})
176 on(|e: On<ValueChange<f32>>, mut cmd: Commands, target: Query<&CarriageListItem>, children: Query<&ChildOf>, mut res: ResMut<CurrentPlacingTrain>| {
177 if let Some(carriage_idx) = get_list_item_idx(e.event_target(), &target, &children) {
178 cmd.entity(e.event_target()).insert(NumberInputValue::F32(e.value));
179 res.carriages[carriage_idx].mass = e.value;
180 } else {
181 tracing::warn!("Failed to update carriage in adding train window, failed to find CarriageListItem!");
182 }
183 })
184 --
185 @label("Power (W):")
186 --
187 @FeathersNumberInput
188 NumberInputPrecision(0)
189 NumberInputValue::F32({carriage_template.power})
190 on(|e: On<ValueChange<f32>>, mut cmd: Commands, target: Query<&CarriageListItem>, children: Query<&ChildOf>, mut res: ResMut<CurrentPlacingTrain>| {
191 if let Some(carriage_idx) = get_list_item_idx(e.event_target(), &target, &children) {
192 cmd.entity(e.event_target()).insert(NumberInputValue::F32(e.value));
193 res.carriages[carriage_idx].power = e.value;
194 } else {
195 tracing::warn!("Failed to update carriage in adding train window, failed to find CarriageListItem!");
196 }
197 })
198 --
199 @label("Bogies")
200 @FeathersSelect {
201 @options: list_rows_from_strings(CarriageBogiesTemplate::iter().map(|s| s.to_string()), selected_bogie_idx),
202 }
203 on(|e: On<ValueChange<Entity>>, target: Query<&CarriageListItem>, children: Query<&ChildOf>, options: Query<&OptionIndex>, mut res: ResMut<CurrentPlacingTrain>| {
204 if let Some(carriage_idx) = get_list_item_idx(e.event_target(), &target, &children) {
205 if let Ok(option) = options.get(e.value) {
206 res.carriages[carriage_idx].bogies = CarriageBogiesTemplate::iter().nth(option.0).unwrap();
207 }
208 } else {
209 tracing::warn!("Failed to update carriage in adding train window, failed to find CarriageListItem!");
210 }
211 })
212 ]
213 ]
214 }
215}
216
217fn submit_add_train(
218 _: On<Activate>,
219 mut n_ui_state: ResMut<NextState<UiState>>,
220 mut n_edit_state: ResMut<NextState<EditorState>>,
221 mut n_track_edit_state: ResMut<NextState<TrainEditorState>>,
222) {
223 n_ui_state.set(UiState::Default);
225 n_edit_state.set(EditorState::Train);
226 n_track_edit_state.set(TrainEditorState::PlacingTrain);
227}
228
229pub fn adding_train_window(starting_template: &CurrentPlacingTrain) -> impl Scene {
230 let starting_carriages: Vec<_> = starting_template
231 .carriages
232 .iter()
233 .enumerate()
234 .map(|(i, t)| carriage_list_item(i, t))
235 .collect();
236
237 bsn! {
238 EditingWindow
239 Node {
240 position_type: PositionType::Absolute,
241 top: px(16),
242 left: px(16),
243 display: Display::Flex,
244 flex_direction: FlexDirection::Column,
245 align_items: AlignItems::Stretch,
246 justify_content: JustifyContent::Start,
247 width: percent(20),
248 min_width: px(200),
249 max_height: percent(80)
250 }
251 DespawnOnExit<UiState>(UiState::AddingTrain)
252 Children [
253 @pane() Children [
254 @pane_header() Children [ Text("Adding train") ThemedText ]
255 --
256 @pane_body() Children [
257 @FeathersButton {
258 @caption: bsn! { Text("Add carriage") ThemedText }
259 }
260 on(|_: On<Activate>, mut cmd: Commands, carriages_list: Single<Entity, With<CarriagesList>>, mut res: ResMut<CurrentPlacingTrain>| {
261 let template = CarriageTemplate::default();
262 let spawned_item = cmd.spawn_scene(carriage_list_item(res.carriages.len(), &template)).id();
263 res.carriages.push(template);
264 cmd.entity(*carriages_list).add_child(spawned_item);
265 })
266 --
267 @subpane()
268 Children [
269 @subpane_header() Children [ Text("Carriages") ThemedText ]
270 --
271 @subpane_body()
272 CarriagesList
273 Children [{starting_carriages}]
274 ]
275 --
276 @FeathersButton {
277 @caption: bsn! { Text("Add train") ThemedText }
278 }
279 on(submit_add_train)
280 ]
281 ]
282 ]
283 on(|_: On<PointerEnter>, mut next_state: ResMut<NextState<IsHoveringUi>>| {
284 next_state.set(IsHoveringUi(true));
285 })
286 on(|_: On<PointerLeave>, mut next_state: ResMut<NextState<IsHoveringUi>>| {
287 next_state.set(IsHoveringUi(false));
288 })
289 }
290}