feat(build-system): enhance CMake configuration and introduce new utility files

- Refactored CMakeLists.txt to streamline project configuration and improve readability.
- Introduced new utility functions in `utils.cmake` for setting compiler flags and managing dependencies.
- Added `dependencies.cmake` to centralize third-party dependency management, enhancing modularity.
- Updated CI workflows to include new build options and improved logging for better feedback during configuration.
- Implemented precompiled headers in various libraries to speed up compilation times.

Benefits:
- Improved maintainability and clarity of the build system.
- Enhanced build performance through precompiled headers.
- Streamlined dependency management for easier integration of third-party libraries.
This commit is contained in:
scawful
2025-10-11 02:44:17 -04:00
parent 31d0337b11
commit f54949bdd8
56 changed files with 2673 additions and 4872 deletions

View File

@@ -1,9 +1,13 @@
#include "cli/tui/chat_tui.h"
#include <algorithm>
#include <cmath>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/time/time.h"
#include "ftxui/component/component.hpp"
#include "ftxui/component/component_base.hpp"
#include "ftxui/component/event.hpp"
@@ -12,6 +16,7 @@
#include "ftxui/dom/table.hpp"
#include "app/rom.h"
#include "cli/tui/autocomplete_ui.h"
#include "cli/tui/tui.h"
namespace yaze {
namespace cli {
@@ -19,6 +24,44 @@ namespace tui {
using namespace ftxui;
namespace {
const std::vector<std::string> kSpinnerFrames = {
"", "", "", "", "", "", "", "", "", ""};
Element RenderPanelCard(const std::string& title, const std::vector<Element>& body,
Color border_color, bool highlight = false) {
auto panel = window(
text(title) | bold,
vbox(body));
if (highlight) {
panel = panel | color(border_color) | bgcolor(Color::GrayDark);
} else {
panel = panel | color(border_color);
}
return panel;
}
Element RenderLatencySparkline(const std::vector<double>& data) {
if (data.empty()) {
return text("No latency data yet") | dim;
}
Elements bars;
for (double d : data) {
bars.push_back(gauge(d) | flex);
}
return hbox(bars);
}
Element RenderMetricLabel(const std::string& icon, const std::string& label,
const std::string& value, Color color) {
return hbox({
text(icon) | ftxui::color(color),
text(" " + label + ": ") | bold,
text(value) | ftxui::color(color)
});
}
} // namespace
ChatTUI::ChatTUI(Rom* rom_context) : rom_context_(rom_context) {
if (rom_context_ != nullptr) {
agent_service_.SetRomContext(rom_context_);
@@ -26,7 +69,19 @@ ChatTUI::ChatTUI(Rom* rom_context) : rom_context_(rom_context) {
} else {
rom_header_ = "No ROM loaded.";
}
auto status = todo_manager_.Initialize();
todo_manager_ready_ = status.ok();
InitializeAutocomplete();
quick_actions_ = {
"List dungeon entrances", "Show sprite palette summary",
"Summarize overworld map", "Find unused rooms",
"Explain ROM header", "Search dialogue for 'Master Sword'",
"Suggest QA checklist", "Show TODO status",
};
}
ChatTUI::~ChatTUI() {
CleanupWorkers();
}
void ChatTUI::SetRomContext(Rom* rom_context) {
@@ -60,15 +115,30 @@ void ChatTUI::Run() {
// Create autocomplete input component
auto input_component = CreateAutocompleteInput(input_message.get(), &autocomplete_engine_);
auto todo_popup_toggle = [this] {
ToggleTodoPopup();
};
auto shortcut_palette_toggle = [this] {
ToggleShortcutPalette();
};
// Handle Enter key BEFORE adding to container
input_component = CatchEvent(input_component, [this, input_message](const Event& event) {
input_component = CatchEvent(input_component, [this, input_message, todo_popup_toggle, shortcut_palette_toggle](const Event& event) {
if (event == Event::Return) {
if (input_message->empty()) return true;
OnSubmit(*input_message);
input_message->clear();
return true;
}
if (event == Event::Special({20})) { // Ctrl+T
todo_popup_toggle();
return true;
}
if (event == Event::Special({11})) { // Ctrl+K
shortcut_palette_toggle();
return true;
}
return false;
});
@@ -78,6 +148,12 @@ void ChatTUI::Run() {
input_message->clear();
});
auto quick_pick_index = std::make_shared<int>(0);
auto quick_pick_menu = Menu(&quick_actions_, quick_pick_index.get());
todo_popup_component_ = CreateTodoPopup();
shortcut_palette_component_ = BuildShortcutPalette();
// Add both input and button to container
auto input_container = Container::Horizontal({
input_component,
@@ -86,7 +162,7 @@ void ChatTUI::Run() {
input_component->TakeFocus();
auto main_renderer = Renderer(input_container, [this, input_component, send_button] {
auto main_renderer = Renderer(input_container, [this, input_component, send_button, quick_pick_menu, quick_pick_index] {
const auto& history = agent_service_.GetHistory();
// Build history view from current history state
@@ -147,24 +223,78 @@ void ChatTUI::Run() {
history_view = vbox(history_elements) | vscroll_indicator | frame | flex;
}
Elements layout_elements = {
text(rom_header_) | bold | center,
separator(),
history_view,
separator(),
};
// Build info panel with responsive layout
auto metrics = CurrentMetrics();
Element header_line = hbox({
text(rom_header_) | bold,
filler(),
agent_busy_.load() ? text(kSpinnerFrames[spinner_index_.load() % kSpinnerFrames.size()]) | color(Color::Yellow)
: text("") | color(Color::GreenLight)
});
std::vector<Element> info_cards;
info_cards.push_back(RenderPanelCard(
"Session",
{
RenderMetricLabel("🕒", "Turns", absl::StrFormat("%d", metrics.turn_index), Color::Cyan),
RenderMetricLabel("🙋", "User", absl::StrFormat("%d", metrics.total_user_messages), Color::White),
RenderMetricLabel("🤖", "Agent", absl::StrFormat("%d", metrics.total_agent_messages), Color::GreenLight),
RenderMetricLabel("🔧", "Tools", absl::StrFormat("%d", metrics.total_tool_calls), Color::YellowLight),
}, Color::GrayLight));
info_cards.push_back(RenderPanelCard(
"Latency",
{
RenderMetricLabel("", "Last", absl::StrFormat("%.2fs", last_response_seconds_), Color::Yellow),
RenderMetricLabel("📈", "Average", absl::StrFormat("%.2fs", metrics.average_latency_seconds), Color::MagentaLight),
RenderLatencySparkline(latency_history_)
}, Color::Magenta, agent_busy_.load()));
info_cards.push_back(RenderPanelCard(
"Shortcuts",
{
text("⌨ Enter ↵ Send") | dim,
text("⌨ Shift+Enter ↩ Multiline") | dim,
text("⌨ /help, /rom_info, /status") | dim,
text("⌨ Ctrl+T TODO overlay · Ctrl+K shortcuts · f fullscreen") | dim,
}, Color::BlueLight));
Elements layout_elements;
layout_elements.push_back(header_line);
layout_elements.push_back(separatorLight());
layout_elements.push_back(
vbox({
hbox({
info_cards[0] | flex,
separator(),
info_cards[1] | flex,
separator(),
info_cards[2] | flex,
}) | flex,
separator(),
history_view |
bgcolor(Color::Black) |
flex
}) | flex);
// Add metrics bar
const auto metrics = agent_service_.GetMetrics();
layout_elements.push_back(separatorLight());
layout_elements.push_back(
hbox({
text(absl::StrFormat("Turns:%d", metrics.turn_index)),
text("Turns: ") | bold,
text(absl::StrFormat("%d", metrics.turn_index)),
separator(),
text(absl::StrFormat("User:%d", metrics.total_user_messages)),
text("User: ") | bold,
text(absl::StrFormat("%d", metrics.total_user_messages)),
separator(),
text(absl::StrFormat("Agent:%d", metrics.total_agent_messages)),
text("Agent: ") | bold,
text(absl::StrFormat("%d", metrics.total_agent_messages)),
separator(),
text(absl::StrFormat("Tools:%d", metrics.total_tool_calls)),
text("Tools: ") | bold,
text(absl::StrFormat("%d", metrics.total_tool_calls)),
filler(),
text("Last response " + absl::StrFormat("%.2fs", last_response_seconds_)) | color(Color::GrayLight)
}) | color(Color::GrayLight)
);
@@ -179,17 +309,38 @@ void ChatTUI::Run() {
// Add input area
layout_elements.push_back(separator());
layout_elements.push_back(
input_component->Render() | flex
vbox({
text("Quick Actions") | bold,
quick_pick_menu->Render() | frame | size(HEIGHT, EQUAL, 5) |
flex,
separatorLight(),
input_component->Render() | flex
})
);
layout_elements.push_back(
hbox({
text("Press Enter to send | ") | dim,
send_button->Render(),
text(" | /help for commands") | dim,
text(" | Tab quick actions · Ctrl+T TODO overlay · Ctrl+K shortcuts") | dim,
}) | center
);
return vbox(layout_elements) | border;
Element base = vbox(layout_elements) | borderRounded | bgcolor(Color::Black);
if ((todo_popup_visible_ && todo_popup_component_) ||
(shortcut_palette_visible_ && shortcut_palette_component_)) {
std::vector<Element> overlays;
overlays.push_back(base);
if (todo_popup_visible_ && todo_popup_component_) {
overlays.push_back(todo_popup_component_->Render());
}
if (shortcut_palette_visible_ && shortcut_palette_component_) {
overlays.push_back(shortcut_palette_component_->Render());
}
base = dbox(overlays);
}
return base;
});
screen_.Loop(main_renderer);
@@ -259,12 +410,181 @@ void ChatTUI::OnSubmit(const std::string& message) {
return;
}
auto response = agent_service_.SendMessage(message);
if (!response.ok()) {
last_error_ = response.status().message();
} else {
last_error_.reset();
LaunchAgentPrompt(message);
}
void ChatTUI::LaunchAgentPrompt(const std::string& prompt) {
if (prompt.empty()) {
return;
}
agent_busy_.store(true);
spinner_running_.store(true);
if (!spinner_thread_.joinable()) {
spinner_thread_ = std::thread([this] {
while (spinner_running_.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(90));
spinner_index_.fetch_add(1);
screen_.PostEvent(Event::Custom);
}
});
}
last_send_time_ = std::chrono::steady_clock::now();
auto future = std::async(std::launch::async, [this, prompt] {
auto response = agent_service_.SendMessage(prompt);
if (!response.ok()) {
last_error_ = response.status().message();
} else {
last_error_.reset();
}
auto end_time = std::chrono::steady_clock::now();
last_response_seconds_ = std::chrono::duration<double>(end_time - last_send_time_).count();
latency_history_.push_back(last_response_seconds_);
if (latency_history_.size() > 30) {
latency_history_.erase(latency_history_.begin());
}
agent_busy_.store(false);
StopSpinner();
screen_.PostEvent(Event::Custom);
});
{
std::lock_guard<std::mutex> lock(worker_mutex_);
worker_futures_.push_back(std::move(future));
}
}
void ChatTUI::CleanupWorkers() {
std::lock_guard<std::mutex> lock(worker_mutex_);
for (auto& future : worker_futures_) {
if (future.valid()) {
future.wait();
}
}
worker_futures_.clear();
StopSpinner();
}
agent::ChatMessage::SessionMetrics ChatTUI::CurrentMetrics() const {
return agent_service_.GetMetrics();
}
void ChatTUI::StopSpinner() {
spinner_running_.store(false);
if (spinner_thread_.joinable()) {
spinner_thread_.join();
}
}
void ChatTUI::ToggleTodoPopup() {
if (!todo_popup_component_) {
todo_popup_component_ = CreateTodoPopup();
}
todo_popup_visible_ = !todo_popup_visible_;
if (todo_popup_visible_ && todo_popup_component_) {
screen_.PostEvent(Event::Custom);
}
}
void ChatTUI::ToggleShortcutPalette() {
if (!shortcut_palette_component_) {
shortcut_palette_component_ = BuildShortcutPalette();
}
shortcut_palette_visible_ = !shortcut_palette_visible_;
if (shortcut_palette_visible_) {
screen_.PostEvent(Event::Custom);
}
}
ftxui::Component ChatTUI::CreateTodoPopup() {
auto refresh_button = Button("Refresh", [this] {
screen_.PostEvent(Event::Custom);
});
auto close_button = Button("Close", [this] {
todo_popup_visible_ = false;
screen_.PostEvent(Event::Custom);
});
auto renderer = Renderer([this, refresh_button, close_button] {
Elements rows;
if (!todo_manager_ready_) {
rows.push_back(text("TODO manager unavailable") | color(Color::Red) | center);
} else {
auto todos = todo_manager_.GetAllTodos();
if (todos.empty()) {
rows.push_back(text("No TODOs tracked") | dim | center);
} else {
for (const auto& item : todos) {
rows.push_back(hbox({
text(absl::StrFormat("[%s]", item.StatusToString())) | color(Color::Yellow),
text(" " + item.description) | flex,
text(item.category.empty() ? "" : absl::StrCat(" (", item.category, ")")) | dim
}));
}
}
}
return dbox({
window(text("📝 TODO Overlay") | bold,
vbox({
separatorLight(),
vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 70),
separatorLight(),
hbox({refresh_button->Render(), text(" "), close_button->Render()}) | center
}))
| size(WIDTH, LESS_THAN, 72) | size(HEIGHT, LESS_THAN, 18) | center
});
});
return renderer;
}
ftxui::Component ChatTUI::BuildShortcutPalette() {
std::vector<std::pair<std::string, std::string>> shortcuts = {
{"Ctrl+T", "Toggle TODO overlay"},
{"Ctrl+K", "Shortcut palette"},
{"Ctrl+L", "Clear chat history"},
{"Ctrl+Shift+S", "Save transcript"},
{"Ctrl+G", "Focus quick actions"},
{"Ctrl+P", "Command palette"},
{"Ctrl+F", "Fullscreen chat"},
{"Esc", "Back to unified layout"},
};
auto close_button = Button("Close", [this] {
shortcut_palette_visible_ = false;
screen_.PostEvent(Event::Custom);
});
auto renderer = Renderer([shortcuts, close_button] {
Elements rows;
for (const auto& [combo, desc] : shortcuts) {
rows.push_back(hbox({text(combo) | bold | color(Color::Cyan), text(" " + desc)}));
}
return dbox({
window(text("⌨ Shortcuts") | bold,
vbox({
separatorLight(),
vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 60),
separatorLight(),
close_button->Render() | center
}))
| size(WIDTH, LESS_THAN, 64) | size(HEIGHT, LESS_THAN, 16) | center
});
});
return renderer;
}
bool ChatTUI::IsPopupOpen() const {
return todo_popup_visible_ || shortcut_palette_visible_;
}
} // namespace tui

View File

@@ -1,11 +1,18 @@
#ifndef YAZE_SRC_CLI_TUI_CHAT_TUI_H_
#define YAZE_SRC_CLI_TUI_CHAT_TUI_H_
#include <atomic>
#include <chrono>
#include <future>
#include <mutex>
#include <optional>
#include <thread>
#include <vector>
#include "ftxui/component/component.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "cli/service/agent/conversational_agent_service.h"
#include "cli/service/agent/todo_manager.h"
#include "cli/util/autocomplete.h"
@@ -19,19 +26,54 @@ namespace tui {
class ChatTUI {
public:
explicit ChatTUI(Rom* rom_context = nullptr);
~ChatTUI();
void Run();
void SetRomContext(Rom* rom_context);
private:
void OnSubmit(const std::string& message);
void LaunchAgentPrompt(const std::string& prompt);
void CleanupWorkers();
void StopSpinner();
void InitializeAutocomplete();
agent::ChatMessage::SessionMetrics CurrentMetrics() const;
// Popup state
void ToggleTodoPopup();
ftxui::Component CreateTodoPopup();
ftxui::Component BuildShortcutPalette();
bool IsPopupOpen() const;
void ToggleShortcutPalette();
ftxui::ScreenInteractive screen_ = ftxui::ScreenInteractive::Fullscreen();
agent::ConversationalAgentService agent_service_;
agent::TodoManager todo_manager_;
Rom* rom_context_ = nullptr;
std::optional<std::string> last_error_;
AutocompleteEngine autocomplete_engine_;
std::string rom_header_;
std::atomic<bool> agent_busy_{false};
std::atomic<bool> spinner_running_{false};
std::atomic<int> spinner_index_{0};
std::vector<std::future<void>> worker_futures_;
mutable std::mutex worker_mutex_;
std::chrono::steady_clock::time_point last_send_time_{};
double last_response_seconds_ = 0.0;
std::vector<double> latency_history_;
std::vector<std::string> quick_actions_;
std::thread spinner_thread_;
// Popup state
bool todo_popup_visible_ = false;
ftxui::Component todo_popup_component_;
ftxui::Component shortcut_palette_component_;
bool shortcut_palette_visible_ = false;
bool todo_manager_ready_ = false;
};
} // namespace tui

View File

@@ -4,9 +4,12 @@
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/screen/terminal.hpp>
#include <numeric>
#include <utility>
#include "absl/strings/str_format.h"
#include "cli/service/agent/conversational_agent_service.h"
#include "cli/z3ed_ascii_logo.h"
namespace yaze {
namespace cli {
@@ -27,7 +30,45 @@ UnifiedLayout::UnifiedLayout(Rom* rom_context)
if (rom_context_) {
state_.current_rom_file = rom_context_->title();
}
state_.active_workflows = {
"ROM Audit",
"Dungeon QA",
"Palette Polish"
};
InitializeTheme();
status_provider_ = [this] {
auto rom_loaded = rom_context_ && rom_context_->is_loaded();
return vbox({
text(rom_loaded ? "✅ Ready" : "⚠ Awaiting ROM") |
color(rom_loaded ? Color::GreenLight : Color::YellowLight),
text(absl::StrFormat("Focus: %s",
state_.command_palette_hint.empty() ?
"Main Menu" : state_.command_palette_hint)) |
dim
});
};
command_summary_provider_ = [] {
return std::vector<std::string>{
"agent::chat — conversational ROM inspector",
"rom::info — metadata & validation",
"dungeon::list — dungeon manifest",
"gfx::export — sprite/palette dump",
"project::build — apply patches"
};
};
todo_provider_ = [] {
return std::vector<std::string>{
"[pending] Implement dungeon diff visualizer",
"[pending] Integrate Claude-style context panes",
"[todo] Hook TODO manager into project manifests"
};
};
// Create components
main_menu_panel_ = CreateMainMenuPanel();
chat_panel_ = CreateChatPanel();
@@ -94,10 +135,61 @@ void UnifiedLayout::ToggleStatus() {
screen_.PostEvent(Event::Custom); // Force screen refresh
}
void UnifiedLayout::ToggleTodoOverlay() {
todo_overlay_visible_ = !todo_overlay_visible_;
if (todo_overlay_visible_) {
if (!todo_overlay_component_ && todo_provider_) {
todo_overlay_component_ = Renderer([this] {
Elements rows;
if (todo_provider_) {
auto items = todo_provider_();
if (items.empty()) {
rows.push_back(text("No TODOs available") | dim | center);
} else {
for (const auto& line : items) {
rows.push_back(text(line));
}
}
}
return dbox({window(text("📝 TODO Overlay") | bold,
vbox({
separatorLight(),
vbox(rows) | frame | size(HEIGHT, LESS_THAN, 15) | size(WIDTH, LESS_THAN, 80),
separatorLight(),
text("Ctrl+T to close • Enter to jump via command palette") | dim | center
})) | center});
});
}
screen_.PostEvent(Event::Custom);
} else {
screen_.PostEvent(Event::Custom);
}
}
void UnifiedLayout::SetLayoutConfig(const LayoutConfig& config) {
config_ = config;
}
void UnifiedLayout::SetStatusProvider(std::function<Element()> provider) {
status_provider_ = std::move(provider);
}
void UnifiedLayout::SetCommandSummaryProvider(std::function<std::vector<std::string>()> provider) {
command_summary_provider_ = std::move(provider);
}
void UnifiedLayout::SetTodoProvider(std::function<std::vector<std::string>()> provider) {
todo_provider_ = std::move(provider);
}
void UnifiedLayout::InitializeTheme() {
auto terminal = Terminal::Size();
if (terminal.dimx < 120) {
config_.right_panel_width = 30;
config_.bottom_panel_height = 12;
}
}
Component UnifiedLayout::CreateMainMenuPanel() {
struct MenuState {
int selected = 0;
@@ -133,13 +225,18 @@ Component UnifiedLayout::CreateMainMenuPanel() {
auto menu = Menu(&state->items, &state->selected, option);
return Renderer(menu, [this, menu, state] {
auto banner = RenderAnimatedBanner();
return vbox({
text("🎮 Z3ED Main Menu") | bold | center,
banner,
separator(),
menu->Render(),
separator(),
RenderCommandHints(),
separator(),
RenderWorkflowLane(),
separator(),
text("↑/↓: Navigate | Enter: Select | q: Quit") | dim | center
});
}) | borderRounded | bgcolor(Color::Black);
});
}
@@ -147,26 +244,40 @@ Component UnifiedLayout::CreateChatPanel() {
// Use the full-featured ChatTUI if available
if (chat_tui_) {
return Renderer([this] {
std::vector<Element> cards;
cards.push_back(vbox({
text("🤖 Overview") | bold,
text("Claude-style assistant for ROM editing"),
text("Press 'f' for fullscreen chat") | dim
}) | borderRounded);
if (rom_context_) {
cards.push_back(vbox({
text("📦 ROM Context") | bold,
text(rom_context_->title()),
text(absl::StrFormat("Size: %d bytes", rom_context_->size())) | dim
}) | borderRounded | color(Color::GreenLight));
} else {
cards.push_back(vbox({
text("⚠ No ROM loaded") | color(Color::Yellow),
text("Use Load ROM from main menu") | dim
}) | borderRounded);
}
cards.push_back(vbox({
text("🛠 Integrations") | bold,
text("• TODO manager status") | dim,
text("• Command palette shortcuts") | dim,
text("• Tool dispatcher metrics") | dim
}) | borderRounded);
return vbox({
text("🤖 AI Chat Assistant") | bold | center | color(Color::Cyan),
RenderPanelHeader(PanelType::kChat),
separator(),
text("Press 'f' for full chat | 'c' to toggle") | center | dim,
RenderResponsiveGrid(cards),
separator(),
hbox({
text("Status: ") | bold,
text(rom_context_ ? "ROM Loaded ✓" : "No ROM") |
color(rom_context_ ? Color::Green : Color::Red)
}) | center,
separator(),
text("Features:") | bold | center,
text(" • Natural language ROM queries") | dim,
text(" • Dungeon & overworld inspection") | dim,
text(" • Sprite & palette analysis") | dim,
text(" • Message & dialogue search") | dim,
text(" • Emulator control (when running)") | dim,
separator(),
text("Type '/help' for commands") | center | dim
});
text("Shortcuts: f fullscreen | c toggle chat | /help commands") | dim | center
}) | borderRounded | bgcolor(Color::Black);
});
}
@@ -227,40 +338,32 @@ Component UnifiedLayout::CreateChatPanel() {
Component UnifiedLayout::CreateStatusPanel() {
return Renderer([this] {
std::string rom_info = rom_context_ ?
absl::StrFormat("ROM: %s | Size: %d bytes",
rom_context_->title(), rom_context_->size()) :
"No ROM loaded";
std::string panel_name;
switch (state_.active_main_panel) {
case PanelType::kMainMenu: panel_name = "Main Menu"; break;
case PanelType::kChat: panel_name = "Chat"; break;
case PanelType::kHexViewer: panel_name = "Hex Viewer"; break;
case PanelType::kPaletteEditor: panel_name = "Palette Editor"; break;
case PanelType::kTodoManager: panel_name = "TODO Manager"; break;
case PanelType::kRomTools: panel_name = "ROM Tools"; break;
case PanelType::kGraphicsTools: panel_name = "Graphics Tools"; break;
case PanelType::kSettings: panel_name = "Settings"; break;
case PanelType::kHelp: panel_name = "Help"; break;
default: panel_name = "Other"; break;
Element rom_info = rom_context_ ?
text(absl::StrFormat("ROM: %s", rom_context_->title())) | color(Color::GreenLight) :
text("ROM: none") | color(Color::RedLight);
Element provider_status = status_provider_ ? status_provider_() : text("Ready") | color(Color::GrayLight);
auto command_tiles = RenderCommandHints();
auto todo_tiles = RenderTodoStack();
std::vector<Element> sections = {
RenderAnimatedBanner(),
separatorLight(),
rom_info,
separatorLight(),
provider_status,
separatorLight(),
command_tiles,
separatorLight(),
todo_tiles
};
if (!state_.current_error.empty()) {
sections.push_back(separatorLight());
sections.push_back(text(state_.current_error) | color(Color::Red) | bold);
}
return vbox({
text("📊 Status") | bold | center,
separator(),
text(rom_info) | color(rom_context_ ? Color::Green : Color::Red),
separator(),
text("Panel: ") | bold,
text(panel_name),
separator(),
text("Layout: ") | bold,
text(absl::StrFormat("Chat: %s | Status: %s",
config_.show_chat ? "ON" : "OFF",
config_.show_status ? "ON" : "OFF")),
separator(),
text("Press 'h' for help, 'q' to quit") | dim | center
});
return vbox(sections) | borderRounded | bgcolor(Color::Black);
});
}
@@ -268,25 +371,35 @@ Component UnifiedLayout::CreateToolsPanel() {
struct ToolsState {
int selected = 0;
std::vector<std::string> items = {
"🔧 ROM Tools",
"🎨 Graphics Tools",
"📝 TODO Manager",
"🔧 ROM Tools (press t)",
"🎨 Graphics Tools (ref gfx::export)",
"📝 TODO Manager (ref todo::list)",
"⚙️ Settings",
"❓ Help"
};
};
auto state = std::make_shared<ToolsState>();
auto menu = Menu(&state->items, &state->selected);
MenuOption option;
option.on_change = [this, state] {
if (!state->items.empty()) {
state_.command_palette_hint = state->items[state->selected];
}
};
auto menu = Menu(&state->items, &state->selected, option);
return Renderer(menu, [this, menu, state] {
return vbox({
text("🛠️ Tools") | bold | center,
RenderPanelHeader(PanelType::kTools),
separator(),
menu->Render(),
separator(),
text("Select a tool category") | dim | center
}) | border;
text("Select a tool category") | dim | center,
separator(),
RenderCommandHints(),
separator(),
RenderWorkflowLane()
}) | borderRounded | bgcolor(Color::Black);
});
}
@@ -337,47 +450,77 @@ Component UnifiedLayout::CreateHexViewerPanel() {
rows.push_back(hbox(row));
}
auto workflow = RenderWorkflowLane();
return vbox({
text("🔍 Hex Viewer") | bold | center,
separator(),
workflow,
separator(),
vbox(rows) | frame | flex,
separator(),
text(absl::StrFormat("Offset: 0x%08X", *offset)) | color(Color::Cyan),
separator(),
text("↑/↓: Scroll | q: Back") | dim | center
}) | border;
}) | borderRounded | bgcolor(Color::Black);
});
}
Component UnifiedLayout::CreatePaletteEditorPanel() {
return Renderer([this] {
return vbox({
text("🎨 Palette Editor") | bold | center,
RenderPanelHeader(PanelType::kPaletteEditor),
separator(),
text("Palette editing functionality") | center,
text("coming soon...") | center | dim,
RenderResponsiveGrid({
vbox({
text("🌈 Overview") | bold,
text("Preview palette indices and colors"),
text("Highlight sprite-specific palettes") | dim
}) | borderRounded | bgcolor(Color::Black),
vbox({
text("🧪 Roadmap") | bold,
text("• Live recolor with undo stack"),
text("• Sprite preview viewport"),
text("• Export to .pal/.act")
}) | borderRounded | bgcolor(Color::Black),
vbox({
text("🗒 TODO") | bold,
text("Link to command palette"),
text("Use animation timeline"),
text("Add palette history panel") | dim
}) | borderRounded | bgcolor(Color::Black)
}),
separator(),
text("This panel will allow editing") | center,
text("color palettes from the ROM") | center,
RenderWorkflowLane(),
separator(),
text("Press 'q' to go back") | dim | center
}) | border;
}) | borderRounded;
});
}
Component UnifiedLayout::CreateTodoManagerPanel() {
return Renderer([this] {
std::vector<Element> todo_cards;
if (todo_provider_) {
for (const auto& item : todo_provider_()) {
todo_cards.push_back(text("" + item));
}
}
if (todo_cards.empty()) {
todo_cards.push_back(text("No TODOs yet") | dim);
}
return vbox({
text("📝 TODO Manager") | bold | center,
RenderPanelHeader(PanelType::kTodoManager),
separator(),
text("TODO management functionality") | center,
text("coming soon...") | center | dim,
vbox(todo_cards) | borderRounded | bgcolor(Color::Black),
separator(),
text("This panel will integrate with") | center,
text("the existing TODO manager") | center,
text("Press Ctrl+T anywhere to toggle the popup todo overlay.") | dim,
separator(),
RenderWorkflowLane(),
separator(),
text("Press 'q' to go back") | dim | center
}) | border;
}) | borderRounded;
});
}
@@ -385,11 +528,11 @@ Component UnifiedLayout::CreateRomToolsPanel() {
struct ToolsState {
int selected = 0;
std::vector<std::string> items = {
"Apply Asar Patch",
"Apply BPS Patch",
"Extract Symbols",
"Validate Assembly",
"Generate Save File",
"Apply Asar Patch — todo#123",
"Apply BPS Patch — todo#124",
"Extract Symbols — todo#098",
"Validate Assembly — todo#087",
"Generate Save File — todo#142",
"Back"
};
};
@@ -412,8 +555,8 @@ Component UnifiedLayout::CreateGraphicsToolsPanel() {
struct ToolsState {
int selected = 0;
std::vector<std::string> items = {
"Palette Editor",
"Hex Viewer",
"Palette Editor — ref gfx::export",
"Hex Viewer — ref rom::hex",
"Back"
};
};
@@ -478,7 +621,7 @@ Component UnifiedLayout::CreateSettingsPanel() {
return Renderer(controls, [this, controls, state, left_width_control, right_width_control,
bottom_height_control, apply_button, reset_button] {
return vbox({
text("⚙️ Layout Configuration") | bold | center | color(Color::Cyan),
RenderPanelHeader(PanelType::kSettings) | color(Color::Cyan),
separator(),
text("Customize the TUI layout") | center | dim,
separator(),
@@ -530,7 +673,7 @@ Component UnifiedLayout::CreateSettingsPanel() {
Component UnifiedLayout::CreateHelpPanel() {
return Renderer([this] {
return vbox({
text("❓ Z3ED Help") | bold | center | color(Color::Cyan),
RenderPanelHeader(PanelType::kHelp) | color(Color::Cyan),
separator(),
text("Unified TUI Layout - ROM Editor & AI Agent") | center | dim,
separator(),
@@ -620,25 +763,39 @@ Component UnifiedLayout::CreateUnifiedLayout() {
}
// Dynamically select right panel
Component right_panel = config_.show_status ? status_panel_ : tools_panel_;
Component right_panel;
if (config_.show_status) {
right_panel = status_panel_;
} else {
right_panel = tools_panel_;
}
// Create horizontal layout
auto top_section = hbox({
left_panel->Render() | flex,
separator(),
right_panel->Render() | size(WIDTH, EQUAL, config_.right_panel_width)
separatorLight(),
right_panel->Render() | size(WIDTH, LESS_THAN, config_.right_panel_width)
});
// Add chat panel if enabled
if (config_.show_chat) {
return vbox({
top_section | flex,
separator(),
chat_panel_->Render() | size(HEIGHT, EQUAL, config_.bottom_panel_height)
});
Element stacked = vbox({
top_section | flex,
separatorLight(),
chat_panel_->Render() | size(HEIGHT, EQUAL, config_.bottom_panel_height)
}) | bgcolor(Color::Black);
if (todo_overlay_visible_ && todo_overlay_component_) {
stacked = dbox({stacked, todo_overlay_component_->Render()});
}
return stacked;
}
return top_section;
Element content = top_section | bgcolor(Color::Black);
if (todo_overlay_visible_ && todo_overlay_component_) {
content = dbox({content, todo_overlay_component_->Render()});
}
return content;
});
}
@@ -653,6 +810,11 @@ bool UnifiedLayout::HandleGlobalEvents(const Event& event) {
}
// Global shortcuts
if (event == Event::Special({20})) { // Ctrl+T
ToggleTodoOverlay();
return true;
}
if (event == Event::Character('q') ||
(event == Event::Character('q') && state_.active_main_panel == PanelType::kMainMenu)) {
screen_.Exit();
@@ -751,9 +913,37 @@ Element UnifiedLayout::RenderStatusBar() {
text(absl::StrFormat("ROM: %s",
state_.current_rom_file.empty() ? "None" : state_.current_rom_file)) | color(Color::Green),
filler(),
text("q: Quit | h: Help | c: Chat | s: Status") | dim
text("Shortcuts: Ctrl+T TODO Overlay | f Full Chat | m Main Menu") | dim
});
}
Element UnifiedLayout::RenderAnimatedBanner() {
return text("🎮 Z3ED CLI") | bold | center;
}
Element UnifiedLayout::RenderWorkflowLane() {
return text("Workflow: Active") | color(Color::Green);
}
Element UnifiedLayout::RenderCommandHints() {
return vbox({
text("Command Hints:") | bold,
text(" Ctrl+T - Toggle TODO overlay"),
text(" f - Full chat mode"),
text(" m - Main menu")
});
}
Element UnifiedLayout::RenderTodoStack() {
return text("TODO Stack: Empty") | dim;
}
Element UnifiedLayout::RenderResponsiveGrid(const std::vector<Element>& tiles) {
if (tiles.empty()) {
return text("No items") | center | dim;
}
return vbox(tiles);
}
} // namespace cli
} // namespace yaze

View File

@@ -4,6 +4,7 @@
#include <ftxui/component/component.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/screen/screen.hpp>
#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -56,6 +57,10 @@ struct PanelState {
bool status_expanded = false;
std::string current_rom_file;
std::string current_error;
std::string command_palette_hint;
std::string todo_summary;
std::vector<std::string> active_workflows;
double last_tool_latency = 0.0;
};
class UnifiedLayout {
@@ -71,12 +76,17 @@ class UnifiedLayout {
void SwitchToolPanel(PanelType panel);
void ToggleChat();
void ToggleStatus();
void ToggleTodoOverlay();
// Configuration
void SetLayoutConfig(const LayoutConfig& config);
LayoutConfig GetLayoutConfig() const { return config_; }
void SetStatusProvider(std::function<ftxui::Element()> provider);
void SetCommandSummaryProvider(std::function<std::vector<std::string>()> provider);
void SetTodoProvider(std::function<std::vector<std::string>()> provider);
private:
void InitializeTheme();
// Component creation
ftxui::Component CreateMainMenuPanel();
ftxui::Component CreateChatPanel();
@@ -100,6 +110,11 @@ class UnifiedLayout {
// Rendering
ftxui::Element RenderPanelHeader(PanelType panel);
ftxui::Element RenderStatusBar();
ftxui::Element RenderAnimatedBanner();
ftxui::Element RenderWorkflowLane();
ftxui::Element RenderCommandHints();
ftxui::Element RenderTodoStack();
ftxui::Element RenderResponsiveGrid(const std::vector<ftxui::Element>& tiles);
// State
ftxui::ScreenInteractive screen_;
@@ -130,6 +145,14 @@ class UnifiedLayout {
// Event handlers
std::function<bool(const ftxui::Event&)> global_event_handler_;
std::function<bool(const ftxui::Event&)> panel_event_handler_;
// External providers
std::function<ftxui::Element()> status_provider_;
std::function<std::vector<std::string>()> command_summary_provider_;
std::function<std::vector<std::string>()> todo_provider_;
bool todo_overlay_visible_ = false;
ftxui::Component todo_overlay_component_;
};
} // namespace cli