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}