Skip to main content

paiagram/
main.rs

1use bevy::{ecs::system::RunSystemOnce, log::LogPlugin, prelude::*};
2use clap::Parser;
3use paiagram_core::settings::UserPreferences;
4use paiagram_core::trip::class;
5use paiagram_core::{entry, graph, i18n, import, problems, route, settings, station, trip};
6use paiagram_rw::{read::ReadPlugin, save::SavePlugin};
7use serde::Deserialize;
8use std::path::PathBuf;
9
10struct PaiagramApp {
11    bevy_app: App,
12}
13
14impl PaiagramApp {
15    fn new(cc: &eframe::CreationContext) -> Self {
16        cc.egui_ctx.global_style_mut(|style| {
17            style.spacing.window_margin = egui::Margin::same(2);
18            style.interaction.selectable_labels = false;
19        });
20        paiagram_ui::apply_custom_fonts(&cc.egui_ctx);
21        if let Some(render_state) = cc.wgpu_render_state.as_ref() {
22            cc.egui_ctx.data_mut(|data| {
23                data.insert_temp(
24                    egui::Id::new("wgpu_adapter_info"),
25                    eframe::egui_wgpu::adapter_info_summary(&render_state.adapter.get_info()),
26                );
27                data.insert_temp(
28                    egui::Id::new("wgpu_target_format"),
29                    render_state.target_format,
30                );
31                let msaa_samples = if cfg!(target_arch = "wasm32") {
32                    1_u32
33                } else {
34                    4_u32
35                };
36                data.insert_temp(egui::Id::new("wgpu_msaa_samples"), msaa_samples);
37            });
38        }
39        let mut app = App::new();
40        app.add_plugins(MinimalPlugins);
41        app.add_plugins(LogPlugin::default());
42        app.add_plugins((
43            paiagram_ui::UiPlugin,
44            entry::EntryPlugin,
45            graph::GraphPlugin,
46            route::RoutePlugin,
47            import::ImportPlugin,
48            trip::TripPlugin,
49            station::StationPlugin,
50            settings::SettingsPlugin,
51            problems::ProblemsPlugin,
52            class::ClassPlugin,
53            ReadPlugin,
54            SavePlugin,
55        ));
56        info!("Initialized Bevy App.");
57        #[cfg(not(target_arch = "wasm32"))]
58        let args = Arguments::parse();
59        #[cfg(target_arch = "wasm32")]
60        let args = parse_web_arguments();
61        if let Err(e) = app.world_mut().run_system_once_with(handle_args, args) {
62            error!("Failed to web arguments: {:?}", e);
63        } else {
64            info!("Arguments handled successfully.");
65        }
66        Self { bevy_app: app }
67    }
68}
69
70#[cfg(target_arch = "wasm32")]
71fn parse_web_arguments() -> Arguments {
72    if let Some(search) =
73        eframe::web_sys::window().and_then(|window| window.location().search().ok())
74    {
75        info!(?search, "Handling web args...");
76        let query = search.strip_prefix('?').unwrap_or(&search);
77        match serde_html_form::from_str::<Arguments>(&query) {
78            Ok(args) => return args,
79            Err(error) => error!("Failed to parse web args: {error}"),
80        }
81    }
82
83    Arguments::default()
84}
85
86impl eframe::App for PaiagramApp {
87    fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
88        egui::Rgba::TRANSPARENT.to_array()
89    }
90    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
91        self.bevy_app.update();
92        paiagram_ui::show_ui(ui, self.bevy_app.world_mut(), frame.info().cpu_usage);
93    }
94}
95
96/// Arguments for the application.
97#[derive(Parser, Default, Deserialize)]
98#[command(version, about, long_about = None)]
99struct Arguments {
100    #[arg(
101        short = 'o',
102        long = "open",
103        help = "Path to a .paiagram file (or any other compatible file formats) to open on startup",
104        num_args = 1..
105    )]
106    #[serde(default)]
107    open: Option<Vec<PathBuf>>,
108    #[arg(long = "locale", help = "Set the localization")]
109    #[serde(default)]
110    locale: Option<String>,
111}
112
113fn handle_args(
114    args: In<Arguments>,
115    mut commands: Commands,
116    mut languages: ResMut<UserPreferences>,
117) {
118    for path in args.open.iter().flatten() {
119        #[cfg(target_arch = "wasm32")]
120        {
121            commands.trigger(import::DownloadFile {
122                url: path.to_string_lossy().into_owned(),
123            });
124            continue;
125        }
126
127        #[cfg(not(target_arch = "wasm32"))]
128        {
129            let content = match std::fs::read(path) {
130                Ok(c) => c,
131                Err(e) => {
132                    error!("Could not open {:?}: {:?}", path, e);
133                    continue;
134                }
135            };
136            if let Err(e) = import::load_and_trigger(path, content, &mut commands) {
137                error!("Could not load {:?}: {:#?}", path, e);
138                continue;
139            }
140        }
141    }
142    if let Some(locale) = args.locale.as_deref() {
143        if languages.lang.set(locale) {
144            info!("Successfully set language {}", locale);
145        } else {
146            error!("Unknown language identifier: {}", locale);
147        }
148    }
149}
150
151#[cfg(not(target_arch = "wasm32"))]
152fn main() -> eframe::Result<()> {
153    i18n::init();
154    let native_options = eframe::NativeOptions {
155        viewport: egui::ViewportBuilder::default()
156            .with_title("Drawer")
157            .with_app_id("Paiagram")
158            .with_inner_size([1280.0, 720.0]),
159        renderer: eframe::Renderer::Wgpu,
160        wgpu_options: eframe::egui_wgpu::WgpuConfiguration {
161            desired_maximum_frame_latency: Some(2),
162            ..default()
163        },
164        multisampling: 4,
165        ..default()
166    };
167    eframe::run_native(
168        "Paiagram Drawer",
169        native_options,
170        Box::new(|cc| Ok(Box::new(PaiagramApp::new(cc)))),
171    )
172}
173
174#[cfg(target_arch = "wasm32")]
175use wasm_bindgen::prelude::*;
176
177#[cfg(target_arch = "wasm32")]
178#[derive(Clone)]
179#[wasm_bindgen]
180pub struct WebHandle {
181    runner: eframe::WebRunner,
182}
183
184#[cfg(target_arch = "wasm32")]
185fn main() {
186    i18n::init();
187    use eframe::wasm_bindgen::JsCast as _;
188    use eframe::web_sys;
189
190    let web_options = eframe::WebOptions::default();
191
192    wasm_bindgen_futures::spawn_local(async {
193        let document = web_sys::window()
194            .expect("No window")
195            .document()
196            .expect("No document");
197
198        let canvas = if let Some(canvas) = document.get_element_by_id("paiagram_canvas") {
199            canvas
200                .dyn_into::<web_sys::HtmlCanvasElement>()
201                .expect("paiagram_canvas was not a HtmlCanvasElement")
202        } else {
203            let canvas = document
204                .create_element("canvas")
205                .expect("Failed to create canvas element");
206            canvas.set_id("paiagram_canvas");
207
208            canvas
209                .set_attribute("style", "display: block; width: 100%; height: 100%;")
210                .ok();
211
212            let body = document.body().expect("Failed to get document body");
213            body.set_attribute(
214                "style",
215                "margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden;",
216            )
217            .ok();
218
219            let html = document.document_element().expect("No document element");
220            html.set_attribute(
221                "style",
222                "margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden;",
223            )
224            .ok();
225
226            body.append_child(&canvas).expect("Failed to append canvas");
227            canvas
228                .dyn_into::<web_sys::HtmlCanvasElement>()
229                .expect("Failed to cast canvas")
230        };
231
232        let start_result = eframe::WebRunner::new()
233            .start(
234                canvas,
235                web_options,
236                Box::new(|cc| Ok(Box::new(PaiagramApp::new(cc)))),
237            )
238            .await;
239
240        if let Some(loading_text) = document.get_element_by_id("loading_text") {
241            match start_result {
242                Ok(_) => {
243                    loading_text.remove();
244                }
245                Err(e) => {
246                    loading_text.set_inner_html(
247                        "<p> The app has crashed. See the developer console for details. </p>",
248                    );
249                    panic!("Failed to start eframe: {e:?}");
250                }
251            }
252        }
253    });
254}