diff options
| author | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-03-26 18:13:49 +0100 |
|---|---|---|
| committer | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-03-26 23:02:28 +0100 |
| commit | c5d45b748d90c79f881c4e054b425d68801dad14 (patch) | |
| tree | b39ddf37fb74d761b98d4e0e8f2abdb1a764273e /src/main.rs | |
| parent | 5f5b94474c83f85d95be5c53d148c4205c17d8e1 (diff) | |
Spin header into its own component
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 377 |
1 files changed, 81 insertions, 296 deletions
diff --git a/src/main.rs b/src/main.rs index e1d3bc9..c9393f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod i18n; mod preferences; mod stages; mod ui_helpers; +mod components; mod icon_names { #![allow(clippy::doc_markdown)] // Generated code from relm4_icons_build @@ -33,29 +34,53 @@ mod icon_names { } use std::cell::RefCell; +use std::convert::identity; use std::path::PathBuf; use crate::actions::Action; -use crate::icon_names::shipped::{ - IMAGE_REGULAR, MENU_LARGE, SPLIT_HORIZONTAL_REGULAR, SPLIT_VERTICAL_REGULAR, ZOOM_IN_REGULAR, - ZOOM_OUT_REGULAR, -}; +use crate::icon_names::shipped::{ ZOOM_IN_REGULAR, ZOOM_OUT_REGULAR }; use cairo::ImageSurface; -use gtk::gio; use gtk::glib; -use relm4::{ - Component, ComponentController, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, - SimpleComponent, - actions::{AccelsPlus, ActionablePlus, RelmAction, RelmActionGroup}, - gtk, adw -}; +use relm4::prelude::*; +use relm4::actions::{AccelsPlus, ActionablePlus, RelmAction, RelmActionGroup}; use adw::prelude::*; + use sourceview5::LanguageManager; use sourceview5::prelude::*; -use stages::{ALL_STAGE_TYPES, LocalizedStageType}; + +use stages::LocalizedStageType; use wmap_parser::{Map, parse}; use wmap_renderer::{Configuration, StageType, render_to_surface}; +use components::header::{Header, HeaderInit}; + +/// Macro to reduce boilerplate when registering actions with accelerators. +macro_rules! register_action { + ($group:expr, $app:expr, $sender:expr, $action_type:ty, $message:expr, $accelerators:expr) => {{ + let sender = $sender.clone(); + let action: RelmAction<$action_type> = RelmAction::new_stateless(move |_| { + let _ = sender.input($message); + }); + $group.add_action(action.clone()); + $app.set_accelerators_for_action::<$action_type>($accelerators); + action + }}; +} + +/// Macro to further reduce the boilerplate, by allowing terser registration. +macro_rules! register_actions { + ($group:expr, $app:expr, $sender:expr, [ + $( $action_type:ty => $message:expr, $accelerators:expr );+ $(;)? + ]) => {{ + $( + register_action!( + $group, $app, $sender, + $action_type, $message, $accelerators + ); + )+ + }}; +} + // Action group and action type declarations relm4::new_action_group!(WindowActionGroup, "window"); relm4::new_stateless_action!(NewAction, WindowActionGroup, "new"); @@ -82,18 +107,6 @@ impl relm4::actions::ActionName for NewFromTemplateAction { const NAME: &'static str = "new-from-template"; } -/// Macro to reduce boilerplate when registering actions with accelerators. -macro_rules! register_action { - ($group:expr, $app:expr, $sender:expr, $action_type:ty, $message:expr, $accelerators:expr) => {{ - let sender = $sender.clone(); - let action: RelmAction<$action_type> = RelmAction::new_stateless(move |_| { - sender.input($message); - }); - $group.add_action(action.clone()); - $app.set_accelerators_for_action::<$action_type>($accelerators); - action - }}; -} struct AppInit { zoom: f64, @@ -103,8 +116,6 @@ struct AppInit { struct AppModel { orientation: gtk::Orientation, - stage_type_list: gtk::StringList, - available_stage_types: Vec<StageType>, stage_type: StageType, source: sourceview5::Buffer, source_view: sourceview5::View, @@ -123,10 +134,9 @@ struct AppModel { preferences: preferences::UserPreferences, preferences_window: Option<relm4::Controller<preferences::PreferencesWindow>>, editor_css_provider: gtk::CssProvider, - templates_menu: gio::Menu, - main_menu: gio::Menu, horizontal_layout_enabled: bool, - layout_action: RelmAction<ChangeOrientationAction> + // Child Components + header: Controller<Header> } impl AppModel { @@ -222,40 +232,6 @@ impl AppModel { } } - fn rebuild_stage_type_list(&mut self) { - let mut stage_types: Vec<StageType> = ALL_STAGE_TYPES.to_vec(); - - for custom in &self.preferences.custom_stages { - stage_types.push(StageType::Custom { - name: custom.name.clone(), - i: custom.stage.i.clone(), - ii: custom.stage.ii.clone(), - iii: custom.stage.iii.clone(), - iv: custom.stage.iv.clone(), - }); - } - - while self.stage_type_list.n_items() > 0 { - self.stage_type_list.remove(0); - } - - for stage_type in &stage_types { - self.stage_type_list.append(&stage_type.localized_name()); - } - - self.available_stage_types = stage_types; - } - - fn rebuild_templates_menu(&self) { - self.templates_menu.remove_all(); - for template in &self.preferences.map_templates { - self.templates_menu.append( - Some(&template.name), - Some(&format!("window.new-from-template::{}", template.id)), - ); - } - } - pub fn open_new_window(file_path: Option<PathBuf>, initial_content: Option<String>) { let controller = AppModel::builder() .launch(AppInit { @@ -273,18 +249,6 @@ impl AppModel { file_registry::store_controller(controller); } - fn init_stage_type_list() -> gtk::StringList { - let stage_types: Vec<String> = ALL_STAGE_TYPES - .iter() - .map(stages::LocalizedStageType::localized_name) - .collect(); - let stage_type_refs: Vec<&str> = stage_types - .iter() - .map(std::string::String::as_str) - .collect(); - gtk::StringList::new(&stage_type_refs) - } - fn init_source_buffer( init: &AppInit, sender: &ComponentSender<Self>, @@ -342,51 +306,6 @@ impl AppModel { (drawing_area, map, render_configuration, stage_type, surface) } - fn init_main_menu(prefs: &preferences::UserPreferences) -> (gio::Menu, gio::Menu) { - let templates_menu = gio::Menu::new(); - for template in &prefs.map_templates { - templates_menu.append( - Some(&template.name), - Some(&format!("window.new-from-template::{}", template.id)), - ); - } - - let main_menu = gio::Menu::new(); - main_menu.append(Some(&tr!("command.file.new")), Some("window.new")); - main_menu.append_submenu( - Some(&tr!("command.file.new_from_template")), - &templates_menu, - ); - main_menu.append(Some(&tr!("command.file.open")), Some("window.open")); - main_menu.append(Some(&tr!("command.file.save")), Some("window.save")); - main_menu.append(Some(&tr!("command.file.save_as")), Some("window.save-as")); - main_menu.append(Some(&tr!("command.file.close")), Some("window.close")); - main_menu.append( - Some(&tr!("command.file.export")), - Some("window.export-image"), - ); - - let layout_section = gio::Menu::new(); - layout_section.append( - Some(&tr!("command.view.use_vertical_layout")), - Some("window.change-orientation"), - ); - main_menu.append_section(None, &layout_section); - - let zoom_section = gio::Menu::new(); - zoom_section.append(Some(&tr!("command.view.zoom_in")), Some("window.zoom-in")); - zoom_section.append(Some(&tr!("command.view.zoom_out")), Some("window.zoom-out")); - main_menu.append_section(None, &zoom_section); - - let prefs_section = gio::Menu::new(); - prefs_section.append( - Some(&tr!("command.application.preferences")), - Some("window.preferences"), - ); - main_menu.append_section(None, &prefs_section); - - (templates_menu, main_menu) - } } #[relm4::component] @@ -403,49 +322,8 @@ impl SimpleComponent for AppModel { set_default_width: constants::DEFAULT_WINDOW_WIDTH, set_default_height: constants::DEFAULT_WINDOW_HEIGHT, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 0, - - adw::HeaderBar { - pack_start = &adw::ToolbarView { - gtk::Button::with_label(&match model.orientation { - gtk::Orientation::Horizontal => tr!("command.view.use_vertical_layout"), - _ => tr!("command.view.use_horizontal_layout") - }) { - #[watch] - set_icon_name: match model.orientation { - gtk::Orientation::Horizontal => SPLIT_VERTICAL_REGULAR, - _ => SPLIT_HORIZONTAL_REGULAR - }, - #[watch] - set_visible: model.horizontal_layout_enabled, - ActionablePlus::set_stateless_action::<ChangeOrientationAction>: &(), - } - }, - pack_end = &adw::ToolbarView { - - gtk::Button::with_label(&tr!("command.file.export")) { - set_icon_name: IMAGE_REGULAR, - ActionablePlus::set_stateless_action::<ExportImageAction>: &(), - }, - - gtk::DropDown { - set_model: Some(&model.stage_type_list), - set_show_arrow: true, - set_selected: 0, - connect_selected_notify[sender] => move |dropdown| { - let selected = dropdown.selected(); - sender.input(Action::StageTypeSelected(selected as usize)); - }, - }, - gtk::MenuButton { - set_icon_name: MENU_LARGE, - #[wrap(Some)] - set_popover = >k::PopoverMenu::from_model(Some(&model.main_menu)) {} - } - } - }, + adw::ToolbarView { + add_top_bar = model.header.widget(), gtk::Paned { #[watch] @@ -477,7 +355,7 @@ impl SimpleComponent for AppModel { } }, - gtk::ActionBar { + add_bottom_bar = >k::ActionBar { pack_end = >k::Box { set_orientation: gtk::Orientation::Horizontal, @@ -528,7 +406,6 @@ impl SimpleComponent for AppModel { root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { - let stage_type_list = Self::init_stage_type_list(); let (source, initial_content, current_file) = Self::init_source_buffer(&init, &sender); @@ -552,30 +429,26 @@ impl SimpleComponent for AppModel { file_registry::register_file(path, &root); } - let prefs = preferences::storage::load(); - let (templates_menu, main_menu) = Self::init_main_menu(&prefs); + let preferences = preferences::storage::load(); - // Actions + let orientation = gtk::Orientation::Horizontal; + let horizontal_layout_enabled = false; + // Actions let application = relm4::main_application(); let mut action_group = RelmActionGroup::<WindowActionGroup>::new(); - register_action!( - action_group, - application, - sender, - ZoomInAction, - Action::ZoomIn, - &["<Primary>equal", "<Primary>plus"] - ); - register_action!( - action_group, - application, - sender, - ZoomOutAction, - Action::ZoomOut, - &["<Primary>minus"] - ); + register_actions!(action_group, application, sender, [ + ZoomInAction => Action::ZoomIn, &["<Primary>equal", "<Primary>plus"]; + ZoomOutAction => Action::ZoomOut, &["<Primary>minus"]; + ExportImageAction => Action::ExportImage, &["<Primary>e"]; + NewAction => Action::New, &["<Primary>n"]; + SaveAction => Action::Save { close_after: false }, &["<Primary>s"]; + SaveAsAction => Action::SaveAs { close_after: false }, &["<Primary><Shift>s"]; + OpenAction => Action::Open, &["<Primary>o"]; + CloseAction => Action::CloseWindow, &["<Primary>w"]; + PreferencesAction => Action::ShowPreferences, &["<Primary>comma"] + ]); let layout_action = register_action!( action_group, application, @@ -584,62 +457,6 @@ impl SimpleComponent for AppModel { Action::ChangeOrientation, &["<Primary>l"] ); - register_action!( - action_group, - application, - sender, - ExportImageAction, - Action::ExportImage, - &["<Primary>e"] - ); - register_action!( - action_group, - application, - sender, - NewAction, - Action::New, - &["<Primary>n"] - ); - register_action!( - action_group, - application, - sender, - SaveAction, - Action::Save { close_after: false }, - &["<Primary>s"] - ); - register_action!( - action_group, - application, - sender, - SaveAsAction, - Action::SaveAs { close_after: false }, - &["<Primary><Shift>s"] - ); - register_action!( - action_group, - application, - sender, - OpenAction, - Action::Open, - &["<Primary>o"] - ); - register_action!( - action_group, - application, - sender, - CloseAction, - Action::CloseWindow, - &["<Primary>w"] - ); - register_action!( - action_group, - application, - sender, - PreferencesAction, - Action::ShowPreferences, - &["<Primary>comma"] - ); let sender_clone = sender.clone(); let action: RelmAction<NewFromTemplateAction> = @@ -649,10 +466,20 @@ impl SimpleComponent for AppModel { action_group.add_action(action); action_group.register_for_widget(&root); + + // Child Components + let header = Header::builder() + .launch(HeaderInit { + layout_action, + custom_stages: preferences.custom_stages.clone(), + templates: preferences.map_templates.clone(), + orientation, + horizontal_layout_enabled + }) + .forward(sender.input_sender(), identity); + let mut model = AppModel { - orientation: gtk::Orientation::Horizontal, - stage_type_list, - available_stage_types: ALL_STAGE_TYPES.to_vec(), + orientation, stage_type, source, source_view: source_view.clone(), @@ -668,17 +495,14 @@ impl SimpleComponent for AppModel { pending_load_events: RefCell::new(0), initialized: false, window: root.clone(), - preferences: prefs, + preferences, preferences_window: None, editor_css_provider, - templates_menu, - main_menu, horizontal_layout_enabled: true, - layout_action + header }; model.apply_preferences(); - model.rebuild_stage_type_list(); model.update_image(); file_registry::register_sender(sender.input_sender().clone()); @@ -723,7 +547,7 @@ impl SimpleComponent for AppModel { fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) { match message { Action::ChangeOrientation => handlers::view::change_orientation(self), - Action::StageTypeSelected(index) => handlers::view::stage_type_selected(self, index), + Action::StageTypeSelected(stage_type) => handlers::view::stage_type_selected(self, stage_type), Action::SourceChanged => { let pending = *self.pending_load_events.borrow(); @@ -785,54 +609,15 @@ impl SimpleComponent for AppModel { Action::MarkInitialized => self.initialized = true, // Layout Transition - Action::DisableHorizontalLayout => { - self.horizontal_layout_enabled = false; - self.layout_action.set_enabled(false); - }, - Action::EnableHorizontalLayout => { - self.horizontal_layout_enabled = true; - self.layout_action.set_enabled(true); - }, + Action::DisableHorizontalLayout => handlers::view::change_horizontal_layout(self, false), + Action::EnableHorizontalLayout => handlers::view::change_horizontal_layout(self, 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.rebuild_stage_type_list(); - self.rebuild_templates_menu(); - self.update_image(); - file_registry::broadcast_to_all_windows(&Action::ReloadPreferences); - } - Action::PreferencesWindowClosed => { - self.preferences_window = None; - } - Action::ReloadPreferences => { - self.preferences = preferences::storage::load(); - self.apply_preferences(); - self.rebuild_stage_type_list(); - self.rebuild_templates_menu(); - self.update_image(); - } + Action::ShowPreferences => handlers::preferences::show(self, &sender), + Action::PreferencesChanged(preferences) => handlers::preferences::update(self, preferences), + Action::PreferencesWindowClosed => handlers::preferences::close(self), + Action::ReloadPreferences => handlers::preferences::reload(self), } } } @@ -842,8 +627,8 @@ fn main() { relm4_icons::initialize_icons(icon_names::GRESOURCE_BYTES, icon_names::RESOURCE_PREFIX); - let prefs = preferences::storage::load(); - let initial_content = prefs + let preferences = preferences::storage::load(); + let initial_content = preferences .map_templates .iter() .find(|t| t.is_default) |