1pub mod resources;
2mod window;
3
4use crate::app::window::editor_window::{EditorWindow, EditorWindowMessage};
5use crate::app::window::license_info_window::LicenseInfoWindow;
6use crate::app::window::main_window::page::item_list::ItemListMessage;
7use crate::app::window::main_window::{MainWindow, MainWindowMessage};
8use crate::app::window::setting_window::SettingWindow;
9use crate::app::window::version_info_window::VersionInfoWindow;
10use crate::app::window::{Window, WindowMessage};
11use iced::border::Radius;
12use iced::widget::{button, space};
13use iced::{Border, Element, Font, Subscription, Task};
14use indexmap::{IndexMap, IndexSet};
15use prehnite_core::font::{get_default_font_family, get_global_font_list, FontLoader};
16use prehnite_core::i18n::{change_lang_bundle, i18n_w, DEFAULT_LANG_ID};
17use prehnite_core::opt_unwrap_or_return;
18use prehnite_core::settings::registry::SettingRegistry;
19use prehnite_core::settings::GlobalSettingKey;
20use prehnite_core::widget::font::{get_default_font, init_default_font, set_font};
21use std::fmt::Debug;
22use tracing::error;
23
24#[derive(Clone, Debug)]
25pub enum WindowType {
26 MainWindow,
27 VersionInfoWindow,
28 SettingWindow,
29 LicenseInfoWindow,
30 BiblioGraphyEditorWindow,
31 BackgroundInfoEditorWindow,
32 EditorWindow(i64),
33}
34
35macro_rules! window_opener {
36 ($self:ident, $(($window_type:pat, $window_struct:path)),*) => {
37 match $self{
38 $(
39 $window_type => <$window_struct>::window_settings(),
40 )*
41 }
42 };
43}
44
45impl WindowType {
46 pub fn open_window(&self) -> (iced::window::Id, Task<iced::window::Id>) {
47 iced::window::open(window_opener!(
48 self,
49 (WindowType::MainWindow, MainWindow),
50 (WindowType::VersionInfoWindow, VersionInfoWindow),
51 (WindowType::SettingWindow, SettingWindow),
52 (WindowType::LicenseInfoWindow, LicenseInfoWindow),
53 (WindowType::BiblioGraphyEditorWindow, LicenseInfoWindow), (WindowType::BackgroundInfoEditorWindow, LicenseInfoWindow), (WindowType::EditorWindow(_), EditorWindow)
56 ))
57 }
58}
59
60#[derive(Debug)]
61struct TypedWindow {
62 pub w_type: WindowType,
63 pub window: Box<dyn Window>,
64}
65
66impl From<(WindowType, Box<dyn Window>)> for TypedWindow {
67 fn from((w_type, window): (WindowType, Box<dyn Window>)) -> Self {
68 Self { w_type, window }
69 }
70}
71
72impl From<TypedWindow> for (WindowType, Box<dyn Window>) {
73 fn from(value: TypedWindow) -> Self {
74 (value.w_type, value.window)
75 }
76}
77
78#[derive(Debug)]
79pub struct PrehniteApp {
80 main_window_id: Option<iced::window::Id>,
81 version_info_window_id: Option<iced::window::Id>,
82 setting_window_id: Option<iced::window::Id>,
83 window: IndexMap<iced::window::Id, TypedWindow>,
84 window_was_shown: IndexSet<iced::window::Id>,
85 license_info_window_id: Option<iced::window::Id>,
86 background_info_editor_window_id: Option<iced::window::Id>,
87 bibliography_editor_window_id: Option<iced::window::Id>,
88 editor_window_id: Option<iced::window::Id>,
89}
90
91#[derive(Clone, Debug)]
92pub enum DaemonMessage {
93 OpenWindow(WindowType),
94 WindowOpened(iced::window::Id),
95 WindowMessage(iced::window::Id, WindowMessage),
96 WindowClosed(iced::window::Id),
97 ReloadFont,
98}
99
100macro_rules! window_creator {
101 ($v_window_type:expr,$(($window_type:pat, $window_struct:path)),*) => {
102 match $v_window_type {
103 $(
104 $window_type => {
105 (Box::new(<$window_struct>::new()), <$window_struct>::init_task())
106 }
107 )*
108 }
109 };
110}
111
112impl PrehniteApp {
113 pub fn run() -> Result<(), iced::Error> {
114 init_default_font(Font::with_name(get_default_font_family()));
115 iced::daemon(Self::new, Self::update, Self::view)
116 .title(Self::title)
117 .subscription(Self::subscription)
118 .load_all_prehnite_bundled_font()
119 .default_font(get_default_font())
120 .run()
121 }
122
123 fn new() -> (Self, Task<DaemonMessage>) {
124 (
125 Self {
126 main_window_id: None,
127 version_info_window_id: None,
128 setting_window_id: None,
129 window: Default::default(),
130 window_was_shown: Default::default(),
131 license_info_window_id: None,
132 background_info_editor_window_id: None,
133 bibliography_editor_window_id: None,
134 editor_window_id: None,
135 },
136 Task::done(DaemonMessage::ReloadFont).chain(Task::done(DaemonMessage::OpenWindow(
137 WindowType::MainWindow,
138 ))),
139 )
140 }
141
142 fn before_window_open(&mut self, window_type: &WindowType) -> Option<Task<DaemonMessage>> {
143 match window_type {
144 WindowType::MainWindow => self.main_window_id.map(iced::window::gain_focus),
145 WindowType::VersionInfoWindow => {
146 self.version_info_window_id.map(iced::window::gain_focus)
147 }
148 WindowType::SettingWindow => self.setting_window_id.map(iced::window::gain_focus),
149 WindowType::LicenseInfoWindow => {
150 self.license_info_window_id.map(iced::window::gain_focus)
151 }
152 WindowType::BiblioGraphyEditorWindow => self
153 .bibliography_editor_window_id
154 .map(iced::window::gain_focus),
155 WindowType::BackgroundInfoEditorWindow => self
156 .background_info_editor_window_id
157 .map(iced::window::gain_focus),
158 WindowType::EditorWindow(_) => self.editor_window_id.map(iced::window::gain_focus),
159 }
160 }
161
162 fn on_window_close(&mut self, window: Option<TypedWindow>) -> Task<DaemonMessage> {
163 if let Some((w_type, _)) = window.map(|v| v.into()) {
164 match w_type {
165 WindowType::MainWindow => return iced::exit(),
166 WindowType::VersionInfoWindow => self.version_info_window_id = None,
167 WindowType::SettingWindow => self.setting_window_id = None,
168 WindowType::LicenseInfoWindow => self.license_info_window_id = None,
169 WindowType::BiblioGraphyEditorWindow => self.bibliography_editor_window_id = None,
170 WindowType::BackgroundInfoEditorWindow => {
171 self.background_info_editor_window_id = None
172 }
173 WindowType::EditorWindow(_) => self.editor_window_id = None,
174 }
175 }
176 Task::none()
177 }
178
179 #[tracing::instrument]
180 fn update(&mut self, message: DaemonMessage) -> Task<DaemonMessage> {
181 match message {
182 DaemonMessage::OpenWindow(w_type) => {
183 if let Some(task) = self.before_window_open(&w_type) {
184 return task;
185 }
186 let (window_id, open_window_task) = w_type.open_window();
187
188 let (mut window, mut init_window_task): (Box<dyn Window>, Task<WindowMessage>) = window_creator!(
190 w_type,
191 (WindowType::MainWindow, MainWindow),
192 (WindowType::VersionInfoWindow, VersionInfoWindow),
193 (WindowType::SettingWindow, SettingWindow),
194 (WindowType::LicenseInfoWindow, LicenseInfoWindow),
195 (WindowType::BiblioGraphyEditorWindow, LicenseInfoWindow), (WindowType::BackgroundInfoEditorWindow, LicenseInfoWindow), (WindowType::EditorWindow(_), EditorWindow)
198 );
199
200 match w_type {
202 WindowType::MainWindow => self.main_window_id = Some(window_id),
203 WindowType::VersionInfoWindow => self.version_info_window_id = Some(window_id),
204 WindowType::SettingWindow => self.setting_window_id = Some(window_id),
205 WindowType::LicenseInfoWindow => self.license_info_window_id = Some(window_id),
206 WindowType::BiblioGraphyEditorWindow => {
207 self.bibliography_editor_window_id = Some(window_id);
208 }
209 WindowType::BackgroundInfoEditorWindow => {
210 self.background_info_editor_window_id = Some(window_id);
211 }
212 WindowType::EditorWindow(_) => self.editor_window_id = Some(window_id),
213 };
214
215 match w_type {
217 WindowType::EditorWindow(id) => {
218 init_window_task = Task::done(WindowMessage::EditorWindowMessage(
219 EditorWindowMessage::ChangeItemFromId(id),
220 ));
221 }
222 _ => {}
223 }
224
225 window.set_window_id(window_id);
227 self.window.insert(window_id, (w_type, window).into());
228 init_window_task
229 .map(move |msg| DaemonMessage::WindowMessage(window_id, msg))
230 .chain(open_window_task.map(DaemonMessage::WindowOpened))
231 }
232 DaemonMessage::WindowOpened(id) => {
233 self.window_was_shown.insert(id);
234 iced::window::gain_focus(id)
235 }
236 DaemonMessage::WindowMessage(id, window_msg) => {
237 match &window_msg {
239 WindowMessage::MainWindowMessage(MainWindowMessage::OpenVersionInfoWindow) => {
240 return Task::done(DaemonMessage::OpenWindow(
241 WindowType::VersionInfoWindow,
242 ));
243 }
244 WindowMessage::MainWindowMessage(MainWindowMessage::OpenSettingWindow) => {
245 return Task::done(DaemonMessage::OpenWindow(WindowType::SettingWindow));
246 }
247 WindowMessage::MainWindowMessage(MainWindowMessage::OpenLicenseInfoWindow) => {
248 return Task::done(DaemonMessage::OpenWindow(
249 WindowType::LicenseInfoWindow,
250 ));
251 }
252 WindowMessage::MainWindowMessage(
253 MainWindowMessage::OpenBibliographyEditorWindow,
254 ) => {
255 return Task::done(DaemonMessage::OpenWindow(
256 WindowType::BiblioGraphyEditorWindow,
257 ));
258 }
259 WindowMessage::MainWindowMessage(
260 MainWindowMessage::OpenBackgroundInfoEditorWindow,
261 ) => {
262 return Task::done(DaemonMessage::OpenWindow(
263 WindowType::BackgroundInfoEditorWindow,
264 ));
265 }
266 WindowMessage::MainWindowMessage(MainWindowMessage::OpenEditorWindow(id)) => {
267 return Task::done(DaemonMessage::OpenWindow(WindowType::EditorWindow(
268 *id,
269 )));
270 }
271 WindowMessage::ReloadFont => {
272 return Task::done(DaemonMessage::ReloadFont);
273 }
274 WindowMessage::ReloadLanguage => {
275 return Task::future(async {
276 let lang_id = SettingRegistry::get(&GlobalSettingKey::Locale.into())
277 .and_then(|v| v.get::<String>())
278 .unwrap_or(DEFAULT_LANG_ID.to_string());
279 change_lang_bundle(lang_id.as_str()).await
280 })
281 .discard();
282 }
283 WindowMessage::MainWindowMessage(MainWindowMessage::ItemList(
284 ItemListMessage::OpenEditor(id),
285 )) => {
286 return Task::done(DaemonMessage::OpenWindow(WindowType::EditorWindow(
287 *id,
288 )));
289 }
290 _ => {}
291 }
292 let window = opt_unwrap_or_return!(self.window.get_mut(&id), {
294 error!("Failed to get window. WindowId: {id}");
295 Task::none()
296 });
297 window
298 .window
299 .update(window_msg)
300 .map(move |msg| DaemonMessage::WindowMessage(id, msg))
301 }
302 DaemonMessage::WindowClosed(id) => {
303 let typed_window = self.window.shift_remove(&id);
304 if self.window.is_empty() {
306 return iced::exit();
307 }
308 self.on_window_close(typed_window)
309 }
310 DaemonMessage::ReloadFont => {
311 let v = SettingRegistry::get(&GlobalSettingKey::Font.into())
312 .and_then(|v| v.get::<String>())
313 .and_then(|v| get_global_font_list().iter().filter(|x| **x == v).next());
314 set_font(v);
315 Task::none()
316 }
317 }
318 }
319
320 #[tracing::instrument]
321 fn view(&'_ self, window_id: iced::window::Id) -> Element<'_, DaemonMessage> {
322 let v = opt_unwrap_or_return!(self.window.get(&window_id), Element::new(space()));
323 v.window
324 .view()
325 .map(move |msg| DaemonMessage::WindowMessage(window_id, msg))
326 }
327
328 fn title(&self, window_id: iced::window::Id) -> String {
329 let v = opt_unwrap_or_return!(self.window.get(&window_id), "unknown".into());
330 v.window.title()
331 }
332
333 fn subscription(&self) -> Subscription<DaemonMessage> {
334 iced::window::close_events().map(DaemonMessage::WindowClosed)
335 }
336}