paiagram/interface/tabs/
line_timetable.rs

1use 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    // shows the a/d time
79    Stop,
80    // shows a "レ" symbol
81    NonStop,
82    // shows "||"
83    DoesNotPass,
84    // shows "··"
85    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    // create schedule -> timetable entry map
101    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            // push the current entry to the corresponding service vector
111            // if it does not exist yet, create it
112            // Note that the vector is guaranteed to be in order since we are iterating
113            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}