paiagram/interface/
tabs.rs

1use std::borrow::Cow;
2
3use bevy::ecs::world::World;
4use egui::{Id, Response, Ui, WidgetText};
5use egui_i18n::tr;
6
7use crate::interface::StatusBarState;
8
9pub mod classes;
10pub mod diagram;
11pub mod displayed_lines;
12pub mod graph;
13pub mod inspector;
14pub mod minesweeper;
15pub mod overview;
16pub mod services;
17pub mod settings;
18pub mod start;
19pub mod station_timetable;
20pub mod tree_view;
21pub mod vehicle;
22
23pub mod all_tabs {
24    pub use super::classes::ClassesTab;
25    pub use super::diagram::DiagramTab;
26    pub use super::displayed_lines::DisplayedLinesTab;
27    pub use super::graph::GraphTab;
28    pub use super::inspector::InspectorTab;
29    pub use super::minesweeper::MinesweeperTab;
30    pub use super::overview::OverviewTab;
31    pub use super::services::ServicesTab;
32    pub use super::settings::SettingsTab;
33    pub use super::start::StartTab;
34    pub use super::station_timetable::StationTimetableTab;
35    pub use super::vehicle::VehicleTab;
36}
37
38/// The page cache. Lots of r/w, few insertions, good locality, fast executions.
39#[derive(Debug)]
40pub struct PageCache<K, V>
41where
42    K: PartialEq + Ord,
43{
44    keys: Vec<K>,
45    vals: Vec<V>,
46}
47
48const LINEAR_THRESHOLD: usize = 64;
49
50impl<K, V> PageCache<K, V>
51where
52    K: PartialEq + Ord,
53{
54    /// Get the page cache or insert with a custom value.
55    pub fn get_mut_or_insert_with<F>(&mut self, query_key: K, make_val: F) -> &mut V
56    where
57        F: FnOnce() -> V,
58    {
59        if self.keys.len() < LINEAR_THRESHOLD
60            && let Some(idx) = self.keys.iter().position(|e| *e == query_key)
61        {
62            return &mut self.vals[idx];
63        }
64        return match self.keys.binary_search(&query_key) {
65            Ok(idx) => &mut self.vals[idx],
66            Err(idx) => {
67                self.keys.insert(idx, query_key);
68                self.vals.insert(idx, make_val());
69                return &mut self.vals[idx];
70            }
71        };
72    }
73}
74
75impl<K, V> Default for PageCache<K, V>
76where
77    K: PartialEq + Ord,
78{
79    fn default() -> Self {
80        Self {
81            keys: Vec::new(),
82            vals: Vec::new(),
83        }
84    }
85}
86
87pub trait Navigatable {
88    fn zoom_x(&self) -> f32;
89    fn zoom_y(&self) -> f32;
90    fn set_zoom(&mut self, zoom_x: f32, zoom_y: f32);
91    fn offset_x(&self) -> f64;
92    fn offset_y(&self) -> f32;
93    fn set_offset(&mut self, offset_x: f64, offset_y: f32);
94    fn allow_axis_zoom(&self) -> bool {
95        false
96    }
97    fn handle_navigation(&mut self, ui: &mut Ui, response: &Response) {
98        let zoom_delta = if self.allow_axis_zoom() {
99            ui.input(|input| input.zoom_delta_2d())
100        } else {
101            let zoom_delta = ui.input(|input| input.zoom_delta());
102            egui::vec2(zoom_delta, zoom_delta)
103        };
104        let scroll_delta = ui.input(|input| input.smooth_scroll_delta);
105        let zooming = (zoom_delta.x - 1.0).abs() > 0.001 || (zoom_delta.y - 1.0).abs() > 0.001;
106
107        if zooming
108            && ui.ui_contains_pointer()
109            && let Some(pos) = response.hover_pos()
110        {
111            let old_zoom_x = self.zoom_x();
112            let old_zoom_y = self.zoom_y();
113            let mut new_zoom_x = old_zoom_x * zoom_delta.x;
114            let mut new_zoom_y = old_zoom_y * zoom_delta.y;
115            let (clamped_x, clamped_y) = self.clamp_zoom(new_zoom_x, new_zoom_y);
116            new_zoom_x = clamped_x;
117            new_zoom_y = clamped_y;
118
119            let rel_pos = (pos - response.rect.min) / response.rect.size();
120            let world_width_before = response.rect.width() as f64 / old_zoom_x as f64;
121            let world_width_after = response.rect.width() as f64 / new_zoom_x as f64;
122            let world_pos_before_x = self.offset_x() + rel_pos.x as f64 * world_width_before;
123            let new_offset_x = world_pos_before_x - rel_pos.x as f64 * world_width_after;
124
125            let world_height_before = response.rect.height() as f64 / old_zoom_y as f64;
126            let world_height_after = response.rect.height() as f64 / new_zoom_y as f64;
127            let world_pos_before_y =
128                self.offset_y() as f64 + rel_pos.y as f64 * world_height_before;
129            let new_offset_y = (world_pos_before_y - rel_pos.y as f64 * world_height_after) as f32;
130
131            self.set_zoom(new_zoom_x, new_zoom_y);
132            self.set_offset(new_offset_x, new_offset_y);
133        }
134        if ui.ui_contains_pointer() || ui.input(|r| r.any_touches()) {
135            let ticks_per_screen_unit = 1.0 / self.zoom_x() as f64;
136            let pan_delta = response.drag_delta() + scroll_delta;
137            let new_offset_x = self.offset_x() - ticks_per_screen_unit * pan_delta.x as f64;
138            let new_offset_y = self.offset_y() - pan_delta.y / self.zoom_y();
139            self.set_offset(new_offset_x, new_offset_y);
140        }
141
142        self.post_navigation(response);
143    }
144    fn clamp_zoom(&self, zoom_x: f32, zoom_y: f32) -> (f32, f32) {
145        (zoom_x, zoom_y)
146    }
147    fn post_navigation(&mut self, _response: &Response) {}
148}
149
150pub trait Tab {
151    /// The internal name of the tab used for identification. This must be a static string.
152    /// The actual displayed name could be different based on e.g. the localization or other contents.
153    const NAME: &'static str;
154    /// Called before rendering the tab.
155    fn pre_render(&mut self, _world: &mut World) {}
156    /// Called after rendering the tab.
157    fn post_render(&mut self, _world: &mut World) {}
158    /// The main display of the tab.
159    fn main_display(&mut self, world: &mut World, ui: &mut Ui);
160    fn edit_display(&mut self, _world: &mut World, ui: &mut Ui) {
161        ui.label(Self::NAME);
162        ui.label(tr!("side-panel-edit-fallback-1"));
163        ui.label(tr!("side-panel-edit-fallback-2"));
164    }
165    fn display_display(&mut self, _world: &mut World, ui: &mut Ui) {
166        ui.label(Self::NAME);
167        ui.label(tr!("side-panel-details-fallback-1"));
168        ui.label(tr!("side-panel-details-fallback-2"));
169    }
170    fn export_display(&mut self, _world: &mut World, ui: &mut Ui) {
171        ui.label(Self::NAME);
172        ui.label(tr!("side-panel-export-fallback-1"));
173        ui.label(tr!("side-panel-export-fallback-2"));
174    }
175    fn title(&self) -> WidgetText {
176        Self::NAME.into()
177    }
178    fn on_tab_button(&self, world: &mut World, response: &Response) {
179        if response.hovered() {
180            let title_text = self.title();
181            let s = &mut world.resource_mut::<StatusBarState>().tooltip;
182            s.clear();
183            s.push_str(self.icon().as_ref());
184            s.push(' ');
185            s.push_str(title_text.text());
186        }
187    }
188    fn id(&self) -> Id {
189        Id::new(Self::NAME)
190    }
191    fn scroll_bars(&self) -> [bool; 2] {
192        [true; 2]
193    }
194    fn frame(&self) -> egui::Frame {
195        egui::Frame::default().inner_margin(egui::Margin::same(6))
196    }
197    fn icon(&self) -> Cow<'static, str> {
198        "🖳".into()
199    }
200}