diff options
| author | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-16 08:26:35 +0100 |
|---|---|---|
| committer | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-16 11:57:13 +0100 |
| commit | 81cd06b3334f2f91f6a1ab62a28e11c7d0c677eb (patch) | |
| tree | f0b0cf37b406eda2dec8db4382aeb5f606cd1f0d /src/preferences/window.rs | |
| parent | 4211b2ae06777d5bbe8261a1ab5c0dd057829a35 (diff) | |
Add preferences
Diffstat (limited to 'src/preferences/window.rs')
| -rw-r--r-- | src/preferences/window.rs | 334 |
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 = >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<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); + } +} |