From 409cf357af2914b3f183c273b30bbec5889d61b7 Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 16:08:44 -0400 Subject: [PATCH] feat: Introduce Agent Chat History Popup for Enhanced User Interaction - Added AgentChatHistoryPopup class to provide a sidebar for displaying recent chat messages, improving user accessibility to chat history. - Integrated the new popup into the editor's source files, ensuring proper inclusion and functionality within the existing UI framework. - Refactored related files to accommodate the new popup, enhancing code organization and maintainability. - Updated CMake configuration to include the new source files, ensuring a seamless build process. --- .../agent_chat_history_popup.cc | 2 +- .../agent_chat_history_popup.h | 6 +- src/app/editor/agent/agent_chat_widget.cc | 13 +- src/app/editor/editor_library.cmake | 2 +- src/app/editor/editor_manager.h | 2 +- src/app/editor/overworld/overworld_editor.cc | 65 ++- src/app/gui/ui_helpers.cc | 399 ++++++++++++++++-- src/app/gui/ui_helpers.h | 130 +++++- 8 files changed, 528 insertions(+), 91 deletions(-) rename src/app/editor/{system => agent}/agent_chat_history_popup.cc (99%) rename src/app/editor/{system => agent}/agent_chat_history_popup.h (94%) diff --git a/src/app/editor/system/agent_chat_history_popup.cc b/src/app/editor/agent/agent_chat_history_popup.cc similarity index 99% rename from src/app/editor/system/agent_chat_history_popup.cc rename to src/app/editor/agent/agent_chat_history_popup.cc index 9bbf10a0..b8aabcb7 100644 --- a/src/app/editor/system/agent_chat_history_popup.cc +++ b/src/app/editor/agent/agent_chat_history_popup.cc @@ -1,4 +1,4 @@ -#include "app/editor/system/agent_chat_history_popup.h" +#include "app/editor/agent/agent_chat_history_popup.h" #include diff --git a/src/app/editor/system/agent_chat_history_popup.h b/src/app/editor/agent/agent_chat_history_popup.h similarity index 94% rename from src/app/editor/system/agent_chat_history_popup.h rename to src/app/editor/agent/agent_chat_history_popup.h index d593acca..48f624b0 100644 --- a/src/app/editor/system/agent_chat_history_popup.h +++ b/src/app/editor/agent/agent_chat_history_popup.h @@ -1,5 +1,5 @@ -#ifndef YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_POPUP_H -#define YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_POPUP_H +#ifndef YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_POPUP_H +#define YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_POPUP_H #include #include @@ -122,4 +122,4 @@ class AgentChatHistoryPopup { } // namespace editor } // namespace yaze -#endif // YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_POPUP_H +#endif // YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_POPUP_H diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index c00ffdfc..83a0273d 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -21,7 +21,7 @@ #include "app/core/project.h" #include "app/editor/agent/agent_chat_history_codec.h" #include "app/editor/agent/agent_ui_theme.h" -#include "app/editor/system/agent_chat_history_popup.h" +#include "app/editor/agent/agent_chat_history_popup.h" #include "app/editor/system/proposal_drawer.h" #include "app/editor/system/toast_manager.h" #include "app/gui/icons.h" @@ -1348,14 +1348,13 @@ void AgentChatWidget::RenderCollaborationPanel() { } void AgentChatWidget::RenderMultimodalPanel() { + const auto& theme = AgentUI::GetTheme(); ImGui::PushID("MultimodalPanel"); - ImVec4 gemini_color = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); // Dense header (no collapsing for small panel) - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.16f, 0.20f, 0.95f)); - ImGui::BeginChild("Multimodal_Panel", ImVec2(0, 100), true); - ImGui::TextColored(gemini_color, ICON_MD_CAMERA " Vision"); - ImGui::Separator(); + AgentUI::PushPanelStyle(); + ImGui::BeginChild("Multimodal_Panel", ImVec2(0, 120), true); // Slightly taller + AgentUI::RenderSectionHeader(ICON_MD_CAMERA, "Vision", theme.provider_gemini); bool can_capture = static_cast(multimodal_callbacks_.capture_snapshot); bool can_send = static_cast(multimodal_callbacks_.send_to_gemini); @@ -1405,7 +1404,7 @@ void AgentChatWidget::RenderMultimodalPanel() { ImGui::SameLine(); if (multimodal_state_.last_capture_path.has_value()) { - ImGui::TextColored(gemini_color, ICON_MD_CHECK_CIRCLE); + ImGui::TextColored(theme.status_success, ICON_MD_CHECK_CIRCLE); } else { ImGui::TextDisabled(ICON_MD_CAMERA_ALT); } diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index c216ebaf..ea6dc064 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -41,7 +41,6 @@ set( app/editor/system/shortcut_manager.cc app/editor/system/popup_manager.cc app/editor/system/proposal_drawer.cc - app/editor/system/agent_chat_history_popup.cc app/editor/agent/agent_chat_history_codec.cc ) @@ -49,6 +48,7 @@ if(YAZE_WITH_GRPC) list(APPEND YAZE_APP_EDITOR_SRC app/editor/agent/agent_editor.cc app/editor/agent/agent_chat_widget.cc + app/editor/agent/agent_chat_history_popup.cc app/editor/agent/agent_ui_theme.cc app/editor/agent/agent_collaboration_coordinator.cc app/editor/agent/network_collaboration_coordinator.cc diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index a15da973..5022580b 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -25,7 +25,7 @@ #include "app/editor/sprite/sprite_editor.h" #include "app/editor/system/popup_manager.h" #include "app/editor/system/proposal_drawer.h" -#include "app/editor/system/agent_chat_history_popup.h" +#include "app/editor/agent/agent_chat_history_popup.h" #ifdef YAZE_WITH_GRPC #include "app/editor/agent/agent_editor.h" #include "app/editor/agent/automation_bridge.h" diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 0d9b8986..5be64421 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -25,6 +25,7 @@ #include "app/gui/icons.h" #include "app/gui/input.h" #include "app/gui/style.h" +#include "app/gui/ui_helpers.h" #include "app/gui/widgets/widget_id_registry.h" #include "app/rom.h" #include "app/zelda3/common.h" @@ -453,16 +454,15 @@ void OverworldEditor::DrawOverworldMapSettings() { // Header with ROM version indicator and upgrade option if (asm_version == 0xFF) { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Vanilla ROM"); - if (ImGui::Button(ICON_MD_UPGRADE " Upgrade to v3")) { + gui::RomVersionBadge("Vanilla ROM", true); + if (gui::IconButton(ICON_MD_UPGRADE, "Upgrade to v3")) { // Show upgrade dialog ImGui::OpenPopup("UpgradeROMVersion"); } HOVER_HINT("Upgrade ROM to support ZSCustomOverworld features"); } else { - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), - "ZSCustomOverworld v%d", asm_version); - if (asm_version < 3 && ImGui::Button(ICON_MD_UPGRADE " Upgrade to v3")) { + gui::RomVersionBadge(absl::StrFormat("ZSCustomOverworld v%d", asm_version).c_str(), false); + if (asm_version < 3 && gui::IconButton(ICON_MD_UPGRADE, "Upgrade to v3")) { ImGui::OpenPopup("UpgradeROMVersion"); } } @@ -482,23 +482,20 @@ void OverworldEditor::DrawOverworldMapSettings() { // Show ASM application option if feature flag is enabled if (core::FeatureFlags::get().overworld.kApplyZSCustomOverworldASM) { - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), - ICON_MD_CODE " ASM Patch Application Enabled"); - ImGui::Text( - "ZSCustomOverworld ASM will be automatically applied to ROM"); + gui::SectionHeader(ICON_MD_CODE, "ASM Patch Application Enabled", gui::GetSuccessColor()); + ImGui::Text("ZSCustomOverworld ASM will be automatically applied to ROM"); ImGui::Separator(); } else { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), - ICON_MD_INFO " ASM Patch Application Disabled"); + gui::SectionHeader(ICON_MD_INFO, "ASM Patch Application Disabled", gui::GetWarningColor()); ImGui::Text("Only version marker will be set. Enable in Feature Flags"); ImGui::Text("for full ASM functionality."); ImGui::Separator(); } - ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), - "Warning: This will modify your ROM!"); + ImGui::TextColored(gui::GetWarningColor(), + ICON_MD_WARNING " Warning: This will modify your ROM!"); - if (ImGui::Button(ICON_MD_CHECK " Upgrade", ImVec2(120, 0))) { + if (gui::ColoredButton(ICON_MD_CHECK " Upgrade", gui::ButtonType::Success, ImVec2(120, 0))) { // Apply ASM if feature flag is enabled if (core::FeatureFlags::get().overworld.kApplyZSCustomOverworldASM) { auto asm_status = ApplyZSCustomOverworldASM(3); @@ -1611,7 +1608,7 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, if (each.is_hole_) { color = ImVec4(255, 255, 0, 200); } - ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, color); + ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, gui::GetEntranceColor()); std::string str = util::HexByte(each.entrance_id_); if (current_mode == EditingMode::ENTRANCES) { @@ -1673,7 +1670,7 @@ void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) { if (each.map_id_ < 0x40 + (current_world_ * 0x40) && each.map_id_ >= (current_world_ * 0x40) && !each.deleted_) { ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, - ImVec4(255, 255, 255, 150)); + gui::GetExitColor()); if (current_mode == EditingMode::EXITS) { each.entity_id_ = i; HandleEntityDragging(&each, ow_map_canvas_.zero_point(), @@ -1724,7 +1721,7 @@ void OverworldEditor::DrawOverworldItems() { // Get the item's bitmap and real X and Y positions if (item.room_map_id_ < 0x40 + (current_world_ * 0x40) && item.room_map_id_ >= (current_world_ * 0x40) && !item.deleted) { - ow_map_canvas_.DrawRect(item.x_, item.y_, 16, 16, ImVec4(255, 0, 0, 150)); + ow_map_canvas_.DrawRect(item.x_, item.y_, 16, 16, gui::GetItemColor()); if (current_mode == EditingMode::ITEMS) { // Check if this item is being clicked and dragged @@ -1784,7 +1781,7 @@ void OverworldEditor::DrawOverworldSprites() { int original_y = sprite.y_; ow_map_canvas_.DrawRect(sprite_x, sprite_y, kTile16Size, kTile16Size, - /*magenta=*/ImVec4(255, 0, 255, 150)); + gui::GetSpriteColor()); if (current_mode == EditingMode::SPRITES) { HandleEntityDragging(&sprite, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(), is_dragging_entity_, @@ -2816,16 +2813,15 @@ void OverworldEditor::DrawOverlayPreview() { void OverworldEditor::DrawMapLockControls() { if (current_map_lock_) { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); - Text("Map Locked: %d (0x%02X)", current_map_, current_map_); - PopStyleColor(); - - if (Button("Unlock Map")) { + gui::LockIndicator(true, absl::StrFormat("Map %d (0x%02X)", current_map_, current_map_).c_str()); + + if (gui::IconButton(ICON_MD_LOCK_OPEN, "Unlock Map")) { current_map_lock_ = false; } } else { - Text("Map: %d (0x%02X) - Click to lock", current_map_, current_map_); - if (Button("Lock Map")) { + gui::LockIndicator(false, absl::StrFormat("Map %d (0x%02X)", current_map_, current_map_).c_str()); + + if (gui::IconButton(ICON_MD_LOCK, "Lock Map")) { current_map_lock_ = true; } } @@ -2957,17 +2953,14 @@ void OverworldEditor::DrawMapPropertiesPanel() { // Header with map info and lock status ImGui::BeginGroup(); - if (current_map_lock_) { - PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); - Text("Map Locked: %d (0x%02X)", current_map_, current_map_); - PopStyleColor(); - } else { - Text("Current Map: %d (0x%02X)", current_map_, current_map_); - } + gui::LockIndicator(current_map_lock_, + absl::StrFormat("Map %d (0x%02X)", current_map_, current_map_).c_str()); SameLine(); - if (Button(current_map_lock_ ? "Unlock" : "Lock")) { - current_map_lock_ = !current_map_lock_; + if (gui::ToggleIconButton(ICON_MD_LOCK_OPEN, ICON_MD_LOCK, + ¤t_map_lock_, + current_map_lock_ ? "Unlock Map" : "Lock Map")) { + // Toggle handled by helper } ImGui::EndGroup(); @@ -3538,9 +3531,7 @@ void OverworldEditor::DrawUsageGrid() { // Set highlight color if needed if (highlight) { - PushStyleColor(ImGuiCol_Button, - ImVec4(1.0f, 0.5f, 0.0f, - 1.0f)); // Or any highlight color + PushStyleColor(ImGuiCol_Button, gui::GetSelectedColor()); } // Create a button or selectable for each square diff --git a/src/app/gui/ui_helpers.cc b/src/app/gui/ui_helpers.cc index 06e59f41..08be1fb8 100644 --- a/src/app/gui/ui_helpers.cc +++ b/src/app/gui/ui_helpers.cc @@ -1,71 +1,414 @@ #include "app/gui/ui_helpers.h" -#include "app/gui/theme_manager.h" + +#include "absl/strings/str_format.h" +#include "app/gui/color.h" #include "app/gui/icons.h" +#include "app/gui/theme_manager.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" namespace yaze { namespace gui { +// ============================================================================ +// Theme and Semantic Colors +// ============================================================================ + ImVec4 GetThemeColor(ImGuiCol idx) { - return ImGui::GetStyle().Colors[idx]; + return ImGui::GetStyle().Colors[idx]; } ImVec4 GetSuccessColor() { - const auto& theme = ThemeManager::Get().GetCurrentTheme(); - return ConvertColorToImVec4(theme.success); + const auto& theme = ThemeManager::Get().GetCurrentTheme(); + return ConvertColorToImVec4(theme.success); } ImVec4 GetWarningColor() { - const auto& theme = ThemeManager::Get().GetCurrentTheme(); - return ConvertColorToImVec4(theme.warning); + const auto& theme = ThemeManager::Get().GetCurrentTheme(); + return ConvertColorToImVec4(theme.warning); } ImVec4 GetErrorColor() { - const auto& theme = ThemeManager::Get().GetCurrentTheme(); - return ConvertColorToImVec4(theme.error); + const auto& theme = ThemeManager::Get().GetCurrentTheme(); + return ConvertColorToImVec4(theme.error); } ImVec4 GetInfoColor() { - const auto& theme = ThemeManager::Get().GetCurrentTheme(); - return ConvertColorToImVec4(theme.info); + const auto& theme = ThemeManager::Get().GetCurrentTheme(); + return ConvertColorToImVec4(theme.info); } ImVec4 GetAccentColor() { - const auto& theme = ThemeManager::Get().GetCurrentTheme(); - return ConvertColorToImVec4(theme.accent); + const auto& theme = ThemeManager::Get().GetCurrentTheme(); + return ConvertColorToImVec4(theme.accent); } -void BeginField(const char* label) { - ImGui::BeginGroup(); +// Entity/Map marker colors +ImVec4 GetEntranceColor() { + return ImVec4(1.0f, 1.0f, 0.0f, 0.6f); // Yellow with transparency +} + +ImVec4 GetExitColor() { + return ImVec4(1.0f, 1.0f, 1.0f, 0.6f); // White with transparency +} + +ImVec4 GetItemColor() { + return ImVec4(1.0f, 0.0f, 0.0f, 0.6f); // Red with transparency +} + +ImVec4 GetSpriteColor() { + return ImVec4(1.0f, 0.0f, 1.0f, 0.6f); // Magenta with transparency +} + +ImVec4 GetSelectedColor() { + const auto& theme = ThemeManager::Get().GetCurrentTheme(); + return ConvertColorToImVec4(theme.accent); +} + +ImVec4 GetLockedColor() { + return ImVec4(1.0f, 0.5f, 0.0f, 1.0f); // Orange for locked items +} + +// Status colors +ImVec4 GetVanillaRomColor() { + return GetWarningColor(); // Yellow from theme +} + +ImVec4 GetCustomRomColor() { + return GetSuccessColor(); // Green from theme +} + +ImVec4 GetModifiedColor() { + const auto& theme = ThemeManager::Get().GetCurrentTheme(); + return ConvertColorToImVec4(theme.accent); +} + +// ============================================================================ +// Layout Helpers +// ============================================================================ + +void BeginField(const char* label, float label_width) { + ImGui::BeginGroup(); + if (label_width > 0.0f) { + ImGui::Text("%s:", label); + ImGui::SameLine(label_width); + } else { ImGui::TextUnformatted(label); ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.6f); + } + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.6f); } void EndField() { - ImGui::PopItemWidth(); - ImGui::EndGroup(); + ImGui::PopItemWidth(); + ImGui::EndGroup(); } +bool BeginPropertyTable(const char* id, int columns, ImGuiTableFlags extra_flags) { + ImGuiTableFlags flags = ImGuiTableFlags_Borders | + ImGuiTableFlags_SizingFixedFit | + extra_flags; + if (ImGui::BeginTable(id, columns, flags)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + return true; + } + return false; +} + +void EndPropertyTable() { + ImGui::EndTable(); +} + +void PropertyRow(const char* label, const char* value) { + ImGui::TableNextColumn(); + ImGui::Text("%s", label); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", value); +} + +void PropertyRow(const char* label, int value) { + ImGui::TableNextColumn(); + ImGui::Text("%s", label); + ImGui::TableNextColumn(); + ImGui::Text("%d", value); +} + +void PropertyRowHex(const char* label, uint8_t value) { + ImGui::TableNextColumn(); + ImGui::Text("%s", label); + ImGui::TableNextColumn(); + ImGui::Text("0x%02X", value); +} + +void PropertyRowHex(const char* label, uint16_t value) { + ImGui::TableNextColumn(); + ImGui::Text("%s", label); + ImGui::TableNextColumn(); + ImGui::Text("0x%04X", value); +} + +void SectionHeader(const char* icon, const char* label, const ImVec4& color) { + ImGui::TextColored(color, "%s %s", icon, label); + ImGui::Separator(); +} + +// ============================================================================ +// Common Widget Patterns +// ============================================================================ + bool IconButton(const char* icon, const char* label, const ImVec2& size) { - std::string button_text = std::string(icon) + " " + std::string(label); - return ImGui::Button(button_text.c_str(), size); + std::string button_text = std::string(icon) + " " + std::string(label); + return ImGui::Button(button_text.c_str(), size); +} + +bool ColoredButton(const char* label, ButtonType type, const ImVec2& size) { + ImVec4 color; + switch (type) { + case ButtonType::Success: color = GetSuccessColor(); break; + case ButtonType::Warning: color = GetWarningColor(); break; + case ButtonType::Error: color = GetErrorColor(); break; + case ButtonType::Info: color = GetInfoColor(); break; + default: color = GetThemeColor(ImGuiCol_Button); break; + } + + ImGui::PushStyleColor(ImGuiCol_Button, color); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w)); + + bool result = ImGui::Button(label, size); + + ImGui::PopStyleColor(3); + return result; +} + +bool ToggleIconButton(const char* icon_on, const char* icon_off, + bool* state, const char* tooltip) { + const char* icon = *state ? icon_on : icon_off; + ImVec4 color = *state ? GetSuccessColor() : GetThemeColor(ImGuiCol_Button); + + ImGui::PushStyleColor(ImGuiCol_Button, color); + bool result = ImGui::SmallButton(icon); + ImGui::PopStyleColor(); + + if (result) *state = !*state; + + if (tooltip && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltip); + } + + return result; } void HelpMarker(const char* desc) { - ImGui::TextDisabled(ICON_MD_HELP_OUTLINE); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted(desc); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + ImGui::TextDisabled(ICON_MD_HELP_OUTLINE); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } void SeparatorText(const char* label) { - ImGui::SeparatorText(label); + ImGui::SeparatorText(label); +} + +void StatusBadge(const char* text, ButtonType type) { + ImVec4 color; + switch (type) { + case ButtonType::Success: color = GetSuccessColor(); break; + case ButtonType::Warning: color = GetWarningColor(); break; + case ButtonType::Error: color = GetErrorColor(); break; + case ButtonType::Info: color = GetInfoColor(); break; + default: color = GetThemeColor(ImGuiCol_Text); break; + } + + ImGui::PushStyleColor(ImGuiCol_Button, color); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 10.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 3)); + ImGui::SmallButton(text); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(); +} + +// ============================================================================ +// Editor-Specific Patterns +// ============================================================================ + +void BeginToolset(const char* id) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(2, 2)); + ImGui::BeginTable(id, 32, ImGuiTableFlags_SizingFixedFit); +} + +void EndToolset() { + ImGui::EndTable(); + ImGui::PopStyleVar(); +} + +void ToolsetButton(const char* icon, bool selected, const char* tooltip, + std::function on_click) { + ImGui::TableNextColumn(); + + if (selected) { + ImGui::PushStyleColor(ImGuiCol_Button, GetAccentColor()); + } + + if (ImGui::Button(icon)) { + if (on_click) on_click(); + } + + if (selected) { + ImGui::PopStyleColor(); + } + + if (tooltip && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltip); + } +} + +void BeginCanvasContainer(const char* id, bool scrollable) { + ImGuiWindowFlags flags = scrollable ? + ImGuiWindowFlags_AlwaysVerticalScrollbar : + ImGuiWindowFlags_None; + ImGui::BeginChild(id, ImVec2(0, 0), true, flags); +} + +void EndCanvasContainer() { + ImGui::EndChild(); +} + +bool EditorTabItem(const char* icon, const char* label, bool* p_open) { + char tab_label[256]; + snprintf(tab_label, sizeof(tab_label), "%s %s", icon, label); + return ImGui::BeginTabItem(tab_label, p_open); +} + +bool ConfirmationDialog(const char* id, const char* title, const char* message, + const char* confirm_text, const char* cancel_text) { + bool confirmed = false; + + if (ImGui::BeginPopupModal(id, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("%s", title); + ImGui::Separator(); + ImGui::TextWrapped("%s", message); + ImGui::Separator(); + + if (ColoredButton(confirm_text, ButtonType::Warning, ImVec2(120, 0))) { + confirmed = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + if (ImGui::Button(cancel_text, ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + return confirmed; +} + +// ============================================================================ +// Visual Indicators +// ============================================================================ + +void StatusIndicator(const char* label, bool active, const char* tooltip) { + ImVec4 color = active ? GetSuccessColor() : GetThemeColor(ImGuiCol_TextDisabled); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + float radius = 5.0f; + + pos.x += radius + 3; + pos.y += ImGui::GetTextLineHeight() * 0.5f; + + draw_list->AddCircleFilled(pos, radius, ImGui::GetColorU32(color)); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8); + ImGui::Text("%s", label); + + if (tooltip && ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", tooltip); + } +} + +void RomVersionBadge(const char* version, bool is_vanilla) { + ImVec4 color = is_vanilla ? GetWarningColor() : GetSuccessColor(); + const char* icon = is_vanilla ? ICON_MD_INFO : ICON_MD_CHECK_CIRCLE; + + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::Text("%s %s", icon, version); + ImGui::PopStyleColor(); +} + +void LockIndicator(bool locked, const char* label) { + if (locked) { + ImGui::TextColored(GetLockedColor(), ICON_MD_LOCK " %s (Locked)", label); + } else { + ImGui::Text(ICON_MD_LOCK_OPEN " %s", label); + } +} + +// ============================================================================ +// Spacing and Alignment +// ============================================================================ + +void VerticalSpacing(float pixels) { + ImGui::Dummy(ImVec2(0, pixels)); +} + +void HorizontalSpacing(float pixels) { + ImGui::Dummy(ImVec2(pixels, 0)); + ImGui::SameLine(); +} + +void CenterText(const char* text) { + float text_width = ImGui::CalcTextSize(text).x; + ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - text_width) * 0.5f); + ImGui::Text("%s", text); +} + +void RightAlign(float width) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + + ImGui::GetContentRegionAvail().x - width); +} + +// ============================================================================ +// Input Helpers +// ============================================================================ + +bool LabeledInputHex(const char* label, uint8_t* value) { + BeginField(label); + ImGui::PushItemWidth(60); + bool changed = ImGui::InputScalar("##hex", ImGuiDataType_U8, value, nullptr, + nullptr, "%02X", + ImGuiInputTextFlags_CharsHexadecimal); + ImGui::PopItemWidth(); + EndField(); + return changed; +} + +bool LabeledInputHex(const char* label, uint16_t* value) { + BeginField(label); + ImGui::PushItemWidth(80); + bool changed = ImGui::InputScalar("##hex", ImGuiDataType_U16, value, nullptr, + nullptr, "%04X", + ImGuiInputTextFlags_CharsHexadecimal); + ImGui::PopItemWidth(); + EndField(); + return changed; +} + +bool IconCombo(const char* icon, const char* label, int* current, + const char* const items[], int count) { + ImGui::Text("%s", icon); + ImGui::SameLine(); + return ImGui::Combo(label, current, items, count); } } // namespace gui diff --git a/src/app/gui/ui_helpers.h b/src/app/gui/ui_helpers.h index 8e681d16..f4e14499 100644 --- a/src/app/gui/ui_helpers.h +++ b/src/app/gui/ui_helpers.h @@ -3,43 +3,147 @@ #include "imgui/imgui.h" #include +#include namespace yaze { namespace gui { // A collection of helper functions and widgets to standardize UI development -// and reduce boilerplate ImGui code. +// and reduce boilerplate ImGui code across all editors. -// --- Theming and Colors --- +// ============================================================================ +// Theme and Semantic Colors +// ============================================================================ -// Gets a color from the current theme. +// Gets a color from the current theme ImVec4 GetThemeColor(ImGuiCol idx); -// Gets a semantic color from the current theme. +// Semantic colors from current theme ImVec4 GetSuccessColor(); ImVec4 GetWarningColor(); ImVec4 GetErrorColor(); ImVec4 GetInfoColor(); ImVec4 GetAccentColor(); -// --- Layout Helpers --- +// Entity/Map marker colors (for overworld, dungeon) +ImVec4 GetEntranceColor(); +ImVec4 GetExitColor(); +ImVec4 GetItemColor(); +ImVec4 GetSpriteColor(); +ImVec4 GetSelectedColor(); +ImVec4 GetLockedColor(); -// Begins a standard row for a label and a widget. -void BeginField(const char* label); -// Ends a field row. +// Status colors +ImVec4 GetVanillaRomColor(); +ImVec4 GetCustomRomColor(); +ImVec4 GetModifiedColor(); + +// ============================================================================ +// Layout Helpers +// ============================================================================ + +// Label + widget field pattern +void BeginField(const char* label, float label_width = 0.0f); void EndField(); -// --- Widget Wrappers --- +// Property table pattern (common in editors) +bool BeginPropertyTable(const char* id, int columns = 2, + ImGuiTableFlags extra_flags = 0); +void EndPropertyTable(); -// A button with an icon from the Material Design icon font. -bool IconButton(const char* icon, const char* label, const ImVec2& size = ImVec2(0, 0)); +// Property row helpers +void PropertyRow(const char* label, const char* value); +void PropertyRow(const char* label, int value); +void PropertyRowHex(const char* label, uint8_t value); +void PropertyRowHex(const char* label, uint16_t value); -// A help marker that shows a tooltip on hover. +// Section headers with icons +void SectionHeader(const char* icon, const char* label, + const ImVec4& color = ImVec4(1, 1, 1, 1)); + +// ============================================================================ +// Common Widget Patterns +// ============================================================================ + +// Button with icon +bool IconButton(const char* icon, const char* label, + const ImVec2& size = ImVec2(0, 0)); + +// Colored button for status actions +enum class ButtonType { Default, Success, Warning, Error, Info }; +bool ColoredButton(const char* label, ButtonType type, + const ImVec2& size = ImVec2(0, 0)); + +// Toggle button with visual state +bool ToggleIconButton(const char* icon_on, const char* icon_off, + bool* state, const char* tooltip = nullptr); + +// Help marker with tooltip void HelpMarker(const char* desc); -// A separator with centered text. +// Separator with text void SeparatorText(const char* label); +// Status badge (pill-shaped colored label) +void StatusBadge(const char* text, ButtonType type = ButtonType::Default); + +// ============================================================================ +// Editor-Specific Patterns +// ============================================================================ + +// Toolset table (horizontal button bar) +void BeginToolset(const char* id); +void EndToolset(); +void ToolsetButton(const char* icon, bool selected, const char* tooltip, + std::function on_click); + +// Canvas container patterns +void BeginCanvasContainer(const char* id, bool scrollable = true); +void EndCanvasContainer(); + +// Tab pattern for editor modes +bool EditorTabItem(const char* icon, const char* label, bool* p_open = nullptr); + +// Modal confirmation dialog +bool ConfirmationDialog(const char* id, const char* title, const char* message, + const char* confirm_text = "OK", + const char* cancel_text = "Cancel"); + +// ============================================================================ +// Visual Indicators +// ============================================================================ + +// Status indicator dot + label +void StatusIndicator(const char* label, bool active, + const char* tooltip = nullptr); + +// ROM version badge +void RomVersionBadge(const char* version, bool is_vanilla); + +// Locked/Unlocked indicator +void LockIndicator(bool locked, const char* label); + +// ============================================================================ +// Spacing and Alignment +// ============================================================================ + +void VerticalSpacing(float pixels = 8.0f); +void HorizontalSpacing(float pixels = 8.0f); +void CenterText(const char* text); +void RightAlign(float width); + +// ============================================================================ +// Input Helpers (complement existing gui::InputHex functions) +// ============================================================================ + +// Labeled hex input with automatic formatting +bool LabeledInputHex(const char* label, uint8_t* value); +bool LabeledInputHex(const char* label, uint16_t* value); + +// Combo with icon +bool IconCombo(const char* icon, const char* label, int* current, + const char* const items[], int count); + } // namespace gui } // namespace yaze