Skip to main content

traaains/ui/train/
adding_train.rs

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/// Component identifying a carriage list item with the index of the carriage in [CurrentPlacingTrain].
53#[derive(Clone, Component, Default)]
54struct CarriageListItem(pub usize);
55
56#[derive(Clone, Component, Default)]
57struct CarriagesList;
58
59#[derive(Component)]
60pub struct AddingTrainWindow;
61
62/// Find parent with [CarriageListItem] component and returns the index.
63fn 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    // find our index
88    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    // state galore
224    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}