paiagram/interface/tabs/
station_timetable.rs

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
157/// Display station times in Japanese style timetable
158pub 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: &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}