// 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 . use gtk::prelude::*; use relm4::prelude::*; use uuid::Uuid; use crate::tr; use crate::actions::PreferencesAction; use crate::components::stage_form; use crate::preferences::models::{CustomStage, StageLabel}; pub struct Stages { stage_forms: FactoryVecDeque, } #[derive(Debug, Clone)] pub enum StagesAction { CustomStageAdded(CustomStage), CustomStageUpdated(CustomStage), CustomStageRemoved(Uuid), // Internal AddCustomStage, ChangeName(Uuid, String), ChangeLabel(Uuid, StageLabel, String), RemoveCustomStage(Uuid), } #[relm4::component(pub)] impl SimpleComponent for Stages { type Init = Vec; type Input = StagesAction; type Output = PreferencesAction; view! { gtk::Box { set_orientation: gtk::Orientation::Vertical, set_margin_all: 20, set_spacing: 10, set_valign: gtk::Align::Start, gtk::Label { set_label: &tr!("Custom Stages"), add_css_class: "heading", set_halign: gtk::Align::Start, }, gtk::Label { set_label: &tr!("Custom stages allow you to define your own evolution axis label."), add_css_class: "dim-label", set_halign: gtk::Align::Start, set_wrap: true, set_margin_bottom: 10, }, gtk::ScrolledWindow { set_vexpand: true, set_min_content_height: 200, gtk::Box { set_orientation: gtk::Orientation::Vertical, set_spacing: 10, set_margin_start: 2, set_margin_end: 2, gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_margin_start: 10, set_spacing: 5, set_hexpand: true, add_css_class: "dim-label", gtk::Label { set_label: &tr!("Name"), set_xalign: 0.0, set_hexpand: true, set_width_chars: 15, }, gtk::Label { set_label: &tr!("Stage I"), set_xalign: 0.0, set_hexpand: true, set_width_chars: 12, }, gtk::Label { set_label: &tr!("Stage II"), set_xalign: 0.0, set_hexpand: true, set_width_chars: 12, }, gtk::Label { set_label: &tr!("Stage III"), set_xalign: 0.0, set_hexpand: true, set_width_chars: 12, }, gtk::Label { set_label: &tr!("Stage IV"), set_xalign: 0.0, set_hexpand: true, set_width_chars: 12, }, gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 0, set_width_request: 32 } }, model.stage_forms.widget() -> >k::ListBox { set_selection_mode: gtk::SelectionMode::None, add_css_class: "boxed-list", } }, }, gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 10, set_halign: gtk::Align::End, gtk::Button::from_icon_name("list-add-symbolic") { set_tooltip_text: Some(&tr!("Add custom stage")), connect_clicked => StagesAction::AddCustomStage, } }, } } fn init( init: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { let mut stage_forms = FactoryVecDeque::builder() .launch(gtk::ListBox::default()) .forward(sender.input_sender(), |output| match output { stage_form::StageFormOutput::SetName(id, name) => { StagesAction::ChangeName(id, name) } stage_form::StageFormOutput::SetLabel(id, label, value) => { StagesAction::ChangeLabel(id, label, value) } stage_form::StageFormOutput::Remove(id) => StagesAction::RemoveCustomStage(id), }); { let mut guard = stage_forms.guard(); for stage in init { guard.push_back(stage); } } let model = Self { stage_forms }; let widgets = view_output!(); ComponentParts { model, widgets } } fn update(&mut self, message: Self::Input, sender: ComponentSender) { match message { StagesAction::CustomStageAdded(stage) => { self.stage_forms.guard().push_back(stage); } StagesAction::CustomStageUpdated(stage) => { if let Some(form) = self .stage_forms .guard() .iter_mut() .find(|s| s.stage.id == stage.id) { form.stage.name = stage.name; form.stage.stage = stage.stage; } } StagesAction::CustomStageRemoved(id) => { let mut guard = self.stage_forms.guard(); let index = guard.iter().position(|s| s.stage.id == id); if let Some(index) = index { guard.remove(index); } } StagesAction::AddCustomStage => { sender.output(PreferencesAction::AddCustomStage).ok(); } StagesAction::RemoveCustomStage(id) => { sender.output(PreferencesAction::RemoveCustomStage(id)).ok(); } StagesAction::ChangeName(id, name) => { sender .output(PreferencesAction::SetCustomStageName(id, name)) .ok(); } StagesAction::ChangeLabel(id, label, value) => { sender .output(PreferencesAction::SetCustomStageLabel(id, label, value)) .ok(); } } } }