// Map, wardley map editor for linux // Copyright (C) 2026 Rubén Beltrán del Río // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero 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 Affero General Public License for more details. // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use gtk::prelude::*; use relm4::prelude::*; use crate::tr; use crate::actions::PreferencesAction; use crate::components::switch; pub struct MapInit { pub show_background: bool, pub use_smart_label_positioning: bool, pub use_custom_font: bool, pub custom_font: String, pub default_export_format: String, } pub struct Map { show_background_switch: Controller, smart_label_positioning_switch: Controller, custom_font_switch: Controller, show_background: bool, use_smart_label_positioning: bool, use_custom_font: bool, custom_font: String, default_export_format: String, } #[derive(Debug, Clone)] pub enum MapAction { SetShowBackground(bool), SetUseCustomFont(bool), SetUseSmartLabelPositioning(bool), SetCustomFont(String), SetDefaultExportFormat(String), // Internal ChangeShowBackground(bool), ChangeUseCustomFont(bool), ChangeUseSmartLabelPositioning(bool), ChangeFontDescription(gtk::FontDialogButton), ChangeDefaultExportFormat(String), } #[relm4::component(pub)] impl SimpleComponent for Map { type Init = MapInit; type Input = MapAction; type Output = PreferencesAction; view! { gtk::Box { set_orientation: gtk::Orientation::Vertical, set_margin_all: 20, set_spacing: 20, set_valign: gtk::Align::Start, gtk::Label { set_label: &tr!("preferences.map.map_style.title"), add_css_class: "heading", set_halign: gtk::Align::Start, }, model.show_background_switch.widget(), model.smart_label_positioning_switch.widget(), model.custom_font_switch.widget(), gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, gtk::Label { set_label: &tr!("preferences.map.font"), }, gtk::FontDialogButton::new(Some(gtk::FontDialog::new())) { #[watch] #[block_signal(font_handler)] set_font_desc: >k::pango::FontDescription::from_string(&model.custom_font), #[watch] set_sensitive: model.use_custom_font, set_hexpand: true, set_use_font: true, set_use_size: false, connect_font_desc_notify[sender] => move |button| { sender.input(MapAction::ChangeFontDescription(button.clone())); } @font_handler, } }, gtk::Label { set_label: &tr!("preferences.map.export.title"), add_css_class: "heading", set_halign: gtk::Align::Start, set_margin_top: 10, }, gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, gtk::Label { set_label: &tr!("preferences.map.export.default_format"), }, gtk::DropDown::new(Some(gtk::StringList::new(&[ &tr!("export_formats.png"), &tr!("export_formats.svg"), ])), None::) { #[watch] #[block_signal(export_handler)] set_selected: match model.default_export_format.as_str() { "svg" => 1, _ => 0 }, connect_selected_notify[sender] => move |dropdown| { let format = match dropdown.selected() { 1 => "svg", _ => "png", }; sender.input(MapAction::ChangeDefaultExportFormat(format.to_string())); } @export_handler, } }, } } fn init( init: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { let show_background_switch = switch::Switch::builder() .launch(switch::SwitchInit { label: tr!("preferences.map.map_style.show_background"), state: init.show_background, }) .forward(sender.input_sender(), move |output| match output { switch::SwitchOutput::SetState(state) => MapAction::ChangeShowBackground(state), }); let custom_font_switch = switch::Switch::builder() .launch(switch::SwitchInit { label: tr!("preferences.map.map_style.use_custom_font"), state: init.use_custom_font, }) .forward(sender.input_sender(), move |output| match output { switch::SwitchOutput::SetState(state) => MapAction::ChangeUseCustomFont(state), }); let smart_label_positioning_switch = switch::Switch::builder() .launch(switch::SwitchInit { label: tr!("preferences.map.map_style.use_smart_label_positioning"), state: init.use_smart_label_positioning, }) .forward(sender.input_sender(), move |output| match output { switch::SwitchOutput::SetState(state) => { MapAction::ChangeUseSmartLabelPositioning(state) } }); let model = Self { show_background: init.show_background, use_smart_label_positioning: init.use_smart_label_positioning, use_custom_font: init.use_custom_font, custom_font: init.custom_font, default_export_format: init.default_export_format, show_background_switch, smart_label_positioning_switch, custom_font_switch, }; let widgets = view_output!(); ComponentParts { model, widgets } } fn update(&mut self, message: Self::Input, sender: ComponentSender) { match message { MapAction::SetUseCustomFont(use_custom_font) => self.use_custom_font = use_custom_font, MapAction::SetShowBackground(show_background) => self.show_background = show_background, MapAction::SetUseSmartLabelPositioning(use_smart_label_positioning) => { self.use_smart_label_positioning = use_smart_label_positioning } MapAction::SetCustomFont(custom_font) => self.custom_font = custom_font, MapAction::SetDefaultExportFormat(default_export_format) => { self.default_export_format = default_export_format } MapAction::ChangeFontDescription(button) => { if let Some(font) = button.font_desc() && let Some(family) = font.family() { sender .output(PreferencesAction::SetCustomFontName(family.to_string())) .ok(); } } MapAction::ChangeShowBackground(state) => { sender .output(PreferencesAction::SetShowMapBackground(state)) .ok(); } MapAction::ChangeUseCustomFont(state) => { sender .output(PreferencesAction::SetUseCustomFont(state)) .ok(); } MapAction::ChangeUseSmartLabelPositioning(state) => { sender .output(PreferencesAction::SetUseSmartLabelPositioning(state)) .ok(); } MapAction::ChangeDefaultExportFormat(format) => { sender .output(PreferencesAction::SetDefaultExportFormat(format)) .ok(); } } } }