// 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)] 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 = >k::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, ) -> ComponentParts { 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) { 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) { 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); } }