aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorRubén Beltrán del Río <jj@r.bdr.sh>2026-01-16 00:08:41 +0100
committerRubén Beltrán del Río <jj@r.bdr.sh>2026-01-16 00:12:53 +0100
commit04b1e6080d99601b8d6343be3140f545cd8f9eae (patch)
tree2180b7d50f2442e5d18a99a87e56def29b02f68e /src/main.rs
parentab6492a9f9b8a65121fcf10ff5a44660bd063af1 (diff)
Separate concerns
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs278
1 files changed, 137 insertions, 141 deletions
diff --git a/src/main.rs b/src/main.rs
index 4a659e9..30f2381 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@ mod actions;
mod constants;
mod dialogs;
mod file_registry;
+mod handlers;
mod stages;
mod icon_names {
@@ -17,8 +18,8 @@ use crate::icon_names::shipped::{
ZOOM_OUT_REGULAR,
};
use cairo::ImageSurface;
-use gtk::prelude::*;
use gtk::glib;
+use gtk::prelude::*;
use relm4::{
Component, ComponentController, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt,
SimpleComponent,
@@ -27,7 +28,7 @@ use relm4::{
};
use sourceview5::LanguageManager;
use sourceview5::prelude::{BufferExt, TextBufferExt, TextViewExt};
-use stages::all_stages;
+use stages::ALL_STAGE_TYPES;
use wmap_parser::{Map, parse};
use wmap_renderer::{Configuration, StageType, render_to_surface};
@@ -39,7 +40,11 @@ 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!(
+ ChangeOrientationAction,
+ WindowActionGroup,
+ "change-orientation"
+);
relm4::new_stateless_action!(ZoomInAction, WindowActionGroup, "zoom-in");
relm4::new_stateless_action!(ZoomOutAction, WindowActionGroup, "zoom-out");
@@ -59,15 +64,78 @@ 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"]);
+ 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);
}
@@ -177,8 +245,8 @@ impl SimpleComponent for AppModel {
gtk::Window {
#[watch]
set_title: Some(&model.window_title()),
- set_default_width: 300,
- set_default_height: 100,
+ set_default_width: constants::DEFAULT_WINDOW_WIDTH,
+ set_default_height: constants::DEFAULT_WINDOW_HEIGHT,
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
@@ -320,7 +388,7 @@ impl SimpleComponent for AppModel {
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
- let stage_types: Vec<&str> = all_stages()
+ let stage_types: Vec<&str> = ALL_STAGE_TYPES
.iter()
.map(|stage_type| stage_type.name())
.collect();
@@ -345,10 +413,12 @@ impl SimpleComponent for AppModel {
source.set_text(&initial_content);
- let sender_for_source = sender.clone();
- source.connect_changed(move |_| {
- sender_for_source.input(Action::SourceChanged);
- });
+ {
+ let sender = sender.clone();
+ source.connect_changed(move |_| {
+ sender.input(Action::SourceChanged);
+ });
+ }
setup_actions(root.clone(), sender.clone());
@@ -387,16 +457,20 @@ impl SimpleComponent for AppModel {
};
model.update_image();
- let sender_for_init = sender.clone();
- glib::idle_add_local_once(move || {
- sender_for_init.input(Action::MarkInitialized);
- });
+ {
+ let sender = sender.clone();
+ glib::idle_add_local_once(move || {
+ sender.input(Action::MarkInitialized);
+ });
+ }
- let sender_for_close = sender.clone();
- root.connect_close_request(move |_| {
- sender_for_close.input(Action::CloseWindow);
- glib::Propagation::Stop
- });
+ {
+ let sender = sender.clone();
+ root.connect_close_request(move |_| {
+ sender.input(Action::CloseWindow);
+ glib::Propagation::Stop
+ });
+ }
let widgets = view_output!();
@@ -405,19 +479,11 @@ impl SimpleComponent for AppModel {
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
match message {
- Action::ChangeOrientation => {
- self.orientation = match self.orientation {
- gtk::Orientation::Horizontal => gtk::Orientation::Vertical,
- _ => gtk::Orientation::Horizontal,
- }
- }
- Action::StageTypeSelected(index) => {
- let all_stages = all_stages();
- if index < all_stages.len() {
- self.stage_type = all_stages[index];
- }
- self.update_image();
- }
+ // View actions
+ Action::ChangeOrientation => handlers::view::change_orientation(self),
+ Action::StageTypeSelected(index) => handlers::view::stage_type_selected(self, index),
+
+ // Source changes
Action::SourceChanged => {
let pending = *self.pending_load_events.borrow();
if pending > 0 {
@@ -428,117 +494,47 @@ impl SimpleComponent for AppModel {
self.map = parse(&self.source_text());
self.update_image();
}
- Action::Zoom(new_zoom) => {
- self.zoom = new_zoom;
- self.update_image();
- }
- Action::ZoomOut => {
- if self.zoom - constants::ZOOM_STEP >= constants::MIN_ZOOM {
- self.zoom -= constants::ZOOM_STEP;
- self.update_image();
- }
- }
- Action::ZoomIn => {
- if self.zoom < constants::MAX_ZOOM {
- self.zoom += constants::ZOOM_STEP;
- self.update_image();
- }
- }
- Action::ExportImage => {}
- Action::New => {
- Self::open_new_window(None);
- }
- Action::Open => {
- dialogs::show_open_dialog(&self.window, self.is_empty_document(), sender);
- }
- Action::Save { close_after } => {
- if let Some(path) = self.current_file.clone() {
- sender.input(Action::SaveToFile { path, close_after });
- } else {
- sender.input(Action::SaveAs { close_after });
- }
- }
- Action::SaveAs { close_after } => {
- dialogs::show_save_dialog(
- &self.window,
- self.current_file.clone(),
- close_after,
- sender,
- );
- }
- Action::LoadFile(path) => {
- match std::fs::read_to_string(&path) {
- Ok(content) => {
- if let Some(ref old_path) = self.current_file {
- file_registry::unregister_file(old_path);
- }
- self.set_source_text(&content);
- file_registry::register_file(&path, &self.window);
- self.current_file = Some(path);
- self.modified = false;
- }
- Err(error) => {
- dialogs::show_error(&self.window, "Error Opening File", &error.to_string());
- }
- }
- }
- Action::SaveToFile { path, close_after } => {
- let content = self.source_text();
- let path = if path.extension().is_none()
- || path.extension().unwrap_or_default() != constants::FILE_EXTENSION
- {
- path.with_extension(constants::FILE_EXTENSION)
- } else {
- path
- };
+ // Zoom actions
+ Action::Zoom(level) => handlers::zoom::zoom(self, level),
+ Action::ZoomIn => handlers::zoom::zoom_in(self),
+ Action::ZoomOut => handlers::zoom::zoom_out(self),
- match std::fs::write(&path, content.as_str()) {
- Ok(()) => {
- if close_after {
- file_registry::unregister_file(&path);
- self.modified = false;
- self.window.close();
- } else {
- if self.current_file.as_ref() != Some(&path) {
- if let Some(ref old_path) = self.current_file {
- file_registry::unregister_file(old_path);
- }
- file_registry::register_file(&path, &self.window);
- }
- self.current_file = Some(path);
- self.modified = false;
- }
- }
- Err(error) => {
- dialogs::show_error(&self.window, "Error Saving File", &error.to_string());
- }
- }
+ // Export (not yet implemented)
+ Action::ExportImage => {
+ // TODO: Implement image export functionality
+ // - Show save dialog with PNG/SVG format picker
+ // - Default filename: current file name with .png or .svg extension
+ // - Call renderer's render_to_png() or render_to_svg() based on selection
}
- Action::MarkInitialized => {
- self.initialized = true;
+
+ // File actions
+ Action::New => Self::open_new_window(None),
+ Action::Open => {
+ dialogs::show_open_dialog(&self.window, self.is_empty_document(), sender)
}
- Action::CloseWindow => {
- if self.modified {
- dialogs::show_unsaved_changes_dialog(
- &self.window,
- self.current_file.clone(),
- sender,
- );
- } else {
- if let Some(ref path) = self.current_file {
- file_registry::unregister_file(path);
- }
- self.window.destroy();
- }
+ Action::Save { close_after } => handlers::file::save(self, close_after, &sender),
+ Action::SaveAs { close_after } => dialogs::show_save_dialog(
+ &self.window,
+ self.current_file.clone(),
+ close_after,
+ sender,
+ ),
+ Action::LoadFile(path) => handlers::file::load_file(self, path),
+ Action::SaveToFile { path, close_after } => {
+ handlers::file::save_to_file(self, path, close_after)
}
+ Action::CloseWindow => handlers::file::close_window(self, &sender),
+
+ // Internal actions
+ Action::MarkInitialized => self.initialized = true,
}
}
}
fn main() {
relm4_icons::initialize_icons(icon_names::GRESOURCE_BYTES, icon_names::RESOURCE_PREFIX);
- let application = RelmApp::new("systems.tranquil.map");
+ let application = RelmApp::new(constants::APP_ID);
application.run::<AppModel>(AppInit {
zoom: 1.0,
file_path: None,