aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorRubén Beltrán del Río <jj@r.bdr.sh>2026-01-15 19:45:33 +0100
committerRubén Beltrán del Río <jj@r.bdr.sh>2026-01-15 23:26:05 +0100
commitab6492a9f9b8a65121fcf10ff5a44660bd063af1 (patch)
tree2810f61515d782eb7069cf26711e9ff5501af91e /src/main.rs
parent326fcd1b1770fcb597d716af2b722c8971453344 (diff)
Extract some logic from main
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs481
1 files changed, 109 insertions, 372 deletions
diff --git a/src/main.rs b/src/main.rs
index 7d1905a..4a659e9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,7 @@
+mod actions;
mod constants;
+mod dialogs;
+mod file_registry;
mod stages;
mod icon_names {
@@ -8,14 +11,14 @@ mod icon_names {
use std::cell::RefCell;
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 cairo::ImageSurface;
use gtk::prelude::*;
-use gtk::{gio, glib};
-use relm4::component::Controller;
+use gtk::glib;
use relm4::{
Component, ComponentController, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt,
SimpleComponent,
@@ -28,11 +31,45 @@ use stages::all_stages;
use wmap_parser::{Map, parse};
use wmap_renderer::{Configuration, StageType, render_to_surface};
-// Thread-local storage for window controllers and open file paths
-// GTK is single-threaded so this is safe
-thread_local! {
- static WINDOW_CONTROLLERS: RefCell<Vec<Controller<AppModel>>> = const { RefCell::new(Vec::new()) };
- static OPEN_FILES: RefCell<Vec<(PathBuf, gtk::Window)>> = const { RefCell::new(Vec::new()) };
+// Action group and action type declarations
+relm4::new_action_group!(WindowActionGroup, "window");
+relm4::new_stateless_action!(NewAction, WindowActionGroup, "new");
+relm4::new_stateless_action!(OpenAction, WindowActionGroup, "open");
+relm4::new_stateless_action!(SaveAction, WindowActionGroup, "save");
+relm4::new_stateless_action!(SaveAsAction, WindowActionGroup, "save-as");
+relm4::new_stateless_action!(CloseAction, WindowActionGroup, "close");
+relm4::new_stateless_action!(ExportImageAction, WindowActionGroup, "export-image");
+relm4::new_stateless_action!(ChangeOrientationAction, WindowActionGroup, "change-orientation");
+relm4::new_stateless_action!(ZoomInAction, WindowActionGroup, "zoom-in");
+relm4::new_stateless_action!(ZoomOutAction, WindowActionGroup, "zoom-out");
+
+/// 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);
+ $app.set_accelerators_for_action::<$action_type>($accelerators);
+ }};
+}
+
+fn setup_actions(window: gtk::Window, sender: ComponentSender<AppModel>) {
+ 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_action!(action_group, application, sender, ChangeOrientationAction, 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"]);
+
+ action_group.register_for_widget(&window);
}
struct AppInit {
@@ -54,30 +91,11 @@ struct AppModel {
drawing_area: gtk::DrawingArea,
current_file: Option<PathBuf>,
modified: bool,
- pending_load_events: RefCell<u32>, // Counter for pending load events to ignore
- initialized: bool, // Flag to prevent modified during widget initialization
+ pending_load_events: RefCell<u32>,
+ initialized: bool,
window: gtk::Window,
}
-#[derive(Debug)]
-enum AppAction {
- ChangeOrientation,
- StageTypeSelected(usize),
- SourceChanged,
- Zoom(f64),
- ZoomOut,
- ZoomIn,
- ExportImage,
- New,
- Open,
- Save{close: bool},
- SaveAs{close: bool},
- LoadFile(PathBuf),
- SaveToFile{path: PathBuf, close: bool},
- MarkInitialized,
- CloseWindow,
-}
-
impl AppModel {
fn update_image(&mut self) {
if let Ok(surface) =
@@ -88,7 +106,6 @@ impl AppModel {
self.surface = surface;
let zoom = self.zoom;
-
let surface_clone = self.surface.clone();
self.drawing_area
.set_draw_func(move |_, cr, _width, _height| {
@@ -119,38 +136,20 @@ impl AppModel {
}
fn is_empty_document(&self) -> bool {
- self.current_file.is_none()
- && !self.modified
- && self
- .source
- .text(&self.source.start_iter(), &self.source.end_iter(), false)
- .is_empty()
+ self.current_file.is_none() && !self.modified && self.source_text().is_empty()
}
- fn create_file_filter() -> gtk::FileFilter {
- let filter = gtk::FileFilter::new();
- filter.add_pattern(&format!("*.{}", constants::FILE_EXTENSION));
- filter.add_mime_type(constants::MIME_TYPE);
- filter.set_name(Some("Wardley Map Files"));
- filter
+ fn source_text(&self) -> glib::GString {
+ self.source
+ .text(&self.source.start_iter(), &self.source.end_iter(), false)
}
- fn show_error_dialog(&self, title: &str, message: &str) {
- let dialog = gtk::MessageDialog::new(
- Some(&self.window),
- gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT,
- gtk::MessageType::Error,
- gtk::ButtonsType::Ok,
- message,
- );
- dialog.set_title(Some(title));
- dialog.connect_response(|dialog, _| {
- dialog.close();
- });
- dialog.show();
+ fn set_source_text(&self, content: &str) {
+ *self.pending_load_events.borrow_mut() += 1;
+ self.source.set_text(content);
}
- fn open_new_window(file_path: Option<PathBuf>) {
+ pub fn open_new_window(file_path: Option<PathBuf>) {
let controller = AppModel::builder()
.launch(AppInit {
zoom: 1.0,
@@ -158,130 +157,19 @@ impl AppModel {
})
.detach();
- let app = relm4::main_application();
+ let application = relm4::main_application();
let window = controller.widget();
- app.add_window(window);
+ application.add_window(window);
window.present();
- // Store the controller to keep the component alive
- WINDOW_CONTROLLERS.with_borrow_mut(|controllers| {
- controllers.push(controller);
- });
- }
-
- fn set_source_text(&self, content: &str) {
- // Increment counter - SourceChanged will decrement it and skip marking modified
- *self.pending_load_events.borrow_mut() += 1;
- self.source.set_text(content);
- // Don't decrement here - let SourceChanged handle it when the event arrives
- }
-
- fn get_window_for_file(path: &PathBuf) -> Option<gtk::Window> {
- OPEN_FILES.with_borrow(|files| {
- files
- .iter()
- .find(|(p, _)| p == path)
- .map(|(_, window)| window.clone())
- })
- }
-
- fn register_open_file(path: &PathBuf, window: &gtk::Window) {
- OPEN_FILES.with_borrow_mut(|files| {
- if !files.iter().any(|(p, _)| p == path) {
- files.push((path.clone(), window.clone()));
- }
- });
- }
-
- fn unregister_open_file(path: &PathBuf) {
- OPEN_FILES.with_borrow_mut(|files| {
- files.retain(|(p, _)| p != path);
- });
- }
-
- /*
- * Sets up actions and keybinds / accelerators. Buttons and menu items
- * should all use actions.
- */
- fn setup_actions(root: gtk::Window, sender: ComponentSender<AppModel>) {
- let app = relm4::main_application();
- let mut action_group = RelmActionGroup::<WindowActionGroup>::new();
-
- let cloned_sender = sender.clone();
- let zoom_in: RelmAction<ZoomInAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::ZoomIn);
- });
- action_group.add_action(zoom_in);
- app.set_accelerators_for_action::<ZoomInAction>(&["<Primary>equal", "<Primary>plus"]);
-
- let cloned_sender = sender.clone();
- let zoom_out: RelmAction<ZoomOutAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::ZoomOut);
- });
- app.set_accelerators_for_action::<ZoomOutAction>(&["<Primary>minus"]);
- action_group.add_action(zoom_out);
-
- let cloned_sender = sender.clone();
- let change_orientation: RelmAction<ChangeOrientationAction> =
- RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::ChangeOrientation);
- });
- app.set_accelerators_for_action::<ChangeOrientationAction>(&["<Primary>l"]);
- action_group.add_action(change_orientation);
-
- let cloned_sender = sender.clone();
- let export_image: RelmAction<ExportImageAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::ExportImage);
- });
- app.set_accelerators_for_action::<ExportImageAction>(&["<Primary>e"]);
- action_group.add_action(export_image);
-
- let cloned_sender = sender.clone();
- let new: RelmAction<NewAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::New);
- });
- app.set_accelerators_for_action::<NewAction>(&["<Primary>n"]);
- action_group.add_action(new);
-
- let cloned_sender = sender.clone();
- let save: RelmAction<SaveAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::Save{close: false});
- });
- app.set_accelerators_for_action::<SaveAction>(&["<Primary>s"]);
- action_group.add_action(save);
-
- let cloned_sender = sender.clone();
- let save_as: RelmAction<SaveAsAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::SaveAs{close: false});
- });
- app.set_accelerators_for_action::<SaveAsAction>(&["<Primary><Shift>s"]);
- action_group.add_action(save_as);
-
- let cloned_sender = sender.clone();
- let open: RelmAction<OpenAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::Open);
- });
- app.set_accelerators_for_action::<OpenAction>(&["<Primary>o"]);
- action_group.add_action(open);
-
- let cloned_sender = sender.clone();
- let close: RelmAction<CloseAction> = RelmAction::new_stateless(move |_| {
- cloned_sender.input(AppAction::CloseWindow);
- });
- app.set_accelerators_for_action::<CloseAction>(&["<Primary>w"]);
- action_group.add_action(close);
-
- action_group.register_for_widget(&root);
+ file_registry::store_controller(controller);
}
}
#[relm4::component]
impl SimpleComponent for AppModel {
- /// The type of the messages that this component can receive.
- type Input = AppAction;
- /// The type of the messages that this component can send.
+ type Input = Action;
type Output = ();
- /// The type of data with which this component will be initialized.
type Init = AppInit;
view! {
@@ -323,15 +211,13 @@ impl SimpleComponent for AppModel {
set_selected: 0,
connect_selected_notify[sender] => move |dropdown| {
let selected = dropdown.selected();
- sender.input(AppAction::StageTypeSelected(selected as usize));
+ sender.input(Action::StageTypeSelected(selected as usize));
},
},
gtk::MenuButton {
set_icon_name: MENU_LARGE,
#[wrap(Some)]
- set_popover = &gtk::PopoverMenu::from_model(Some(&main_menu)) {
- add_child: (&popover_child, "my_widget"),
- }
+ set_popover = &gtk::PopoverMenu::from_model(Some(&main_menu)) {}
}
}
},
@@ -391,7 +277,7 @@ impl SimpleComponent for AppModel {
set_value_pos: gtk::PositionType::Left,
set_width_request: 100,
connect_value_changed[sender] => move |scale| {
- sender.input(AppAction::Zoom(scale.value()));
+ sender.input(Action::Zoom(scale.value()));
}
},
@@ -405,15 +291,11 @@ impl SimpleComponent for AppModel {
}
}
}
- },
- popover_child = gtk::Spinner {
- set_spinning: true,
}
}
menu! {
main_menu: {
- custom: "Map",
"New Map" => NewAction,
"Open..." => OpenAction,
"Save" => SaveAction,
@@ -433,7 +315,6 @@ impl SimpleComponent for AppModel {
}
}
- /// Initialize the UI and model.
fn init(
init: Self::Init,
root: Self::Root,
@@ -446,17 +327,13 @@ impl SimpleComponent for AppModel {
let stage_type_list = gtk::StringList::new(&stage_types);
let source = sourceview5::Buffer::new(None);
-
- // Enable syntax highlighting
source.set_highlight_syntax(true);
- // Set default language
let language_manager = LanguageManager::default();
if let Some(language) = language_manager.language("wmap") {
source.set_language(Some(&language));
}
- // Load file content if a file path was provided
let (initial_content, current_file) = if let Some(ref path) = init.file_path {
match std::fs::read_to_string(path) {
Ok(content) => (content, Some(path.clone())),
@@ -466,20 +343,14 @@ impl SimpleComponent for AppModel {
(String::new(), None)
};
- // Set initial text BEFORE connecting the change handler
- // This prevents marking the document as modified on initial load
source.set_text(&initial_content);
- // Connect change handler AFTER setting initial text
- let cloned_sender = sender.clone();
+ let sender_for_source = sender.clone();
source.connect_changed(move |_| {
- cloned_sender.input(AppAction::SourceChanged);
+ sender_for_source.input(Action::SourceChanged);
});
- /* Attach Actions ----------------------------------------------------*/
- AppModel::setup_actions(root.clone(), sender.clone());
-
- /* Initialize Model --------------------------------------------------*/
+ setup_actions(root.clone(), sender.clone());
let drawing_area = gtk::DrawingArea::new();
let map = parse(&initial_content);
@@ -492,9 +363,8 @@ impl SimpleComponent for AppModel {
.expect("Failed to create fallback surface")
});
- // Register open file if we have one
if let Some(ref path) = current_file {
- Self::register_open_file(path, &root);
+ file_registry::register_file(path, &root);
}
let mut model = AppModel {
@@ -517,16 +387,14 @@ impl SimpleComponent for AppModel {
};
model.update_image();
- // Schedule MarkInitialized to be sent after widget setup is complete
- let sender_clone = sender.clone();
+ let sender_for_init = sender.clone();
glib::idle_add_local_once(move || {
- sender_clone.input(AppAction::MarkInitialized);
+ sender_for_init.input(Action::MarkInitialized);
});
- // Connect close-request to handle unsaved changes
- let sender_clone = sender.clone();
+ let sender_for_close = sender.clone();
root.connect_close_request(move |_| {
- sender_clone.input(AppAction::CloseWindow);
+ sender_for_close.input(Action::CloseWindow);
glib::Propagation::Stop
});
@@ -537,155 +405,86 @@ impl SimpleComponent for AppModel {
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
match message {
- AppAction::ChangeOrientation => {
+ Action::ChangeOrientation => {
self.orientation = match self.orientation {
gtk::Orientation::Horizontal => gtk::Orientation::Vertical,
_ => gtk::Orientation::Horizontal,
}
}
- AppAction::StageTypeSelected(index) => {
+ Action::StageTypeSelected(index) => {
let all_stages = all_stages();
if index < all_stages.len() {
self.stage_type = all_stages[index];
}
self.update_image();
}
- AppAction::SourceChanged => {
- // Check if this is from a programmatic load
+ Action::SourceChanged => {
let pending = *self.pending_load_events.borrow();
if pending > 0 {
*self.pending_load_events.borrow_mut() -= 1;
} else if self.initialized {
- // Only mark as modified if initialized and not from a load
self.modified = true;
}
- let text =
- self.source
- .text(&self.source.start_iter(), &self.source.end_iter(), false);
- self.map = parse(&text);
+ self.map = parse(&self.source_text());
self.update_image();
}
- AppAction::Zoom(new_zoom) => {
+ Action::Zoom(new_zoom) => {
self.zoom = new_zoom;
self.update_image();
}
- AppAction::ZoomOut => {
+ Action::ZoomOut => {
if self.zoom - constants::ZOOM_STEP >= constants::MIN_ZOOM {
self.zoom -= constants::ZOOM_STEP;
self.update_image();
}
}
- AppAction::ZoomIn => {
+ Action::ZoomIn => {
if self.zoom < constants::MAX_ZOOM {
self.zoom += constants::ZOOM_STEP;
self.update_image();
}
}
- AppAction::ExportImage => {}
- AppAction::New => {
+ Action::ExportImage => {}
+ Action::New => {
Self::open_new_window(None);
}
- AppAction::Open => {
- let dialog = gtk::FileChooserDialog::new(
- Some("Open Map"),
- Some(&self.window),
- gtk::FileChooserAction::Open,
- &[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Open", gtk::ResponseType::Accept),
- ],
- );
- dialog.add_filter(&Self::create_file_filter());
-
- let sender_clone = sender.clone();
- let is_empty = self.is_empty_document();
- dialog.connect_response(move |dialog, response| {
- dialog.close();
- if response == gtk::ResponseType::Accept
- && let Some(file) = dialog.file()
- && let Some(path) = file.path()
- {
- // Check if file is already open - if so, focus that window
- if let Some(existing_window) = Self::get_window_for_file(&path) {
- existing_window.present();
- return;
- }
-
- if is_empty {
- sender_clone.input(AppAction::LoadFile(path));
- } else {
- Self::open_new_window(Some(path));
- }
- }
- });
- dialog.present();
+ Action::Open => {
+ dialogs::show_open_dialog(&self.window, self.is_empty_document(), sender);
}
- AppAction::Save{close} => {
+ Action::Save { close_after } => {
if let Some(path) = self.current_file.clone() {
- sender.input(AppAction::SaveToFile{path, close});
+ sender.input(Action::SaveToFile { path, close_after });
} else {
- sender.input(AppAction::SaveAs{close});
+ sender.input(Action::SaveAs { close_after });
}
}
- AppAction::SaveAs{close} => {
- let dialog = gtk::FileChooserDialog::new(
- Some("Save Map As"),
- Some(&self.window),
- gtk::FileChooserAction::Save,
- &[
- ("Cancel", gtk::ResponseType::Cancel),
- ("Save", gtk::ResponseType::Accept),
- ],
+ Action::SaveAs { close_after } => {
+ dialogs::show_save_dialog(
+ &self.window,
+ self.current_file.clone(),
+ close_after,
+ sender,
);
- dialog.add_filter(&Self::create_file_filter());
-
- if let Some(ref current_path) = self.current_file {
- if let Some(parent) = current_path.parent() {
- let folder = gio::File::for_path(parent);
- dialog.set_current_folder(Some(&folder)).ok();
- }
- if let Some(filename) = current_path.file_name() {
- dialog.set_current_name(filename.to_str().unwrap_or("map.wmap"));
- }
- } else {
- dialog.set_current_name("map.wmap");
- }
-
- let sender_clone = sender.clone();
- dialog.connect_response(move |dialog, response| {
- dialog.close();
- if response == gtk::ResponseType::Accept
- && let Some(file) = dialog.file()
- && let Some(path) = file.path()
- {
- sender_clone.input(AppAction::SaveToFile{path, close});
- }
- });
- dialog.present();
}
- AppAction::LoadFile(path) => {
+ Action::LoadFile(path) => {
match std::fs::read_to_string(&path) {
Ok(content) => {
- // Unregister old file if any
if let Some(ref old_path) = self.current_file {
- Self::unregister_open_file(old_path);
+ file_registry::unregister_file(old_path);
}
self.set_source_text(&content);
- Self::register_open_file(&path, &self.window);
+ file_registry::register_file(&path, &self.window);
self.current_file = Some(path);
self.modified = false;
}
Err(error) => {
- self.show_error_dialog("Error Opening File", &error.to_string());
+ dialogs::show_error(&self.window, "Error Opening File", &error.to_string());
}
}
}
- AppAction::SaveToFile{path, close} => {
- let content =
- self.source
- .text(&self.source.start_iter(), &self.source.end_iter(), false);
+ Action::SaveToFile { path, close_after } => {
+ let content = self.source_text();
- // Ensure the file has the correct extension
let path = if path.extension().is_none()
|| path.extension().unwrap_or_default() != constants::FILE_EXTENSION
{
@@ -696,76 +495,39 @@ impl SimpleComponent for AppModel {
match std::fs::write(&path, content.as_str()) {
Ok(()) => {
- if close {
- Self::unregister_open_file(&path);
+ if close_after {
+ file_registry::unregister_file(&path);
self.modified = false;
self.window.close();
} else {
- // Update file registration if path changed
if self.current_file.as_ref() != Some(&path) {
if let Some(ref old_path) = self.current_file {
- Self::unregister_open_file(old_path);
+ file_registry::unregister_file(old_path);
}
- Self::register_open_file(&path, &self.window);
+ file_registry::register_file(&path, &self.window);
}
self.current_file = Some(path);
self.modified = false;
}
}
Err(error) => {
- self.show_error_dialog("Error Saving File", &error.to_string());
+ dialogs::show_error(&self.window, "Error Saving File", &error.to_string());
}
}
}
- AppAction::MarkInitialized => {
+ Action::MarkInitialized => {
self.initialized = true;
}
- AppAction::CloseWindow => {
+ Action::CloseWindow => {
if self.modified {
- // Show confirmation dialog
- let dialog = gtk::MessageDialog::new(
- Some(&self.window),
- gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT,
- gtk::MessageType::Warning,
- gtk::ButtonsType::None,
- "Save changes before closing?",
+ dialogs::show_unsaved_changes_dialog(
+ &self.window,
+ self.current_file.clone(),
+ sender,
);
- dialog.set_title(Some("Unsaved Changes"));
- dialog.add_button("Don't Save", gtk::ResponseType::Reject);
- dialog.add_button("Cancel", gtk::ResponseType::Cancel);
- dialog.add_button("Save", gtk::ResponseType::Accept);
-
- let window = self.window.clone();
- let current_file = self.current_file.clone();
- let sender_clone = sender.clone();
- dialog.connect_response(move |dialog, response| {
- dialog.close();
- match response {
- // Save / Save As and Close.
- gtk::ResponseType::Accept => {
- if current_file.is_some() {
- sender_clone.input(AppAction::Save{close: true});
- } else {
- sender_clone.input(AppAction::SaveAs{close: true});
- }
- }
-
- // Don't save, just close
- gtk::ResponseType::Reject => {
- if let Some(ref path) = current_file {
- Self::unregister_open_file(path);
- }
- window.destroy();
- }
-
- // Cancel - do nothing, keep window open
- _ => {}
- }
- });
- dialog.show();
} else {
if let Some(ref path) = self.current_file {
- Self::unregister_open_file(path);
+ file_registry::unregister_file(path);
}
self.window.destroy();
}
@@ -774,35 +536,10 @@ impl SimpleComponent for AppModel {
}
}
-/* ACTIONS ********************************************************************/
-
-relm4::new_action_group!(WindowActionGroup, "window");
-
-/* Map File */
-relm4::new_stateless_action!(NewAction, WindowActionGroup, "new");
-relm4::new_stateless_action!(OpenAction, WindowActionGroup, "open");
-relm4::new_stateless_action!(SaveAction, WindowActionGroup, "save");
-relm4::new_stateless_action!(SaveAsAction, WindowActionGroup, "save-as");
-relm4::new_stateless_action!(CloseAction, WindowActionGroup, "close");
-relm4::new_stateless_action!(ExportImageAction, WindowActionGroup, "export-image");
-
-/* Layout */
-relm4::new_stateless_action!(
- ChangeOrientationAction,
- WindowActionGroup,
- "change-orientation"
-);
-
-/* Map View */
-relm4::new_stateless_action!(ZoomInAction, WindowActionGroup, "zoom-in");
-relm4::new_stateless_action!(ZoomOutAction, WindowActionGroup, "zoom-out");
-
-/* MAIN ***********************************************************************/
-
fn main() {
relm4_icons::initialize_icons(icon_names::GRESOURCE_BYTES, icon_names::RESOURCE_PREFIX);
- let app = RelmApp::new("systems.tranquil.map");
- app.run::<AppModel>(AppInit {
+ let application = RelmApp::new("systems.tranquil.map");
+ application.run::<AppModel>(AppInit {
zoom: 1.0,
file_path: None,
});