1use crate::{
2 interface::{
3 AppTab, UiCommand,
4 tabs::{Tab, vehicle},
5 },
6 intervals::{Depot, Station, StationCache},
7 units::time::TimetableTime,
8 vehicles::{
9 AdjustTimetableEntry, TimetableAdjustment, Vehicle,
10 entries::{
11 ActualRouteEntry, TimetableEntry, TimetableEntryCache, TravelMode, VehicleSchedule,
12 VehicleScheduleCache,
13 },
14 services::VehicleService,
15 vehicle_set::VehicleSet,
16 },
17};
18use bevy::{
19 ecs::{
20 entity::Entity,
21 hierarchy::{ChildOf, Children},
22 message::{MessageReader, MessageWriter},
23 name::Name,
24 query::With,
25 system::{In, InMut, Local, Query},
26 },
27 log::{error, info},
28};
29use egui::{Button, Frame, Label, Rect, RichText, Sense, Separator, UiBuilder, Widget, vec2};
30use egui_table::{Column, Table, TableDelegate};
31
32#[derive(PartialEq, Debug, Clone, Copy)]
33pub struct StationTimetableTab {
34 pub station_entity: Entity,
35}
36
37impl Tab for StationTimetableTab {
38 const NAME: &'static str = "Station Timetable";
39 fn main_display(&mut self, world: &mut bevy::ecs::world::World, ui: &mut egui::Ui) {
40 if let Err(e) =
41 world.run_system_cached_with(show_station_timetable, (ui, self.station_entity))
42 {
43 error!("UI Error while displaying station timetable page: {}", e)
44 }
45 }
46 fn id(&self) -> egui::Id {
47 egui::Id::new(self.station_entity)
48 }
49}
50
51struct TableCache<'a> {
52 msg_open_ui: MessageWriter<'a, UiCommand>,
53 times: &'a [Vec<(&'a str, &'a str, TimetableTime, Entity)>],
54 page_settings: &'a PageSettings,
55}
56
57impl TableDelegate for TableCache<'_> {
58 fn default_row_height(&self) -> f32 {
59 let mut height = 20.0 + 7.0;
60 if self.page_settings.show_service_name {
61 height += 20.0;
62 }
63 if self.page_settings.show_terminal_station {
64 height += 20.0;
65 }
66 height
67 }
68 fn header_cell_ui(&mut self, ui: &mut egui::Ui, cell: &egui_table::HeaderCellInfo) {}
69 fn cell_ui(&mut self, ui: &mut egui::Ui, cell: &egui_table::CellInfo) {
70 let i = cell.row_nr as usize;
71 match cell.col_nr {
72 0 => {
73 ui.style_mut().spacing.item_spacing.x = 0.0;
74 ui.set_width(ui.available_width() - 1.0);
75 ui.centered_and_justified(|ui| {
76 ui.label(format!("{:02}", i));
77 });
78 ui.add(Separator::default().spacing(0.0).vertical());
79 }
80 1 => {
81 let entries = &self.times[i];
82 ui.style_mut().spacing.item_spacing.x = 0.0;
83 for (station_name, service_name, time, entity) in entries {
84 ui.add_space(6.0);
85 let (rect, resp) = ui.allocate_exact_size(
86 vec2(
87 if self.page_settings.show_service_name
88 || self.page_settings.show_terminal_station
89 {
90 67.0
91 } else {
92 20.0
93 },
94 ui.available_height(),
95 ),
96 Sense::click(),
97 );
98 let response = ui
99 .scope_builder(
100 UiBuilder::new().sense(Sense::click()).max_rect(rect),
101 |ui| {
102 let response = ui.response();
103 let visuals = ui.style().interact(&response);
104 let mut stroke = visuals.bg_stroke;
105 stroke.width = 1.5;
106 Frame::canvas(ui.style())
107 .fill(visuals.bg_fill.gamma_multiply(0.2))
108 .stroke(stroke)
109 .show(ui, |ui| {
110 ui.set_width(ui.available_width());
111 ui.vertical_centered_justified(|ui| {
112 if self.page_settings.show_service_name {
113 ui.add(Label::new(*service_name).truncate());
114 }
115 ui.monospace(time.to_hmsd().1.to_string());
116 if self.page_settings.show_terminal_station {
117 ui.add(Label::new(*station_name).truncate());
118 }
119 });
120 });
121 },
122 )
123 .response;
124 if response.clicked() {
125 self.msg_open_ui
126 .write(UiCommand::OpenOrFocusTab(AppTab::Vehicle(
127 vehicle::VehicleTab(*entity),
128 )));
129 }
130 }
131 }
132 _ => unreachable!(),
133 };
134 }
135}
136
137#[derive(Default)]
138pub struct SelectedLineCache {
139 vehicle_set: Option<Entity>,
140 children: Vec<Entity>,
141 name: String,
142}
143
144pub struct PageSettings {
145 show_service_name: bool,
146 show_terminal_station: bool,
147}
148impl Default for PageSettings {
149 fn default() -> Self {
150 Self {
151 show_service_name: true,
152 show_terminal_station: true,
153 }
154 }
155}
156
157pub fn show_station_timetable(
159 (InMut(ui), In(station)): (InMut<egui::Ui>, In<Entity>),
160 vehicle_sets: Query<(Entity, &Children, &Name), With<VehicleSet>>,
161 vehicles: Query<(Entity, &Name, &VehicleScheduleCache, &VehicleSchedule), With<Vehicle>>,
162 station_names: Query<&Name, With<Station>>,
163 station_caches: Query<&StationCache>,
164 service_names: Query<&Name, With<VehicleService>>,
165 timetable_entries: Query<(&TimetableEntry, &TimetableEntryCache, &ChildOf)>,
166 msg_open_ui: MessageWriter<UiCommand>,
167 mut page_settings: Local<PageSettings>,
168 mut selected_line_cache: Local<SelectedLineCache>,
169) {
170 let mut selected_line_info: Option<Entity> = selected_line_cache.vehicle_set;
171 egui::ComboBox::from_label("Vehicle set")
172 .selected_text(selected_line_cache.name.as_str())
173 .show_ui(ui, |ui| {
174 for (vehicle_set_entity, children, name) in vehicle_sets.iter() {
175 if ui
176 .selectable_value(
177 &mut selected_line_info,
178 Some(vehicle_set_entity),
179 name.as_str(),
180 )
181 .clicked()
182 {
183 selected_line_cache.name = name.to_string();
184 selected_line_cache.vehicle_set = Some(vehicle_set_entity);
185 selected_line_cache.children = children.to_vec();
186 }
187 }
188 });
189 if selected_line_info.is_none() {
190 ui.label("No vehicle set selected.");
191 return;
192 }
193 ui.checkbox(&mut page_settings.show_service_name, "Show service name");
194 ui.checkbox(
195 &mut page_settings.show_terminal_station,
196 "Show terminus station",
197 );
198 let mut times: Vec<Vec<(&str, &str, TimetableTime, Entity)>> = vec![Vec::new(); 24];
199 if let Ok(station_cache) = station_caches.get(station) {
200 for (entry, entry_cache, parent) in station_cache
201 .passing_entries
202 .iter()
203 .filter_map(|e| timetable_entries.get(*e).ok())
204 {
205 if !selected_line_cache.children.contains(&parent.0) {
206 continue;
207 }
208 if entry.departure.is_none() {
209 continue;
210 }
211 let Some(estimate) = &entry_cache.estimate else {
212 continue;
213 };
214 let (hour, ..) = estimate.departure.to_hmsd();
215 let index = hour.rem_euclid(24) as usize;
216 let mut terminal_name = "---";
217 let mut service_name = "---";
218 if let Ok((_, _, schedule_cache, _schedule)) = vehicles.get(parent.0)
219 && let Some(entry_service) = entry.service
220 && let Some(last_entry_entity) = schedule_cache
221 .get_service_last_entry(entry_service)
222 .map(ActualRouteEntry::inner)
223 && let Ok((last_entry, _, _)) = timetable_entries.get(last_entry_entity)
224 && let Ok(name) = station_names.get(last_entry.station)
225 {
226 terminal_name = name
227 }
228 if let Some(entry_service) = entry.service
229 && let Ok(name) = service_names.get(entry_service)
230 {
231 service_name = name;
232 }
233 times[index].push((terminal_name, service_name, estimate.departure, parent.0))
234 }
235 }
236 for time in &mut times {
237 time.sort_by_key(|k| k.2.to_hmsd().1);
238 }
239 let mut table_cache = TableCache {
240 msg_open_ui: msg_open_ui,
241 times: ×,
242 page_settings: &page_settings,
243 };
244 Table::new()
245 .num_rows(24u64)
246 .headers(vec![])
247 .columns(vec![
248 Column::new(30.0).resizable(false),
249 Column::new(500.0).resizable(true),
250 ])
251 .num_sticky_cols(1)
252 .show(ui, &mut table_cache);
253}