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