paiagram/interface/tabs/
vehicle.rs

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