aboutsummaryrefslogtreecommitdiff
path: root/src/components/header.rs
diff options
context:
space:
mode:
authorRubén Beltrán del Río <jj@r.bdr.sh>2026-03-26 18:13:49 +0100
committerRubén Beltrán del Río <jj@r.bdr.sh>2026-03-26 23:02:28 +0100
commitc5d45b748d90c79f881c4e054b425d68801dad14 (patch)
treeb39ddf37fb74d761b98d4e0e8f2abdb1a764273e /src/components/header.rs
parent5f5b94474c83f85d95be5c53d148c4205c17d8e1 (diff)
Spin header into its own component
Diffstat (limited to 'src/components/header.rs')
-rw-r--r--src/components/header.rs234
1 files changed, 234 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 = &gtk::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 = &gtk::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;
+ }
+}