// 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();
}
}
}
}