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