diff options
| -rw-r--r-- | src/actions.rs | 1 | ||||
| -rw-r--r-- | src/dialogs.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 138 | ||||
| -rw-r--r-- | src/preferences/models.rs | 4 | ||||
| -rw-r--r-- | src/preferences/storage.rs | 1 |
5 files changed, 114 insertions, 32 deletions
diff --git a/src/actions.rs b/src/actions.rs index 4d8523c..ee25e20 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -35,6 +35,7 @@ pub enum Action { ExportImage, ExportToFile { path: PathBuf, format: ImageFormat }, New, + NewFromTemplateById(String), Open, Save { close_after: bool }, SaveAs { close_after: bool }, diff --git a/src/dialogs.rs b/src/dialogs.rs index 752f99d..6ee7717 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -67,7 +67,7 @@ pub fn show_open_dialog( if is_empty_document { sender.input(Action::LoadFile(path)); } else { - AppModel::open_new_window(Some(path)); + AppModel::open_new_window(Some(path), None); } } }); diff --git a/src/main.rs b/src/main.rs index 1b15b22..4ba95c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ use crate::icon_names::shipped::{ ZOOM_OUT_REGULAR, }; use cairo::ImageSurface; +use gtk::gio; use gtk::glib; use gtk::prelude::*; use relm4::{ @@ -151,11 +152,34 @@ fn setup_actions(window: gtk::Window, sender: ComponentSender<AppModel>) { ); action_group.register_for_widget(&window); + + // Register new-from-template action with string parameter (template ID) + let template_action = + gio::SimpleAction::new("new-from-template", Some(glib::VariantTy::STRING)); + { + let sender = sender.clone(); + template_action.connect_activate(move |_, param| { + if let Some(variant) = param + && let Some(template_id) = variant.get::<String>() + { + sender.input(Action::NewFromTemplateById(template_id)); + } + }); + } + window.insert_action_group( + "win", + Some(&{ + let group = gio::SimpleActionGroup::new(); + group.add_action(&template_action); + group + }), + ); } struct AppInit { zoom: f64, file_path: Option<PathBuf>, + initial_content: Option<String>, } struct AppModel { @@ -180,6 +204,8 @@ 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, } impl AppModel { @@ -306,11 +332,23 @@ impl AppModel { self.available_stage_types = stage_types; } - pub fn open_new_window(file_path: Option<PathBuf>) { + fn rebuild_templates_menu(&self) { + self.templates_menu.remove_all(); + for template in &self.preferences.map_templates { + let item = gio::MenuItem::new( + Some(&template.name), + Some(&format!("win.new-from-template::{}", template.id)), + ); + self.templates_menu.append_item(&item); + } + } + + pub fn open_new_window(file_path: Option<PathBuf>, initial_content: Option<String>) { let controller = AppModel::builder() .launch(AppInit { zoom: 1.0, file_path, + initial_content, }) .detach(); @@ -374,7 +412,7 @@ impl SimpleComponent for AppModel { gtk::MenuButton { set_icon_name: MENU_LARGE, #[wrap(Some)] - set_popover = >k::PopoverMenu::from_model(Some(&main_menu)) {} + set_popover = >k::PopoverMenu::from_model(Some(&model.main_menu)) {} } } }, @@ -451,30 +489,6 @@ impl SimpleComponent for AppModel { } } - menu! { - main_menu: { - "New Map" => NewAction, - "Open..." => OpenAction, - "Save" => SaveAction, - "Save As..." => SaveAsAction, - "Close" => CloseAction, - "Export Map as Image" => ExportImageAction, - section!{ - match model.orientation { - gtk::Orientation::Horizontal => "Use Vertical Layout", - _ => "Use Horizontal Layout" - } => ChangeOrientationAction, - }, - section! { - "Zoom In" => ZoomInAction, - "Zoom Out" => ZoomOutAction, - }, - section! { - "Preferences" => PreferencesAction, - }, - } - } - fn init( init: Self::Init, root: Self::Root, @@ -500,7 +514,7 @@ impl SimpleComponent for AppModel { Err(_) => (String::new(), None), } } else { - (String::new(), None) + (init.initial_content.unwrap_or_default(), None) }; source.set_text(&initial_content); @@ -543,6 +557,43 @@ impl SimpleComponent for AppModel { // Load preferences let prefs = preferences::storage::load(); + let templates_menu = gio::Menu::new(); + + // Populate templates menu from preferences + for template in &prefs.map_templates { + let item = gio::MenuItem::new( + Some(&template.name), + Some(&format!("win.new-from-template::{}", template.id)), + ); + templates_menu.append_item(&item); + } + + // Build main menu + let main_menu = gio::Menu::new(); + main_menu.append(Some("New Map"), Some("window.new")); + main_menu.append_submenu(Some("New from Template"), &templates_menu); + main_menu.append(Some("Open..."), Some("window.open")); + main_menu.append(Some("Save"), Some("window.save")); + main_menu.append(Some("Save As..."), Some("window.save-as")); + main_menu.append(Some("Close"), Some("window.close")); + main_menu.append(Some("Export Map as Image"), Some("window.export-image")); + + let layout_section = gio::Menu::new(); + layout_section.append( + Some("Use Vertical Layout"), + Some("window.change-orientation"), + ); + main_menu.append_section(None, &layout_section); + + let zoom_section = gio::Menu::new(); + zoom_section.append(Some("Zoom In"), Some("window.zoom-in")); + zoom_section.append(Some("Zoom Out"), Some("window.zoom-out")); + main_menu.append_section(None, &zoom_section); + + let prefs_section = gio::Menu::new(); + prefs_section.append(Some("Preferences"), Some("window.preferences")); + main_menu.append_section(None, &prefs_section); + let mut model = AppModel { orientation: gtk::Orientation::Horizontal, stage_type_list, @@ -565,6 +616,8 @@ impl SimpleComponent for AppModel { preferences: prefs, preferences_window: None, editor_css_provider, + templates_menu, + main_menu, }; // Apply preferences and rebuild stage type list @@ -627,7 +680,24 @@ impl SimpleComponent for AppModel { } // File actions - Action::New => Self::open_new_window(None), + Action::New => { + // Use default template if one exists + let content = self + .preferences + .map_templates + .iter() + .find(|t| t.is_default) + .map(|t| t.content.clone()); + Self::open_new_window(None, content); + } + Action::NewFromTemplateById(template_id) => { + if let Ok(id) = uuid::Uuid::parse_str(&template_id) + && let Some(template) = + self.preferences.map_templates.iter().find(|t| t.id == id) + { + Self::open_new_window(None, Some(template.content.clone())); + } + } Action::Open => { dialogs::show_open_dialog(&self.window, self.is_empty_document(), sender) } @@ -671,6 +741,7 @@ impl SimpleComponent for AppModel { self.preferences = prefs; self.apply_preferences(); self.rebuild_stage_type_list(); + self.rebuild_templates_menu(); self.update_image(); // Broadcast to all other windows to reload preferences file_registry::broadcast_to_all_windows(Action::ReloadPreferences); @@ -683,6 +754,7 @@ impl SimpleComponent for AppModel { self.preferences = preferences::storage::load(); self.apply_preferences(); self.rebuild_stage_type_list(); + self.rebuild_templates_menu(); self.update_image(); } } @@ -691,9 +763,19 @@ impl SimpleComponent for AppModel { fn main() { relm4_icons::initialize_icons(icon_names::GRESOURCE_BYTES, icon_names::RESOURCE_PREFIX); + + // Load preferences to get default template for initial window + let prefs = preferences::storage::load(); + let initial_content = prefs + .map_templates + .iter() + .find(|t| t.is_default) + .map(|t| t.content.clone()); + let application = RelmApp::new(constants::APP_ID); application.run::<AppModel>(AppInit { zoom: 1.0, file_path: None, + initial_content, }); } diff --git a/src/preferences/models.rs b/src/preferences/models.rs index 2cf4665..e980805 100644 --- a/src/preferences/models.rs +++ b/src/preferences/models.rs @@ -174,8 +174,8 @@ impl Default for UserPreferences { map_templates: vec![ Template::new( - "Default".to_string(), - "title My Map\n\nanchor Business [0.95, 0.63]\nanchor Public [0.95, 0.78]\ncomponent User Needs [0.9, 0.50]\n".to_string(), + "Basic Map".to_string(), + "Anchor (95, 80)\nUsers (90, 20)\nWalking App (70, 20)\nWalking App Database (50, 60)\nUser Interface (80, 50)\nWeb Server (60, 30)\nCRM (30, 70)\n\nUsers -- Walking App\nUsers -- User Interface\nWalking App -- Walking App Database\nWalking App -- Web Server\nUser Interface -- Web Server\nWeb Server -- CRM\n\n[Note] (80, 5) Social walking app for everyone\n\n[Group] Client Side, Walking App, User Interface\n\n[Evolution] Walking App +5\n\n[Inertia] CRM\n".to_string(), true, ), Template::new("Empty".to_string(), String::new(), false), diff --git a/src/preferences/storage.rs b/src/preferences/storage.rs index 889b503..83db786 100644 --- a/src/preferences/storage.rs +++ b/src/preferences/storage.rs @@ -50,7 +50,6 @@ pub fn load() -> UserPreferences { pub fn save(prefs: &UserPreferences) -> Result<(), std::io::Error> { let path = preferences_path(); - // Ensure config directory exists if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } |