paiagram/interface/
about.rs1use crate::rw_data::ModifyData;
2use bevy::ecs::{message::MessageWriter, system::InMut};
3use egui::{Id, Modal, OpenUrl, Popup};
4use rfd::AsyncFileDialog;
5use std::collections::VecDeque;
6use std::sync::{Arc, Mutex, OnceLock};
7
8#[cfg(not(target_arch = "wasm32"))]
9use bevy::tasks::IoTaskPool;
10
11#[cfg(target_arch = "wasm32")]
12use wasm_bindgen_futures::spawn_local;
13
14static FILE_IMPORT_QUEUE: OnceLock<Arc<Mutex<VecDeque<ModifyData>>>> = OnceLock::new();
15
16fn shared_queue() -> Arc<Mutex<VecDeque<ModifyData>>> {
17 FILE_IMPORT_QUEUE
18 .get_or_init(|| Arc::new(Mutex::new(VecDeque::new())))
19 .clone()
20}
21
22fn show_import_button(
23 ui: &mut egui::Ui,
24 label: &'static str,
25 filter_label: &'static str,
26 extensions: &'static [&'static str],
27 constructor: fn(String) -> ModifyData,
28) {
29 if ui.button(label).clicked() {
30 start_file_import(filter_label, extensions, constructor);
31 }
32}
33
34fn start_file_import(
35 filter_label: &'static str,
36 extensions: &'static [&'static str],
37 constructor: fn(String) -> ModifyData,
38) {
39 let queue = shared_queue();
40 let task = async move {
41 if let Some(content) = pick_file_contents(filter_label, extensions).await {
42 if let Ok(mut guard) = queue.lock() {
43 guard.push_back(constructor(content));
44 }
45 }
46 };
47
48 #[cfg(target_arch = "wasm32")]
49 spawn_local(task);
50
51 #[cfg(not(target_arch = "wasm32"))]
52 IoTaskPool::get().spawn(task).detach();
53}
54
55async fn pick_file_contents(
56 filter_label: &'static str,
57 extensions: &'static [&'static str],
58) -> Option<String> {
59 let file = AsyncFileDialog::new()
60 .add_filter(filter_label, extensions)
61 .pick_file()
62 .await?;
63 let bytes = file.read().await;
64 String::from_utf8(bytes).ok()
65}
66
67pub fn show_about(
68 (InMut(ui), InMut(modal_open)): (InMut<egui::Ui>, InMut<bool>),
69 mut msg_read_file: MessageWriter<ModifyData>,
70) {
71 let queue = shared_queue();
72 let pending: Vec<_> = {
73 let mut guard = queue.lock().unwrap();
74 guard.drain(..).collect()
75 };
76 drop(queue);
77 for message in pending {
78 msg_read_file.write(message);
79 }
80
81 ui.horizontal(|ui| {
82 let button = ui.button("File");
83 Popup::menu(&button).show(|ui| {
84 if ui.button("Read...").clicked() {
85 }
87 if ui.button("Save As...").clicked() {
88 }
90 ui.separator();
91 show_import_button(
92 ui,
93 "Import qETRC...",
94 "qETRC Files",
95 &["pyetgr", "json"],
96 ModifyData::LoadQETRC,
97 );
98 show_import_button(
99 ui,
100 "Import OuDiaSecond...",
101 "OuDiaSecond Files",
102 &["oud2"],
103 ModifyData::LoadOuDiaSecond,
104 );
105 });
106 let button = ui.button("Help");
107 Popup::menu(&button).show(|ui| {
108 if ui.button("Check for Updates").clicked() {
109 }
111 if ui.button("Report Issue").clicked() {
112 ui.ctx().open_url(OpenUrl {
113 url: "https://github.com/WenSimEHRP/Paiagram/issues".into(),
114 new_tab: true,
115 });
116 };
117 if ui.button("Help Translate").clicked() {
118 ui.ctx().open_url(OpenUrl {
119 url: "https://github.com/WenSimEHRP/Paiagram/issues".into(),
121 new_tab: true,
122 });
123 };
124 ui.separator();
125 if ui.button("Documentation").clicked() {
126 ui.ctx().open_url(OpenUrl {
127 url: "https://github.com/WenSimEHRP/Paiagram/issues".into(),
129 new_tab: true,
130 });
131 };
132 if ui.button("About Paiagram").clicked() {
133 *modal_open = true;
134 };
135 });
136 });
137 if *modal_open {
138 let modal = Modal::new(Id::new("Modal B")).show(ui.ctx(), |ui| {
139 ui.set_width(500.0);
140 ui.heading("About");
141 ui.label(format!("Paiagram ({})", git_version::git_version!()));
142 ui.label("© 2025 Jeremy Gao");
143 ui.label("Paiagram is a free and open-source application licensed under the AGPL v3.0 license.");
144 ui.separator();
145 egui::ScrollArea::vertical().show(ui, |ui| {
146 ui.set_width(ui.available_width());
147 ui.monospace(include_str!("../../LICENSE.md"));
148 });
149 });
150
151 if modal.should_close() {
152 *modal_open = false;
153 }
154 }
155}