diff options
| author | UMTS at Teleco <crt@teleco.ch> | 2025-12-13 02:51:15 +0100 |
|---|---|---|
| committer | UMTS at Teleco <crt@teleco.ch> | 2025-12-13 02:51:15 +0100 |
| commit | 8323fdd73272a2882781aba3c499ba0be3dff2a6 (patch) | |
| tree | ffbf86473933e69cfaeef30d5c6ea7e5b494856c /src/core/components/interactions.rs | |
Diffstat (limited to 'src/core/components/interactions.rs')
| -rw-r--r-- | src/core/components/interactions.rs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/src/core/components/interactions.rs b/src/core/components/interactions.rs new file mode 100644 index 0000000..ab9e5ac --- /dev/null +++ b/src/core/components/interactions.rs @@ -0,0 +1,225 @@ +use eframe::egui; +use std::collections::HashMap; + +/// Optional input field for confirmation dialogs +#[allow(dead_code)] +#[derive(Clone)] +pub struct ConfirmInputField { + pub label: String, + pub hint: String, + pub value: String, + pub multiline: bool, +} + +#[allow(dead_code)] +impl ConfirmInputField { + pub fn new(label: impl Into<String>) -> Self { + Self { + label: label.into(), + hint: String::new(), + value: String::new(), + multiline: false, + } + } + + pub fn hint(mut self, hint: impl Into<String>) -> Self { + self.hint = hint.into(); + self + } + + pub fn multiline(mut self, multiline: bool) -> Self { + self.multiline = multiline; + self + } +} + +/// A reusable confirmation dialog for destructive actions +pub struct ConfirmDialog { + pub title: String, + pub message: String, + pub item_name: Option<String>, + pub item_id: Option<String>, + pub show: bool, + pub is_dangerous: bool, + pub confirm_text: String, + pub cancel_text: String, + pub input_fields: Vec<ConfirmInputField>, +} + +impl ConfirmDialog { + pub fn new(title: impl Into<String>, message: impl Into<String>) -> Self { + Self { + title: title.into(), + message: message.into(), + item_name: None, + item_id: None, + show: false, + is_dangerous: true, + confirm_text: "Confirm".to_string(), + cancel_text: "Cancel".to_string(), + input_fields: Vec::new(), + } + } + + #[allow(dead_code)] + pub fn with_item(mut self, name: impl Into<String>, id: impl Into<String>) -> Self { + self.item_name = Some(name.into()); + self.item_id = Some(id.into()); + self + } + + #[allow(dead_code)] + pub fn dangerous(mut self, dangerous: bool) -> Self { + self.is_dangerous = dangerous; + self + } + + #[allow(dead_code)] + pub fn confirm_text(mut self, text: impl Into<String>) -> Self { + self.confirm_text = text.into(); + self + } + + #[allow(dead_code)] + pub fn cancel_text(mut self, text: impl Into<String>) -> Self { + self.cancel_text = text.into(); + self + } + + #[allow(dead_code)] + pub fn with_input_field(mut self, field: ConfirmInputField) -> Self { + self.input_fields.push(field); + self + } + + pub fn open(&mut self, name: impl Into<String>, id: impl Into<String>) { + self.item_name = Some(name.into()); + self.item_id = Some(id.into()); + self.show = true; + } + + pub fn close(&mut self) { + self.show = false; + self.item_name = None; + self.item_id = None; + // Clear input field values + for field in &mut self.input_fields { + field.value.clear(); + } + } + + /// Get the values of input fields as a HashMap + #[allow(dead_code)] + pub fn get_input_values(&self) -> HashMap<String, String> { + self.input_fields + .iter() + .map(|field| (field.label.clone(), field.value.clone())) + .collect() + } + + /// Shows the dialog and returns Some(true) if confirmed, Some(false) if cancelled, None if still open + pub fn show_dialog(&mut self, ctx: &egui::Context) -> Option<bool> { + if !self.show { + return None; + } + + let mut result = None; + let mut keep_open = true; + + let screen_rect = ctx.input(|i| { + i.viewport().inner_rect.unwrap_or(egui::Rect::from_min_max( + egui::pos2(0.0, 0.0), + egui::pos2(1920.0, 1080.0), + )) + }); + let mut default_pos = screen_rect.center() - egui::vec2(180.0, 120.0); + default_pos.x = default_pos.x.max(0.0); + default_pos.y = default_pos.y.max(0.0); + + egui::Window::new(&self.title) + .collapsible(false) + .resizable(false) + .movable(true) + .default_pos(default_pos) + .open(&mut keep_open) + .show(ctx, |ui| { + ui.label(&self.message); + + if let (Some(name), Some(id)) = (&self.item_name, &self.item_id) { + ui.add_space(8.0); + ui.label(egui::RichText::new(format!("Name: {}", name)).strong()); + ui.label(egui::RichText::new(format!("ID: {}", id)).strong()); + } + + if self.is_dangerous { + ui.add_space(12.0); + ui.colored_label( + egui::Color32::from_rgb(244, 67, 54), + "⚠ This action cannot be undone!", + ); + } + + // Render input fields if any + if !self.input_fields.is_empty() { + ui.add_space(12.0); + ui.separator(); + ui.add_space(8.0); + + for field in &mut self.input_fields { + ui.label(&field.label); + if field.multiline { + ui.add( + egui::TextEdit::multiline(&mut field.value) + .hint_text(&field.hint) + .desired_rows(3), + ); + } else { + ui.add( + egui::TextEdit::singleline(&mut field.value).hint_text(&field.hint), + ); + } + ui.add_space(4.0); + } + } + + ui.add_space(12.0); + + ui.horizontal(|ui| { + if ui.button(&self.cancel_text).clicked() { + result = Some(false); + self.close(); + } + ui.add_space(8.0); + + let confirm_button = if self.is_dangerous { + ui.add( + egui::Button::new( + egui::RichText::new(&self.confirm_text).color(egui::Color32::WHITE), + ) + .fill(egui::Color32::from_rgb(244, 67, 54)), + ) + } else { + ui.button(&self.confirm_text) + }; + + if confirm_button.clicked() { + result = Some(true); + self.close(); + } + }); + }); + + if !keep_open { + self.close(); + result = Some(false); + } + + result + } +} + +impl Default for ConfirmDialog { + fn default() -> Self { + Self::new("Confirm Action", "Are you sure?") + } +} |
