aboutsummaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/AppConfig.hpp5
-rw-r--r--source/EditorCommandPalette.cpp826
-rw-r--r--source/EditorCommandPalette.hpp109
-rw-r--r--source/EditorCore.cpp2
-rw-r--r--source/EditorCore.hpp2
5 files changed, 349 insertions, 595 deletions
diff --git a/source/AppConfig.hpp b/source/AppConfig.hpp
index b6c1fab..d797708 100644
--- a/source/AppConfig.hpp
+++ b/source/AppConfig.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <imgui.h>
#include <filesystem>
#include <string>
@@ -12,6 +13,10 @@ inline float mainWidnowWidth;
inline float mainWindowHeight;
inline float mainWindowAspectRatio;
+// TODO add a bold font
+inline ImFont* fontRegular = nullptr;
+inline ImFont* fontBold = nullptr;
+
// Duplicate each as path and string so that on non-UTF-8 platforms (e.g. Windows) we can easily do string manipulation on the paths
// NOTE: even though non-const, these should not be modified outside of main()
inline std::filesystem::path dataDirPath;
diff --git a/source/EditorCommandPalette.cpp b/source/EditorCommandPalette.cpp
index 02ff65a..0e7b894 100644
--- a/source/EditorCommandPalette.cpp
+++ b/source/EditorCommandPalette.cpp
@@ -1,608 +1,281 @@
#include "EditorCommandPalette.hpp"
+#include "AppConfig.hpp"
+#include "EditorUtils.hpp"
#include "FuzzyMatch.hpp"
+#include "Utils.hpp"
+#include <GLFW/glfw3.h>
#include <imgui.h>
+#include <misc/cpp/imgui_stdlib.h>
#include <algorithm>
-#include <cstring>
#include <limits>
#include <utility>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
-namespace ImCmd {
-// =================================================================
-// Private forward decls
-// =================================================================
+using namespace std::literals;
-struct StackFrame;
-class ExecutionManager;
-
-struct SearchResult;
-class SearchManager;
-
-struct CommandOperationRegister;
-struct CommandOperationUnregister;
-struct CommandOperation;
-struct Context;
-
-struct ItemExtraData;
-struct Instance;
-
-// =================================================================
-// Private interface
-// =================================================================
-
-struct StackFrame {
- std::vector<std::string> Options;
- int SelectedOption = -1;
-};
-
-class ExecutionManager {
-private:
- Instance* m_Instance;
- Command* m_ExecutingCommand = nullptr;
- std::vector<StackFrame> m_CallStack;
-
-public:
- ExecutionManager(Instance& instance)
- : m_Instance{ &instance } {}
-
- int GetItemCount() const;
- const char* GetItem(int idx) const;
- void SelectItem(int idx);
-
- void PushOptions(std::vector<std::string> options);
-};
-
-struct SearchResult {
- int ItemIndex;
- int Score;
- int MatchCount;
- uint8_t Matches[32];
-};
-
-class SearchManager {
-private:
- Instance* m_Instance;
-
-public:
- std::vector<SearchResult> SearchResults;
- char SearchText[std::numeric_limits<uint8_t>::max() + 1];
-
-public:
- SearchManager(Instance& instance)
- : m_Instance{ &instance } {
- std::memset(SearchText, 0, sizeof(SearchText));
- }
-
- int GetItemCount() const;
- const char* GetItem(int idx) const;
-
- bool IsActive() const;
-
- void SetSearchText(const char* text);
- void ClearSearchText();
- void RefreshSearchResults();
-};
-
-struct CommandOperationRegister {
- Command Candidate;
-};
-
-struct CommandOperationUnregister {
- const char* Name;
-};
-
-struct CommandOperation {
- enum OpType {
- OpType_Register,
- OpType_Unregister,
- };
-
- OpType Type;
- int Index;
-};
-
-struct Context {
- ImGuiStorage Instances;
- Instance* CurrentCommandPalette = nullptr;
- std::vector<Command> Commands;
- std::vector<CommandOperationRegister> PendingRegisterOps;
- std::vector<CommandOperationUnregister> PendingUnregisterOps;
- std::vector<CommandOperation> PendingOps;
- ImFont* Fonts[ImCmdTextType_COUNT] = {};
- ImU32 FontColors[ImCmdTextType_COUNT] = {};
- int CommandStorageLocks = 0;
- bool HasFontColorOverride[ImCmdTextType_COUNT] = {};
- bool IsExecuting = false;
- bool IsTerminating = false;
-
- struct
- {
- bool ItemSelected = false;
- } LastCommandPaletteStatus;
-
- struct
- {
- const char* NewSearchText = nullptr;
- bool FocusSearchBox = false;
- } NextCommandPaletteActions;
-
- void RegisterCommand(Command command) {
- auto location = std::lower_bound(
- Commands.begin(),
- Commands.end(),
- command,
- [](const Command& a, const Command& b) -> bool {
- return strcmp(a.Name, b.Name) < 0;
- });
- Commands.insert(location, std::move(command));
- }
-
- bool UnregisterCommand(const char* name) {
- struct Comparator {
- bool operator()(const Command& command, const char* str) const {
- return strcmp(command.Name, str) < 0;
- }
-
- bool operator()(const char* str, const Command& command) const {
- return strcmp(str, command.Name) < 0;
- }
- };
-
- auto range = std::equal_range(Commands.begin(), Commands.end(), name, Comparator{});
- Commands.erase(range.first, range.second);
-
- return range.first != range.second;
- }
-
- bool CommitOps() {
- if (IsCommandStorageLocked()) {
- return false;
- }
-
- for (auto& operation : PendingOps) {
- switch (operation.Type) {
- case CommandOperation::OpType_Register: {
- auto& op = PendingRegisterOps[operation.Index];
- RegisterCommand(std::move(op.Candidate));
- } break;
-
- case CommandOperation::OpType_Unregister: {
- auto& op = PendingUnregisterOps[operation.Index];
- UnregisterCommand(op.Name);
- } break;
- }
- }
-
- bool had_action = !PendingOps.empty();
- PendingRegisterOps.clear();
- PendingUnregisterOps.clear();
- PendingOps.clear();
-
- return had_action;
- }
-
- bool IsCommandStorageLocked() const {
- return CommandStorageLocks > 0;
- }
-};
-
-struct ItemExtraData {
- bool Hovered = false;
- bool Held = false;
-};
-
-struct Instance {
- ExecutionManager Session;
- SearchManager Search;
- std::vector<ItemExtraData> ExtraData;
-
- int CurrentSelectedItem = 0;
-
- struct
- {
- bool RefreshSearch = false;
- bool ClearSearch = false;
- } PendingActions;
-
- Instance()
- : Session(*this)
- , Search(*this) {}
-};
-
-static Context gContext;
-
-// =================================================================
-// Private implementation
-// =================================================================
-
-int ExecutionManager::GetItemCount() const {
- if (m_ExecutingCommand) {
- return static_cast<int>(m_CallStack.back().Options.size());
- } else {
- return static_cast<int>(gContext.Commands.size());
- }
+bool EditorCommandExecuteContext::IsInitiated() const {
+ return mCommand != nullptr;
}
-const char* ExecutionManager::GetItem(int idx) const {
- if (m_ExecutingCommand) {
- return m_CallStack.back().Options[idx].c_str();
- } else {
- return gContext.Commands[idx].Name;
- }
+const EditorCommand* EditorCommandExecuteContext::GetCurrentCommand() const {
+ return mCommand;
}
-template <class... Ts>
-static void InvokeSafe(const std::function<void(Ts...)>& func, Ts... args) {
- if (func) {
- func(std::forward<Ts>(args)...);
+void EditorCommandExecuteContext::Initiate(const EditorCommand& command) {
+ if (mCommand == nullptr) {
+ mCommand = &command;
}
}
-void ExecutionManager::SelectItem(int idx) {
- auto cmd = m_ExecutingCommand;
- size_t initial_call_stack_height = m_CallStack.size();
- if (cmd == nullptr) {
- cmd = m_ExecutingCommand = &gContext.Commands[idx];
- ++gContext.CommandStorageLocks;
-
- gContext.IsExecuting = true;
- InvokeSafe(m_ExecutingCommand->InitialCallback); // Calls ::Prompt()
- gContext.IsExecuting = false;
- } else {
- m_CallStack.back().SelectedOption = idx;
-
- gContext.IsExecuting = true;
- InvokeSafe(cmd->SubsequentCallback, idx); // Calls ::Prompt()
- gContext.IsExecuting = false;
- }
-
- size_t final_call_stack_height = m_CallStack.size();
- if (initial_call_stack_height == final_call_stack_height) {
-
- gContext.IsTerminating = true;
- InvokeSafe(m_ExecutingCommand->TerminatingCallback); // Shouldn't call ::Prompt()
- gContext.IsTerminating = false;
-
- m_ExecutingCommand = nullptr;
- m_CallStack.clear();
- --gContext.CommandStorageLocks;
-
- // If the executed command involved subcommands...
- if (final_call_stack_height > 0) {
- m_Instance->PendingActions.ClearSearch = true;
- m_Instance->CurrentSelectedItem = 0;
- }
-
- gContext.LastCommandPaletteStatus.ItemSelected = true;
- } else {
- // Something new is prompted
- // It doesn't make sense for "current selected item" to persists through completely different set of options
- m_Instance->PendingActions.ClearSearch = true;
- m_Instance->CurrentSelectedItem = 0;
- }
+void EditorCommandExecuteContext::Prompt(std::vector<std::string> options) {
+ assert(mCommand != nullptr);
+ mCurrentOptions = std::move(options);
+ ++mDepth;
}
-void ExecutionManager::PushOptions(std::vector<std::string> options) {
- m_CallStack.push_back({});
- auto& frame = m_CallStack.back();
-
- frame.Options = std::move(options);
-
- m_Instance->PendingActions.ClearSearch = true;
+void EditorCommandExecuteContext::Finish() {
+ assert(mCommand != nullptr);
+ mCommand = nullptr;
+ mCurrentOptions.clear();
+ mDepth = 0;
}
-int SearchManager::GetItemCount() const {
- return static_cast<int>(SearchResults.size());
+int EditorCommandExecuteContext::GetExecutionDepth() const {
+ return mDepth;
}
-const char* SearchManager::GetItem(int idx) const {
- int actualIdx = SearchResults[idx].ItemIndex;
- return m_Instance->Session.GetItem(actualIdx);
-}
+struct EditorCommandPalette::SearchResult {
+ int itemIndex;
+ int score;
+ int matchCount;
+ uint8_t matches[32];
+};
-bool SearchManager::IsActive() const {
- return SearchText[0] != '\0';
-}
+struct EditorCommandPalette::Item {
+ bool hovered = false;
+ bool held = false;
+};
-void SearchManager::SetSearchText(const char* text) {
- // Note: must detect clang first because clang-cl.exe defines both _MSC_VER and __clang__, but only accepts #pragma clang
-#if defined(__GNUC__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(__clang__)
-# pragma clang diagnostic push
-# pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#elif defined(_MSC_VER)
-# pragma warning(push)
-# pragma warning(disable : 4996)
-#endif
- // Copy at most IM_ARRAYSIZE(SearchText) chars from `text` to `SearchText`
- std::strncpy(SearchText, text, IM_ARRAYSIZE(SearchText));
-#if defined(__GNUC__)
-# pragma GCC diagnostic pop
-#elif defined(__clang__)
-# pragma clang diagnostic pop
-#elif defined(_MSC_VER)
-# pragma warning(pop)
-#endif
- RefreshSearchResults();
-}
+EditorCommandPalette::EditorCommandPalette() = default;
+EditorCommandPalette::~EditorCommandPalette() = default;
-void SearchManager::ClearSearchText() {
- std::memset(SearchText, 0, IM_ARRAYSIZE(SearchText));
- SearchResults.clear();
+namespace P6503_UNITY_ID {
+std::string MakeCommandName(std::string_view category, std::string_view name) {
+ std::string result;
+ constexpr auto infix = ": "sv;
+ result.reserve(category.size() + infix.size() + name.size());
+ result.append(category);
+ result.append(infix);
+ result.append(name);
+ return result;
}
+} // namespace P6503_UNITY_ID
-void SearchManager::RefreshSearchResults() {
- m_Instance->CurrentSelectedItem = 0;
- SearchResults.clear();
-
- int item_count = m_Instance->Session.GetItemCount();
- for (int i = 0; i < item_count; ++i) {
- const char* text = m_Instance->Session.GetItem(i);
- SearchResult result;
- if (FuzzyMatch::Search(SearchText, text, result.Score, result.Matches, IM_ARRAYSIZE(result.Matches), result.MatchCount)) {
- result.ItemIndex = i;
- SearchResults.push_back(result);
- }
- }
+void EditorCommandPalette::AddCommand(std::string_view category, std::string_view name, EditorCommand command) {
+ command.name = P6503_UNITY_ID::MakeCommandName(category, name);
- std::sort(
- SearchResults.begin(),
- SearchResults.end(),
- [](const SearchResult& a, const SearchResult& b) -> bool {
- // We want the biggest element first
- return a.Score > b.Score;
+ auto location = std::lower_bound(
+ mCommands.begin(),
+ mCommands.end(),
+ command,
+ [](const EditorCommand& a, const EditorCommand& b) -> bool {
+ return a.name < b.name;
});
-}
-
-// =================================================================
-// API implementation
-// =================================================================
-
-void AddCommand(Command command) {
- if (gContext.IsCommandStorageLocked()) {
- gContext.PendingRegisterOps.push_back(CommandOperationRegister{ std::move(command) });
- CommandOperation op;
- op.Type = CommandOperation::OpType_Register;
- op.Index = static_cast<int>(gContext.PendingRegisterOps.size()) - 1;
- gContext.PendingOps.push_back(op);
- } else {
- gContext.RegisterCommand(std::move(command));
- }
+ auto iter = mCommands.insert(location, std::move(command));
- if (auto current = gContext.CurrentCommandPalette) {
- current->PendingActions.RefreshSearch = true;
- }
+ InvalidateSearchResults();
}
-void RemoveCommand(const char* name) {
- if (gContext.IsCommandStorageLocked()) {
- gContext.PendingUnregisterOps.push_back(CommandOperationUnregister{ name });
- CommandOperation op;
- op.Type = CommandOperation::OpType_Unregister;
- op.Index = static_cast<int>(gContext.PendingUnregisterOps.size()) - 1;
- gContext.PendingOps.push_back(op);
- } else {
- gContext.UnregisterCommand(name);
- }
-
- if (auto current = gContext.CurrentCommandPalette) {
- current->PendingActions.RefreshSearch = true;
- }
+void EditorCommandPalette::RemoveCommand(std::string_view category, std::string_view name) {
+ auto commandName = P6503_UNITY_ID::MakeCommandName(category, name);
+ RemoveCommand(commandName);
}
-void SetStyleFont(ImCmdTextType type, ImFont* font) {
- gContext.Fonts[type] = font;
-}
-
-void SetStyleColor(ImCmdTextType type, ImU32 color) {
- gContext.FontColors[type] = color;
- gContext.HasFontColorOverride[type] = true;
-}
+void EditorCommandPalette::RemoveCommand(const std::string& commandName) {
+ struct Comparator {
+ bool operator()(const EditorCommand& command, const std::string& str) const {
+ return command.name < str;
+ }
-void ClearStyleColor(ImCmdTextType type) {
- gContext.HasFontColorOverride[type] = false;
-}
+ bool operator()(const std::string& str, const EditorCommand& command) const {
+ return str < command.name;
+ }
+ };
-void SetNextCommandPaletteSearch(const char* text) {
- IM_ASSERT(text != nullptr);
- gContext.NextCommandPaletteActions.NewSearchText = text;
-}
+ auto range = std::equal_range(mCommands.begin(), mCommands.end(), commandName, Comparator{});
+ mCommands.erase(range.first, range.second);
-void SetNextCommandPaletteSearchBoxFocused() {
- gContext.NextCommandPaletteActions.FocusSearchBox = true;
+ InvalidateSearchResults();
}
-void ShowCommandPalette(const char* name) {
- auto& gi = *[&]() {
- auto id = ImHashStr(name);
- if (auto ptr = gContext.Instances.GetVoidPtr(id)) {
- return reinterpret_cast<Instance*>(ptr);
- } else {
- auto instance = new Instance();
- gContext.Instances.SetVoidPtr(id, instance);
- return instance;
- }
- }();
+void EditorCommandPalette::Show(bool* open) {
+ // Center window horizontally, align top vertically
+ ImGui::SetNextWindowPos(ImVec2(ImGui::GetMainViewport()->Size.x / 2, 0), ImGuiCond_Always, ImVec2(0.5f, 0.0f));
+ ImGui::SetNextWindowSizeRelScreen(0.3f, 0.0f);
+ ImGui::Begin("Command Palette", open, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar);
float width = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x;
- float search_result_window_height = 400.0f; // TODO config
-
- // BEGIN this command palette
- gContext.CurrentCommandPalette = &gi;
- ImGui::PushID(name);
- gContext.LastCommandPaletteStatus = {};
-
- // BEGIN processing PendingActions
- bool refresh_search = gi.PendingActions.RefreshSearch;
- refresh_search |= gContext.CommitOps();
-
- if (auto text = gContext.NextCommandPaletteActions.NewSearchText) {
- refresh_search = false;
- if (text[0] == '\0') {
- gi.Search.ClearSearchText();
- } else {
- gi.Search.SetSearchText(text);
+ if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows) || mShouldCloseNextFrame) {
+ // Close popup when user unfocused the command palette window (clicking elsewhere)
+ // or some action requested closing this window
+ mShouldCloseNextFrame = false;
+ if (open) {
+ *open = false;
}
- } else if (gi.PendingActions.ClearSearch) {
- refresh_search = false;
- gi.Search.ClearSearchText();
}
- if (refresh_search) {
- gi.Search.RefreshSearchResults();
- }
+ if (ImGui::IsWindowAppearing() || mFocusSearchBox) {
+ mFocusSearchBox = false;
- gi.PendingActions = {};
- // END procesisng PendingActions
-
- if (gContext.NextCommandPaletteActions.FocusSearchBox) {
// Focus the search box when user first brings command palette window up
// Note: this only affects the next frame
ImGui::SetKeyboardFocusHere(0);
}
ImGui::SetNextItemWidth(width);
- if (ImGui::InputText("##SearchBox", gi.Search.SearchText, IM_ARRAYSIZE(gi.Search.SearchText))) {
+ if (ImGui::InputText("##", &mSearchText)) {
// Search string updated, update search results
- gi.Search.RefreshSearchResults();
- }
- ImGui::BeginChild("SearchResults", ImVec2(width, search_result_window_height));
+ mFocusedItemId = 0;
+ mSearchResults.clear();
- auto window = ImGui::GetCurrentWindow();
- auto draw_list = window->DrawList;
+ size_t itemCount;
+ if (mExeCtx.GetExecutionDepth() == 0) {
+ itemCount = mCommands.size();
+ } else {
+ itemCount = mExeCtx.mCurrentOptions.size();
+ }
- auto font_regular = gContext.Fonts[ImCmdTextType_Regular];
- if (!font_regular) {
- font_regular = ImGui::GetDrawListSharedData()->Font;
- }
- auto font_highlight = gContext.Fonts[ImCmdTextType_Highlight];
- if (!font_highlight) {
- font_highlight = ImGui::GetDrawListSharedData()->Font;
- }
+ for (size_t i = 0; i < itemCount; ++i) {
+ const char* text;
+ if (mExeCtx.GetExecutionDepth() == 0) {
+ text = mCommands[i].name.c_str();
+ } else {
+ text = mExeCtx.mCurrentOptions[i].c_str();
+ }
- ImU32 text_color_regular;
- ImU32 text_color_highlight;
- if (gContext.HasFontColorOverride[ImCmdTextType_Regular]) {
- text_color_regular = gContext.FontColors[ImCmdTextType_Regular];
- } else {
- text_color_regular = ImGui::GetColorU32(ImGuiCol_Text);
- }
- if (gContext.HasFontColorOverride[ImCmdTextType_Highlight]) {
- text_color_highlight = gContext.FontColors[ImCmdTextType_Highlight];
- } else {
- text_color_highlight = ImGui::GetColorU32(ImGuiCol_Text);
+ SearchResult result{
+ .itemIndex = (int)i,
+ };
+ if (FuzzyMatch::Search(mSearchText.c_str(), text, result.score, result.matches, std::size(result.matches), result.matchCount)) {
+ mSearchResults.push_back(result);
+ }
+ }
+
+ std::sort(
+ mSearchResults.begin(),
+ mSearchResults.end(),
+ [](const SearchResult& a, const SearchResult& b) -> bool {
+ // We want the biggest element first
+ return a.score > b.score;
+ });
}
- auto item_hovered_color = ImGui::GetColorU32(ImGuiCol_HeaderHovered);
- auto item_active_color = ImGui::GetColorU32(ImGuiCol_HeaderActive);
- auto item_selected_color = ImGui::GetColorU32(ImGuiCol_Header);
+ ImGui::BeginChild("SearchResults", ImVec2(width, 300), false, ImGuiWindowFlags_AlwaysAutoResize);
+ auto window = ImGui::GetCurrentWindow();
- int item_count;
- if (gi.Search.IsActive()) {
- item_count = gi.Search.GetItemCount();
- } else {
- item_count = gi.Session.GetItemCount();
- }
+ auto& io = ImGui::GetIO();
+ auto dlSharedData = ImGui::GetDrawListSharedData();
- if (gi.ExtraData.size() < item_count) {
- gi.ExtraData.resize(item_count);
+ auto textColor = ImGui::GetColorU32(ImGuiCol_Text);
+ auto itemHoveredColor = ImGui::GetColorU32(ImGuiCol_HeaderHovered);
+ auto itemActiveColor = ImGui::GetColorU32(ImGuiCol_HeaderActive);
+ auto itemSelectedColor = ImGui::GetColorU32(ImGuiCol_Header);
+
+ int itemCount = GetItemCount();
+ if (mItems.size() < itemCount) {
+ mItems.resize(itemCount);
}
// Flag used to delay item selection until after the loop ends
- bool select_focused_item = false;
- for (int i = 0; i < item_count; ++i) {
+ bool selectFocusedItem = false;
+ for (size_t i = 0; i < itemCount; ++i) {
auto id = window->GetID(static_cast<int>(i));
ImVec2 size{
ImGui::GetContentRegionAvail().x,
- ImMax(font_regular->FontSize, font_highlight->FontSize),
+ dlSharedData->Font->FontSize,
};
ImRect rect{
window->DC.CursorPos,
window->DC.CursorPos + ImGui::CalcItemSize(size, 0.0f, 0.0f),
};
- bool& hovered = gi.ExtraData[i].Hovered;
- bool& held = gi.ExtraData[i].Held;
+ bool& hovered = mItems[i].hovered;
+ bool& held = mItems[i].held;
if (held && hovered) {
- draw_list->AddRectFilled(rect.Min, rect.Max, item_active_color);
+ window->DrawList->AddRectFilled(rect.Min, rect.Max, itemActiveColor);
} else if (hovered) {
- draw_list->AddRectFilled(rect.Min, rect.Max, item_hovered_color);
- } else if (gi.CurrentSelectedItem == i) {
- draw_list->AddRectFilled(rect.Min, rect.Max, item_selected_color);
+ window->DrawList->AddRectFilled(rect.Min, rect.Max, itemHoveredColor);
+ } else if (mFocusedItemId == i) {
+ window->DrawList->AddRectFilled(rect.Min, rect.Max, itemSelectedColor);
}
- if (gi.Search.IsActive()) {
+ auto item = GetItem(i);
+ if (item.indexType == SearchResultIndex) {
// Iterating search results: draw text with highlights at matched chars
- auto& search_result = gi.Search.SearchResults[i];
- auto text = gi.Search.GetItem(i);
-
- auto text_pos = window->DC.CursorPos;
- int range_begin;
- int range_end;
- int last_range_end = 0;
+ auto& searchResult = mSearchResults[i];
+ auto textPos = window->DC.CursorPos;
+ int rangeBegin;
+ int rangeEnd;
+ int lastRangeEnd = 0;
- auto DrawCurrentRange = [&]() {
- if (range_begin != last_range_end) {
+ auto DrawCurrentRange = [&]() -> void {
+ if (rangeBegin != lastRangeEnd) {
// Draw normal text between last highlighted range end and current highlighted range start
- auto begin = text + last_range_end;
- auto end = text + range_begin;
- draw_list->AddText(text_pos, text_color_regular, begin, end);
+ auto begin = item.text + lastRangeEnd;
+ auto end = item.text + rangeBegin;
+ window->DrawList->AddText(textPos, textColor, begin, end);
- auto segment_size = font_regular->CalcTextSizeA(font_regular->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end);
- text_pos.x += segment_size.x;
+ auto segmentSize = dlSharedData->Font->CalcTextSizeA(dlSharedData->Font->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end);
+ textPos.x += segmentSize.x;
}
- auto begin = text + range_begin;
- auto end = text + range_end;
- draw_list->AddText(font_highlight, font_highlight->FontSize, text_pos, text_color_highlight, begin, end);
+ auto begin = item.text + rangeBegin;
+ auto end = item.text + rangeEnd;
+ window->DrawList->AddText(AppConfig::fontBold, AppConfig::fontBold->FontSize, textPos, textColor, begin, end);
- auto segment_size = font_highlight->CalcTextSizeA(font_highlight->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end);
- text_pos.x += segment_size.x;
+ auto segmentSize = AppConfig::fontBold->CalcTextSizeA(AppConfig::fontBold->FontSize, std::numeric_limits<float>::max(), 0.0f, begin, end);
+ textPos.x += segmentSize.x;
};
- IM_ASSERT(search_result.MatchCount >= 1);
- range_begin = search_result.Matches[0];
- range_end = range_begin;
+ assert(searchResult.matchCount >= 1);
+ rangeBegin = searchResult.matches[0];
+ rangeEnd = rangeBegin;
- int last_char_idx = -1;
- for (int j = 0; j < search_result.MatchCount; ++j) {
- int char_idx = search_result.Matches[j];
+ int lastCharIdx = -1;
+ for (int j = 0; j < searchResult.matchCount; ++j) {
+ int charIdx = searchResult.matches[j];
- if (char_idx == last_char_idx + 1) {
+ if (charIdx == lastCharIdx + 1) {
// These 2 indices are equal, extend our current range by 1
- ++range_end;
+ ++rangeEnd;
} else {
DrawCurrentRange();
- last_range_end = range_end;
- range_begin = char_idx;
- range_end = char_idx + 1;
+ lastRangeEnd = rangeEnd;
+ rangeBegin = charIdx;
+ rangeEnd = charIdx + 1;
}
- last_char_idx = char_idx;
+ lastCharIdx = charIdx;
}
// Draw the remaining range (if any)
- if (range_begin != range_end) {
+ if (rangeBegin != rangeEnd) {
DrawCurrentRange();
}
// Draw the text after the last range (if any)
- draw_list->AddText(text_pos, text_color_regular, text + range_end); // Draw until \0
+ window->DrawList->AddText(textPos, textColor, item.text + rangeEnd); // Draw until \0
} else {
// Iterating everything else: draw text as-is, there is no highlights
- auto text = gi.Session.GetItem(i);
- auto text_pos = window->DC.CursorPos;
- draw_list->AddText(text_pos, text_color_regular, text);
+ window->DrawList->AddText(window->DC.CursorPos, textColor, item.text);
}
ImGui::ItemSize(rect);
@@ -610,95 +283,124 @@ void ShowCommandPalette(const char* name) {
continue;
}
if (ImGui::ButtonBehavior(rect, id, &hovered, &held)) {
- gi.CurrentSelectedItem = i;
- select_focused_item = true;
+ mFocusedItemId = i;
+ selectFocusedItem = true;
}
}
- if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow))) {
- gi.CurrentSelectedItem = ImMax(gi.CurrentSelectedItem - 1, 0);
- } else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow))) {
- gi.CurrentSelectedItem = ImMin(gi.CurrentSelectedItem + 1, item_count - 1);
+ if (ImGui::IsKeyPressed(GLFW_KEY_UP)) {
+ mFocusedItemId = std::max(mFocusedItemId - 1, 0);
+ } else if (ImGui::IsKeyPressed(GLFW_KEY_DOWN)) {
+ mFocusedItemId = std::min(mFocusedItemId + 1, itemCount - 1);
}
- if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter)) || select_focused_item) {
- if (gi.Search.IsActive() && !gi.Search.SearchResults.empty()) {
- auto idx = gi.Search.SearchResults[gi.CurrentSelectedItem].ItemIndex;
- gi.Session.SelectItem(idx);
- } else {
- gi.Session.SelectItem(gi.CurrentSelectedItem);
- }
+ if (ImGui::IsKeyPressed(GLFW_KEY_ENTER) || selectFocusedItem) {
+ SelectFocusedItem();
}
ImGui::EndChild();
- gContext.NextCommandPaletteActions = {};
-
- ImGui::PopID();
- gContext.CurrentCommandPalette = nullptr;
- // END this command palette
+ ImGui::End();
}
-bool IsAnyItemSelected() {
- return gContext.LastCommandPaletteStatus.ItemSelected;
+size_t EditorCommandPalette::GetItemCount() const {
+ int depth = mExeCtx.GetExecutionDepth();
+ if (depth == 0) {
+ if (mSearchText.empty()) {
+ return mCommands.size();
+ } else {
+ return mSearchResults.size();
+ }
+ } else {
+ if (mSearchText.empty()) {
+ return mExeCtx.mCurrentOptions.size();
+ } else {
+ return mSearchResults.size();
+ }
+ }
}
-void RemoveCache(const char* name) {
- auto& instances = gContext.Instances;
- auto id = ImHashStr(name);
- if (auto ptr = instances.GetVoidPtr(id)) {
- auto instance = reinterpret_cast<Instance*>(ptr);
- instances.SetVoidPtr(id, nullptr);
- delete instance;
+EditorCommandPalette::ItemInfo EditorCommandPalette::GetItem(size_t idx) const {
+ ItemInfo option;
+
+ int depth = mExeCtx.GetExecutionDepth();
+ if (depth == 0) {
+ if (mSearchText.empty()) {
+ option.text = mCommands[idx].name.c_str();
+ option.command = &mCommands[idx];
+ option.itemId = idx;
+ option.indexType = DirectIndex;
+ } else {
+ auto id = mSearchResults[idx].itemIndex;
+ option.text = mCommands[id].name.c_str();
+ option.command = &mCommands[id];
+ option.itemId = id;
+ option.indexType = SearchResultIndex;
+ }
+ option.itemType = CommandItem;
+ } else {
+ assert(mExeCtx.GetCurrentCommand() != nullptr);
+ if (mSearchText.empty()) {
+ option.text = mExeCtx.mCurrentOptions[idx].c_str();
+ option.command = mExeCtx.GetCurrentCommand();
+ option.itemId = idx;
+ option.indexType = DirectIndex;
+ } else {
+ auto id = mSearchResults[idx].itemIndex;
+ option.text = mExeCtx.mCurrentOptions[id].c_str();
+ option.command = mExeCtx.GetCurrentCommand();
+ option.itemId = id;
+ option.indexType = SearchResultIndex;
+ }
+ option.itemType = CommandOptionItem;
}
+
+ return option;
}
-void RemoveAllCaches() {
- auto& instances = gContext.Instances;
- for (auto& entry : instances.Data) {
- auto instance = reinterpret_cast<Instance*>(entry.val_p);
- entry.val_p = nullptr;
- delete instance;
+void EditorCommandPalette::SelectFocusedItem() {
+ if (mFocusedItemId < 0 || mFocusedItemId >= GetItemCount()) {
+ return;
}
- instances = {};
-}
-void SetNextWindowAffixedTop(ImGuiCond cond) {
- auto viewport = ImGui::GetMainViewport()->Size;
+ auto selectedItem = GetItem(mFocusedItemId);
+ auto& command = *selectedItem.command;
- // Center window horizontally, align top vertically
- ImGui::SetNextWindowPos(ImVec2(viewport.x / 2, 0), cond, ImVec2(0.5f, 0.0f));
-}
+ int depth = mExeCtx.GetExecutionDepth();
+ if (depth == 0) {
+ assert(!mExeCtx.IsInitiated());
-void ShowCommandPaletteWindow(const char* name, bool* p_open) {
- auto viewport = ImGui::GetMainViewport()->Size;
+ mExeCtx.Initiate(*selectedItem.command);
+ if (command.callback) {
+ command.callback(mExeCtx);
- SetNextWindowAffixedTop();
- ImGui::SetNextWindowSize(ImVec2(viewport.x * 0.3f, 0.0f));
- ImGui::Begin(name, nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar);
+ mFocusSearchBox = true;
+ // Don't invalidate search results if no further actions have been requested (returning to global list of commands)
+ if (mExeCtx.IsInitiated()) {
+ InvalidateSearchResults();
+ }
+ } else {
+ mExeCtx.Finish();
+ }
+ } else {
+ assert(mExeCtx.IsInitiated());
+ assert(command.subsequentCallback);
+ command.subsequentCallback(mExeCtx, selectedItem.itemId);
- if (ImGui::IsWindowAppearing()) {
- SetNextCommandPaletteSearchBoxFocused();
+ mFocusSearchBox = true;
+ InvalidateSearchResults();
}
- ShowCommandPalette(name);
-
- if (IsAnyItemSelected()) {
- *p_open = false;
- }
- if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
- // Close popup when user unfocused the command palette window (clicking elsewhere)
- *p_open = false;
+ // This action terminated execution, close command palette window
+ if (!mExeCtx.IsInitiated()) {
+ if (command.terminate) {
+ command.terminate();
+ }
+ mShouldCloseNextFrame = true;
}
-
- ImGui::End();
}
-void Prompt(std::vector<std::string> options) {
- IM_ASSERT(gContext.CurrentCommandPalette != nullptr);
- IM_ASSERT(gContext.IsExecuting);
- IM_ASSERT(!gContext.IsTerminating);
-
- auto& gi = *gContext.CurrentCommandPalette;
- gi.Session.PushOptions(std::move(options));
+void EditorCommandPalette::InvalidateSearchResults() {
+ mSearchText.clear();
+ mSearchResults.clear();
+ mFocusedItemId = 0;
}
-} // namespace ImCmd
diff --git a/source/EditorCommandPalette.hpp b/source/EditorCommandPalette.hpp
index 1603f41..101344d 100644
--- a/source/EditorCommandPalette.hpp
+++ b/source/EditorCommandPalette.hpp
@@ -2,48 +2,93 @@
#include <imgui.h>
#include <cstddef>
-#include <cstdint>
#include <functional>
#include <string>
-#include <vector>
+#include <string_view>
-enum ImCmdTextType {
- ImCmdTextType_Regular,
- ImCmdTextType_Highlight,
- ImCmdTextType_COUNT,
+class EditorCommandExecuteContext;
+class EditorCommand {
+public:
+ std::string name;
+ std::function<void(EditorCommandExecuteContext& ctx)> callback;
+ std::function<void(EditorCommandExecuteContext& ctx, size_t selectedOptionId)> subsequentCallback;
+ std::function<void()> terminate;
};
-namespace ImCmd {
-struct Command {
- const char* Name;
- std::function<void()> InitialCallback;
- std::function<void(int selected_option)> SubsequentCallback;
- std::function<void()> TerminatingCallback;
+class EditorCommandExecuteContext {
+ friend class EditorCommandPalette;
+
+private:
+ const EditorCommand* mCommand = nullptr;
+ std::vector<std::string> mCurrentOptions;
+ int mDepth = 0;
+
+public:
+ bool IsInitiated() const;
+ const EditorCommand* GetCurrentCommand() const;
+ void Initiate(const EditorCommand& command);
+
+ void Prompt(std::vector<std::string> options);
+ void Finish();
+
+ /// Return the number of prompts that the user is currently completing. For example, when the user opens command
+ /// palette fresh and selects a command, 0 is returned. If the command asks some prompt, and then the user selects
+ /// again, 1 is returned.
+ int GetExecutionDepth() const;
};
-// Command management
-void AddCommand(Command command);
-void RemoveCommand(const char* name);
+class EditorCommandPalette {
+private:
+ struct SearchResult;
+ struct Item;
+
+ std::vector<EditorCommand> mCommands;
+ std::vector<Item> mItems;
+ std::vector<SearchResult> mSearchResults;
+ std::string mSearchText;
+ EditorCommandExecuteContext mExeCtx;
+ int mFocusedItemId = 0;
+ bool mFocusSearchBox = false;
+ bool mShouldCloseNextFrame = false;
-// Styling
-void SetStyleFont(ImCmdTextType type, ImFont* font);
-void SetStyleColor(ImCmdTextType type, ImU32 color);
-void ClearStyleColor(ImCmdTextType type); //< Clear the style color for the given type, defaulting to ImGuiCol_Text
+public:
+ EditorCommandPalette();
+ ~EditorCommandPalette();
-// Command palette widget
-void SetNextCommandPaletteSearch(const char* text);
-void SetNextCommandPaletteSearchBoxFocused();
-void ShowCommandPalette(const char* name);
-bool IsAnyItemSelected();
+ EditorCommandPalette(const EditorCommandPalette&) = delete;
+ EditorCommandPalette& operator=(const EditorCommandPalette&) = delete;
+ EditorCommandPalette(EditorCommandPalette&&) = default;
+ EditorCommandPalette& operator=(EditorCommandPalette&&) = default;
-void RemoveCache(const char* name);
-void RemoveAllCaches();
+ void AddCommand(std::string_view category, std::string_view name, EditorCommand command);
+ void RemoveCommand(std::string_view category, std::string_view name);
+ void RemoveCommand(const std::string& commandName);
-// Command palette widget in a window helper
-void SetNextWindowAffixedTop(ImGuiCond cond = 0);
-void ShowCommandPaletteWindow(const char* name, bool* p_open);
+ void Show(bool* open = nullptr);
-// Command responses, only call these in command callbacks (except TerminatingCallback)
-void Prompt(std::vector<std::string> options);
+ enum ItemType {
+ CommandItem,
+ CommandOptionItem,
+ };
-} // namespace ImCmd
+ enum IndexType {
+ DirectIndex,
+ SearchResultIndex,
+ };
+
+ struct ItemInfo {
+ const char* text;
+ const EditorCommand* command;
+ int itemId;
+ ItemType itemType;
+ IndexType indexType;
+ };
+
+ size_t GetItemCount() const;
+ ItemInfo GetItem(size_t idx) const;
+
+ void SelectFocusedItem();
+
+private:
+ void InvalidateSearchResults();
+};
diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp
index 601a850..8288df3 100644
--- a/source/EditorCore.cpp
+++ b/source/EditorCore.cpp
@@ -338,7 +338,7 @@ void EditorInstance::Show() {
mWindowVisible_CommandPalette = !mWindowVisible_CommandPalette;
}
if (mWindowVisible_CommandPalette) {
- ImCmd::ShowCommandPaletteWindow("Command Palette", &mWindowVisible_CommandPalette);
+ mEdCommandPalette.Show(&mWindowVisible_CommandPalette);
}
if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) {
diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp
index b473b2e..af35616 100644
--- a/source/EditorCore.hpp
+++ b/source/EditorCore.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "EditorAttachment.hpp"
+#include "EditorCommandPalette.hpp"
#include "GameObject.hpp"
#include "Ires.hpp"
#include "RcPtr.hpp"
@@ -61,6 +62,7 @@ private:
GameWorld* mWorld;
GameObject* mPopupCurrent_GameObject = nullptr;
RcPtr<SpriteDefinition> mSpriteView_Instance;
+ EditorCommandPalette mEdCommandPalette;
EditorInspector mEdInspector;
EditorContentBrowser mEdContentBrowser;
int mSpriteView_Frame;