use crate::core::components::filter_builder::FilterBuilder; use eframe::egui; use egui_phosphor::variants::regular as icons; use serde::{Deserialize, Serialize}; use std::collections::HashMap; pub struct RibbonUI { pub active_tab: String, pub search_texts: HashMap, pub checkboxes: HashMap, pub number_fields: HashMap, pub filter_builder: FilterBuilder, } impl Default for RibbonUI { fn default() -> Self { let mut number_fields = HashMap::new(); number_fields.insert("inventory_limit".to_string(), 100); number_fields.insert("templates_limit".to_string(), 200); Self { active_tab: "Dashboard".to_string(), search_texts: HashMap::new(), checkboxes: HashMap::new(), number_fields, filter_builder: FilterBuilder::new(), } } } impl RibbonUI { pub fn preferred_height(&self) -> f32 { 135.0 } pub fn get_active_view(&self) -> Option { Some(self.active_tab.clone()) } pub fn show(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) -> Option { // Clear one-shot trigger flags from previous frame so clicks only fire once // NOTE: inventory_filter_changed and templates_filter_changed are NOT cleared here - they're cleared by their views after processing for key in [ "item_lookup_trigger", "inventory_limit_refresh_trigger", // Inventory Actions "inventory_action_add", "inventory_action_delete", "inventory_action_edit_easy", "inventory_action_edit_adv", "inventory_action_print_label", // Inventory Quick Actions "inventory_quick_inventarize_room", "inventory_quick_add_multiple_from_template", // Templates "templates_limit_changed", "templates_action_new", "templates_action_edit", "templates_action_delete", ] { self.checkboxes.insert(key.to_string(), false); } ui.vertical(|ui| { // Tab headers row ui.horizontal(|ui| { let tabs = vec![ "Dashboard", "Inventory", "Categories", "Zones", "Borrowing", "Audits", "Suppliers", "Issues", "Printers", "Label Templates", "Item Templates", ]; for tab in &tabs { if ui.selectable_label(self.active_tab == *tab, *tab).clicked() { self.active_tab = tab.to_string(); // Update filter columns based on the active tab match *tab { "Zones" => self.filter_builder.set_columns_for_context("zones"), "Inventory" => self.filter_builder.set_columns_for_context("assets"), _ => {} } } } }); ui.separator(); // Content area with fixed height I dont even know what this here fucking does tbh let ribbon_height = 90.0; ui.allocate_ui_with_layout( egui::vec2(ui.available_width(), ribbon_height), egui::Layout::left_to_right(egui::Align::Min), |ui| { egui::ScrollArea::horizontal().show(ui, |ui| { ui.horizontal(|ui| match self.active_tab.as_str() { "Dashboard" => self.show_dashboard_tab(ui, ribbon_height), "Inventory" => self.show_inventory_tab(ui, ribbon_height), "Categories" => self.show_categories_tab(ui, ribbon_height), "Zones" => self.show_zones_tab(ui, ribbon_height), "Borrowing" => self.show_borrowing_tab(ui, ribbon_height), "Audits" => self.show_audits_tab(ui, ribbon_height), "Item Templates" => self.show_templates_tab(ui, ribbon_height), "Suppliers" => self.show_suppliers_tab(ui, ribbon_height), "Issues" => self.show_issues_tab(ui, ribbon_height), "Printers" => self.show_printers_tab(ui, ribbon_height), "Label Templates" => self.show_label_templates_tab(ui, ribbon_height), _ => {} }); }); }, ); }); None } /// Render a 2-row grid of actions with consistent column widths and set checkbox triggers by key. fn render_actions_grid_with_keys( &mut self, ui: &mut egui::Ui, grid_id: &str, items: &[(String, &str)], ) { let rows: usize = 2; if items.is_empty() { return; } let cols: usize = (items.len() + rows - 1) / rows; let pad_x = ui.style().spacing.button_padding.x; let row_height: f32 = 24.0; let mut col_widths = vec![0.0f32; cols]; for col in 0..cols { for row in 0..rows { let idx = col * rows + row; if idx < items.len() { let text = &items[idx].0; let text_width = 8.0 * text.len() as f32; col_widths[col] = col_widths[col].max(text_width + pad_x * 2.0); } } } egui::Grid::new(grid_id) .num_columns(cols) .spacing(egui::vec2(8.0, 8.0)) .show(ui, |ui| { for row in 0..rows { for col in 0..cols { let idx = col * rows + row; if idx < items.len() { let (label, key) = &items[idx]; let w = col_widths[col]; if ui .add_sized([w, row_height], egui::Button::new(label.clone())) .clicked() { self.checkboxes.insert(key.to_string(), true); } } else { ui.allocate_space(egui::vec2(col_widths[col], row_height)); } } ui.end_row(); } }); } fn show_dashboard_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("View"); egui::ScrollArea::vertical() .id_salt("dashboard_view_scroll") .show(ui, |ui| if ui.button("Refresh").clicked() {}); }); }); // Stats section removed for now ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Widgets"); egui::ScrollArea::vertical() .id_salt("dashboard_widgets_scroll") .show(ui, |ui| { ui.checkbox( self.checkboxes .entry("show_pie".to_string()) .or_insert(true), "Show Pie Chart", ); ui.checkbox( self.checkboxes .entry("show_timeline".to_string()) .or_insert(true), "Show Timeline", ); }); }); }); } fn show_inventory_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); // 2x2 grid: Add, Edit, Remove, Print Label (Edit supports Alt for Advanced) let labels = [ &format!("{} {}", icons::PLUS, "Add Item"), &format!("{} {}", icons::PENCIL, "Edit"), &format!("{} {}", icons::TRASH, "Remove"), &format!("{} {}", icons::PRINTER, "Print Label"), ]; // 4 labels -> 2x2 grid let rows: usize = 2; let cols: usize = 2; let row_height: f32 = 24.0; let pad_x = ui.style().spacing.button_padding.x; // Compute column widths based on text let mut col_widths = vec![0.0f32; cols]; for col in 0..cols { for row in 0..rows { let idx = col * rows + row; let text = labels[idx]; let text_width = 8.0 * text.len() as f32; col_widths[col] = col_widths[col].max(text_width + pad_x * 2.0); } } egui::Grid::new("inventory_actions_grid") .num_columns(cols) .spacing(egui::vec2(8.0, 8.0)) .show(ui, |ui| { for row in 0..rows { for col in 0..cols { let idx = col * rows + row; let w = col_widths[col]; let button = egui::Button::new(labels[idx]); let clicked = ui.add_sized([w, row_height], button).clicked(); if clicked { // If user holds Alt while clicking Edit, trigger Advanced Edit instead of Easy let alt_held = ui.input(|i| i.modifiers.alt); match idx { 0 => { self.checkboxes .insert("inventory_action_add".to_string(), true); } 1 => { if alt_held { self.checkboxes.insert( "inventory_action_edit_adv".to_string(), true, ); } else { self.checkboxes.insert( "inventory_action_edit_easy".to_string(), true, ); } } 2 => { self.checkboxes.insert( "inventory_action_delete".to_string(), true, ); } 3 => { self.checkboxes.insert( "inventory_action_print_label".to_string(), true, ); } _ => {} } } } ui.end_row(); } }); }); }); // Import/Export hidden for now (planned-only) ui.group(|ui| { ui.set_height(ribbon_height); ui.set_width(200.0); // Reduced width for compact design ui.vertical(|ui| { ui.label("Filter"); egui::ScrollArea::vertical() .id_salt("inventory_filter_scroll") .show(ui, |ui| { // Compact FilterBuilder with popup let filter_changed = self.filter_builder.show_compact(ui); if filter_changed { // Set trigger for filter application self.checkboxes .insert("inventory_filter_changed".to_string(), true); } }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("View"); egui::ScrollArea::vertical() .id_salt("inventory_view_scroll") .show(ui, |ui| { let old_show_retired = self.checkboxes.get("show_retired").copied().unwrap_or(true); let mut show_retired = old_show_retired; ui.checkbox(&mut show_retired, "Show Retired"); if show_retired != old_show_retired { self.checkboxes .insert("show_retired".to_string(), show_retired); self.checkboxes .insert("inventory_filter_changed".to_string(), true); } if ui.button("❓ Help").clicked() { self.checkboxes .insert("inventory_show_help".to_string(), true); } }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Quick Actions"); egui::ScrollArea::vertical() .id_salt("inventory_quick_actions_scroll") .show(ui, |ui| { // Ensure both controls share the same visual width while stacking vertically let pad_x = ui.style().spacing.button_padding.x; let w1 = 8.0 * "Inventarize Room".len() as f32 + pad_x * 2.0; let w2 = 8.0 * "Add from...".len() as f32 + pad_x * 2.0; let w = w1.max(w2).max(130.0); if ui .add_sized([w, 24.0], egui::Button::new("Inventarize Room")) .clicked() { self.checkboxes .insert("inventory_quick_inventarize_room".to_string(), true); } // Use a fixed-width ComboBox as a dropdown to ensure equal width egui::ComboBox::from_id_salt("inventory_add_from_combo") .width(w) .selected_text("Add from...") .show_ui(ui, |ui| { if ui.selectable_label(false, "Add from Template").clicked() { self.checkboxes.insert( "inventory_add_from_template_single".to_string(), true, ); } if ui .selectable_label(false, "Add Multiple from Template") .clicked() { self.checkboxes.insert( "inventory_add_from_template_multiple".to_string(), true, ); } if ui .selectable_label(false, "Add using Another Item") .clicked() { self.checkboxes .insert("inventory_add_from_item_single".to_string(), true); } if ui .selectable_label(false, "Add Multiple from Another Item") .clicked() { self.checkboxes.insert( "inventory_add_from_item_multiple".to_string(), true, ); } }); }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Query"); egui::ScrollArea::vertical() .id_salt("inventory_query_scroll") .show(ui, |ui| { ui.horizontal(|ui| { ui.label("Limit:"); let limit = self .number_fields .entry("inventory_limit".to_string()) .or_insert(100); let mut limit_str = limit.to_string(); let response = ui.add_sized( [60.0, 20.0], egui::TextEdit::singleline(&mut limit_str), ); // Trigger refresh on Enter or when value changes let enter_pressed = response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); if response.changed() || enter_pressed { if let Ok(val) = limit_str.parse::() { *limit = val; if enter_pressed { // Set trigger for inventory refresh let trigger = self .checkboxes .entry("inventory_limit_refresh_trigger".to_string()) .or_insert(false); *trigger = true; } } } }); let no_limit_changed = ui .checkbox( self.checkboxes .entry("inventory_no_limit".to_string()) .or_insert(false), "Maximum", ) .changed(); if no_limit_changed { // Trigger refresh when no limit checkbox changes let trigger = self .checkboxes .entry("inventory_limit_refresh_trigger".to_string()) .or_insert(false); *trigger = true; } }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Lookup"); egui::ScrollArea::vertical() .id_salt("inventory_lookup_scroll") .show(ui, |ui| { ui.label("Item Lookup:"); ui.horizontal(|ui| { let lookup_text = self .search_texts .entry("item_lookup".to_string()) .or_insert_with(String::new); let response = ui.add_sized( [120.0, 30.0], egui::TextEdit::singleline(lookup_text).hint_text("Tag or ID"), ); // Trigger search on Enter key let enter_pressed = response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); if ui.button("Search").clicked() || enter_pressed { // Set trigger flag for inventory view to detect let trigger = self .checkboxes .entry("item_lookup_trigger".to_string()) .or_insert(false); *trigger = true; } }); }); }); }); } fn show_categories_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { // Clear one-shot action triggers from previous frame for key in [ "categories_refresh", "categories_add", "categories_edit", "categories_delete", ] { self.checkboxes.insert(key.to_string(), false); } ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); // Match Inventory layout: [Add, Edit, Delete, Refresh] (Refresh where Inventory has Print) let items = vec![ ( format!("{} {}", icons::PLUS, "Add Category"), "categories_add", ), ( format!("{} {}", icons::PENCIL, "Edit Category"), "categories_edit", ), ( format!("{} {}", icons::TRASH, "Delete Category"), "categories_delete", ), ( format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"), "categories_refresh", ), ]; self.render_actions_grid_with_keys(ui, "categories_actions_grid", &items); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Search"); egui::ScrollArea::vertical() .id_salt("categories_filter_scroll") .show(ui, |ui| { let search_text = self .search_texts .entry("categories_search".to_string()) .or_insert_with(String::new); ui.text_edit_singleline(search_text); }); }); }); } fn show_zones_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { // Clear one-shot action triggers from previous frame for key in [ "zones_action_add", "zones_action_edit", "zones_action_remove", ] { self.checkboxes.insert(key.to_string(), false); } ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ( format!("{} {}", icons::PLUS, "Add Zone"), "zones_action_add", ), ( format!("{} {}", icons::PENCIL, "Edit Zone"), "zones_action_edit", ), ]; self.render_actions_grid_with_keys(ui, "zones_actions_grid", &items); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.set_width(200.0); // Reduced width for compact design ui.vertical(|ui| { ui.label("Filter"); egui::ScrollArea::vertical() .id_salt("zones_filter_scroll") .show(ui, |ui| { // Compact FilterBuilder with popup (same as Inventory) let filter_changed = self.filter_builder.show_compact(ui); if filter_changed { // Set trigger for filter application self.checkboxes .insert("zones_filter_changed".to_string(), true); } }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("View"); egui::ScrollArea::vertical() .id_salt("zones_view_scroll") .show(ui, |ui| { // Use new key `zones_show_empty` but mirror to legacy key for compatibility let show_empty = self .checkboxes .get("zones_show_empty") .copied() .unwrap_or(true); let mut local = show_empty; if ui.checkbox(&mut local, "Show Empty").changed() { self.checkboxes .insert("zones_show_empty".to_string(), local); self.checkboxes .insert("show_empty_zones".to_string(), local); } ui.checkbox( self.checkboxes .entry("zones_show_items".to_string()) .or_insert(true), "Show Items in Zones", ); }); }); }); } fn show_borrowing_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { // Clear one-shot action triggers from previous frame for key in [ "borrowing_action_checkout", "borrowing_action_return", "borrowing_action_register", "borrowing_action_refresh", ] { self.checkboxes.insert(key.to_string(), false); } ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ( format!("{} {}", icons::ARROW_LEFT, "Check Out"), "borrowing_action_checkout", ), ( format!("{} {}", icons::ARROW_RIGHT, "Return"), "borrowing_action_return", ), ( format!("{} {}", icons::PENCIL, "Register"), "borrowing_action_register", ), ( format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"), "borrowing_action_refresh", ), ]; self.render_actions_grid_with_keys(ui, "borrowing_actions_grid", &items); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.set_width(200.0); ui.vertical(|ui| { ui.label("Filter"); egui::ScrollArea::vertical() .id_salt("borrowing_filter_scroll") .show(ui, |ui| { // Compact FilterBuilder with popup let filter_changed = self.filter_builder.show_compact(ui); if filter_changed { // Set trigger for filter application self.checkboxes .insert("borrowing_filter_changed".to_string(), true); } }); }); }); } fn show_audits_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ("New Audit".to_string(), "audits_action_new"), ("View Audit".to_string(), "audits_action_view"), ("Export Report".to_string(), "audits_action_export"), ]; self.render_actions_grid_with_keys(ui, "audits_actions_grid", &items); }); }); } fn show_templates_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { // Clear one-shot action triggers from previous frame for key in [ "templates_action_new", "templates_action_edit", "templates_action_delete", ] { self.checkboxes.insert(key.to_string(), false); } ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ( format!("{} {}", icons::PLUS, "Add Template"), "templates_action_new", ), ( format!("{} {}", icons::PENCIL, "Edit Template"), "templates_action_edit", ), ]; self.render_actions_grid_with_keys(ui, "templates_actions_grid", &items); }); }); // Import/Export removed for now ui.group(|ui| { ui.set_height(ribbon_height); ui.set_width(200.0); // Reduced width for compact design ui.vertical(|ui| { ui.label("Filter"); egui::ScrollArea::vertical() .id_salt("templates_filter_scroll") .show(ui, |ui| { // Compact FilterBuilder with popup let filter_changed = self.filter_builder.show_compact(ui); if filter_changed { // Set trigger for filter application self.checkboxes .insert("templates_filter_changed".to_string(), true); } }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Query"); egui::ScrollArea::vertical() .id_salt("templates_query_scroll") .show(ui, |ui| { ui.horizontal(|ui| { ui.label("Limit:"); let limit = self .number_fields .entry("templates_limit".to_string()) .or_insert(200); let mut limit_str = limit.to_string(); let response = ui.add_sized( [60.0, 20.0], egui::TextEdit::singleline(&mut limit_str), ); // Trigger refresh on Enter or when value changes let enter_pressed = response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); if response.changed() || enter_pressed { if let Ok(val) = limit_str.parse::() { *limit = val; self.checkboxes .insert("templates_limit_changed".to_string(), true); } } }); }); }); }); } fn show_issues_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ("Report Issue".to_string(), "issues_action_report"), ("View Issue".to_string(), "issues_action_view"), ("Resolve Issue".to_string(), "issues_action_resolve"), ]; self.render_actions_grid_with_keys(ui, "issues_actions_grid", &items); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Filter"); egui::ScrollArea::vertical() .id_salt("issues_filter_scroll") .show(ui, |ui| { let search_text = self .search_texts .entry("issue_search".to_string()) .or_insert_with(String::new); ui.text_edit_singleline(search_text); }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("View"); egui::ScrollArea::vertical() .id_salt("issues_view_scroll") .show(ui, |ui| { ui.checkbox( self.checkboxes .entry("show_resolved".to_string()) .or_insert(false), "Show Resolved", ); ui.checkbox( self.checkboxes .entry("show_high_priority".to_string()) .or_insert(true), "High Priority Only", ); }); }); }); } fn show_suppliers_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { for key in [ "suppliers_action_new", "suppliers_action_edit", "suppliers_action_delete", ] { self.checkboxes.insert(key.to_string(), false); } ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ( format!("{} {}", icons::PLUS, "New Supplier"), "suppliers_action_new", ), ( format!("{} {}", icons::PENCIL, "Edit Supplier"), "suppliers_action_edit", ), ]; self.render_actions_grid_with_keys(ui, "suppliers_actions_grid", &items); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Search"); egui::ScrollArea::vertical() .id_salt("suppliers_filter_scroll") .show(ui, |ui| { let search_text = self .search_texts .entry("supplier_search".to_string()) .or_insert_with(String::new); ui.text_edit_singleline(search_text); }); }); }); } pub fn show_printers_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { // Clear one-shot action triggers from previous frame for key in [ "printers_action_add", "printers_action_refresh", "printers_view_print_history", "printers_default_changed", ] { self.checkboxes.insert(key.to_string(), false); } ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ( format!("{} {}", icons::PLUS, "Add Printer"), "printers_action_add", ), ( format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"), "printers_action_refresh", ), ]; self.render_actions_grid_with_keys(ui, "printers_actions_grid", &items); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Settings"); egui::ScrollArea::vertical() .id_salt("printers_settings_scroll") .show(ui, |ui| { ui.label("Default Printer:"); // Get printer data injected by PrintersView let printers_json = self .search_texts .get("_printers_list") .cloned() .unwrap_or_default(); let current_default_str = self .search_texts .get("_printers_current_default") .cloned() .unwrap_or_default(); let current_default = if current_default_str.is_empty() { None } else { current_default_str.parse::().ok() }; if printers_json.is_empty() { ui.label("(Loading...)"); } else { // Parse printer list let printers: Vec = serde_json::from_str(&printers_json).unwrap_or_default(); let current_name = if let Some(id) = current_default { printers .iter() .find(|p| p.get("id").and_then(|v| v.as_i64()) == Some(id)) .and_then(|p| p.get("printer_name")) .and_then(|v| v.as_str()) .unwrap_or("Unknown") .to_string() } else { "None".to_string() }; egui::ComboBox::from_id_salt("ribbon_default_printer_selector") .selected_text(¤t_name) .show_ui(ui, |ui| { // None option if ui .selectable_label(current_default.is_none(), "None") .clicked() { self.search_texts.insert( "printers_default_id".to_string(), "".to_string(), ); self.checkboxes .insert("printers_default_changed".to_string(), true); } // Printer options for printer in &printers { if let (Some(id), Some(name)) = ( printer.get("id").and_then(|v| v.as_i64()), printer.get("printer_name").and_then(|v| v.as_str()), ) { let is_selected = current_default == Some(id); if ui.selectable_label(is_selected, name).clicked() { self.search_texts.insert( "printers_default_id".to_string(), id.to_string(), ); self.checkboxes.insert( "printers_default_changed".to_string(), true, ); } } } }); } }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("View"); egui::ScrollArea::vertical() .id_salt("printers_view_scroll") .show(ui, |ui| { if ui .button(format!( "{} {}", icons::ARROWS_CLOCKWISE, "View Print History" )) .clicked() { self.checkboxes .insert("printers_view_print_history".to_string(), true); } }); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Search"); egui::ScrollArea::vertical() .id_salt("printers_search_scroll") .show(ui, |ui| { let search_text = self .search_texts .entry("printers_search".to_string()) .or_insert_with(String::new); ui.text_edit_singleline(search_text); }); }); }); } fn show_label_templates_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) { // Clear one-shot action triggers from previous frame for key in ["labels_action_add", "labels_action_refresh"] { self.checkboxes.insert(key.to_string(), false); } ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Actions"); let items = vec![ ( format!("{} {}", icons::PLUS, "Add Template"), "labels_action_add", ), ( format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"), "labels_action_refresh", ), ]; self.render_actions_grid_with_keys(ui, "labels_actions_grid", &items); }); }); ui.group(|ui| { ui.set_height(ribbon_height); ui.vertical(|ui| { ui.label("Search"); egui::ScrollArea::vertical() .id_salt("labels_search_scroll") .show(ui, |ui| { let search_text = self .search_texts .entry("labels_search".to_string()) .or_insert_with(String::new); ui.text_edit_singleline(search_text); }); }); }); } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct RibbonConfig {}