feat: Allow any custom content to be displayed in the command palette

This commit is contained in:
WerWolv
2025-08-15 20:17:58 +02:00
parent 0ebe4150ae
commit 762eacb7c8
9 changed files with 130 additions and 128 deletions

View File

@@ -21,12 +21,14 @@ EXPORT_MODULE namespace hex {
namespace impl {
using QueryResultCallback = std::function<void(std::string)>;
struct QueryResult {
std::string name;
std::function<void(std::string)> callback;
QueryResultCallback callback;
};
using ContentDisplayCallback = std::function<void(std::string)>;
using ContentDisplayCallback = std::function<void()>;
using DisplayCallback = std::function<std::string(std::string)>;
using ExecuteCallback = std::function<std::optional<std::string>(std::string)>;
using QueryCallback = std::function<std::vector<QueryResult>(std::string)>;
@@ -46,10 +48,15 @@ EXPORT_MODULE namespace hex {
DisplayCallback displayCallback;
};
struct ContentDisplay {
bool showSearchBox;
ContentDisplayCallback callback;
};
const std::vector<Entry>& getEntries();
const std::vector<Handler>& getHandlers();
std::optional<ContentDisplayCallback>& getDisplayedContent();
std::optional<ContentDisplay>& getDisplayedContent();
}
@@ -86,6 +93,12 @@ EXPORT_MODULE namespace hex {
* @param displayCallback Display callback that will be called to display the content
*/
void setDisplayedContent(const impl::ContentDisplayCallback &displayCallback);
/**
* @brief Opens the command palette window, displaying a user defined interface
* @param displayCallback Display callback that will be called to display the content
*/
void openWithContent(const impl::ContentDisplayCallback &displayCallback);
}
}

View File

@@ -71,34 +71,8 @@ namespace hex {
/**
* @brief Requests the Pattern editor to run the current code
*
* This is only ever used in the introduction tutorial.
*
* FIXME: the name is misleading, as for now this activates the pattern's auto-evaluation rather than a
* one-off execution
*/
EVENT_DEF(RequestRunPatternCode);
/**
* @brief Request to load a pattern language file
*
* FIXME: this request is unused, as now another component is responsible for pattern file loading.
* This request should be scrapped.
*
* @param path the pattern file's path
* @param bool track changes to the file on disk
*
*/
EVENT_DEF(RequestLoadPatternLanguageFile, std::fs::path, bool);
/**
* @brief Request to save a pattern language file
*
* FIXME: this request is unused, as now another component is responsible for pattern file saving.
* This request should be scrapped.
*
* @param path the pattern file's path
*/
EVENT_DEF(RequestSavePatternLanguageFile, std::fs::path);
EVENT_DEF(RequestTriggerPatternEvaluation);
/**
* @brief Requests ImHex to open and process a file
@@ -116,4 +90,9 @@ namespace hex {
*/
EVENT_DEF(RequestAddVirtualFile, std::fs::path, std::vector<u8>, Region);
/**
* @brief Requests the command palette to be opened
*/
EVENT_DEF(RequestOpenCommandPalette);
}

View File

@@ -36,6 +36,7 @@
#include <algorithm>
#include <filesystem>
#include <jthread.hpp>
#include <hex/api/events/requests_interaction.hpp>
#if defined(OS_WEB)
#include <emscripten.h>
@@ -613,8 +614,8 @@ namespace hex {
return *s_handlers;
}
static AutoReset<std::optional<ContentDisplayCallback>> s_displayedContent;
std::optional<ContentDisplayCallback>& getDisplayedContent() {
static AutoReset<std::optional<ContentDisplay>> s_displayedContent;
std::optional<ContentDisplay>& getDisplayedContent() {
return *s_displayedContent;
}
@@ -633,7 +634,12 @@ namespace hex {
}
void setDisplayedContent(const impl::ContentDisplayCallback &displayCallback) {
impl::s_displayedContent = displayCallback;
impl::s_displayedContent = impl::ContentDisplay { true, displayCallback };
}
void openWithContent(const impl::ContentDisplayCallback &displayCallback) {
RequestOpenCommandPalette::post();
impl::s_displayedContent = impl::ContentDisplay { false, displayCallback };
}
}

View File

@@ -566,7 +566,7 @@ namespace hex::plugin::builtin {
}
RequestSetPatternLanguageCode::post(patternSourceCode);
RequestRunPatternCode::post();
RequestTriggerPatternEvaluation::post();
});
}

View File

@@ -454,7 +454,7 @@ namespace hex::plugin::builtin {
continue;
result.emplace_back(name, [&toolEntry](const auto &) {
ContentRegistry::CommandPalette::setDisplayedContent([&toolEntry](const auto) {
ContentRegistry::CommandPalette::setDisplayedContent([&toolEntry]() {
toolEntry.function();
});
});

View File

@@ -75,7 +75,7 @@ namespace hex::plugin::builtin {
})
.onAppear([] {
RequestSetPatternLanguageCode::post("\n\n\n\n\n\nstruct Test {\n u8 value;\n};\n\nTest test @ 0x00;");
RequestRunPatternCode::post();
RequestTriggerPatternEvaluation::post();
})
.allowSkip();
}

View File

@@ -3,9 +3,12 @@
#include <hex/api/imhex_api/system.hpp>
#include <hex/api/localization_manager.hpp>
#include <hex/api/task_manager.hpp>
#include <hex/api/events/events_provider.hpp>
#include <hex/api/events/events_gui.hpp>
#include <hex/api/events/requests_gui.hpp>
#include <hex/api/events/events_interaction.hpp>
#include <hex/api/events/requests_interaction.hpp>
#include <hex/ui/view.hpp>
#include <hex/helpers/utils.hpp>
@@ -24,7 +27,6 @@
#include <toasts/toast_notification.hpp>
#include <csignal>
#include <hex/api/events/events_interaction.hpp>
namespace hex::plugin::builtin {
@@ -423,8 +425,10 @@ namespace hex::plugin::builtin {
EventProviderChanged::subscribe([](auto, auto) { providerJustChanged = true; });
static prv::Provider *rightClickedProvider = nullptr;
EventSearchBoxClicked::subscribe([](ImGuiMouseButton button){
if (button == ImGuiMouseButton_Right) {
EventSearchBoxClicked::subscribe([](ImGuiMouseButton button) {
if (button == ImGuiMouseButton_Left) {
RequestOpenCommandPalette::post();
} else if (button == ImGuiMouseButton_Right) {
rightClickedProvider = ImHexApi::Provider::get();
RequestOpenPopup::post("ProviderMenu");
}

View File

@@ -2,51 +2,51 @@
#include <hex/api/content_registry/command_palette.hpp>
#include <wolv/utils/guards.hpp>
#include <hex/api/events/requests_gui.hpp>
#include <hex/api/events/events_interaction.hpp>
#include <hex/api/events/requests_interaction.hpp>
#include "imstb_textedit.h"
#include <imgui_internal.h>
#include <fonts/vscode_icons.hpp>
namespace hex::plugin::builtin {
ViewCommandPalette::ViewCommandPalette() : View::Special("hex.builtin.view.command_palette.name") {
// Add global shortcut to open the command palette
ShortcutManager::addGlobalShortcut(CTRLCMD + SHIFT + Keys::P, "hex.builtin.view.command_palette.name", [this] {
RequestOpenPopup::post("hex.builtin.view.command_palette.name"_lang);
m_commandPaletteOpen = true;
m_justOpened = true;
RequestOpenCommandPalette::post();
});
EventSearchBoxClicked::subscribe([this](ImGuiMouseButton button) {
if (button == ImGuiMouseButton_Left) {
m_commandPaletteOpen = true;
m_justOpened = true;
}
RequestOpenCommandPalette::subscribe([this]() {
m_commandPaletteOpen = true;
m_justOpened = true;
ContentRegistry::CommandPalette::impl::getDisplayedContent().reset();
});
}
void ViewCommandPalette::drawAlwaysVisibleContent() {
auto &displayedContent = ContentRegistry::CommandPalette::impl::getDisplayedContent();
if (m_justOpened) {
ImGui::OpenPopup("hex.builtin.view.command_palette.name"_lang);
ContentRegistry::CommandPalette::impl::getDisplayedContent().reset();
}
// If the command palette is hidden, don't draw it
if (!m_commandPaletteOpen) return;
auto windowPos = ImHexApi::System::getMainWindowPosition();
auto windowSize = ImHexApi::System::getMainWindowSize();
const auto &displayedContent = ContentRegistry::CommandPalette::impl::getDisplayedContent();
const auto windowPos = ImHexApi::System::getMainWindowPosition();
const auto windowSize = ImHexApi::System::getMainWindowSize();
ImGui::SetNextWindowPos(ImVec2(windowPos.x + windowSize.x * 0.5F, windowPos.y), ImGuiCond_Always, ImVec2(0.5F, 0.0F));
if (!displayedContent.has_value())
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
else
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), ImVec2(FLT_MAX, FLT_MAX));
ImGui::SetNextWindowSizeConstraints(ImVec2(this->getMinSize().x, 20_scaled), ImVec2(FLT_MAX, FLT_MAX));
if (ImGui::BeginPopup("hex.builtin.view.command_palette.name"_lang)) {
const bool hasSearchBox = !displayedContent.has_value() || displayedContent->showSearchBox;
if (ImGui::BeginPopup("hex.builtin.view.command_palette.name"_lang, !hasSearchBox ? ImGuiWindowFlags_MenuBar : ImGuiWindowFlags_None)) {
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindowRead());
ImGui::BringWindowToFocusFront(ImGui::GetCurrentWindowRead());
@@ -54,82 +54,92 @@ namespace hex::plugin::builtin {
if (ImGui::IsKeyDown(ImGuiKey_Escape))
ImGui::CloseCurrentPopup();
if (hasSearchBox) {
const auto buttonColor = [](float alpha) {
return ImU32(ImColor(ImGui::GetStyleColorVec4(ImGuiCol_ModalWindowDimBg) * ImVec4(1, 1, 1, alpha)));
};
const auto buttonColor = [](float alpha) {
return ImU32(ImColor(ImGui::GetStyleColorVec4(ImGuiCol_ModalWindowDimBg) * ImVec4(1, 1, 1, alpha)));
};
// Draw the main input text box
ImGui::PushItemWidth(-1);
ImGui::PushStyleColor(ImGuiCol_FrameBg, buttonColor(0.5F));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, buttonColor(0.7F));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, buttonColor(0.9F));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0_scaled);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4_scaled);
// Draw the main input text box
ImGui::PushItemWidth(-1);
ImGui::PushStyleColor(ImGuiCol_FrameBg, buttonColor(0.5F));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, buttonColor(0.7F));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, buttonColor(0.9F));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0_scaled);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4_scaled);
if (ImGui::InputText("##command_input", m_commandBuffer)) {
m_lastResults = this->getCommandResults(m_commandBuffer);
}
ImGui::SetItemKeyOwner(ImGuiKey_LeftAlt, ImGuiInputFlags_CondActive);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
ImGui::PopItemWidth();
ImGui::SetItemDefaultFocus();
if (m_moveCursorToEnd) {
auto textState = ImGui::GetInputTextState(ImGui::GetID("##command_input"));
if (textState != nullptr) {
auto stb = reinterpret_cast<STB_TexteditState*>(textState->Stb);
stb->cursor =
stb->select_start =
stb->select_end = m_commandBuffer.size();
if (ImGui::InputText("##command_input", m_commandBuffer)) {
m_lastResults = this->getCommandResults(m_commandBuffer);
}
m_moveCursorToEnd = false;
}
ImGui::SetItemKeyOwner(ImGuiKey_LeftAlt, ImGuiInputFlags_CondActive);
// Handle giving back focus to the input text box
if (m_focusInputTextBox) {
ImGui::SetKeyboardFocusHere(-1);
ImGui::ActivateItemByID(ImGui::GetID("##command_input"));
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
ImGui::PopItemWidth();
ImGui::SetItemDefaultFocus();
m_focusInputTextBox = false;
m_moveCursorToEnd = true;
}
if (m_moveCursorToEnd) {
auto textState = ImGui::GetInputTextState(ImGui::GetID("##command_input"));
if (textState != nullptr) {
auto stb = reinterpret_cast<STB_TexteditState*>(textState->Stb);
stb->cursor =
stb->select_start =
stb->select_end = m_commandBuffer.size();
}
m_moveCursorToEnd = false;
}
// Execute the currently selected command when pressing enter
if (ImGui::IsItemFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false))) {
bool closePalette = true;
if (!m_lastResults.empty()) {
auto &[displayResult, matchedCommand, callback] = m_lastResults.front();
// Handle giving back focus to the input text box
if (m_focusInputTextBox) {
ImGui::SetKeyboardFocusHere(-1);
ImGui::ActivateItemByID(ImGui::GetID("##command_input"));
if (auto result = callback(matchedCommand); result.has_value()) {
m_commandBuffer = result.value();
closePalette = false;
m_focusInputTextBox = true;
m_focusInputTextBox = false;
m_moveCursorToEnd = true;
}
// Execute the currently selected command when pressing enter
if (ImGui::IsItemFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false))) {
bool closePalette = true;
if (!m_lastResults.empty()) {
auto &[displayResult, matchedCommand, callback] = m_lastResults.front();
if (auto result = callback(matchedCommand); result.has_value()) {
m_commandBuffer = result.value();
closePalette = false;
m_focusInputTextBox = true;
}
}
if (closePalette) {
ImGui::CloseCurrentPopup();
}
}
if (closePalette) {
ImGui::CloseCurrentPopup();
// Focus the input text box when the popup is opened
if (m_justOpened) {
focusInputTextBox();
m_lastResults = this->getCommandResults("");
m_commandBuffer.clear();
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y);
ImGui::Separator();
}
// Focus the input text box when the popup is opened
if (m_justOpened) {
focusInputTextBox();
m_lastResults = this->getCommandResults("");
m_commandBuffer.clear();
m_justOpened = false;
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y);
ImGui::Separator();
m_justOpened = false;
// Draw the results
if (displayedContent.has_value()) {
(*displayedContent)(m_commandBuffer);
if (!displayedContent->showSearchBox){
if (ImGui::BeginMenuBar()) {
ImGui::TextUnformatted(ICON_VS_TARGET);
ImGui::EndMenuBar();
}
displayedContent->callback();
}
} else {
if (ImGui::BeginChild("##results", ImGui::GetContentRegionAvail(), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
u32 id = 1;
@@ -252,4 +262,4 @@ namespace hex::plugin::builtin {
return results;
}
}
}

View File

@@ -238,7 +238,7 @@ namespace hex::plugin::builtin {
ViewPatternEditor::~ViewPatternEditor() {
RequestPatternEditorSelectionChange::unsubscribe(this);
RequestSetPatternLanguageCode::unsubscribe(this);
RequestRunPatternCode::unsubscribe(this);
RequestTriggerPatternEvaluation::unsubscribe(this);
EventFileLoaded::unsubscribe(this);
EventProviderChanged::unsubscribe(this);
EventProviderClosed::unsubscribe(this);
@@ -1854,20 +1854,10 @@ namespace hex::plugin::builtin {
m_textEditor.get(provider).setCursorPosition(coords);
});
RequestLoadPatternLanguageFile::subscribe(this, [this](const std::fs::path &path, bool trackFile) {
this->loadPatternFile(path, ImHexApi::Provider::get(), trackFile);
});
RequestRunPatternCode::subscribe(this, [this] {
RequestTriggerPatternEvaluation::subscribe(this, [this] {
m_triggerAutoEvaluate = true;
});
RequestSavePatternLanguageFile::subscribe(this, [this](const std::fs::path &path) {
auto provider = ImHexApi::Provider::get();
wolv::io::File file(path, wolv::io::File::Mode::Create);
file.writeString(wolv::util::trim(m_textEditor.get(provider).getText()));
});
RequestSetPatternLanguageCode::subscribe(this, [this](const std::string &code) {
auto provider = ImHexApi::Provider::get();
m_textEditor.get(provider).setText(wolv::util::preprocessText(code));