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