paiagram/interface/tabs/
vehicle.rs

1use crate::interface::tabs::{Tab, station_timetable::StationTimetableTab};
2use crate::interface::widgets::timetable_popup;
3use crate::interface::{AppTab, UiCommand};
4use crate::vehicles::entries::{ActualRouteEntry, TimetableEntryCache, VehicleScheduleCache};
5use crate::vehicles::entries::{TimetableEntry, VehicleSchedule};
6use crate::vehicles::{AdjustTimetableEntry, TimetableAdjustment};
7use bevy::ecs::entity::MapEntities;
8use bevy::prelude::*;
9use egui::{Popup, Ui};
10use egui_table::{CellInfo, HeaderCellInfo, Table, TableDelegate, columns::Column};
11use serde::{Deserialize, Serialize};
12
13#[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)]
14pub struct VehicleTab(pub Entity);
15
16impl MapEntities for VehicleTab {
17    fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
18        self.0.map_entities(entity_mapper);
19    }
20}
21
22impl Tab for VehicleTab {
23    const NAME: &'static str = "Vehicle";
24    fn main_display(&mut self, world: &mut bevy::ecs::world::World, ui: &mut Ui) {
25        if let Err(e) = world.run_system_cached_with(show_vehicle, (ui, self.0)) {
26            error!("UI Error while displaying vehicle page: {}", e)
27        }
28    }
29    fn title(&self) -> egui::WidgetText {
30        "Vehicle".into()
31    }
32    fn id(&self) -> egui::Id {
33        egui::Id::new(format!("VehicleTab_{:?}", self.0))
34    }
35    fn scroll_bars(&self) -> [bool; 2] {
36        [false; 2]
37    }
38}
39
40const COLUMN_NAMES: &[&str] = &["Station", "Arri.", "Dept.", "Service", "Track", "Parent"];
41
42struct TimetableInfo<'a> {
43    station_name: Option<&'a str>,
44    service_name: Option<&'a str>,
45    track_name: Option<&'a str>,
46    parent_name: Option<&'a str>,
47    entry: &'a TimetableEntry,
48    entry_cache: &'a TimetableEntryCache,
49}
50
51struct TableCache<'a> {
52    entries: Vec<TimetableInfo<'a>>,
53    timetable_entities: Option<&'a [ActualRouteEntry]>,
54    msg_sender: MessageWriter<'a, AdjustTimetableEntry>,
55    msg_open_ui: MessageWriter<'a, UiCommand>,
56    vehicle_set: Entity,
57}
58
59impl<'a> TableCache<'a> {
60    fn new(
61        vehicle_schedule_cache: &'a VehicleScheduleCache,
62        timetable_entries: &'a Query<(&TimetableEntry, &TimetableEntryCache, &ChildOf)>,
63        names: &'a Query<(Entity, &Name)>,
64        msg_sender: MessageWriter<'a, AdjustTimetableEntry>,
65        msg_open_ui: MessageWriter<'a, UiCommand>,
66        vehicle_set: Entity,
67    ) -> Self {
68        let schedule_length = vehicle_schedule_cache
69            .actual_route
70            .as_ref()
71            .map_or(0, |r| r.len());
72        let mut entries = Vec::with_capacity(schedule_length);
73        for timetable_entry_entity in vehicle_schedule_cache.actual_route.iter().flatten() {
74            let Ok((entry, entry_cache, parent)) =
75                timetable_entries.get(timetable_entry_entity.inner())
76            else {
77                continue;
78            };
79            let station_name = names
80                .get(entry.station.entity())
81                .ok()
82                .and_then(|(_, name)| Some(name.as_str()));
83            let service_name = match entry.service {
84                Some(service_entity) => names
85                    .get(service_entity.entity())
86                    .ok()
87                    .and_then(|(_, name)| Some(name.as_str())),
88                None => None,
89            };
90            let track_name = match entry.track {
91                Some(track_entity) => names
92                    .get(track_entity)
93                    .ok()
94                    .and_then(|(_, name)| Some(name.as_str())),
95                None => None,
96            };
97            let parent_name = names
98                .get(parent.0)
99                .ok()
100                .and_then(|(_, name)| Some(name.as_str()));
101            let info = TimetableInfo {
102                station_name,
103                service_name,
104                track_name,
105                parent_name,
106                entry,
107                entry_cache,
108            };
109            entries.push(info);
110        }
111        Self {
112            entries,
113            msg_sender,
114            msg_open_ui,
115            vehicle_set,
116            timetable_entities: vehicle_schedule_cache.actual_route.as_deref(),
117        }
118    }
119}
120
121impl<'a> TableDelegate for TableCache<'a> {
122    fn header_cell_ui(&mut self, ui: &mut Ui, cell: &HeaderCellInfo) {
123        ui.add_space(4.0);
124        ui.style_mut().spacing.item_spacing.x = 4.0;
125        ui.label(COLUMN_NAMES[cell.group_index]);
126        ui.add_space(4.0);
127    }
128    fn cell_ui(&mut self, ui: &mut Ui, cell: &CellInfo) {
129        let i = cell.row_nr as usize;
130        ui.add_space(4.0);
131        ui.style_mut().spacing.item_spacing.x = 4.0;
132        egui::Frame::new().show(ui, |ui| match cell.col_nr {
133            0 => {
134                if ui.button("☰").clicked() {
135                    info!("123");
136                }
137                if ui.button("ℹ").clicked() {
138                    self.msg_open_ui
139                        .write(UiCommand::OpenOrFocusTab(AppTab::StationTimetable(
140                            StationTimetableTab {
141                                station_entity: self.entries[i].entry.station(),
142                            },
143                        )));
144                }
145                ui.label(self.entries[i].station_name.unwrap_or("---"));
146            }
147            1 => {
148                let response = ui.button(self.entries[i].entry.arrival.to_string());
149                let Some(timetable_entities) = self.timetable_entities else {
150                    return;
151                };
152                Popup::menu(&response)
153                    .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
154                    .show(|ui| {
155                        timetable_popup::popup(
156                            timetable_entities[i].inner(),
157                            (self.entries[i].entry, self.entries[i].entry_cache),
158                            if i == 0 {
159                                None
160                            } else {
161                                Some((self.entries[i - 1].entry, self.entries[i - 1].entry_cache))
162                            },
163                            &mut self.msg_sender,
164                            ui,
165                            true,
166                        )
167                    });
168            }
169            2 => {
170                let response = ui.button(
171                    self.entries[i]
172                        .entry
173                        .departure
174                        .map(|t| t.to_string())
175                        .unwrap_or("||".to_string()),
176                );
177                let Some(timetable_entities) = self.timetable_entities else {
178                    return;
179                };
180                Popup::menu(&response)
181                    .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
182                    .show(|ui| {
183                        timetable_popup::popup(
184                            timetable_entities[i].inner(),
185                            (self.entries[i].entry, self.entries[i].entry_cache),
186                            if i == 0 {
187                                None
188                            } else {
189                                Some((self.entries[i - 1].entry, self.entries[i - 1].entry_cache))
190                            },
191                            &mut self.msg_sender,
192                            ui,
193                            false,
194                        )
195                    });
196            }
197            3 => {
198                ui.label(self.entries[i].service_name.unwrap_or("---"));
199            }
200            4 => {
201                ui.label(self.entries[i].track_name.unwrap_or("---"));
202            }
203            5 => {
204                ui.label(self.entries[i].parent_name.unwrap_or("---"));
205            }
206            _ => unreachable!(),
207        });
208        ui.add_space(4.0);
209    }
210}
211
212pub fn show_vehicle(
213    (InMut(ui), In(entity)): (InMut<egui::Ui>, In<Entity>),
214    schedules: Query<(&VehicleSchedule, &VehicleScheduleCache, &ChildOf)>,
215    timetable_entries: Query<(&TimetableEntry, &TimetableEntryCache, &ChildOf)>,
216    names: Query<(Entity, &Name)>,
217    mut msg_sender: MessageWriter<AdjustTimetableEntry>,
218    msg_open_ui: MessageWriter<UiCommand>,
219    mut show: Local<bool>,
220) {
221    let Ok((vehicle_schedule, vehicle_schedule_cache, parent)) = schedules.get(entity) else {
222        ui.label("The vehicle does not exist.");
223        return;
224    };
225    if ui.button("Refresh").clicked() {
226        for (schedule, _, _) in schedules {
227            for entity in schedule.entities.iter().cloned() {
228                msg_sender.write(AdjustTimetableEntry {
229                    entity,
230                    adjustment: TimetableAdjustment::PassThrough,
231                });
232            }
233        }
234    }
235    let old_show = *show;
236    ui.selectable_value(&mut *show, !old_show, "show");
237    if !*show {
238        return;
239    }
240    let mut current_table_cache = TableCache::new(
241        vehicle_schedule_cache,
242        &timetable_entries,
243        &names,
244        msg_sender,
245        msg_open_ui,
246        parent.0,
247    );
248    Table::new()
249        .num_rows(vehicle_schedule.entities.len() as u64)
250        .columns(
251            (0..COLUMN_NAMES.len())
252                .map(|v| match v {
253                    0 => Column::new(100.0).resizable(true),
254                    1 => Column::new(90.0).resizable(false),
255                    2 => Column::new(90.0).resizable(false),
256                    3 => Column::new(100.0).resizable(true),
257                    4 => Column::new(100.0).resizable(true),
258                    5 => Column::new(100.0).resizable(true),
259                    _ => unreachable!(),
260                })
261                .collect::<Vec<_>>(),
262        )
263        .num_sticky_cols(1)
264        .show(ui, &mut current_table_cache);
265}