paiagram/interface/tabs/
station_timetable.rs

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            // SAFETY: entity is expected to be a valid Station when loaded.
57            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
188/// Display station times in Japanese style timetable
189pub 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: &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}