diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/header.rs | 234 | ||||
| -rw-r--r-- | src/components/mod.rs | 16 |
2 files changed, 250 insertions, 0 deletions
diff --git a/src/components/header.rs b/src/components/header.rs new file mode 100644 index 0000000..7a39490 --- /dev/null +++ b/src/components/header.rs @@ -0,0 +1,234 @@ +use gtk::{gio, prelude::*}; +use relm4::prelude::*; +use relm4::actions::{ActionablePlus, RelmAction}; + +use wmap_renderer::StageType; + +use crate::tr; +use crate::preferences::models::{CustomStage, Template}; +use crate::stages::{ALL_STAGE_TYPES, LocalizedStageType}; + +use crate::{ + ChangeOrientationAction, ExportImageAction, actions::Action +}; +use crate::icon_names::shipped::{ + IMAGE_REGULAR, MENU_LARGE, SPLIT_HORIZONTAL_REGULAR, SPLIT_VERTICAL_REGULAR +}; + +// Model and Input Messages + +pub struct Header { + stage_type_list: gtk::StringList, + available_stage_types: Vec<StageType>, + orientation: gtk::Orientation, + horizontal_layout_enabled: bool, + templates_menu: gio::Menu, + main_menu: gio::Menu, + layout_action: RelmAction<ChangeOrientationAction> +} + +pub struct HeaderInit { + pub layout_action: RelmAction<ChangeOrientationAction>, + pub custom_stages: Vec<CustomStage>, + pub templates: Vec<Template>, + pub orientation: gtk::Orientation, + pub horizontal_layout_enabled: bool, +} + +#[derive(Debug, Clone)] +pub enum HeaderAction { + SetHorizontalLayoutEnabled(bool), + SetLayoutOrientation(gtk::Orientation), + SetTemplates(Vec<Template>), + SetCustomStages(Vec<CustomStage>), + + // Internal + StageTypeSelected(usize) +} + +#[relm4::component(pub)] +impl SimpleComponent for Header { + type Init = HeaderInit; + type Input = HeaderAction; + type Output = Action; + + view! { + 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 = >k::Box { + + 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(HeaderAction::StageTypeSelected(selected as usize)); + }, + }, + gtk::MenuButton { + set_icon_name: MENU_LARGE, + #[wrap(Some)] + set_popover = >k::PopoverMenu::from_model(Some(&model.main_menu)) {} + } + } + } + } + + fn init( + init: Self::Init, + root: Self::Root, + sender: ComponentSender<Self>, + ) -> ComponentParts<Self> { + let (templates_menu, main_menu) = Self::init_main_menu(&init.templates); + + let stage_type_list = Self::init_stage_type_list(); + + let mut model = Header { + stage_type_list, + available_stage_types: ALL_STAGE_TYPES.to_vec(), + orientation: init.orientation, + horizontal_layout_enabled: init.horizontal_layout_enabled, + layout_action: init.layout_action, + templates_menu, + main_menu + }; + let widgets = view_output!(); + + model.rebuild_custom_stages(&init.custom_stages); + + ComponentParts { model, widgets } + } + + fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) { + match message { + HeaderAction::SetHorizontalLayoutEnabled(is_enabled) => { + self.horizontal_layout_enabled = is_enabled; + self.layout_action.set_enabled(is_enabled); + }, + HeaderAction::SetLayoutOrientation(orientation) => self.orientation = orientation, + HeaderAction::SetTemplates(templates) => { + self.templates_menu.remove_all(); + for template in &templates { + self.templates_menu.append( + Some(&template.name), + Some(&format!("window.new-from-template::{}", template.id)), + ); + } + }, + HeaderAction::SetCustomStages(stages) => { + self.rebuild_custom_stages(&stages); + }, + HeaderAction::StageTypeSelected(index) => { + if index < self.available_stage_types.len() + && let Some(stage_type) = self.available_stage_types.get(index) + { + let _ = sender.output(Action::StageTypeSelected(stage_type.clone())); + } + }, + } + } +} + +impl Header { + fn init_main_menu(templates: &Vec<Template>) -> (gio::Menu, gio::Menu) { + let templates_menu = gio::Menu::new(); + for template in 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) + } + + fn init_stage_type_list() -> gtk::StringList { + let stage_types: Vec<String> = ALL_STAGE_TYPES + .iter() + .map(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 rebuild_custom_stages(&mut self, stages: &Vec<CustomStage>) { + let mut stage_types: Vec<StageType> = ALL_STAGE_TYPES.to_vec(); + + for custom in 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; + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..d32bbea --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,16 @@ +// Map, wardley map editor for linux +// Copyright (C) 2026 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +pub mod header; |