refactor(editor): streamline command palette and project management UI

- Removed the save as menu and new project menu from EditorManager, delegating their functionality to PopupManager for better separation of concerns.
- Introduced a command palette in UICoordinator, enhancing user experience with fuzzy search capabilities for commands.
- Updated UI components to improve clarity and maintainability, ensuring a more efficient workflow for users.

Benefits:
- Enhances the organization of UI elements, leading to a more intuitive user experience.
- Improves code maintainability by consolidating UI management responsibilities within dedicated components.
This commit is contained in:
scawful
2025-10-15 13:13:21 -04:00
parent a7d07fca9e
commit a45e16e04a
5 changed files with 269 additions and 400 deletions

View File

@@ -950,7 +950,6 @@ void EditorManager::DrawContextSensitiveCardControl() {
*/
void EditorManager::DrawMenuBar() {
static bool show_display_settings = false;
static bool save_as_menu = false;
if (ImGui::BeginMenuBar()) {
// Delegate menu building to MenuOrchestrator
@@ -1034,173 +1033,6 @@ void EditorManager::DrawMenuBar() {
emulator_.Run(current_rom_);
}
// Enhanced Command Palette UI with Fuzzy Search (managed by UICoordinator)
// TODO: Move this to UI
if (ui_coordinator_ && ui_coordinator_->IsCommandPaletteVisible()) {
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
bool show_palette = true;
if (ImGui::Begin(absl::StrFormat("%s Command Palette", ICON_MD_SEARCH).c_str(),
&show_palette, ImGuiWindowFlags_NoCollapse)) {
// Search input with focus management
static char query[256] = {};
static int selected_idx = 0;
ImGui::SetNextItemWidth(-100);
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere();
selected_idx = 0;
}
bool input_changed = ImGui::InputTextWithHint(
"##cmd_query",
absl::StrFormat("%s Search commands (fuzzy matching enabled)...",
ICON_MD_SEARCH)
.c_str(),
query, IM_ARRAYSIZE(query));
ImGui::SameLine();
if (ImGui::Button(absl::StrFormat("%s Clear", ICON_MD_CLEAR).c_str())) {
query[0] = '\0';
input_changed = true;
selected_idx = 0;
}
ImGui::Separator();
// Fuzzy filter commands with scoring
std::vector<std::pair<int, std::pair<std::string, std::string>>>
scored_commands;
std::string query_lower = query;
std::transform(query_lower.begin(), query_lower.end(),
query_lower.begin(), ::tolower);
for (const auto& entry : shortcut_manager_.GetShortcuts()) {
const auto& name = entry.first;
const auto& shortcut = entry.second;
std::string name_lower = name;
std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(),
::tolower);
int score = 0;
if (query[0] == '\0') {
score = 1; // Show all when no query
} else if (name_lower.find(query_lower) == 0) {
score = 1000; // Starts with
} else if (name_lower.find(query_lower) != std::string::npos) {
score = 500; // Contains
} else {
// Fuzzy match - characters in order
size_t text_idx = 0, query_idx = 0;
while (text_idx < name_lower.length() &&
query_idx < query_lower.length()) {
if (name_lower[text_idx] == query_lower[query_idx]) {
score += 10;
query_idx++;
}
text_idx++;
}
if (query_idx != query_lower.length())
score = 0;
}
if (score > 0) {
std::string shortcut_text =
shortcut.keys.empty()
? ""
: absl::StrFormat("(%s)",
PrintShortcut(shortcut.keys).c_str());
scored_commands.push_back({score, {name, shortcut_text}});
}
}
std::sort(scored_commands.begin(), scored_commands.end(),
[](const auto& a, const auto& b) { return a.first > b.first; });
// Display results with categories
if (ImGui::BeginTabBar("CommandCategories")) {
if (ImGui::BeginTabItem(ICON_MD_LIST " All Commands")) {
if (ImGui::BeginTable("CommandPaletteTable", 3,
ImGuiTableFlags_ScrollY |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_SizingStretchProp,
ImVec2(0, -30))) {
ImGui::TableSetupColumn("Command",
ImGuiTableColumnFlags_WidthStretch, 0.5f);
ImGui::TableSetupColumn("Shortcut",
ImGuiTableColumnFlags_WidthStretch, 0.3f);
ImGui::TableSetupColumn("Score", ImGuiTableColumnFlags_WidthStretch,
0.2f);
ImGui::TableHeadersRow();
for (size_t i = 0; i < scored_commands.size(); ++i) {
const auto& [score, cmd_pair] = scored_commands[i];
const auto& [command_name, shortcut_text] = cmd_pair;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(static_cast<int>(i));
bool is_selected = (static_cast<int>(i) == selected_idx);
if (ImGui::Selectable(command_name.c_str(), is_selected,
ImGuiSelectableFlags_SpanAllColumns)) {
selected_idx = i;
const auto& shortcuts = shortcut_manager_.GetShortcuts();
auto it = shortcuts.find(command_name);
if (it != shortcuts.end() && it->second.callback) {
it->second.callback();
if (ui_coordinator_) {
ui_coordinator_->SetCommandPaletteVisible(false);
}
}
}
ImGui::PopID();
ImGui::TableNextColumn();
ImGui::TextDisabled("%s", shortcut_text.c_str());
ImGui::TableNextColumn();
if (score > 0)
ImGui::TextDisabled("%d", score);
}
ImGui::EndTable();
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(ICON_MD_HISTORY " Recent")) {
ImGui::Text("Recent commands coming soon...");
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(ICON_MD_STAR " Frequent")) {
ImGui::Text("Frequent commands coming soon...");
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
// Status bar with tips
ImGui::Separator();
ImGui::Text("%s %zu commands | Score: fuzzy match", ICON_MD_INFO,
scored_commands.size());
ImGui::SameLine();
ImGui::TextDisabled("| ↑↓=Navigate | Enter=Execute | Esc=Close");
}
ImGui::End();
// Update visibility state
if (!show_palette && ui_coordinator_) {
ui_coordinator_->SetCommandPaletteVisible(false);
}
}
// Enhanced Global Search UI (managed by UICoordinator)
// TODO: Move this to UI
if (ui_coordinator_ && ui_coordinator_->IsGlobalSearchVisible()) {
@@ -1418,143 +1250,6 @@ void EditorManager::DrawMenuBar() {
}
}
if (save_as_menu) {
ImGui::Begin("Save ROM As", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Text("%s Save ROM to new location", ICON_MD_SAVE_AS);
ImGui::Separator();
static std::string save_as_filename = "";
if (current_rom_ && save_as_filename.empty()) {
save_as_filename = current_rom_->title();
}
ImGui::InputText("Filename", &save_as_filename);
ImGui::Separator();
if (ImGui::Button(absl::StrFormat("%s Browse...", ICON_MD_FOLDER_OPEN).c_str(),
gui::kDefaultModalSize)) {
// Use save file dialog for ROM files
auto file_path =
util::FileDialogWrapper::ShowSaveFileDialog(save_as_filename, "sfc");
if (!file_path.empty()) {
save_as_filename = file_path;
}
}
ImGui::SameLine();
if (ImGui::Button(absl::StrFormat("%s Save", ICON_MD_SAVE).c_str(),
gui::kDefaultModalSize)) {
if (!save_as_filename.empty()) {
// Ensure proper file extension
std::string final_filename = save_as_filename;
if (final_filename.find(".sfc") == std::string::npos &&
final_filename.find(".smc") == std::string::npos) {
final_filename += ".sfc";
}
status_ = SaveRomAs(final_filename);
if (status_.ok()) {
save_as_menu = false;
toast_manager_.Show(
absl::StrFormat("ROM saved as: %s", final_filename),
editor::ToastType::kSuccess);
} else {
toast_manager_.Show(
absl::StrFormat("Failed to save ROM: %s", status_.message()),
editor::ToastType::kError);
}
}
}
ImGui::SameLine();
if (ImGui::Button(absl::StrFormat("%s Cancel", ICON_MD_CANCEL).c_str(),
gui::kDefaultModalSize)) {
save_as_menu = false;
}
ImGui::End();
}
if (new_project_menu) {
ImGui::Begin("New Project", &new_project_menu, ImGuiWindowFlags_AlwaysAutoResize);
static std::string save_as_filename = "";
ImGui::InputText("Project Name", &save_as_filename);
if (ImGui::Button(absl::StrFormat("%s Destination Folder", ICON_MD_FOLDER).c_str(),
gui::kDefaultModalSize)) {
current_project_.filepath = FileDialogWrapper::ShowOpenFolderDialog();
}
ImGui::SameLine();
ImGui::Text("%s", current_project_.filepath.c_str());
if (ImGui::Button(absl::StrFormat("%s ROM File", ICON_MD_VIDEOGAME_ASSET).c_str(),
gui::kDefaultModalSize)) {
current_project_.rom_filename = FileDialogWrapper::ShowOpenFileDialog();
}
ImGui::SameLine();
ImGui::Text("%s", current_project_.rom_filename.c_str());
if (ImGui::Button(absl::StrFormat("%s Labels File", ICON_MD_LABEL).c_str(),
gui::kDefaultModalSize)) {
current_project_.labels_filename =
FileDialogWrapper::ShowOpenFileDialog();
}
ImGui::SameLine();
ImGui::Text("%s", current_project_.labels_filename.c_str());
if (ImGui::Button(absl::StrFormat("%s Code Folder", ICON_MD_CODE).c_str(),
gui::kDefaultModalSize)) {
current_project_.code_folder = FileDialogWrapper::ShowOpenFolderDialog();
}
ImGui::SameLine();
ImGui::Text("%s", current_project_.code_folder.c_str());
ImGui::Separator();
if (ImGui::Button(absl::StrFormat("%s Choose Project File Location", ICON_MD_SAVE)
.c_str(),
gui::kDefaultModalSize)) {
auto project_file_path =
util::FileDialogWrapper::ShowSaveFileDialog(save_as_filename, "yaze");
if (!project_file_path.empty()) {
// Ensure .yaze extension
if (project_file_path.find(".yaze") == std::string::npos) {
project_file_path += ".yaze";
}
// Update project filepath to the chosen location
current_project_.filepath = project_file_path;
// Also set the project directory to the parent directory
size_t last_slash = project_file_path.find_last_of("/\\");
if (last_slash != std::string::npos) {
std::string project_dir = project_file_path.substr(0, last_slash);
ImGui::Text("Project will be saved to: %s", project_dir.c_str());
}
}
}
if (ImGui::Button(absl::StrFormat("%s Create Project", ICON_MD_ADD).c_str(),
gui::kDefaultModalSize)) {
if (!current_project_.filepath.empty()) {
new_project_menu = false;
status_ = current_project_.Create(save_as_filename,
current_project_.filepath);
if (status_.ok()) {
status_ = current_project_.Save();
}
} else {
toast_manager_.Show("Please choose a project file location first",
editor::ToastType::kWarning);
}
}
ImGui::SameLine();
if (ImGui::Button("Cancel", gui::kDefaultModalSize)) {
new_project_menu = false;
}
ImGui::End();
}
// Workspace preset dialogs
if (show_save_workspace_preset_) {
ImGui::Begin("Save Workspace Preset", &show_save_workspace_preset_,