aboutsummaryrefslogtreecommitdiff
path: root/src/preferences/window.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/preferences/window.rs')
-rw-r--r--src/preferences/window.rs334
1 files changed, 334 insertions, 0 deletions
diff --git a/src/preferences/window.rs b/src/preferences/window.rs
new file mode 100644
index 0000000..d3874f2
--- /dev/null
+++ b/src/preferences/window.rs
@@ -0,0 +1,334 @@
+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// Suppress warnings from relm4 view! macro internals
+#![allow(unused_assignments)]
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
+
+use std::path::PathBuf;
+
+use gtk::prelude::*;
+use relm4::{ComponentParts, ComponentSender, SimpleComponent, gtk};
+
+use super::models::UserPreferences;
+use super::pages::{editor, general, map, stages, templates};
+use super::storage;
+
+/// Messages for preferences window
+#[derive(Debug)]
+pub enum PreferencesInput {
+ // General page actions
+ Export,
+ Import,
+ ImportFromFile(PathBuf),
+ ExportToFile(PathBuf),
+ ResetToDefaults,
+
+ // Preference updates from pages
+ SetShowMapBackground(bool),
+ SetUseCustomFont(bool),
+ SetCustomFontName(String),
+ SetDefaultExportFormat(String),
+ SetUseSmartLabelPositioning(bool),
+
+ SetUseCustomEditorFont(bool),
+ SetCustomEditorFontName(String),
+ SetEditorFontSize(f64),
+ SetSoftWrapLines(bool),
+
+ // Templates
+ AddTemplate(String),
+ RemoveTemplate(uuid::Uuid),
+ SetTemplateContent(uuid::Uuid, String),
+ #[allow(dead_code)]
+ SetTemplateName(uuid::Uuid, String),
+ SetDefaultTemplate(uuid::Uuid),
+
+ // Custom stages
+ AddCustomStage,
+ RemoveCustomStage(uuid::Uuid),
+ SetCustomStageName(uuid::Uuid, String),
+ SetCustomStageLabel(uuid::Uuid, StageLabel, String),
+
+ // Close
+ Close,
+}
+
+/// Which stage label to update (Roman numerals I-IV)
+#[derive(Debug, Clone, Copy)]
+#[allow(clippy::upper_case_acronyms)]
+pub enum StageLabel {
+ I,
+ II,
+ III,
+ IV,
+}
+
+/// Output messages to parent app
+#[derive(Debug)]
+pub enum PreferencesOutput {
+ PreferencesChanged(UserPreferences),
+ Closed,
+}
+
+pub struct PreferencesWindow {
+ preferences: UserPreferences,
+ window: gtk::Window,
+ // Widget references for pages
+ general_page: general::GeneralPage,
+ editor_page: editor::EditorPage,
+ map_page: map::MapPage,
+ stages_page: stages::StagesPage,
+ templates_page: templates::TemplatesPage,
+}
+
+#[relm4::component(pub)]
+impl SimpleComponent for PreferencesWindow {
+ type Init = UserPreferences;
+ type Input = PreferencesInput;
+ type Output = PreferencesOutput;
+
+ view! {
+ gtk::Window {
+ set_title: Some("Preferences"),
+ set_default_width: 700,
+ set_default_height: 500,
+ set_modal: true,
+ set_resizable: true,
+
+ connect_close_request[sender] => move |_| {
+ sender.input(PreferencesInput::Close);
+ gtk::glib::Propagation::Proceed
+ },
+
+ gtk::Box {
+ set_orientation: gtk::Orientation::Vertical,
+
+ gtk::HeaderBar {
+ #[wrap(Some)]
+ set_title_widget = &gtk::StackSwitcher {
+ set_stack: Some(&stack),
+ },
+ },
+
+ #[name = "stack"]
+ gtk::Stack {
+ set_transition_type: gtk::StackTransitionType::SlideLeftRight,
+ set_vexpand: true,
+
+ add_titled[Some("general"), "General"] = &model.general_page.widget() -> gtk::Box {},
+ add_titled[Some("editor"), "Editor"] = &model.editor_page.widget() -> gtk::Box {},
+ add_titled[Some("map"), "Map"] = &model.map_page.widget() -> gtk::Box {},
+ add_titled[Some("stages"), "Stages"] = &model.stages_page.widget() -> gtk::Box {},
+ add_titled[Some("templates"), "Templates"] = &model.templates_page.widget() -> gtk::Box {},
+ },
+ },
+ }
+ }
+
+ fn init(
+ prefs: Self::Init,
+ root: Self::Root,
+ sender: ComponentSender<Self>,
+ ) -> ComponentParts<Self> {
+ let general_page = general::GeneralPage::new(sender.input_sender().clone());
+ let editor_page = editor::EditorPage::new(&prefs, sender.input_sender().clone());
+ let map_page = map::MapPage::new(&prefs, sender.input_sender().clone());
+ let stages_page = stages::StagesPage::new(&prefs, sender.input_sender().clone());
+ let templates_page = templates::TemplatesPage::new(&prefs, sender.input_sender().clone());
+
+ let model = PreferencesWindow {
+ preferences: prefs,
+ window: root.clone(),
+ general_page,
+ editor_page,
+ map_page,
+ stages_page,
+ templates_page,
+ };
+
+ let widgets = view_output!();
+
+ ComponentParts { model, widgets }
+ }
+
+ fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
+ match message {
+ // General page actions
+ PreferencesInput::Export => {
+ general::show_export_dialog(sender.input_sender(), &self.window);
+ }
+ PreferencesInput::Import => {
+ general::show_import_dialog(sender.input_sender(), &self.window);
+ }
+ PreferencesInput::ImportFromFile(path) => {
+ if storage::import_from_file(&mut self.preferences, &path).is_ok() {
+ self.sync_ui_from_preferences();
+ self.save_and_notify(&sender);
+ }
+ }
+ PreferencesInput::ExportToFile(path) => {
+ let _ = storage::export_to_file(&self.preferences, &path);
+ }
+ PreferencesInput::ResetToDefaults => {
+ self.preferences = UserPreferences::default();
+ self.sync_ui_from_preferences();
+ self.save_and_notify(&sender);
+ }
+
+ // Map preferences
+ PreferencesInput::SetShowMapBackground(v) => {
+ self.preferences.show_map_background = v;
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetUseCustomFont(v) => {
+ self.preferences.use_custom_font = v;
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetCustomFontName(v) => {
+ self.preferences.custom_font_name = v;
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetDefaultExportFormat(v) => {
+ self.preferences.default_export_format = v;
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetUseSmartLabelPositioning(v) => {
+ self.preferences.use_smart_label_positioning = v;
+ self.save_and_notify(&sender);
+ }
+
+ // Editor preferences
+ PreferencesInput::SetUseCustomEditorFont(v) => {
+ self.preferences.use_custom_editor_font = v;
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetCustomEditorFontName(v) => {
+ self.preferences.custom_editor_font_name = v;
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetEditorFontSize(v) => {
+ self.preferences.editor_font_size = v;
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetSoftWrapLines(v) => {
+ self.preferences.soft_wrap_lines = v;
+ self.save_and_notify(&sender);
+ }
+
+ // Templates
+ PreferencesInput::AddTemplate(name) => {
+ let template = super::models::Template::new(name, String::new(), false);
+ self.preferences.map_templates.push(template);
+ self.templates_page.refresh(&self.preferences);
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::RemoveTemplate(id) => {
+ self.preferences.map_templates.retain(|t| t.id != id);
+ self.templates_page.refresh(&self.preferences);
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetTemplateContent(id, content) => {
+ if let Some(template) = self
+ .preferences
+ .map_templates
+ .iter_mut()
+ .find(|t| t.id == id)
+ {
+ template.content = content;
+ self.save_and_notify(&sender);
+ }
+ }
+ PreferencesInput::SetTemplateName(id, name) => {
+ if let Some(template) = self
+ .preferences
+ .map_templates
+ .iter_mut()
+ .find(|t| t.id == id)
+ {
+ template.name = name;
+ self.templates_page.refresh(&self.preferences);
+ self.save_and_notify(&sender);
+ }
+ }
+ PreferencesInput::SetDefaultTemplate(id) => {
+ self.preferences.set_default_template(id);
+ self.templates_page.refresh(&self.preferences);
+ self.save_and_notify(&sender);
+ }
+
+ // Custom stages
+ PreferencesInput::AddCustomStage => {
+ let stage = super::models::CustomStage::placeholder_default();
+ self.preferences.custom_stages.push(stage);
+ self.stages_page.refresh(&self.preferences);
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::RemoveCustomStage(id) => {
+ self.preferences.custom_stages.retain(|s| s.id != id);
+ self.stages_page.refresh(&self.preferences);
+ self.save_and_notify(&sender);
+ }
+ PreferencesInput::SetCustomStageName(id, name) => {
+ if let Some(stage) = self
+ .preferences
+ .custom_stages
+ .iter_mut()
+ .find(|s| s.id == id)
+ {
+ stage.name = name;
+ self.save_and_notify(&sender);
+ }
+ }
+ PreferencesInput::SetCustomStageLabel(id, label, value) => {
+ if let Some(stage) = self
+ .preferences
+ .custom_stages
+ .iter_mut()
+ .find(|s| s.id == id)
+ {
+ match label {
+ StageLabel::I => stage.stage.i = value,
+ StageLabel::II => stage.stage.ii = value,
+ StageLabel::III => stage.stage.iii = value,
+ StageLabel::IV => stage.stage.iv = value,
+ }
+ self.save_and_notify(&sender);
+ }
+ }
+
+ PreferencesInput::Close => {
+ sender.output(PreferencesOutput::Closed).ok();
+ }
+ }
+ }
+}
+
+impl PreferencesWindow {
+ fn save_and_notify(&self, sender: &ComponentSender<Self>) {
+ let _ = storage::save(&self.preferences);
+ sender
+ .output(PreferencesOutput::PreferencesChanged(
+ self.preferences.clone(),
+ ))
+ .ok();
+ }
+
+ fn sync_ui_from_preferences(&mut self) {
+ self.editor_page.sync_from(&self.preferences);
+ self.map_page.sync_from(&self.preferences);
+ self.stages_page.refresh(&self.preferences);
+ self.templates_page.refresh(&self.preferences);
+ }
+}