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}