paiagram/interface/tabs/
line_timetable.rs1use crate::intervals::Station;
2use crate::lines::DisplayedLine;
3use crate::vehicles::{ArrivalType, Schedule, Service, TimetableEntry, Vehicle};
4use bevy::prelude::*;
5use bevy_egui::egui::{self};
6use egui_deferred_table::{
7 AxisParameters, CellIndex, DeferredTable, DeferredTableDataSource, DeferredTableRenderer,
8 TableDimensions,
9};
10use indexmap::IndexMap;
11
12struct LineTimetableDataSource {
13 dimensions: TableDimensions,
14 cells: Vec<Option<ArrivalType>>,
15}
16
17impl LineTimetableDataSource {
18 fn new(row_count: usize, column_count: usize) -> Self {
19 let cell_count = row_count.saturating_mul(column_count);
20 Self {
21 dimensions: TableDimensions {
22 row_count,
23 column_count,
24 },
25 cells: vec![None; cell_count],
26 }
27 }
28
29 fn set(&mut self, row: usize, column: usize, value: ArrivalType) {
30 if self.dimensions.column_count == 0 {
31 return;
32 }
33 let idx = row * self.dimensions.column_count + column;
34 if idx < self.cells.len() {
35 self.cells[idx] = Some(value);
36 }
37 }
38
39 fn arrival(&self, row: usize, column: usize) -> Option<ArrivalType> {
40 if self.dimensions.column_count == 0 {
41 return None;
42 }
43 let idx = row * self.dimensions.column_count + column;
44 self.cells.get(idx).and_then(|value| *value)
45 }
46}
47
48impl DeferredTableDataSource for LineTimetableDataSource {
49 fn get_dimensions(&self) -> TableDimensions {
50 self.dimensions
51 }
52}
53
54struct LineTimetableRenderer;
55
56impl DeferredTableRenderer<LineTimetableDataSource> for LineTimetableRenderer {
57 fn render_cell(
58 &self,
59 ui: &mut bevy_egui::egui::Ui,
60 cell_index: CellIndex,
61 source: &LineTimetableDataSource,
62 ) {
63 let text = match source.arrival(cell_index.row, cell_index.column) {
64 Some(arrival) => match arrival {
65 ArrivalType::At(time) => time.to_hhmm_string_no_colon(),
66 _ => "··".to_string(),
67 },
68 None => "··".to_string(),
69 };
70 ui.with_layout(
71 egui::Layout::centered_and_justified(egui::Direction::LeftToRight),
72 |ui| ui.monospace(text),
73 );
74 }
75}
76
77enum LineEntryState {
78 Stop,
80 NonStop,
82 DoesNotPass,
84 Ended,
86}
87
88pub fn show_line_timetable(
89 (InMut(ui), In(entity)): (InMut<egui::Ui>, In<Entity>),
90 displayed_lines: Query<(&Name, &DisplayedLine)>,
91 schedules: Query<&Schedule, With<Vehicle>>,
92 services: Query<&Name, With<Service>>,
93 entries: Query<&TimetableEntry>,
94 stations: Query<&Name, With<Station>>,
95) {
96 let Ok((_line_name, line)) = displayed_lines.get(entity) else {
97 ui.label("No displayed lines found for this entity.");
98 return;
99 };
100 let mut service_entries: IndexMap<Entity, Vec<(Entity, Entity)>> = IndexMap::new();
102 for schedule in schedules {
103 for entry_entity in &schedule.1 {
104 let Ok(entry) = entries.get(*entry_entity) else {
105 continue;
106 };
107 let Some(service) = entry.service else {
108 continue;
109 };
110 service_entries
114 .entry(service)
115 .or_default()
116 .push((entry.station, *entry_entity));
117 }
118 }
119 let row_count = line.0.len();
120 let column_count = service_entries.len();
121
122 if row_count == 0 || column_count == 0 {
123 ui.label("No timetable data available for this line.");
124 return;
125 }
126
127 let mut data_source = LineTimetableDataSource::new(row_count, column_count);
128
129 for (column_index, (_service_entity, entries_for_service)) in service_entries.iter().enumerate()
130 {
131 for (row_index, (station_entity, _)) in line.0.iter().enumerate() {
132 if let Some((_station, entry_entity)) = entries_for_service
133 .iter()
134 .find(|(station, _)| station == station_entity)
135 {
136 if let Ok(entry) = entries.get(*entry_entity) {
137 data_source.set(row_index, column_index, entry.arrival);
138 }
139 }
140 }
141 }
142
143 let mut column_parameters: Vec<AxisParameters> = Vec::with_capacity(column_count);
144 for i in 0..column_count {
145 column_parameters.push(
146 AxisParameters::default()
147 .name({
148 services
149 .get(*service_entries.get_index(i).unwrap().0)
150 .map(|name| name.as_str().to_owned())
151 .unwrap_or_else(|_| "<unknown>".to_string())
152 })
153 .default_dimension(28.0)
154 .resizable(false),
155 );
156 }
157
158 let station_names: Vec<String> = line
159 .0
160 .iter()
161 .map(|(station_entity, _)| {
162 stations
163 .get(*station_entity)
164 .map(|name| name.as_str().to_owned())
165 .unwrap_or_else(|_| "<unknown>".to_string())
166 })
167 .collect();
168
169 let row_parameters: Vec<AxisParameters> = station_names
170 .iter()
171 .map(|name| {
172 AxisParameters::default()
173 .name(name.clone())
174 .default_dimension(14.0)
175 .resizable(false)
176 })
177 .collect();
178
179 let mut table = DeferredTable::new(ui.id().with("line_timetable"))
180 .default_cell_size(egui::vec2(80.0, 14.0))
181 .selectable_rows_disabled()
182 .highlight_hovered_cell();
183
184 if !column_parameters.is_empty() {
185 table = table.column_parameters(&column_parameters);
186 }
187
188 if !row_parameters.is_empty() {
189 table = table.row_parameters(&row_parameters);
190 }
191
192 let mut renderer = LineTimetableRenderer;
193 let (_response, _actions) = table.show(ui, &mut data_source, &mut renderer);
194}