// Copyright (C) 2024 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 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 General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see https://map.tranquil.systems. use gtk::prelude::*; use relm4::Sender; use relm4::gtk; use uuid::Uuid; use super::super::models::UserPreferences; use super::super::window::{PreferencesInput, StageLabel}; pub struct StagesPage { container: gtk::Box, list_box: gtk::ListBox, sender: Sender, } impl StagesPage { pub fn new(prefs: &UserPreferences, sender: Sender) -> Self { let container = gtk::Box::new(gtk::Orientation::Vertical, 10); container.set_margin_top(20); container.set_margin_bottom(20); container.set_margin_start(20); container.set_margin_end(20); // Header let header_label = gtk::Label::new(Some("Custom Stages")); header_label.add_css_class("heading"); header_label.set_halign(gtk::Align::Start); container.append(&header_label); let help_label = gtk::Label::new(Some( "Custom stages allow you to define your own evolution axis labels. These will be available for rendering when the renderer supports custom stages.", )); help_label.add_css_class("dim-label"); help_label.set_halign(gtk::Align::Start); help_label.set_wrap(true); help_label.set_margin_bottom(10); container.append(&help_label); // Column headers let header_row = gtk::Box::new(gtk::Orientation::Horizontal, 5); header_row.add_css_class("dim-label"); let name_header = gtk::Label::new(Some("Name")); name_header.set_width_chars(15); name_header.set_xalign(0.0); header_row.append(&name_header); let i_header = gtk::Label::new(Some("Stage I")); i_header.set_width_chars(12); i_header.set_xalign(0.0); header_row.append(&i_header); let ii_header = gtk::Label::new(Some("Stage II")); ii_header.set_width_chars(12); ii_header.set_xalign(0.0); header_row.append(&ii_header); let iii_header = gtk::Label::new(Some("Stage III")); iii_header.set_width_chars(12); iii_header.set_xalign(0.0); header_row.append(&iii_header); let iv_header = gtk::Label::new(Some("Stage IV")); iv_header.set_width_chars(12); iv_header.set_xalign(0.0); header_row.append(&iv_header); // Spacer for delete button column let spacer = gtk::Box::new(gtk::Orientation::Horizontal, 0); spacer.set_width_request(32); header_row.append(&spacer); container.append(&header_row); // Scrollable list area let scrolled = gtk::ScrolledWindow::new(); scrolled.set_vexpand(true); scrolled.set_min_content_height(200); let list_box = gtk::ListBox::new(); list_box.set_selection_mode(gtk::SelectionMode::None); list_box.add_css_class("boxed-list"); scrolled.set_child(Some(&list_box)); container.append(&scrolled); // Add button let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 10); button_box.set_halign(gtk::Align::End); let add_button = gtk::Button::from_icon_name("list-add-symbolic"); add_button.set_tooltip_text(Some("Add custom stage")); { let sender = sender.clone(); add_button.connect_clicked(move |_| { sender.emit(PreferencesInput::AddCustomStage); }); } button_box.append(&add_button); container.append(&button_box); let page = Self { container, list_box, sender, }; page.populate_stages(prefs); page } pub fn widget(&self) -> gtk::Box { self.container.clone() } pub fn refresh(&self, prefs: &UserPreferences) { // Clear existing rows while let Some(child) = self.list_box.first_child() { self.list_box.remove(&child); } self.populate_stages(prefs); } fn populate_stages(&self, prefs: &UserPreferences) { for stage in &prefs.custom_stages { let row = self.create_stage_row(stage.id, &stage.name, &stage.stage); self.list_box.append(&row); } } fn create_stage_row( &self, id: Uuid, name: &str, stage: &super::super::models::Stage, ) -> gtk::ListBoxRow { let row = gtk::ListBoxRow::new(); let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5); hbox.set_margin_top(5); hbox.set_margin_bottom(5); hbox.set_margin_start(5); hbox.set_margin_end(5); // Name entry let name_entry = gtk::Entry::new(); name_entry.set_text(name); name_entry.set_width_chars(15); { let sender = self.sender.clone(); name_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageName( id, entry.text().to_string(), )); }); } hbox.append(&name_entry); // Stage I entry let i_entry = gtk::Entry::new(); i_entry.set_text(&stage.i); i_entry.set_width_chars(12); { let sender = self.sender.clone(); i_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::I, entry.text().to_string(), )); }); } hbox.append(&i_entry); // Stage II entry let ii_entry = gtk::Entry::new(); ii_entry.set_text(&stage.ii); ii_entry.set_width_chars(12); { let sender = self.sender.clone(); ii_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::Ii, entry.text().to_string(), )); }); } hbox.append(&ii_entry); // Stage III entry let iii_entry = gtk::Entry::new(); iii_entry.set_text(&stage.iii); iii_entry.set_width_chars(12); { let sender = self.sender.clone(); iii_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::Iii, entry.text().to_string(), )); }); } hbox.append(&iii_entry); // Stage IV entry let iv_entry = gtk::Entry::new(); iv_entry.set_text(&stage.iv); iv_entry.set_width_chars(12); { let sender = self.sender.clone(); iv_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::Iv, entry.text().to_string(), )); }); } hbox.append(&iv_entry); // Delete button let delete_button = gtk::Button::from_icon_name("list-remove-symbolic"); delete_button.add_css_class("flat"); delete_button.set_tooltip_text(Some("Remove stage")); { let sender = self.sender.clone(); delete_button.connect_clicked(move |_| { sender.emit(PreferencesInput::RemoveCustomStage(id)); }); } hbox.append(&delete_button); row.set_child(Some(&hbox)); row } }