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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user