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