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/main.rs | |
| parent | 4211b2ae06777d5bbe8261a1ab5c0dd057829a35 (diff) | |
Add preferences
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 128 |
1 files changed, 124 insertions, 4 deletions
diff --git a/src/main.rs b/src/main.rs index 2162845..6a7bd34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,12 @@ +// Suppress warnings from relm4 view! macro internals +#![allow(unused_assignments)] + mod actions; mod constants; mod dialogs; mod file_registry; mod handlers; +mod preferences; mod stages; mod icon_names { @@ -47,6 +51,7 @@ relm4::new_stateless_action!( ); relm4::new_stateless_action!(ZoomInAction, WindowActionGroup, "zoom-in"); relm4::new_stateless_action!(ZoomOutAction, WindowActionGroup, "zoom-out"); +relm4::new_stateless_action!(PreferencesAction, WindowActionGroup, "preferences"); /// Macro to reduce boilerplate when registering actions with accelerators. macro_rules! register_action { @@ -136,6 +141,14 @@ fn setup_actions(window: gtk::Window, sender: ComponentSender<AppModel>) { Action::CloseWindow, &["<Primary>w"] ); + register_action!( + action_group, + application, + sender, + PreferencesAction, + Action::ShowPreferences, + &["<Primary>comma"] + ); action_group.register_for_widget(&window); } @@ -150,6 +163,7 @@ struct AppModel { stage_type_list: gtk::StringList, stage_type: StageType, source: sourceview5::Buffer, + source_view: sourceview5::View, map: Map, zoom: f64, render_configuration: Configuration, @@ -162,12 +176,15 @@ struct AppModel { pending_load_events: RefCell<u32>, initialized: bool, window: gtk::Window, + preferences: preferences::UserPreferences, + preferences_window: Option<relm4::Controller<preferences::PreferencesWindow>>, + editor_css_provider: gtk::CssProvider, } impl AppModel { fn update_image(&mut self) { if let Ok(surface) = - render_to_surface(&self.map, self.stage_type, &self.render_configuration) + render_to_surface(&self.map, &self.stage_type, &self.render_configuration) { self.surface_width = (surface.width() as f64 * self.zoom).round() as i32; self.surface_height = (surface.height() as f64 * self.zoom).round() as i32; @@ -217,6 +234,48 @@ impl AppModel { self.source.set_text(content); } + fn apply_preferences(&mut self) { + // Apply editor preferences + self.source_view + .set_wrap_mode(if self.preferences.soft_wrap_lines { + gtk::WrapMode::Word + } else { + gtk::WrapMode::None + }); + + // Build CSS for editor font + let css = if self.preferences.use_custom_editor_font { + self.source_view.set_monospace(false); + format!( + "textview {{ font-family: \"{}\"; font-size: {}pt; }}", + self.preferences.custom_editor_font_name, self.preferences.editor_font_size as i32 + ) + } else { + // Use monospace but still apply font size + self.source_view.set_monospace(true); + format!( + "textview {{ font-size: {}pt; }}", + self.preferences.editor_font_size as i32 + ) + }; + + // Update the CSS provider (reusing the same one avoids accumulation) + self.editor_css_provider.load_from_data(&css); + + // Apply render configuration + self.render_configuration.options.smart_label_positioning = + self.preferences.use_smart_label_positioning; + self.render_configuration.options.show_background = self.preferences.show_map_background; + + // Apply map font + if self.preferences.use_custom_font { + self.render_configuration.theme.fonts.face = self.preferences.custom_font_name.clone(); + } else { + // Reset to default font + self.render_configuration.theme.fonts.face = String::from("sans-serif"); + } + } + pub fn open_new_window(file_path: Option<PathBuf>) { let controller = AppModel::builder() .launch(AppInit { @@ -297,9 +356,9 @@ impl SimpleComponent for AppModel { #[wrap(Some)] set_start_child = >k::ScrolledWindow { - sourceview5::View { + #[local_ref] + source_view -> sourceview5::View { set_expand: true, - set_monospace: true, set_buffer: Some(&model.source) } }, @@ -380,6 +439,9 @@ impl SimpleComponent for AppModel { "Zoom In" => ZoomInAction, "Zoom Out" => ZoomOutAction, }, + section! { + "Preferences" => PreferencesAction, + }, } } @@ -422,13 +484,24 @@ impl SimpleComponent for AppModel { setup_actions(root.clone(), sender.clone()); + // Create source view + let source_view = sourceview5::View::with_buffer(&source); + source_view.set_monospace(true); + + // Create CSS provider for editor font styling + let editor_css_provider = gtk::CssProvider::new(); + source_view.style_context().add_provider( + &editor_css_provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); + let drawing_area = gtk::DrawingArea::new(); let map = parse(&initial_content); let mut render_configuration = Configuration::default(); let stage_type = StageType::Activities; render_configuration.options.smart_label_positioning = true; let surface = - render_to_surface(&map, stage_type, &render_configuration).unwrap_or_else(|_| { + render_to_surface(&map, &stage_type, &render_configuration).unwrap_or_else(|_| { ImageSurface::create(cairo::Format::ARgb32, 1, 1) .expect("Failed to create fallback surface") }); @@ -437,11 +510,15 @@ impl SimpleComponent for AppModel { file_registry::register_file(path, &root); } + // Load preferences + let prefs = preferences::storage::load(); + let mut model = AppModel { orientation: gtk::Orientation::Horizontal, stage_type_list, stage_type, source, + source_view: source_view.clone(), map, zoom: init.zoom, render_configuration, @@ -454,7 +531,13 @@ impl SimpleComponent for AppModel { pending_load_events: RefCell::new(0), initialized: false, window: root.clone(), + preferences: prefs, + preferences_window: None, + editor_css_provider, }; + + // Apply preferences + model.apply_preferences(); model.update_image(); { @@ -528,6 +611,43 @@ impl SimpleComponent for AppModel { // Internal actions Action::MarkInitialized => self.initialized = true, + + // Preferences + Action::ShowPreferences => { + if self.preferences_window.is_none() { + let controller = preferences::PreferencesWindow::builder() + .transient_for(&self.window) + .launch(self.preferences.clone()) + .forward(sender.input_sender(), |output| match output { + preferences::window::PreferencesOutput::PreferencesChanged(prefs) => { + Action::PreferencesChanged(prefs) + } + preferences::window::PreferencesOutput::Closed => { + Action::PreferencesWindowClosed + } + }); + self.preferences_window = Some(controller); + } + if let Some(ref controller) = self.preferences_window { + controller.widget().present(); + } + } + Action::PreferencesChanged(prefs) => { + self.preferences = prefs; + self.apply_preferences(); + self.update_image(); + // Broadcast to all other windows to reload preferences + file_registry::broadcast_to_all_windows(Action::ReloadPreferences); + } + Action::PreferencesWindowClosed => { + self.preferences_window = None; + } + Action::ReloadPreferences => { + // Reload preferences from disk (another window changed them) + self.preferences = preferences::storage::load(); + self.apply_preferences(); + self.update_image(); + } } } } |