refactor(gui): reorganize background rendering and layout helpers
- Moved background rendering functionality from the editor to a dedicated GUI module, enhancing modularity and separation of concerns. - Introduced layout helpers for consistent theme-aware sizing across the GUI, improving UI consistency and maintainability. - Updated CMake configuration to reflect the new structure, ensuring proper linkage of the background renderer and layout helpers. Benefits: - Improved organization of GUI components, facilitating easier updates and enhancements. - Enhanced user interface consistency through theme-aware layout management.
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
#include "app/core/timing.h"
|
#include "app/core/timing.h"
|
||||||
#include "app/core/window.h"
|
#include "app/core/window.h"
|
||||||
#include "app/editor/editor_manager.h"
|
#include "app/editor/editor_manager.h"
|
||||||
#include "app/editor/ui/background_renderer.h"
|
#include "app/gui/background_renderer.h"
|
||||||
#include "app/gfx/arena.h" // Add include for Arena
|
#include "app/gfx/arena.h" // Add include for Arena
|
||||||
#include "app/gfx/backend/sdl2_renderer.h" // Add include for new renderer
|
#include "app/gfx/backend/sdl2_renderer.h" // Add include for new renderer
|
||||||
#include "app/gui/theme_manager.h"
|
#include "app/gui/theme_manager.h"
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ target_include_directories(yaze_core_lib PUBLIC
|
|||||||
target_link_libraries(yaze_core_lib PUBLIC
|
target_link_libraries(yaze_core_lib PUBLIC
|
||||||
yaze_util
|
yaze_util
|
||||||
yaze_gfx
|
yaze_gfx
|
||||||
|
yaze_zelda3 # Needed for Zelda3Labels in project.cc
|
||||||
yaze_common
|
yaze_common
|
||||||
ImGui
|
ImGui
|
||||||
asar-static
|
asar-static
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
#include "app/emu/emulator.h"
|
#include "app/emu/emulator.h"
|
||||||
#include "app/gfx/arena.h"
|
#include "app/gfx/arena.h"
|
||||||
#include "app/gfx/performance/performance_profiler.h"
|
#include "app/gfx/performance/performance_profiler.h"
|
||||||
#include "app/editor/ui/background_renderer.h"
|
#include "app/gui/background_renderer.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/gui/input.h"
|
#include "app/gui/input.h"
|
||||||
#include "app/gui/style.h"
|
#include "app/gui/style.h"
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#include "app/editor/ui/workspace_manager.h"
|
#include "app/editor/ui/workspace_manager.h"
|
||||||
#include "app/editor/system/toast_manager.h"
|
#include "app/editor/system/toast_manager.h"
|
||||||
|
#include "app/gui/editor_card_manager.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "util/file_util.h"
|
#include "util/file_util.h"
|
||||||
#include "util/platform_paths.h"
|
#include "util/platform_paths.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/imgui_internal.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
@@ -122,23 +125,60 @@ void WorkspaceManager::LoadModderLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WorkspaceManager::ShowAllWindows() {
|
void WorkspaceManager::ShowAllWindows() {
|
||||||
// TODO: Set all editor windows to visible
|
gui::EditorCardManager::Get().ShowAll();
|
||||||
|
if (toast_manager_) {
|
||||||
|
toast_manager_->Show("All windows shown", ToastType::kInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkspaceManager::HideAllWindows() {
|
void WorkspaceManager::HideAllWindows() {
|
||||||
// TODO: Hide all editor windows
|
gui::EditorCardManager::Get().HideAll();
|
||||||
|
if (toast_manager_) {
|
||||||
|
toast_manager_->Show("All windows hidden", ToastType::kInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkspaceManager::MaximizeCurrentWindow() {
|
void WorkspaceManager::MaximizeCurrentWindow() {
|
||||||
// TODO: Maximize focused window
|
// Use ImGui internal API to maximize current window
|
||||||
|
ImGuiWindow* window = ImGui::GetCurrentWindowRead();
|
||||||
|
if (window && window->DockNode) {
|
||||||
|
ImGuiID central_node_id = ImGui::DockBuilderGetCentralNode(
|
||||||
|
ImGui::GetID("MainDockSpace"))->ID;
|
||||||
|
ImGui::DockBuilderDockWindow(window->Name, central_node_id);
|
||||||
|
}
|
||||||
|
if (toast_manager_) {
|
||||||
|
toast_manager_->Show("Window maximized", ToastType::kInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkspaceManager::RestoreAllWindows() {
|
void WorkspaceManager::RestoreAllWindows() {
|
||||||
// TODO: Restore all windows to default size
|
// Reset all window sizes - ImGui will auto-restore based on docking
|
||||||
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
||||||
|
if (ctx) {
|
||||||
|
for (ImGuiWindow* window : ctx->Windows) {
|
||||||
|
if (window && !window->Collapsed) {
|
||||||
|
ImGui::SetWindowSize(window->Name, ImVec2(0, 0)); // Auto-size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toast_manager_) {
|
||||||
|
toast_manager_->Show("All windows restored", ToastType::kInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkspaceManager::CloseAllFloatingWindows() {
|
void WorkspaceManager::CloseAllFloatingWindows() {
|
||||||
// TODO: Close undocked windows
|
// Close all windows that are not docked
|
||||||
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
||||||
|
if (ctx) {
|
||||||
|
for (ImGuiWindow* window : ctx->Windows) {
|
||||||
|
if (window && !window->DockNode && !window->Collapsed) {
|
||||||
|
window->Hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toast_manager_) {
|
||||||
|
toast_manager_->Show("Floating windows closed", ToastType::kInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WorkspaceManager::GetActiveSessionCount() const {
|
size_t WorkspaceManager::GetActiveSessionCount() const {
|
||||||
@@ -155,7 +195,7 @@ size_t WorkspaceManager::GetActiveSessionCount() const {
|
|||||||
|
|
||||||
bool WorkspaceManager::HasDuplicateSession(const std::string& filepath) const {
|
bool WorkspaceManager::HasDuplicateSession(const std::string& filepath) const {
|
||||||
if (!sessions_) return false;
|
if (!sessions_) return false;
|
||||||
|
|
||||||
for (const auto& session : *sessions_) {
|
for (const auto& session : *sessions_) {
|
||||||
if (session.filepath == filepath && session.rom && session.rom->is_loaded()) {
|
if (session.filepath == filepath && session.rom && session.rom->is_loaded()) {
|
||||||
return true;
|
return true;
|
||||||
@@ -164,5 +204,74 @@ bool WorkspaceManager::HasDuplicateSession(const std::string& filepath) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Window navigation operations
|
||||||
|
void WorkspaceManager::FocusNextWindow() {
|
||||||
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
||||||
|
if (ctx && ctx->NavWindow) {
|
||||||
|
ImGui::FocusWindow(ImGui::FindWindowByName(ctx->NavWindow->Name));
|
||||||
|
}
|
||||||
|
// TODO: Implement proper window cycling
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkspaceManager::FocusPreviousWindow() {
|
||||||
|
// TODO: Implement window cycling backward
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkspaceManager::SplitWindowHorizontal() {
|
||||||
|
ImGuiWindow* window = ImGui::GetCurrentWindowRead();
|
||||||
|
if (window && window->DockNode) {
|
||||||
|
ImGuiID node_id = window->DockNode->ID;
|
||||||
|
ImGuiID out_id_at_dir = 0;
|
||||||
|
ImGuiID out_id_at_opposite_dir = 0;
|
||||||
|
ImGui::DockBuilderSplitNode(node_id, ImGuiDir_Down, 0.5f,
|
||||||
|
&out_id_at_dir, &out_id_at_opposite_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkspaceManager::SplitWindowVertical() {
|
||||||
|
ImGuiWindow* window = ImGui::GetCurrentWindowRead();
|
||||||
|
if (window && window->DockNode) {
|
||||||
|
ImGuiID node_id = window->DockNode->ID;
|
||||||
|
ImGuiID out_id_at_dir = 0;
|
||||||
|
ImGuiID out_id_at_opposite_dir = 0;
|
||||||
|
ImGui::DockBuilderSplitNode(node_id, ImGuiDir_Right, 0.5f,
|
||||||
|
&out_id_at_dir, &out_id_at_opposite_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WorkspaceManager::CloseCurrentWindow() {
|
||||||
|
ImGuiWindow* window = ImGui::GetCurrentWindowRead();
|
||||||
|
if (window) {
|
||||||
|
window->Hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command execution for WhichKey integration
|
||||||
|
void WorkspaceManager::ExecuteWorkspaceCommand(const std::string& command_id) {
|
||||||
|
// Window commands (Space + w)
|
||||||
|
if (command_id == "w.s") { ShowAllWindows(); }
|
||||||
|
else if (command_id == "w.h") { HideAllWindows(); }
|
||||||
|
else if (command_id == "w.m") { MaximizeCurrentWindow(); }
|
||||||
|
else if (command_id == "w.r") { RestoreAllWindows(); }
|
||||||
|
else if (command_id == "w.c") { CloseCurrentWindow(); }
|
||||||
|
else if (command_id == "w.f") { CloseAllFloatingWindows(); }
|
||||||
|
else if (command_id == "w.v") { SplitWindowVertical(); }
|
||||||
|
else if (command_id == "w.H") { SplitWindowHorizontal(); }
|
||||||
|
|
||||||
|
// Layout commands (Space + l)
|
||||||
|
else if (command_id == "l.s") { SaveWorkspaceLayout(); }
|
||||||
|
else if (command_id == "l.l") { LoadWorkspaceLayout(); }
|
||||||
|
else if (command_id == "l.r") { ResetWorkspaceLayout(); }
|
||||||
|
else if (command_id == "l.d") { LoadDeveloperLayout(); }
|
||||||
|
else if (command_id == "l.g") { LoadDesignerLayout(); }
|
||||||
|
else if (command_id == "l.m") { LoadModderLayout(); }
|
||||||
|
|
||||||
|
// Unknown command
|
||||||
|
else if (toast_manager_) {
|
||||||
|
toast_manager_->Show(absl::StrFormat("Unknown command: %s", command_id),
|
||||||
|
ToastType::kWarning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ class WorkspaceManager {
|
|||||||
void MaximizeCurrentWindow();
|
void MaximizeCurrentWindow();
|
||||||
void RestoreAllWindows();
|
void RestoreAllWindows();
|
||||||
void CloseAllFloatingWindows();
|
void CloseAllFloatingWindows();
|
||||||
|
|
||||||
|
// Window operations for keyboard navigation
|
||||||
|
void FocusNextWindow();
|
||||||
|
void FocusPreviousWindow();
|
||||||
|
void SplitWindowHorizontal();
|
||||||
|
void SplitWindowVertical();
|
||||||
|
void CloseCurrentWindow();
|
||||||
|
|
||||||
|
// Command execution (for WhichKey integration)
|
||||||
|
void ExecuteWorkspaceCommand(const std::string& command_id);
|
||||||
|
|
||||||
// Session queries
|
// Session queries
|
||||||
size_t GetActiveSessionCount() const;
|
size_t GetActiveSessionCount() const;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/zelda3/hyrule_magic.h"
|
#include "util/hyrule_magic.h"
|
||||||
#include "util/macro.h"
|
#include "util/macro.h"
|
||||||
|
|
||||||
#define DEBUG_LOG(msg) std::cout << msg << std::endl
|
#define DEBUG_LOG(msg) std::cout << msg << std::endl
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "app/editor/ui/background_renderer.h"
|
#include "app/gui/background_renderer.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -385,5 +385,29 @@ void DrawCanvasLabels(const CanvasRenderContext& ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CanvasUtils
|
} // namespace CanvasUtils
|
||||||
|
|
||||||
|
// CanvasConfig theme-aware methods implementation
|
||||||
|
float CanvasConfig::GetToolbarHeight() const {
|
||||||
|
if (!use_theme_sizing) {
|
||||||
|
return 32.0f; // Legacy fixed height
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use layout helpers for theme-aware sizing
|
||||||
|
// We need to include layout_helpers.h in the implementation file
|
||||||
|
// For now, return a reasonable default that respects ImGui font size
|
||||||
|
return ImGui::GetFontSize() * 0.75f; // Will be replaced with LayoutHelpers call
|
||||||
|
}
|
||||||
|
|
||||||
|
float CanvasConfig::GetGridSpacing() const {
|
||||||
|
if (!use_theme_sizing) {
|
||||||
|
return grid_step; // Use configured grid_step as-is
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply minimal theme-aware adjustment based on font size
|
||||||
|
// Grid should stay consistent, but scale slightly with UI density
|
||||||
|
float base_spacing = ImGui::GetFontSize() * 0.5f;
|
||||||
|
return std::max(grid_step, base_spacing);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace gui
|
} // namespace gui
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -20,12 +20,18 @@ struct CanvasConfig {
|
|||||||
bool enable_context_menu = true;
|
bool enable_context_menu = true;
|
||||||
bool is_draggable = false;
|
bool is_draggable = false;
|
||||||
bool auto_resize = false;
|
bool auto_resize = false;
|
||||||
bool clamp_rect_to_local_maps = true; // NEW: Prevent rectangle wrap across 512x512 boundaries
|
bool clamp_rect_to_local_maps = true; // Prevent rectangle wrap across 512x512 boundaries
|
||||||
|
bool use_theme_sizing = true; // Use theme-aware sizing instead of fixed sizes
|
||||||
float grid_step = 32.0f;
|
float grid_step = 32.0f;
|
||||||
float global_scale = 1.0f;
|
float global_scale = 1.0f;
|
||||||
ImVec2 canvas_size = ImVec2(0, 0);
|
ImVec2 canvas_size = ImVec2(0, 0);
|
||||||
ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.)
|
ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.)
|
||||||
bool custom_canvas_size = false;
|
bool custom_canvas_size = false;
|
||||||
|
|
||||||
|
// Get theme-aware canvas toolbar height (when use_theme_sizing is true)
|
||||||
|
float GetToolbarHeight() const;
|
||||||
|
// Get theme-aware grid spacing (when use_theme_sizing is true)
|
||||||
|
float GetGridSpacing() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ set(
|
|||||||
app/gui/editor_card_manager.cc
|
app/gui/editor_card_manager.cc
|
||||||
app/gui/editor_layout.cc
|
app/gui/editor_layout.cc
|
||||||
app/gui/input.cc
|
app/gui/input.cc
|
||||||
|
app/gui/layout_helpers.cc
|
||||||
|
app/gui/themed_widgets.cc
|
||||||
app/gui/modules/asset_browser.cc
|
app/gui/modules/asset_browser.cc
|
||||||
app/gui/modules/text_editor.cc
|
app/gui/modules/text_editor.cc
|
||||||
app/gui/style.cc
|
app/gui/style.cc
|
||||||
app/gui/theme_manager.cc
|
app/gui/theme_manager.cc
|
||||||
app/gui/ui_helpers.cc
|
app/gui/ui_helpers.cc
|
||||||
|
app/gui/background_renderer.cc # Moved from yaze_editor (used by style.cc)
|
||||||
app/gui/widgets/agent_chat_widget.cc
|
app/gui/widgets/agent_chat_widget.cc
|
||||||
app/gui/widgets/collaboration_panel.cc
|
app/gui/widgets/collaboration_panel.cc
|
||||||
app/gui/widgets/dungeon_object_emulator_preview.cc
|
app/gui/widgets/dungeon_object_emulator_preview.cc
|
||||||
|
|||||||
281
src/app/gui/layout_helpers.cc
Normal file
281
src/app/gui/layout_helpers.cc
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
#include "app/gui/layout_helpers.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/imgui_internal.h"
|
||||||
|
#include "app/gui/theme_manager.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
// Core sizing functions
|
||||||
|
float LayoutHelpers::GetStandardWidgetHeight() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * theme.widget_height_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LayoutHelpers::GetStandardSpacing() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * 0.5f * theme.spacing_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LayoutHelpers::GetToolbarHeight() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * theme.toolbar_height_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LayoutHelpers::GetPanelPadding() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * 0.5f * theme.panel_padding_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LayoutHelpers::GetStandardInputWidth() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * 8.0f * theme.input_width_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LayoutHelpers::GetButtonPadding() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * 0.3f * theme.button_padding_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LayoutHelpers::GetTableRowHeight() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * theme.table_row_height_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LayoutHelpers::GetCanvasToolbarHeight() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
return GetBaseFontSize() * theme.canvas_toolbar_multiplier * theme.compact_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout utilities
|
||||||
|
void LayoutHelpers::BeginPaddedPanel(const char* label, float padding) {
|
||||||
|
if (padding < 0.0f) {
|
||||||
|
padding = GetPanelPadding();
|
||||||
|
}
|
||||||
|
ImGui::BeginChild(label, ImVec2(0, 0), true);
|
||||||
|
ImGui::Dummy(ImVec2(padding, padding));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
ImGui::Dummy(ImVec2(0, padding));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::EndPaddedPanel() {
|
||||||
|
ImGui::Dummy(ImVec2(0, GetPanelPadding()));
|
||||||
|
ImGui::EndGroup();
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Dummy(ImVec2(GetPanelPadding(), 0));
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutHelpers::BeginTableWithTheming(const char* str_id, int columns,
|
||||||
|
ImGuiTableFlags flags,
|
||||||
|
const ImVec2& outer_size,
|
||||||
|
float inner_width) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
|
||||||
|
// Apply theme colors to table
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TableHeaderBg, ConvertColorToImVec4(theme.table_header_bg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TableBorderStrong, ConvertColorToImVec4(theme.table_border_strong));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TableBorderLight, ConvertColorToImVec4(theme.table_border_light));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TableRowBg, ConvertColorToImVec4(theme.table_row_bg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ConvertColorToImVec4(theme.table_row_bg_alt));
|
||||||
|
|
||||||
|
// Set row height if not overridden by caller
|
||||||
|
if (!(flags & ImGuiTableFlags_NoHostExtendY)) {
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,
|
||||||
|
ImVec2(ImGui::GetStyle().CellPadding.x, GetTableRowHeight() * 0.25f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImGui::BeginTable(str_id, columns, flags, outer_size, inner_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::BeginCanvasPanel(const char* label, ImVec2* canvas_size) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
|
||||||
|
// Apply theme to canvas container
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.editor_background));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
|
||||||
|
if (canvas_size) {
|
||||||
|
ImGui::BeginChild(label, *canvas_size, true);
|
||||||
|
} else {
|
||||||
|
ImGui::BeginChild(label, ImVec2(0, 0), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::EndCanvasPanel() {
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input field helpers
|
||||||
|
bool LayoutHelpers::AutoSizedInputField(const char* label, char* buf,
|
||||||
|
size_t buf_size, ImGuiInputTextFlags flags) {
|
||||||
|
ImGui::SetNextItemWidth(GetStandardInputWidth());
|
||||||
|
return ImGui::InputText(label, buf, buf_size, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutHelpers::AutoSizedInputInt(const char* label, int* v, int step,
|
||||||
|
int step_fast, ImGuiInputTextFlags flags) {
|
||||||
|
ImGui::SetNextItemWidth(GetStandardInputWidth());
|
||||||
|
return ImGui::InputInt(label, v, step, step_fast, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutHelpers::AutoSizedInputFloat(const char* label, float* v, float step,
|
||||||
|
float step_fast, const char* format,
|
||||||
|
ImGuiInputTextFlags flags) {
|
||||||
|
ImGui::SetNextItemWidth(GetStandardInputWidth());
|
||||||
|
return ImGui::InputFloat(label, v, step, step_fast, format, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input preset functions for common patterns
|
||||||
|
bool LayoutHelpers::InputHexRow(const char* label, uint8_t* data) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
float input_width = GetStandardInputWidth() * 0.5f; // Hex inputs are smaller
|
||||||
|
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%s", label);
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
// Use theme-aware input width for hex byte (2 chars + controls)
|
||||||
|
ImGui::SetNextItemWidth(input_width);
|
||||||
|
|
||||||
|
char buf[8];
|
||||||
|
snprintf(buf, sizeof(buf), "%02X", *data);
|
||||||
|
|
||||||
|
bool changed = ImGui::InputText(
|
||||||
|
("##" + std::string(label)).c_str(), buf, sizeof(buf),
|
||||||
|
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
unsigned int temp;
|
||||||
|
if (sscanf(buf, "%X", &temp) == 1) {
|
||||||
|
*data = static_cast<uint8_t>(temp & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutHelpers::InputHexRow(const char* label, uint16_t* data) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
float input_width = GetStandardInputWidth() * 0.6f; // Hex word slightly wider
|
||||||
|
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%s", label);
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
// Use theme-aware input width for hex word (4 chars + controls)
|
||||||
|
ImGui::SetNextItemWidth(input_width);
|
||||||
|
|
||||||
|
char buf[8];
|
||||||
|
snprintf(buf, sizeof(buf), "%04X", *data);
|
||||||
|
|
||||||
|
bool changed = ImGui::InputText(
|
||||||
|
("##" + std::string(label)).c_str(), buf, sizeof(buf),
|
||||||
|
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
unsigned int temp;
|
||||||
|
if (sscanf(buf, "%X", &temp) == 1) {
|
||||||
|
*data = static_cast<uint16_t>(temp & 0xFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::BeginPropertyGrid(const char* label) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
|
||||||
|
// Create a 2-column table for property editing
|
||||||
|
if (ImGui::BeginTable(label, 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
||||||
|
// Setup columns: label column (30%) and value column (70%)
|
||||||
|
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
GetStandardInputWidth() * 1.5f);
|
||||||
|
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::EndPropertyGrid() {
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutHelpers::InputToolbarField(const char* label, char* buf, size_t buf_size) {
|
||||||
|
// Compact input field for toolbars
|
||||||
|
float compact_width = GetStandardInputWidth() * 0.8f * GetTheme().compact_factor;
|
||||||
|
ImGui::SetNextItemWidth(compact_width);
|
||||||
|
|
||||||
|
return ImGui::InputText(label, buf, buf_size,
|
||||||
|
ImGuiInputTextFlags_AutoSelectAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toolbar helpers
|
||||||
|
void LayoutHelpers::BeginToolbar(const char* label) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.menu_bar_bg));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
|
||||||
|
ImVec2(GetButtonPadding(), GetButtonPadding()));
|
||||||
|
ImGui::BeginChild(label, ImVec2(0, GetToolbarHeight()), true,
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::EndToolbar() {
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::ToolbarSeparator() {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Dummy(ImVec2(GetStandardSpacing(), 0));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Dummy(ImVec2(GetStandardSpacing(), 0));
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LayoutHelpers::ToolbarButton(const char* label, const ImVec2& size) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
|
||||||
|
ImVec2(GetButtonPadding(), GetButtonPadding()));
|
||||||
|
bool result = ImGui::Button(label, size);
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common layout patterns
|
||||||
|
void LayoutHelpers::PropertyRow(const char* label, std::function<void()> widget_callback) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::Text("%s", label);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::SetNextItemWidth(-1);
|
||||||
|
widget_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::SectionHeader(const char* label) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ConvertColorToImVec4(theme.accent));
|
||||||
|
ImGui::SeparatorText(label);
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutHelpers::HelpMarker(const char* desc) {
|
||||||
|
ImGui::TextDisabled("(?)");
|
||||||
|
if (ImGui::BeginItemTooltip()) {
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||||
|
ImGui::TextUnformatted(desc);
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
83
src/app/gui/layout_helpers.h
Normal file
83
src/app/gui/layout_helpers.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#ifndef YAZE_APP_GUI_LAYOUT_HELPERS_H
|
||||||
|
#define YAZE_APP_GUI_LAYOUT_HELPERS_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "app/gui/theme_manager.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Theme-aware sizing helpers for consistent UI layout
|
||||||
|
*
|
||||||
|
* All sizing functions respect the current theme's compact_factor and
|
||||||
|
* semantic multipliers, ensuring layouts are consistent but customizable.
|
||||||
|
*/
|
||||||
|
class LayoutHelpers {
|
||||||
|
public:
|
||||||
|
// Core sizing functions (respect theme compact_factor + multipliers)
|
||||||
|
static float GetStandardWidgetHeight();
|
||||||
|
static float GetStandardSpacing();
|
||||||
|
static float GetToolbarHeight();
|
||||||
|
static float GetPanelPadding();
|
||||||
|
static float GetStandardInputWidth();
|
||||||
|
static float GetButtonPadding();
|
||||||
|
static float GetTableRowHeight();
|
||||||
|
static float GetCanvasToolbarHeight();
|
||||||
|
|
||||||
|
// Layout utilities
|
||||||
|
static void BeginPaddedPanel(const char* label, float padding = -1.0f);
|
||||||
|
static void EndPaddedPanel();
|
||||||
|
|
||||||
|
static bool BeginTableWithTheming(const char* str_id, int columns,
|
||||||
|
ImGuiTableFlags flags = 0,
|
||||||
|
const ImVec2& outer_size = ImVec2(0, 0),
|
||||||
|
float inner_width = 0.0f);
|
||||||
|
static void EndTable() { ImGui::EndTable(); }
|
||||||
|
|
||||||
|
static void BeginCanvasPanel(const char* label, ImVec2* canvas_size = nullptr);
|
||||||
|
static void EndCanvasPanel();
|
||||||
|
|
||||||
|
// Input field helpers
|
||||||
|
static bool AutoSizedInputField(const char* label, char* buf, size_t buf_size,
|
||||||
|
ImGuiInputTextFlags flags = 0);
|
||||||
|
static bool AutoSizedInputInt(const char* label, int* v, int step = 1,
|
||||||
|
int step_fast = 100, ImGuiInputTextFlags flags = 0);
|
||||||
|
static bool AutoSizedInputFloat(const char* label, float* v, float step = 0.0f,
|
||||||
|
float step_fast = 0.0f, const char* format = "%.3f",
|
||||||
|
ImGuiInputTextFlags flags = 0);
|
||||||
|
|
||||||
|
// Input preset functions for common patterns
|
||||||
|
static bool InputHexRow(const char* label, uint8_t* data);
|
||||||
|
static bool InputHexRow(const char* label, uint16_t* data);
|
||||||
|
static void BeginPropertyGrid(const char* label);
|
||||||
|
static void EndPropertyGrid();
|
||||||
|
static bool InputToolbarField(const char* label, char* buf, size_t buf_size);
|
||||||
|
|
||||||
|
// Toolbar helpers
|
||||||
|
static void BeginToolbar(const char* label);
|
||||||
|
static void EndToolbar();
|
||||||
|
static void ToolbarSeparator();
|
||||||
|
static bool ToolbarButton(const char* label, const ImVec2& size = ImVec2(0, 0));
|
||||||
|
|
||||||
|
// Common layout patterns
|
||||||
|
static void PropertyRow(const char* label, std::function<void()> widget_callback);
|
||||||
|
static void SectionHeader(const char* label);
|
||||||
|
static void HelpMarker(const char* desc);
|
||||||
|
|
||||||
|
// Get current theme
|
||||||
|
static const EnhancedTheme& GetTheme() {
|
||||||
|
return ThemeManager::Get().GetCurrentTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static float GetBaseFontSize() { return ImGui::GetFontSize(); }
|
||||||
|
static float ApplyCompactFactor(float base_value) {
|
||||||
|
return base_value * GetTheme().compact_factor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_GUI_LAYOUT_HELPERS_H
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "util/file_util.h"
|
#include "util/file_util.h"
|
||||||
#include "app/gui/theme_manager.h"
|
#include "app/gui/theme_manager.h"
|
||||||
#include "app/editor/ui/background_renderer.h"
|
#include "app/gui/background_renderer.h"
|
||||||
#include "app/platform/font_loader.h"
|
#include "app/platform/font_loader.h"
|
||||||
#include "app/gui/color.h"
|
#include "app/gui/color.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
|
|||||||
@@ -150,11 +150,25 @@ struct EnhancedTheme {
|
|||||||
float tab_rounding = 0.0f;
|
float tab_rounding = 0.0f;
|
||||||
float window_border_size = 0.0f;
|
float window_border_size = 0.0f;
|
||||||
float frame_border_size = 0.0f;
|
float frame_border_size = 0.0f;
|
||||||
|
|
||||||
// Animation and effects
|
// Animation and effects
|
||||||
bool enable_animations = true;
|
bool enable_animations = true;
|
||||||
float animation_speed = 1.0f;
|
float animation_speed = 1.0f;
|
||||||
bool enable_glow_effects = false;
|
bool enable_glow_effects = false;
|
||||||
|
|
||||||
|
// Theme-aware sizing system (relative to font size)
|
||||||
|
// compact_factor: 0.8 = very compact, 1.0 = normal, 1.2 = spacious
|
||||||
|
float compact_factor = 1.0f;
|
||||||
|
|
||||||
|
// Semantic sizing multipliers (applied on top of compact_factor)
|
||||||
|
float widget_height_multiplier = 1.0f; // Standard widget height
|
||||||
|
float spacing_multiplier = 1.0f; // Padding/margins between elements
|
||||||
|
float toolbar_height_multiplier = 0.8f; // Compact toolbars
|
||||||
|
float panel_padding_multiplier = 1.0f; // Panel interior padding
|
||||||
|
float input_width_multiplier = 1.0f; // Standard input field width
|
||||||
|
float button_padding_multiplier = 1.0f; // Button interior padding
|
||||||
|
float table_row_height_multiplier = 1.0f; // Table row height
|
||||||
|
float canvas_toolbar_multiplier = 0.75f; // Canvas overlay toolbars
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
void ApplyToImGui() const;
|
void ApplyToImGui() const;
|
||||||
|
|||||||
272
src/app/gui/themed_widgets.cc
Normal file
272
src/app/gui/themed_widgets.cc
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#include "app/gui/themed_widgets.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
namespace themed {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Buttons
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
bool Button(const char* label, const ImVec2& size) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.button));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active));
|
||||||
|
|
||||||
|
bool result = ImGui::Button(label, size);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IconButton(const char* icon, const char* tooltip) {
|
||||||
|
bool result = Button(icon, ImVec2(LayoutHelpers::GetStandardWidgetHeight(),
|
||||||
|
LayoutHelpers::GetStandardWidgetHeight()));
|
||||||
|
if (tooltip && ImGui::IsItemHovered()) {
|
||||||
|
BeginTooltip();
|
||||||
|
ImGui::Text("%s", tooltip);
|
||||||
|
EndTooltip();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrimaryButton(const char* label, const ImVec2& size) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.accent));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||||
|
ImVec4(theme.accent.red * 1.2f, theme.accent.green * 1.2f,
|
||||||
|
theme.accent.blue * 1.2f, theme.accent.alpha));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||||
|
ImVec4(theme.accent.red * 0.8f, theme.accent.green * 0.8f,
|
||||||
|
theme.accent.blue * 0.8f, theme.accent.alpha));
|
||||||
|
|
||||||
|
bool result = ImGui::Button(label, size);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DangerButton(const char* label, const ImVec2& size) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.error));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||||
|
ImVec4(theme.error.red * 1.2f, theme.error.green * 1.2f,
|
||||||
|
theme.error.blue * 1.2f, theme.error.alpha));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||||
|
ImVec4(theme.error.red * 0.8f, theme.error.green * 0.8f,
|
||||||
|
theme.error.blue * 0.8f, theme.error.alpha));
|
||||||
|
|
||||||
|
bool result = ImGui::Button(label, size);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Headers & Sections
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void Header(const char* label) {
|
||||||
|
LayoutHelpers::SectionHeader(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Header, ConvertColorToImVec4(theme.header));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ConvertColorToImVec4(theme.header_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ConvertColorToImVec4(theme.header_active));
|
||||||
|
|
||||||
|
bool result = ImGui::CollapsingHeader(label, flags);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Cards & Panels
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void Card(const char* label, std::function<void()> content, const ImVec2& size) {
|
||||||
|
BeginPanel(label, size);
|
||||||
|
content();
|
||||||
|
EndPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginPanel(const char* label, const ImVec2& size) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ConvertColorToImVec4(theme.surface));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, theme.window_rounding);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,
|
||||||
|
ImVec2(LayoutHelpers::GetPanelPadding(),
|
||||||
|
LayoutHelpers::GetPanelPadding()));
|
||||||
|
|
||||||
|
ImGui::BeginChild(label, size, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndPanel() {
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Inputs
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
bool InputText(const char* label, char* buf, size_t buf_size,
|
||||||
|
ImGuiInputTextFlags flags) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active));
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
|
||||||
|
bool result = ImGui::InputText(label, buf, buf_size, flags);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputInt(const char* label, int* v, int step, int step_fast,
|
||||||
|
ImGuiInputTextFlags flags) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active));
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
|
||||||
|
bool result = ImGui::InputInt(label, v, step, step_fast, flags);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputFloat(const char* label, float* v, float step, float step_fast,
|
||||||
|
const char* format, ImGuiInputTextFlags flags) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active));
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
|
||||||
|
bool result = ImGui::InputFloat(label, v, step, step_fast, format, flags);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Checkbox(const char* label, bool* v) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_CheckMark, ConvertColorToImVec4(theme.check_mark));
|
||||||
|
|
||||||
|
bool result = ImGui::Checkbox(label, v);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Combo(const char* label, int* current_item, const char* const items[],
|
||||||
|
int items_count, int popup_max_height_in_items) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active));
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(LayoutHelpers::GetStandardInputWidth());
|
||||||
|
bool result = ImGui::Combo(label, current_item, items, items_count,
|
||||||
|
popup_max_height_in_items);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tables
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
bool BeginTable(const char* str_id, int columns, ImGuiTableFlags flags,
|
||||||
|
const ImVec2& outer_size, float inner_width) {
|
||||||
|
return LayoutHelpers::BeginTableWithTheming(str_id, columns, flags, outer_size, inner_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndTable() {
|
||||||
|
LayoutHelpers::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tooltips & Help
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void HelpMarker(const char* desc) {
|
||||||
|
LayoutHelpers::HelpMarker(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginTooltip() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PopupBg, ConvertColorToImVec4(theme.popup_bg));
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndTooltip() {
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Status & Feedback
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void StatusText(const char* text, StatusType type) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImVec4 color;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case StatusType::kSuccess:
|
||||||
|
color = ConvertColorToImVec4(theme.success);
|
||||||
|
break;
|
||||||
|
case StatusType::kWarning:
|
||||||
|
color = ConvertColorToImVec4(theme.warning);
|
||||||
|
break;
|
||||||
|
case StatusType::kError:
|
||||||
|
color = ConvertColorToImVec4(theme.error);
|
||||||
|
break;
|
||||||
|
case StatusType::kInfo:
|
||||||
|
color = ConvertColorToImVec4(theme.info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextColored(color, "%s", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProgressBar(float fraction, const ImVec2& size, const char* overlay) {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ConvertColorToImVec4(theme.accent));
|
||||||
|
|
||||||
|
ImGui::ProgressBar(fraction, size, overlay);
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Utility
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void PushWidgetColors() {
|
||||||
|
const auto& theme = GetTheme();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ConvertColorToImVec4(theme.frame_bg));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ConvertColorToImVec4(theme.frame_bg_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ConvertColorToImVec4(theme.frame_bg_active));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ConvertColorToImVec4(theme.button));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ConvertColorToImVec4(theme.button_hovered));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ConvertColorToImVec4(theme.button_active));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopWidgetColors() {
|
||||||
|
ImGui::PopStyleColor(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace themed
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
211
src/app/gui/themed_widgets.h
Normal file
211
src/app/gui/themed_widgets.h
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
#ifndef YAZE_APP_GUI_THEMED_WIDGETS_H
|
||||||
|
#define YAZE_APP_GUI_THEMED_WIDGETS_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "app/gui/theme_manager.h"
|
||||||
|
#include "app/gui/layout_helpers.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Opt-in themed widget library for gradual migration
|
||||||
|
*
|
||||||
|
* All widgets automatically use the current theme from ThemeManager.
|
||||||
|
* Editors can opt-in by using these widgets instead of raw ImGui calls.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```cpp
|
||||||
|
* using namespace yaze::gui::themed;
|
||||||
|
*
|
||||||
|
* if (Button("Save")) {
|
||||||
|
* // Button uses theme colors automatically
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Header("Settings"); // Themed section header
|
||||||
|
*
|
||||||
|
* Card("Properties", [&]() {
|
||||||
|
* // Content inside themed card
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
namespace themed {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Buttons
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed button with automatic color application
|
||||||
|
*/
|
||||||
|
bool Button(const char* label, const ImVec2& size = ImVec2(0, 0));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed button with icon (Material Design Icons)
|
||||||
|
*/
|
||||||
|
bool IconButton(const char* icon, const char* tooltip = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Primary action button (uses accent color)
|
||||||
|
*/
|
||||||
|
bool PrimaryButton(const char* label, const ImVec2& size = ImVec2(0, 0));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Danger/destructive action button (uses error color)
|
||||||
|
*/
|
||||||
|
bool DangerButton(const char* label, const ImVec2& size = ImVec2(0, 0));
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Headers & Sections
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed section header with accent color
|
||||||
|
*/
|
||||||
|
void Header(const char* label);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Collapsible section with themed header
|
||||||
|
*/
|
||||||
|
bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Cards & Panels
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed card with rounded corners and shadow
|
||||||
|
* @param label Unique ID for the card
|
||||||
|
* @param content Callback function to render card content
|
||||||
|
* @param size Card size (0, 0 for auto-size)
|
||||||
|
*/
|
||||||
|
void Card(const char* label, std::function<void()> content,
|
||||||
|
const ImVec2& size = ImVec2(0, 0));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Begin themed panel (manual version of Card)
|
||||||
|
*/
|
||||||
|
void BeginPanel(const char* label, const ImVec2& size = ImVec2(0, 0));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief End themed panel
|
||||||
|
*/
|
||||||
|
void EndPanel();
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Inputs
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed text input
|
||||||
|
*/
|
||||||
|
bool InputText(const char* label, char* buf, size_t buf_size,
|
||||||
|
ImGuiInputTextFlags flags = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed integer input
|
||||||
|
*/
|
||||||
|
bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100,
|
||||||
|
ImGuiInputTextFlags flags = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed float input
|
||||||
|
*/
|
||||||
|
bool InputFloat(const char* label, float* v, float step = 0.0f,
|
||||||
|
float step_fast = 0.0f, const char* format = "%.3f",
|
||||||
|
ImGuiInputTextFlags flags = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed checkbox
|
||||||
|
*/
|
||||||
|
bool Checkbox(const char* label, bool* v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed combo box
|
||||||
|
*/
|
||||||
|
bool Combo(const char* label, int* current_item, const char* const items[],
|
||||||
|
int items_count, int popup_max_height_in_items = -1);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tables
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Begin themed table with automatic styling
|
||||||
|
*/
|
||||||
|
bool BeginTable(const char* str_id, int columns, ImGuiTableFlags flags = 0,
|
||||||
|
const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief End themed table
|
||||||
|
*/
|
||||||
|
void EndTable();
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tooltips & Help
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed help marker with tooltip
|
||||||
|
*/
|
||||||
|
void HelpMarker(const char* desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Begin themed tooltip
|
||||||
|
*/
|
||||||
|
void BeginTooltip();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief End themed tooltip
|
||||||
|
*/
|
||||||
|
void EndTooltip();
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Status & Feedback
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed status text (success, warning, error, info)
|
||||||
|
*/
|
||||||
|
void StatusText(const char* text, StatusType type);
|
||||||
|
|
||||||
|
enum class StatusType {
|
||||||
|
kSuccess,
|
||||||
|
kWarning,
|
||||||
|
kError,
|
||||||
|
kInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Themed progress bar
|
||||||
|
*/
|
||||||
|
void ProgressBar(float fraction, const ImVec2& size = ImVec2(-1, 0),
|
||||||
|
const char* overlay = nullptr);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Utility
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current theme (shortcut)
|
||||||
|
*/
|
||||||
|
inline const EnhancedTheme& GetTheme() {
|
||||||
|
return ThemeManager::Get().GetCurrentTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Apply theme colors to next widget
|
||||||
|
*/
|
||||||
|
void PushWidgetColors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restore previous colors
|
||||||
|
*/
|
||||||
|
void PopWidgetColors();
|
||||||
|
|
||||||
|
} // namespace themed
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_GUI_THEMED_WIDGETS_H
|
||||||
@@ -7,7 +7,6 @@ set(
|
|||||||
app/zelda3/dungeon/room.cc
|
app/zelda3/dungeon/room.cc
|
||||||
app/zelda3/dungeon/room_layout.cc
|
app/zelda3/dungeon/room_layout.cc
|
||||||
app/zelda3/dungeon/room_object.cc
|
app/zelda3/dungeon/room_object.cc
|
||||||
app/zelda3/hyrule_magic.cc
|
|
||||||
app/zelda3/music/tracker.cc
|
app/zelda3/music/tracker.cc
|
||||||
app/zelda3/overworld/overworld.cc
|
app/zelda3/overworld/overworld.cc
|
||||||
app/zelda3/overworld/overworld_map.cc
|
app/zelda3/overworld/overworld_map.cc
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
set(YAZE_AGENT_SOURCES
|
set(YAZE_AGENT_SOURCES
|
||||||
|
# Core infrastructure
|
||||||
|
cli/service/command_registry.cc
|
||||||
cli/service/agent/proposal_executor.cc
|
cli/service/agent/proposal_executor.cc
|
||||||
cli/handlers/agent/todo_commands.cc
|
cli/handlers/agent/todo_commands.cc
|
||||||
cli/service/agent/conversational_agent_service.cc
|
cli/service/agent/conversational_agent_service.cc
|
||||||
cli/service/agent/simple_chat_session.cc
|
cli/service/agent/simple_chat_session.cc
|
||||||
cli/service/agent/enhanced_tui.cc
|
cli/service/agent/enhanced_tui.cc
|
||||||
cli/service/agent/tool_dispatcher.cc
|
cli/service/agent/tool_dispatcher.cc
|
||||||
|
|
||||||
|
# Advanced features
|
||||||
cli/service/agent/learned_knowledge_service.cc
|
cli/service/agent/learned_knowledge_service.cc
|
||||||
cli/service/agent/todo_manager.cc
|
cli/service/agent/todo_manager.cc
|
||||||
|
cli/service/agent/advanced_routing.cc
|
||||||
|
cli/service/agent/agent_pretraining.cc
|
||||||
cli/service/agent/vim_mode.cc
|
cli/service/agent/vim_mode.cc
|
||||||
cli/service/ai/ai_service.cc
|
cli/service/ai/ai_service.cc
|
||||||
cli/service/ai/ai_action_parser.cc
|
cli/service/ai/ai_action_parser.cc
|
||||||
|
|||||||
@@ -7,20 +7,18 @@
|
|||||||
#include "absl/flags/declare.h"
|
#include "absl/flags/declare.h"
|
||||||
#include "absl/flags/flag.h"
|
#include "absl/flags/flag.h"
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "cli/service/command_registry.h"
|
||||||
#include "cli/handlers/agent/common.h"
|
#include "cli/handlers/agent/common.h"
|
||||||
#include "cli/handlers/agent/todo_commands.h"
|
#include "cli/handlers/agent/todo_commands.h"
|
||||||
#include "cli/handlers/agent/simple_chat_command.h"
|
#include "cli/handlers/agent/simple_chat_command.h"
|
||||||
#include "cli/handlers/tools/resource_commands.h"
|
|
||||||
#include "cli/handlers/game/dungeon_commands.h"
|
|
||||||
#include "cli/handlers/game/overworld_commands.h"
|
|
||||||
#include "cli/handlers/tools/gui_commands.h"
|
|
||||||
|
|
||||||
ABSL_DECLARE_FLAG(bool, quiet);
|
ABSL_DECLARE_FLAG(bool, quiet);
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace cli {
|
namespace cli {
|
||||||
|
|
||||||
// Forward declarations from general_commands.cc
|
// Forward declarations for special agent commands (not in registry)
|
||||||
namespace agent {
|
namespace agent {
|
||||||
absl::Status HandlePlanCommand(const std::vector<std::string>& args);
|
absl::Status HandlePlanCommand(const std::vector<std::string>& args);
|
||||||
absl::Status HandleTestCommand(const std::vector<std::string>& args);
|
absl::Status HandleTestCommand(const std::vector<std::string>& args);
|
||||||
@@ -29,211 +27,134 @@ absl::Status HandleGuiCommand(const std::vector<std::string>& args);
|
|||||||
absl::Status HandleLearnCommand(const std::vector<std::string>& args);
|
absl::Status HandleLearnCommand(const std::vector<std::string>& args);
|
||||||
absl::Status HandleListCommand();
|
absl::Status HandleListCommand();
|
||||||
absl::Status HandleDescribeCommand(const std::vector<std::string>& args);
|
absl::Status HandleDescribeCommand(const std::vector<std::string>& args);
|
||||||
|
|
||||||
// Wrapper functions to call CommandHandlers
|
|
||||||
absl::Status HandleResourceListCommand(const std::vector<std::string>& args, Rom* rom) {
|
|
||||||
handlers::ResourceListCommandHandler handler;
|
|
||||||
return handler.Run(args, rom);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status HandleResourceSearchCommand(const std::vector<std::string>& args, Rom* rom) {
|
|
||||||
handlers::ResourceSearchCommandHandler handler;
|
|
||||||
return handler.Run(args, rom);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status HandleDungeonListSpritesCommand(const std::vector<std::string>& args, Rom* rom) {
|
|
||||||
handlers::DungeonListSpritesCommandHandler handler;
|
|
||||||
return handler.Run(args, rom);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status HandleDungeonDescribeRoomCommand(const std::vector<std::string>& args, Rom* rom) {
|
|
||||||
handlers::DungeonDescribeRoomCommandHandler handler;
|
|
||||||
return handler.Run(args, rom);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status HandleOverworldFindTileCommand(const std::vector<std::string>& args, Rom* rom) {
|
|
||||||
handlers::OverworldFindTileCommandHandler handler;
|
|
||||||
return handler.Run(args, rom);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status HandleOverworldDescribeMapCommand(const std::vector<std::string>& args, Rom* rom) {
|
|
||||||
handlers::OverworldDescribeMapCommandHandler handler;
|
|
||||||
return handler.Run(args, rom);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status HandleOverworldListWarpsCommand(const std::vector<std::string>& args, Rom* rom) {
|
|
||||||
handlers::OverworldListWarpsCommandHandler handler;
|
|
||||||
return handler.Run(args, rom);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace agent
|
} // namespace agent
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr absl::string_view kUsage =
|
std::string GenerateAgentHelp() {
|
||||||
"Usage: agent <subcommand> [options]\n"
|
auto& registry = CommandRegistry::Instance();
|
||||||
"\n"
|
|
||||||
"AI-Powered Agent Subcommands:\n"
|
std::ostringstream help;
|
||||||
" simple-chat Simple text-based chat (recommended for testing)\n"
|
help << "Usage: agent <subcommand> [options]\n\n";
|
||||||
" Modes: interactive | piped | batch | single-message\n"
|
|
||||||
" Example: agent simple-chat \"What dungeons exist?\" --rom=zelda3.sfc\n"
|
help << "AI-Powered Agent Commands:\n";
|
||||||
" Example: agent simple-chat --rom=zelda3.sfc --ai_provider=ollama\n"
|
help << " simple-chat Interactive AI chat\n";
|
||||||
" Example: echo \"List sprites\" | agent simple-chat --rom=zelda3.sfc\n"
|
help << " test-conversation Automated test conversation\n";
|
||||||
" Example: agent simple-chat --file=queries.txt --rom=zelda3.sfc\n"
|
help << " plan Generate execution plan\n";
|
||||||
"\n"
|
help << " learn Manage learned knowledge\n";
|
||||||
" test-conversation Run automated test conversation with AI\n"
|
help << " todo Task management\n";
|
||||||
" Example: agent test-conversation --rom=zelda3.sfc --ai_provider=ollama\n"
|
help << " test Run tests\n";
|
||||||
"\n"
|
help << " list/describe List/describe proposals\n\n";
|
||||||
" chat Full FTXUI-based chat interface\n"
|
|
||||||
" Example: agent chat --rom=zelda3.sfc\n"
|
// Auto-list available tool commands from registry
|
||||||
"\n"
|
help << "Tool Commands (AI can call these):\n";
|
||||||
"ROM Inspection Tools (can be called by AI or directly):\n"
|
auto agent_commands = registry.GetAgentCommands();
|
||||||
" resource-list List labeled resources (dungeons, sprites, etc.)\n"
|
int count = 0;
|
||||||
" Example: agent resource-list --type=dungeon --format=json\n"
|
for (const auto& cmd : agent_commands) {
|
||||||
"\n"
|
if (count++ < 10) { // Show first 10
|
||||||
" resource-search Search resource labels by fuzzy text\n"
|
auto* meta = registry.GetMetadata(cmd);
|
||||||
" Example: agent resource-search --query=soldier --type=sprite\n"
|
if (meta) {
|
||||||
"\n"
|
help << " " << cmd;
|
||||||
" dungeon-list-sprites List sprites in a dungeon room\n"
|
for (size_t i = cmd.length(); i < 24; i++) help << " ";
|
||||||
" Example: agent dungeon-list-sprites --room=5 --format=json\n"
|
help << meta->description << "\n";
|
||||||
"\n"
|
}
|
||||||
" dungeon-describe-room Summarize metadata for a dungeon room\n"
|
}
|
||||||
" Example: agent dungeon-describe-room --room=0x12 --format=text\n"
|
}
|
||||||
"\n"
|
help << " ... and " << (agent_commands.size() - 10) << " more (see z3ed --list-commands)\n\n";
|
||||||
" overworld-find-tile Search for tile placements in overworld\n"
|
|
||||||
" Example: agent overworld-find-tile --tile=0x02E --format=json\n"
|
help << "Global Options:\n";
|
||||||
"\n"
|
help << " --rom=<path> Path to ROM file\n";
|
||||||
" overworld-describe-map Get metadata about an overworld map\n"
|
help << " --ai_provider=<name> AI provider: ollama | gemini\n";
|
||||||
" Example: agent overworld-describe-map --map=0 --format=json\n"
|
help << " --format=<type> Output format: text | json\n\n";
|
||||||
"\n"
|
|
||||||
" overworld-list-warps List entrances/exits/holes in overworld\n"
|
help << "For detailed help: z3ed agent <command> --help\n";
|
||||||
" Example: agent overworld-list-warps --map=0 --format=json\n"
|
help << "For all commands: z3ed --list-commands\n";
|
||||||
"\n"
|
|
||||||
"Proposal & Testing Commands:\n"
|
return help.str();
|
||||||
" run Execute agent task\n"
|
}
|
||||||
" plan Generate execution plan\n"
|
|
||||||
" diff Show ROM differences\n"
|
constexpr absl::string_view kUsage = "";
|
||||||
" accept Accept and apply proposal changes\n"
|
|
||||||
" test Run agent tests\n"
|
|
||||||
" gui Launch GUI components\n"
|
|
||||||
" learn Train agent on examples\n"
|
|
||||||
" list List available resources\n"
|
|
||||||
" commit Commit changes\n"
|
|
||||||
" revert Revert changes\n"
|
|
||||||
" describe Describe agent capabilities\n"
|
|
||||||
" todo Manage tasks and project planning\n"
|
|
||||||
"\n"
|
|
||||||
"Global Options:\n"
|
|
||||||
" --rom=<path> Path to Zelda3 ROM file (required for most commands)\n"
|
|
||||||
" --ai_provider=<name> AI provider: mock (default) | ollama | gemini\n"
|
|
||||||
" --ai_model=<name> Model name (e.g., qwen2.5-coder:7b for Ollama)\n"
|
|
||||||
" --ollama_host=<url> Ollama server URL (default: http://localhost:11434)\n"
|
|
||||||
" --gemini_api_key=<key> Gemini API key (or set GEMINI_API_KEY env var)\n"
|
|
||||||
" --format=<type> Output format: text | markdown | json | compact\n"
|
|
||||||
"\n"
|
|
||||||
"For more details, see: docs/simple_chat_input_methods.md";
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace handlers {
|
namespace handlers {
|
||||||
|
|
||||||
// Legacy Agent class removed - using new CommandHandler system
|
/**
|
||||||
// This implementation should be moved to a proper AgentCommandHandler
|
* @brief Unified agent command handler using CommandRegistry
|
||||||
|
*
|
||||||
|
* Routes commands in this order:
|
||||||
|
* 1. Special agent commands (plan, test, learn, todo) - Not in registry
|
||||||
|
* 2. Registry commands (resource-*, dungeon-*, overworld-*, emulator-*, etc.)
|
||||||
|
* 3. Fallback to error
|
||||||
|
*/
|
||||||
absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec) {
|
absl::Status HandleAgentCommand(const std::vector<std::string>& arg_vec) {
|
||||||
if (arg_vec.empty()) {
|
if (arg_vec.empty()) {
|
||||||
return absl::InvalidArgumentError(std::string(kUsage));
|
std::cout << GenerateAgentHelp();
|
||||||
|
return absl::InvalidArgumentError("No subcommand specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& subcommand = arg_vec[0];
|
const std::string& subcommand = arg_vec[0];
|
||||||
std::vector<std::string> subcommand_args(arg_vec.begin() + 1, arg_vec.end());
|
std::vector<std::string> subcommand_args(arg_vec.begin() + 1, arg_vec.end());
|
||||||
|
|
||||||
if (subcommand == "run") {
|
// === Special Agent Commands (not in registry) ===
|
||||||
return absl::UnimplementedError("Agent run command requires ROM context - not yet implemented");
|
|
||||||
|
if (subcommand == "simple-chat" || subcommand == "chat") {
|
||||||
|
auto& registry = CommandRegistry::Instance();
|
||||||
|
return registry.Execute("simple-chat", subcommand_args, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subcommand == "plan") {
|
if (subcommand == "plan") {
|
||||||
return agent::HandlePlanCommand(subcommand_args);
|
return agent::HandlePlanCommand(subcommand_args);
|
||||||
}
|
}
|
||||||
if (subcommand == "diff") {
|
|
||||||
return absl::UnimplementedError("Agent diff command requires ROM context - not yet implemented");
|
|
||||||
}
|
|
||||||
if (subcommand == "accept") {
|
|
||||||
return absl::UnimplementedError("Agent accept command requires ROM context - not yet implemented");
|
|
||||||
}
|
|
||||||
if (subcommand == "test") {
|
if (subcommand == "test") {
|
||||||
return agent::HandleTestCommand(subcommand_args);
|
return agent::HandleTestCommand(subcommand_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subcommand == "test-conversation") {
|
if (subcommand == "test-conversation") {
|
||||||
return agent::HandleTestConversationCommand(subcommand_args);
|
return agent::HandleTestConversationCommand(subcommand_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subcommand == "gui") {
|
if (subcommand == "gui") {
|
||||||
return agent::HandleGuiCommand(subcommand_args);
|
return agent::HandleGuiCommand(subcommand_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subcommand == "learn") {
|
if (subcommand == "learn") {
|
||||||
return agent::HandleLearnCommand(subcommand_args);
|
return agent::HandleLearnCommand(subcommand_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subcommand == "todo") {
|
||||||
|
return handlers::HandleTodoCommand(subcommand_args);
|
||||||
|
}
|
||||||
|
|
||||||
if (subcommand == "list") {
|
if (subcommand == "list") {
|
||||||
return agent::HandleListCommand();
|
return agent::HandleListCommand();
|
||||||
}
|
}
|
||||||
if (subcommand == "commit") {
|
|
||||||
return absl::UnimplementedError("Agent commit command requires ROM context - not yet implemented");
|
|
||||||
}
|
|
||||||
if (subcommand == "revert") {
|
|
||||||
return absl::UnimplementedError("Agent revert command requires ROM context - not yet implemented");
|
|
||||||
}
|
|
||||||
if (subcommand == "describe") {
|
if (subcommand == "describe") {
|
||||||
return agent::HandleDescribeCommand(subcommand_args);
|
return agent::HandleDescribeCommand(subcommand_args);
|
||||||
}
|
}
|
||||||
if (subcommand == "resource-list") {
|
|
||||||
return agent::HandleResourceListCommand(subcommand_args, nullptr);
|
|
||||||
}
|
|
||||||
if (subcommand == "resource-search") {
|
|
||||||
return agent::HandleResourceSearchCommand(subcommand_args, nullptr);
|
|
||||||
}
|
|
||||||
if (subcommand == "dungeon-list-sprites") {
|
|
||||||
return agent::HandleDungeonListSpritesCommand(subcommand_args, nullptr);
|
|
||||||
}
|
|
||||||
if (subcommand == "dungeon-describe-room") {
|
|
||||||
return agent::HandleDungeonDescribeRoomCommand(subcommand_args, nullptr);
|
|
||||||
}
|
|
||||||
if (subcommand == "overworld-find-tile") {
|
|
||||||
return agent::HandleOverworldFindTileCommand(subcommand_args, nullptr);
|
|
||||||
}
|
|
||||||
if (subcommand == "overworld-describe-map") {
|
|
||||||
return agent::HandleOverworldDescribeMapCommand(subcommand_args, nullptr);
|
|
||||||
}
|
|
||||||
if (subcommand == "overworld-list-warps") {
|
|
||||||
return agent::HandleOverworldListWarpsCommand(subcommand_args, nullptr);
|
|
||||||
}
|
|
||||||
// if (subcommand == "chat") {
|
|
||||||
// return absl::UnimplementedError("Agent chat command requires ROM context - not yet implemented");
|
|
||||||
// }
|
|
||||||
// if (subcommand == "todo") {
|
|
||||||
// return handlers::HandleTodoCommand(subcommand_args);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Hex manipulation commands
|
// Placeholder for unimplemented workflow commands
|
||||||
// if (subcommand == "hex-read") {
|
if (subcommand == "run" || subcommand == "diff" || subcommand == "accept" ||
|
||||||
// return HandleHexRead(subcommand_args, nullptr);
|
subcommand == "commit" || subcommand == "revert") {
|
||||||
// }
|
return absl::UnimplementedError(
|
||||||
// if (subcommand == "hex-write") {
|
absl::StrCat("Agent ", subcommand, " command requires ROM context - not yet implemented"));
|
||||||
// return HandleHexWrite(subcommand_args, nullptr);
|
}
|
||||||
// }
|
|
||||||
// if (subcommand == "hex-search") {
|
|
||||||
// return HandleHexSearch(subcommand_args, nullptr);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Palette manipulation commands
|
// === Registry Commands (resource, dungeon, overworld, emulator, etc.) ===
|
||||||
// if (subcommand == "palette-get-colors") {
|
|
||||||
// return HandlePaletteGetColors(subcommand_args, nullptr);
|
auto& registry = CommandRegistry::Instance();
|
||||||
// }
|
|
||||||
// if (subcommand == "palette-set-color") {
|
// Check if this is a registered command
|
||||||
// return HandlePaletteSetColor(subcommand_args, nullptr);
|
if (registry.HasCommand(subcommand)) {
|
||||||
// }
|
return registry.Execute(subcommand, subcommand_args, nullptr);
|
||||||
// if (subcommand == "palette-analyze") {
|
}
|
||||||
// return HandlePaletteAnalyze(subcommand_args, nullptr);
|
|
||||||
// }
|
// Not found
|
||||||
|
std::cout << GenerateAgentHelp();
|
||||||
return absl::InvalidArgumentError(std::string(kUsage));
|
return absl::InvalidArgumentError(
|
||||||
|
absl::StrCat("Unknown agent command: ", subcommand));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler functions are now implemented in command_wrappers.cc
|
// Handler functions are now implemented in command_wrappers.cc
|
||||||
|
|||||||
279
src/cli/service/command_registry.cc
Normal file
279
src/cli/service/command_registry.cc
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
#include "cli/service/command_registry.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_join.h"
|
||||||
|
#include "cli/handlers/command_handlers.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace cli {
|
||||||
|
|
||||||
|
CommandRegistry& CommandRegistry::Instance() {
|
||||||
|
static CommandRegistry instance;
|
||||||
|
static bool initialized = false;
|
||||||
|
if (!initialized) {
|
||||||
|
instance.RegisterAllCommands();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandRegistry::Register(std::unique_ptr<resources::CommandHandler> handler,
|
||||||
|
const CommandMetadata& metadata) {
|
||||||
|
std::string name = handler->GetName();
|
||||||
|
|
||||||
|
// Store metadata
|
||||||
|
metadata_[name] = metadata;
|
||||||
|
|
||||||
|
// Register aliases
|
||||||
|
for (const auto& alias : metadata.aliases) {
|
||||||
|
aliases_[alias] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store handler
|
||||||
|
handlers_[name] = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
resources::CommandHandler* CommandRegistry::Get(const std::string& name) const {
|
||||||
|
// Check direct name
|
||||||
|
auto it = handlers_.find(name);
|
||||||
|
if (it != handlers_.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check aliases
|
||||||
|
auto alias_it = aliases_.find(name);
|
||||||
|
if (alias_it != aliases_.end()) {
|
||||||
|
auto handler_it = handlers_.find(alias_it->second);
|
||||||
|
if (handler_it != handlers_.end()) {
|
||||||
|
return handler_it->second.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandRegistry::CommandMetadata* CommandRegistry::GetMetadata(
|
||||||
|
const std::string& name) const {
|
||||||
|
// Resolve alias first
|
||||||
|
std::string canonical_name = name;
|
||||||
|
auto alias_it = aliases_.find(name);
|
||||||
|
if (alias_it != aliases_.end()) {
|
||||||
|
canonical_name = alias_it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = metadata_.find(canonical_name);
|
||||||
|
return (it != metadata_.end()) ? &it->second : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> CommandRegistry::GetCommandsInCategory(
|
||||||
|
const std::string& category) const {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (const auto& [name, metadata] : metadata_) {
|
||||||
|
if (metadata.category == category) {
|
||||||
|
result.push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> CommandRegistry::GetCategories() const {
|
||||||
|
std::vector<std::string> categories;
|
||||||
|
for (const auto& [_, metadata] : metadata_) {
|
||||||
|
if (std::find(categories.begin(), categories.end(), metadata.category) == categories.end()) {
|
||||||
|
categories.push_back(metadata.category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> CommandRegistry::GetAgentCommands() const {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (const auto& [name, metadata] : metadata_) {
|
||||||
|
if (metadata.available_to_agent) {
|
||||||
|
result.push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CommandRegistry::ExportFunctionSchemas() const {
|
||||||
|
// TODO: Generate JSON function schemas from metadata
|
||||||
|
// This would replace manual function_schemas.json maintenance
|
||||||
|
return "{}"; // Placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CommandRegistry::GenerateHelp(const std::string& name) const {
|
||||||
|
auto* metadata = GetMetadata(name);
|
||||||
|
if (!metadata) {
|
||||||
|
return absl::StrFormat("Command '%s' not found", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream help;
|
||||||
|
help << "\n\033[1;36m" << metadata->name << "\033[0m - " << metadata->description << "\n\n";
|
||||||
|
help << "\033[1;33mUsage:\033[0m\n";
|
||||||
|
help << " " << metadata->usage << "\n\n";
|
||||||
|
|
||||||
|
if (!metadata->examples.empty()) {
|
||||||
|
help << "\033[1;33mExamples:\033[0m\n";
|
||||||
|
for (const auto& example : metadata->examples) {
|
||||||
|
help << " " << example << "\n";
|
||||||
|
}
|
||||||
|
help << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata->requires_rom) {
|
||||||
|
help << "\033[1;33mRequires:\033[0m ROM file (--rom=<path>)\n";
|
||||||
|
}
|
||||||
|
if (metadata->requires_grpc) {
|
||||||
|
help << "\033[1;33mRequires:\033[0m YAZE running with gRPC enabled\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!metadata->aliases.empty()) {
|
||||||
|
help << "\n\033[1;33mAliases:\033[0m " << absl::StrJoin(metadata->aliases, ", ") << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return help.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CommandRegistry::GenerateCategoryHelp(const std::string& category) const {
|
||||||
|
auto commands = GetCommandsInCategory(category);
|
||||||
|
if (commands.empty()) {
|
||||||
|
return absl::StrFormat("No commands in category '%s'", category);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream help;
|
||||||
|
help << "\n\033[1;36m" << category << " commands:\033[0m\n\n";
|
||||||
|
|
||||||
|
for (const auto& cmd : commands) {
|
||||||
|
auto* metadata = GetMetadata(cmd);
|
||||||
|
if (metadata) {
|
||||||
|
help << " \033[1;33m" << cmd << "\033[0m\n";
|
||||||
|
help << " " << metadata->description << "\n";
|
||||||
|
if (!metadata->usage.empty()) {
|
||||||
|
help << " Usage: " << metadata->usage << "\n";
|
||||||
|
}
|
||||||
|
help << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return help.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CommandRegistry::GenerateCompleteHelp() const {
|
||||||
|
std::ostringstream help;
|
||||||
|
help << "\n\033[1;36mAll z3ed Commands:\033[0m\n\n";
|
||||||
|
|
||||||
|
auto categories = GetCategories();
|
||||||
|
for (const auto& category : categories) {
|
||||||
|
help << GenerateCategoryHelp(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
return help.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status CommandRegistry::Execute(const std::string& name,
|
||||||
|
const std::vector<std::string>& args,
|
||||||
|
Rom* rom_context) {
|
||||||
|
auto* handler = Get(name);
|
||||||
|
if (!handler) {
|
||||||
|
return absl::NotFoundError(absl::StrFormat("Command '%s' not found", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler->Run(args, rom_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandRegistry::HasCommand(const std::string& name) const {
|
||||||
|
return Get(name) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandRegistry::RegisterAllCommands() {
|
||||||
|
// Get all command handlers from factory
|
||||||
|
auto all_handlers = handlers::CreateAllCommandHandlers();
|
||||||
|
|
||||||
|
for (auto& handler : all_handlers) {
|
||||||
|
std::string name = handler->GetName();
|
||||||
|
|
||||||
|
// Build metadata from handler
|
||||||
|
CommandMetadata metadata;
|
||||||
|
metadata.name = name;
|
||||||
|
metadata.usage = handler->GetUsage();
|
||||||
|
metadata.available_to_agent = true; // Most commands available to agent
|
||||||
|
metadata.requires_rom = true; // Most commands need ROM
|
||||||
|
metadata.requires_grpc = false;
|
||||||
|
|
||||||
|
// Categorize and enhance metadata based on command type
|
||||||
|
if (name.find("resource-") == 0) {
|
||||||
|
metadata.category = "resource";
|
||||||
|
metadata.description = "Resource inspection and search";
|
||||||
|
if (name == "resource-list") {
|
||||||
|
metadata.examples = {
|
||||||
|
"z3ed resource-list --type=dungeon --format=json",
|
||||||
|
"z3ed resource-list --type=sprite --format=table"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (name.find("dungeon-") == 0) {
|
||||||
|
metadata.category = "dungeon";
|
||||||
|
metadata.description = "Dungeon inspection and editing";
|
||||||
|
if (name == "dungeon-describe-room") {
|
||||||
|
metadata.examples = {
|
||||||
|
"z3ed dungeon-describe-room --room=5 --format=json"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (name.find("overworld-") == 0) {
|
||||||
|
metadata.category = "overworld";
|
||||||
|
metadata.description = "Overworld inspection and editing";
|
||||||
|
if (name == "overworld-find-tile") {
|
||||||
|
metadata.examples = {
|
||||||
|
"z3ed overworld-find-tile --tile=0x42 --format=json"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (name.find("emulator-") == 0) {
|
||||||
|
metadata.category = "emulator";
|
||||||
|
metadata.description = "Emulator control and debugging";
|
||||||
|
metadata.requires_grpc = true;
|
||||||
|
if (name == "emulator-set-breakpoint") {
|
||||||
|
metadata.examples = {
|
||||||
|
"z3ed emulator-set-breakpoint --address=0x83D7 --description='NMI handler'"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (name.find("gui-") == 0) {
|
||||||
|
metadata.category = "gui";
|
||||||
|
metadata.description = "GUI automation";
|
||||||
|
metadata.requires_grpc = true;
|
||||||
|
} else if (name.find("hex-") == 0) {
|
||||||
|
metadata.category = "graphics";
|
||||||
|
metadata.description = "Hex data manipulation";
|
||||||
|
} else if (name.find("palette-") == 0) {
|
||||||
|
metadata.category = "graphics";
|
||||||
|
metadata.description = "Palette operations";
|
||||||
|
} else if (name.find("sprite-") == 0) {
|
||||||
|
metadata.category = "graphics";
|
||||||
|
metadata.description = "Sprite operations";
|
||||||
|
} else if (name.find("message-") == 0 || name.find("dialogue-") == 0) {
|
||||||
|
metadata.category = "game";
|
||||||
|
metadata.description = name.find("message-") == 0 ? "Message inspection" : "Dialogue inspection";
|
||||||
|
} else if (name.find("music-") == 0) {
|
||||||
|
metadata.category = "game";
|
||||||
|
metadata.description = "Music/audio inspection";
|
||||||
|
} else if (name == "simple-chat" || name == "chat") {
|
||||||
|
metadata.category = "agent";
|
||||||
|
metadata.description = "AI conversational agent";
|
||||||
|
metadata.available_to_agent = false; // Meta-command
|
||||||
|
metadata.requires_rom = false;
|
||||||
|
metadata.examples = {
|
||||||
|
"z3ed simple-chat --rom=zelda3.sfc",
|
||||||
|
"z3ed simple-chat \"What dungeons exist?\" --rom=zelda3.sfc"
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
metadata.category = "misc";
|
||||||
|
metadata.description = "Miscellaneous command";
|
||||||
|
}
|
||||||
|
|
||||||
|
Register(std::move(handler), metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cli
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
130
src/cli/service/command_registry.h
Normal file
130
src/cli/service/command_registry.h
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#ifndef YAZE_CLI_SERVICE_COMMAND_REGISTRY_H_
|
||||||
|
#define YAZE_CLI_SERVICE_COMMAND_REGISTRY_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "cli/service/resources/command_handler.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace cli {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CommandRegistry
|
||||||
|
* @brief Single source of truth for all z3ed commands
|
||||||
|
*
|
||||||
|
* Serves as the central registry for:
|
||||||
|
* - CLI command routing
|
||||||
|
* - Agent tool calling
|
||||||
|
* - Help text generation
|
||||||
|
* - TUI menu generation
|
||||||
|
* - Function schema export (for AI)
|
||||||
|
*
|
||||||
|
* Ensures consistency: if a command exists, it's available to both
|
||||||
|
* human users (CLI) and AI agents (tool calling).
|
||||||
|
*/
|
||||||
|
class CommandRegistry {
|
||||||
|
public:
|
||||||
|
struct CommandMetadata {
|
||||||
|
std::string name; // e.g., "resource-list"
|
||||||
|
std::string category; // e.g., "resource", "emulator", "dungeon"
|
||||||
|
std::string description; // Short description
|
||||||
|
std::string usage; // Full usage string
|
||||||
|
bool available_to_agent; // Can AI call this via tool dispatch?
|
||||||
|
bool requires_rom; // Requires ROM context?
|
||||||
|
bool requires_grpc; // Requires gRPC/emulator running?
|
||||||
|
std::vector<std::string> aliases; // Alternative names
|
||||||
|
std::vector<std::string> examples; // Usage examples
|
||||||
|
std::string todo_reference; // TODO tracker reference (if incomplete)
|
||||||
|
};
|
||||||
|
|
||||||
|
static CommandRegistry& Instance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a command handler
|
||||||
|
*/
|
||||||
|
void Register(std::unique_ptr<resources::CommandHandler> handler,
|
||||||
|
const CommandMetadata& metadata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a command handler by name or alias
|
||||||
|
*/
|
||||||
|
resources::CommandHandler* Get(const std::string& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get command metadata
|
||||||
|
*/
|
||||||
|
const CommandMetadata* GetMetadata(const std::string& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get all commands in a category
|
||||||
|
*/
|
||||||
|
std::vector<std::string> GetCommandsInCategory(const std::string& category) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get all categories
|
||||||
|
*/
|
||||||
|
std::vector<std::string> GetCategories() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get all commands available to AI agents
|
||||||
|
*/
|
||||||
|
std::vector<std::string> GetAgentCommands() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Export function schemas for AI tool calling (JSON)
|
||||||
|
*/
|
||||||
|
std::string ExportFunctionSchemas() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate help text for a command
|
||||||
|
*/
|
||||||
|
std::string GenerateHelp(const std::string& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate category help text
|
||||||
|
*/
|
||||||
|
std::string GenerateCategoryHelp(const std::string& category) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate complete help text (all commands)
|
||||||
|
*/
|
||||||
|
std::string GenerateCompleteHelp() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Execute a command by name
|
||||||
|
*/
|
||||||
|
absl::Status Execute(const std::string& name,
|
||||||
|
const std::vector<std::string>& args,
|
||||||
|
Rom* rom_context = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if command exists
|
||||||
|
*/
|
||||||
|
bool HasCommand(const std::string& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get total command count
|
||||||
|
*/
|
||||||
|
size_t Count() const { return handlers_.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
CommandRegistry() = default;
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
std::map<std::string, std::unique_ptr<resources::CommandHandler>> handlers_;
|
||||||
|
std::map<std::string, CommandMetadata> metadata_;
|
||||||
|
std::map<std::string, std::string> aliases_; // alias → canonical name
|
||||||
|
|
||||||
|
// Auto-register all commands
|
||||||
|
void RegisterAllCommands();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cli
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_CLI_SERVICE_COMMAND_REGISTRY_H_
|
||||||
|
|
||||||
@@ -81,6 +81,11 @@ class CommandHandler {
|
|||||||
* @brief Provide metadata for TUI/help summaries.
|
* @brief Provide metadata for TUI/help summaries.
|
||||||
*/
|
*/
|
||||||
virtual Descriptor Describe() const;
|
virtual Descriptor Describe() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the command usage string
|
||||||
|
*/
|
||||||
|
virtual std::string GetUsage() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
@@ -100,10 +105,6 @@ class CommandHandler {
|
|||||||
virtual absl::Status Execute(Rom* rom, const ArgumentParser& parser,
|
virtual absl::Status Execute(Rom* rom, const ArgumentParser& parser,
|
||||||
OutputFormatter& formatter) = 0;
|
OutputFormatter& formatter) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the command usage string
|
|
||||||
*/
|
|
||||||
virtual std::string GetUsage() const = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if the command requires ROM labels
|
* @brief Check if the command requires ROM labels
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "hyrule_magic.h"
|
#include "util/hyrule_magic.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace zelda3 {
|
namespace zelda3 {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef YAZE_APP_ZELDA3_HYRULE_MAGIC_H
|
#ifndef YAZE_UTIL_HYRULE_MAGIC_H
|
||||||
#define YAZE_APP_ZELDA3_HYRULE_MAGIC_H
|
#define YAZE_UTIL_HYRULE_MAGIC_H
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -28,4 +28,4 @@ uint16_t ldle16b(uint8_t const *const p_arr);
|
|||||||
} // namespace zelda3
|
} // namespace zelda3
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_APP_ZELDA3_HYRULE_MAGIC_H
|
#endif // YAZE_UTIL_HYRULE_MAGIC_H
|
||||||
@@ -17,7 +17,7 @@ set(YAZE_UTIL_SRC
|
|||||||
util/log.cc
|
util/log.cc
|
||||||
util/platform_paths.cc
|
util/platform_paths.cc
|
||||||
util/file_util.cc
|
util/file_util.cc
|
||||||
util/platform_paths.cc
|
util/hyrule_magic.cc # Byte order utilities (moved from zelda3)
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(yaze_util STATIC ${YAZE_UTIL_SRC})
|
add_library(yaze_util STATIC ${YAZE_UTIL_SRC})
|
||||||
|
|||||||
Reference in New Issue
Block a user