yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
command_palette.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <ftxui/component/component.hpp>
5#include <ftxui/component/screen_interactive.hpp>
6#include <ftxui/dom/elements.hpp>
7
8#include "cli/tui/tui.h"
9// #include "cli/handlers/graphics/hex_commands.h"
10// #include "cli/handlers/graphics/palette_commands.h"
11
12namespace yaze {
13namespace cli {
14
15using namespace ftxui;
16
17namespace {
18// A simple fuzzy search implementation
19int fuzzy_match(const std::string& query, const std::string& target) {
20 if (query.empty()) return 1;
21 if (target.empty()) return 0;
22
23 int score = 0;
24 int query_idx = 0;
25 int target_idx = 0;
26 int consecutive_matches = 0;
27
28 while (query_idx < query.length() && target_idx < target.length()) {
29 if (std::tolower(query[query_idx]) == std::tolower(target[target_idx])) {
30 score += 1 + consecutive_matches;
31 consecutive_matches++;
32 query_idx++;
33 } else {
34 consecutive_matches = 0;
35 }
36 target_idx++;
37 }
38
39 return (query_idx == query.length()) ? score : 0;
40}
41}
42
44 struct PaletteState {
45 std::string query;
46 int selected = 0;
47 std::string status_msg;
48 };
49
50 struct Cmd {
51 std::string name;
52 std::string cat;
53 std::string desc;
54 std::string usage;
55 std::function<absl::Status()> exec;
56 int score = 0;
57 };
58
59 auto state = std::make_shared<PaletteState>();
60
61 static std::vector<Cmd> cmds = {
62 {"hex-read", "🔢 Hex", "Read ROM bytes",
63 "--address=0x1C800 --length=16 --format=both",
64 []() { return absl::OkStatus(); }},
65
66 {"hex-write", "🔢 Hex", "Write ROM bytes",
67 "--address=0x1C800 --data=\"FF 00\"",
68 []() { return absl::OkStatus(); }},
69
70 {"hex-search", "🔢 Hex", "Search byte pattern",
71 "--pattern=\"FF 00 ?? 12\"",
72 []() { return absl::OkStatus(); }},
73
74 {"palette-get", "🎨 Palette", "Get palette colors",
75 "--group=0 --palette=0 --format=hex",
76 []() { return absl::OkStatus(); }},
77
78 {"palette-set", "🎨 Palette", "Set palette color",
79 "--group=0 --palette=0 --index=5 --color=FF0000",
80 []() { return absl::OkStatus(); }},
81
82 {"palette-analyze", "🎨 Palette", "Analyze palette",
83 "--type=palette --id=0/0",
84 []() { return absl::OkStatus(); }},
85 };
86
87 auto search_input = Input(&state->query, "Search commands...");
88
89 auto menu = Renderer([state] {
90 std::vector<int> filtered_idx;
91 if (state->query.empty()) {
92 for (size_t i = 0; i < cmds.size(); ++i) {
93 filtered_idx.push_back(i);
94 }
95 } else {
96 for (size_t i = 0; i < cmds.size(); ++i) {
97 cmds[i].score = fuzzy_match(state->query, cmds[i].name);
98 if (cmds[i].score > 0) {
99 filtered_idx.push_back(i);
100 }
101 }
102 std::sort(filtered_idx.begin(), filtered_idx.end(), [](int a, int b) {
103 return cmds[a].score > cmds[b].score;
104 });
105 }
106 Elements items;
107 for (size_t i = 0; i < filtered_idx.size(); ++i) {
108 int idx = filtered_idx[i];
109 auto item = hbox({
110 text(cmds[idx].cat) | color(Color::GrayLight),
111 text(" "),
112 text(cmds[idx].name) | bold,
113 });
114 if (static_cast<int>(i) == state->selected) {
115 item = item | inverted | focus;
116 }
117 items.push_back(item);
118 }
119 return vbox(items) | vscroll_indicator | frame;
120 });
121
122 auto execute_command = [state] {
123 std::vector<int> filtered_idx;
124 if (state->query.empty()) {
125 for (size_t i = 0; i < cmds.size(); ++i) {
126 filtered_idx.push_back(i);
127 }
128 } else {
129 for (size_t i = 0; i < cmds.size(); ++i) {
130 cmds[i].score = fuzzy_match(state->query, cmds[i].name);
131 if (cmds[i].score > 0) {
132 filtered_idx.push_back(i);
133 }
134 }
135 std::sort(filtered_idx.begin(), filtered_idx.end(), [](int a, int b) {
136 return cmds[a].score > cmds[b].score;
137 });
138 }
139
140 if (state->selected < static_cast<int>(filtered_idx.size())) {
141 int cmd_idx = filtered_idx[state->selected];
142 auto status = cmds[cmd_idx].exec();
143 state->status_msg = status.ok() ? "✓ Success: Command executed." : "✗ Error: " + std::string(status.message());
144 }
145 };
146
147 auto back_btn = Button("Back", [] {
148 app_context.current_layout = LayoutID::kMainMenu;
149 ScreenInteractive::Active()->ExitLoopClosure()();
150 });
151
152 auto container = Container::Vertical({search_input, menu, back_btn});
153
154 return Renderer(container, [container, search_input, menu, back_btn, state] {
155 std::vector<int> filtered_idx;
156 if (state->query.empty()) {
157 for (size_t i = 0; i < cmds.size(); ++i) {
158 filtered_idx.push_back(i);
159 }
160 } else {
161 for (size_t i = 0; i < cmds.size(); ++i) {
162 cmds[i].score = fuzzy_match(state->query, cmds[i].name);
163 if (cmds[i].score > 0) {
164 filtered_idx.push_back(i);
165 }
166 }
167 std::sort(filtered_idx.begin(), filtered_idx.end(), [](int a, int b) {
168 return cmds[a].score > cmds[b].score;
169 });
170 }
171
172 Element details = text("Select a command to see details.") | dim;
173 if (state->selected < static_cast<int>(filtered_idx.size())) {
174 int idx = filtered_idx[state->selected];
175 details = vbox({
176 text(cmds[idx].desc) | bold,
177 separator(),
178 text("Usage: " + cmds[idx].name + " " + cmds[idx].usage) | color(Color::Cyan),
179 });
180 }
181
182 return vbox({
183 text("⚡ Command Palette") | bold | center | color(Color::Cyan),
184 text(app_context.rom.is_loaded() ? "ROM: " + app_context.rom.title() : "No ROM loaded") | center | dim,
185 separator(),
186 hbox({text("🔍 "), search_input->Render() | flex}),
187 separator(),
188 hbox({
189 menu->Render() | flex,
190 separator(),
191 details | flex,
192 }),
193 separator(),
194 hbox({ back_btn->Render() }) | center,
195 separator(),
196 text(state->status_msg) | center | (state->status_msg.find("✓") != 0 ? color(Color::Green) : color(Color::Red)),
197 text("↑↓: Navigate | Enter: Execute | Esc: Back") | center | dim,
198 }) | border | flex;
199 }) | CatchEvent([state, execute_command](const Event& e) {
200 if (e == Event::Return) {
201 execute_command();
202 return true;
203 }
204 if (e == Event::ArrowUp) {
205 if (state->selected > 0) state->selected--;
206 return true;
207 }
208 if (e == Event::ArrowDown) {
209 // Calculate filtered_idx size
210 std::vector<int> filtered_idx;
211 if (state->query.empty()) {
212 for (size_t i = 0; i < cmds.size(); ++i) {
213 filtered_idx.push_back(i);
214 }
215 } else {
216 for (size_t i = 0; i < cmds.size(); ++i) {
217 cmds[i].score = fuzzy_match(state->query, cmds[i].name);
218 if (cmds[i].score > 0) {
219 filtered_idx.push_back(i);
220 }
221 }
222 }
223 if (state->selected < static_cast<int>(filtered_idx.size()) - 1) state->selected++;
224 return true;
225 }
226 return false;
227 });
228}
229
230} // namespace cli
231} // namespace yaze
ftxui::Component Render() override
Definition cli.h:17
int fuzzy_match(const std::string &query, const std::string &target)
Main namespace for the application.
Definition controller.cc:20