prehnite/app/
mod.rs

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), // TODO
54            (WindowType::BackgroundInfoEditorWindow, LicenseInfoWindow), // TODO
55            (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                // ウィンドウを構築
189                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), // TODO
196                    (WindowType::BackgroundInfoEditorWindow, LicenseInfoWindow), // TODO
197                    (WindowType::EditorWindow(_), EditorWindow)
198                );
199
200                // 最大1つまでに限定されているウィンドウのIDを保持
201                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                // その他特殊処理
216                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                // ウィンドウを登録し、開く
226                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                // デーモンに移譲されたメッセージを処理
238                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                // ウィンドウごとのメッセージを処理
293                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                // ウィンドウが存在しないならアプリケーションを終了
305                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}