diff --git a/plugins/MyPlugin/plugin.cpp b/plugins/MyPlugin/plugin.cpp index e78815a..000796c 100644 --- a/plugins/MyPlugin/plugin.cpp +++ b/plugins/MyPlugin/plugin.cpp @@ -39,4 +39,4 @@ static Plugin info { on_load, on_unload, on_update, on_draw_ui }; -extern "C" __declspec(dllexport) Plugin* get_plugin() { return &info; } \ No newline at end of file +PLUGIN_EXPORT Plugin* get_plugin() { return &info; } \ No newline at end of file diff --git a/scripts/build_engine.sh b/scripts/build_engine.sh old mode 100644 new mode 100755 diff --git a/scripts/build_plugin.sh b/scripts/build_plugin.sh old mode 100644 new mode 100755 diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp index b4e0806..a964970 100644 --- a/src/editor/editor.cpp +++ b/src/editor/editor.cpp @@ -15,10 +15,12 @@ ImGuizmo::OPERATION gizmo_mode = ImGuizmo::TRANSLATE; int renaming_index = -1; char rename_buf[128] = ""; bool scene_asset_dragging = false; + std::string dragged_scene_asset_name; bool file_dragging = false; int dragged_file_index = -1; int dragged_target_folder_index = -1; + std::unordered_map tex_cache; bool show_about_window = false; bool has_clipboard = false; diff --git a/src/editor/editor.h b/src/editor/editor.h index 3a48822..3186840 100644 --- a/src/editor/editor.h +++ b/src/editor/editor.h @@ -7,14 +7,17 @@ #include namespace editor_internal { + extern ImGuizmo::OPERATION gizmo_mode; extern int renaming_index; extern char rename_buf[128]; extern bool scene_asset_dragging; + extern std::string dragged_scene_asset_name; extern bool file_dragging; extern int dragged_file_index; extern int dragged_target_folder_index; + extern std::unordered_map tex_cache; extern bool show_about_window; extern bool has_clipboard; diff --git a/src/editor/editor_assets.cpp b/src/editor/editor_assets.cpp index 4a83059..4a9af59 100644 --- a/src/editor/editor_assets.cpp +++ b/src/editor/editor_assets.cpp @@ -16,19 +16,6 @@ #include #include -#ifdef _WIN32 -#define NOMINMAX 1 -#define WIN32_LEAN_AND_MEAN -#define CloseWindow WinAPICloseWindow -#define ShowCursor WinAPIShowCursor -#define Rectangle WinAPIRectangle -#include -#include -#undef CloseWindow -#undef ShowCursor -#undef Rectangle -#endif - #define lang LanguageManager::get() namespace fs = std::filesystem; @@ -572,7 +559,7 @@ void draw_assets_ui(Editor& editor) { else { #ifdef _WIN32 - ShellExecuteA(nullptr, "open", full_path.string().c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ShellExecuteA(nullptr, "open", full_path.string().c_str(), nullptr, nullptr, 1); #endif } } diff --git a/src/editor/editor_ui.cpp b/src/editor/editor_ui.cpp index c59389c..5dd27fe 100644 --- a/src/editor/editor_ui.cpp +++ b/src/editor/editor_ui.cpp @@ -1136,7 +1136,7 @@ void delete_entity(Editor& editor, Entity* entity, Shader shader) { editor.scene.selected = -1; } -void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { +void draw_ui(Editor& editor, Shader shader, FlyCamera camera, PluginContext* plugin_ctx) { using namespace editor_internal; ImGuizmo::BeginFrame(); @@ -1152,6 +1152,8 @@ void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu(lang.word("file"))) { + editor.plugin_manager->draw_ui_region(UI_MENU_FILE, *plugin_ctx); + if (ImGui::MenuItem(lang.word("save"), "Ctrl+S")) project_save(editor.project_path, editor.scene); ImGui::Separator(); if (ImGui::MenuItem(lang.word("exit"))) CloseWindow(); @@ -1159,6 +1161,8 @@ void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { } if (ImGui::BeginMenu(lang.word("edit"))) { + editor.plugin_manager->draw_ui_region(UI_MENU_EDIT, *plugin_ctx); + if (ImGui::MenuItem(lang.word("undo"), "Ctrl+Z")) editor.undo(); if (ImGui::MenuItem(lang.word("redo"), "Ctrl+Y")) editor.redo(); @@ -1219,6 +1223,8 @@ void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { } if (ImGui::BeginMenu(lang.word("help"))) { + editor.plugin_manager->draw_ui_region(UI_MENU_HELP, *plugin_ctx); + if (ImGui::MenuItem(lang.word("about"))) show_about_window = true; ImGui::EndMenu(); } @@ -1230,6 +1236,7 @@ void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { if (show_hierarchy) { ImGui::Begin(lang.word("hierarchy"), &show_hierarchy); + editor.plugin_manager->draw_ui_region(UI_HIERARCHY, *plugin_ctx); auto draw_entity_item = [&](int entity_index) { Entity& entity = editor.scene.entities[entity_index]; @@ -1434,6 +1441,7 @@ void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { if (show_inspector) { ImGui::Begin(lang.word("inspector"), &show_inspector); + editor.plugin_manager->draw_ui_region(UI_INSPECTOR, *plugin_ctx); ImGui::Text(lang.word("mode")); ImGui::SameLine(); @@ -1484,6 +1492,7 @@ void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { if (show_scene) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); if (ImGui::Begin(lang.word("scene"), &show_scene)) { + editor.plugin_manager->draw_ui_region(UI_INSPECTOR, *plugin_ctx); g_scene_window_pos = ImGui::GetCursorScreenPos(); g_scene_window_size = ImGui::GetContentRegionAvail(); @@ -1585,6 +1594,6 @@ void draw_ui(Editor& editor, Shader shader, FlyCamera camera) { } } -void Editor::draw_ui(Shader shader, FlyCamera camera) { - ::draw_ui(*this, shader, camera); -} +void Editor::draw_ui(Shader shader, FlyCamera camera, PluginContext* ctx) { + ::draw_ui(*this, shader, camera, ctx); +} \ No newline at end of file diff --git a/src/editor/editor_ui.h b/src/editor/editor_ui.h index 23d1009..7760a3f 100644 --- a/src/editor/editor_ui.h +++ b/src/editor/editor_ui.h @@ -30,7 +30,7 @@ void paste_entity(Editor& editor); void dublicate_entity(Editor& editor, Entity* entity); void delete_entity(Editor& editor, Entity* entity, Shader shader); -void draw_ui(Editor& editor, Shader shader, FlyCamera camera); +void draw_ui(Editor& editor, Shader shader, FlyCamera camera, PluginContext* ctx); void draw_gizmo(Editor& editor, FlyCamera camera); void handle_scene_asset_drop(Editor& editor, Camera3D camera); diff --git a/src/headers/editor.h b/src/headers/editor.h index 0002084..1943e2c 100644 --- a/src/headers/editor.h +++ b/src/headers/editor.h @@ -5,6 +5,7 @@ #include "models.h" #include #include +#include struct SceneState { std::vector entities; @@ -19,9 +20,11 @@ struct Editor { std::stack undo_stack; std::stack redo_stack; + PluginManager* plugin_manager = nullptr; + std::filesystem::path current_asset_path; - void draw_ui(Shader shader, FlyCamera camera); + void draw_ui(Shader shader, FlyCamera camera, PluginContext* ctx); void draw_assets_ui(); void handle_input(); void draw_entity_with_texture(Entity& e); diff --git a/src/main.cpp b/src/main.cpp index f6b276b..b8f00e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,9 @@ Shader shadowmap_shader = {0}; RenderTexture2D shadow_map = {0}; RenderTexture2D scene_rt = {0}; +PluginManager* g_plugin_manager = nullptr; +PluginContext* ctx = nullptr; + extern bool g_is_scene_hovered; extern bool g_is_scene_active; static Shader shadowcaster_shader = {0}; @@ -76,40 +79,64 @@ static void reload_editor_fonts(const std::string& language_code) { io.Fonts->Build(); } +static void init_plugin_context(PluginContext* ctx) { + ctx->ui_begin = [](const char* t) { return ImGui::Begin(t); }; + ctx->ui_end = []() { ImGui::End(); }; + ctx->ui_begin_menu = [](const char* l) { return ImGui::BeginMenu(l); }; + ctx->ui_end_menu = []() { ImGui::EndMenu(); }; + ctx->ui_menu_item = [](const char* l) { return ImGui::MenuItem(l); }; + ctx->ui_text = [](const char* t) { ImGui::Text("%s", t); }; + ctx->ui_button = [](const char* l) { return ImGui::Button(l); }; + ctx->ui_checkbox = [](const char* l, bool* v) { return ImGui::Checkbox(l, v); }; + ctx->ui_slider_float = [](const char* l, float* v, float mn, float mx) { return ImGui::SliderFloat(l, v, mn, mx); }; + ctx->ui_input_float = [](const char* l, float* v) { return ImGui::InputFloat(l, v); }; + ctx->ui_color_edit3 = [](const char* l, float c[3]) { return ImGui::ColorEdit3(l, c); }; + ctx->ui_separator = []() { ImGui::Separator(); }; + ctx->ui_same_line = []() { ImGui::SameLine(); }; + + ctx->register_ui_callback = [](UIRegion region, PluginUICallback callback) { + if (g_plugin_manager) g_plugin_manager->register_ui_callback(region, callback); + }; +} + static void update_plugins(PluginManager& plugin_manager, Editor& editor) { static Editor* s_editor = nullptr; s_editor = &editor; - PluginContext ctx; - ctx.delta_time = GetDeltaTime(); - ctx.entity_count = (int)s_editor->scene.entities.size(); - ctx.selected = &s_editor->scene.selected; - - ctx.ui_begin = [](const char* t) { return ImGui::Begin(t); }; - ctx.ui_end = []() { ImGui::End(); }; - ctx.ui_text = [](const char* t) { ImGui::Text("%s", t); }; - ctx.ui_button = [](const char* l) { return ImGui::Button(l); }; - ctx.ui_checkbox = [](const char* l, bool* v) { return ImGui::Checkbox(l, v); }; - ctx.ui_slider_float = [](const char* l, float* v, float mn, float mx) { return ImGui::SliderFloat(l, v, mn, mx); }; - ctx.ui_input_float = [](const char* l, float* v) { return ImGui::InputFloat(l, v); }; - ctx.ui_color_edit3 = [](const char* l, float c[3]) { return ImGui::ColorEdit3(l, c); }; - ctx.ui_separator = []() { ImGui::Separator(); }; - ctx.ui_same_line = []() { ImGui::SameLine(); }; - - ctx.entity_get_name = [](int i) -> const char* { return s_editor->scene.entities[i].name.c_str(); }; - ctx.entity_get_position = [](int i, float* x, float* y, float* z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) { *x = t->position.x; *y = t->position.y; *z = t->position.z; } }; - ctx.entity_get_rotation = [](int i, float* x, float* y, float* z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) { *x = t->rotation.x; *y = t->rotation.y; *z = t->rotation.z; } }; - ctx.entity_get_scale = [](int i, float* x, float* y, float* z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) { *x = t->scale.x; *y = t->scale.y; *z = t->scale.z; } }; - ctx.entity_get_color = [](int i, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) { if (auto* m = s_editor->scene.entities[i].get_material_component()) { *r = m->color.r; *g = m->color.g; *b = m->color.b; *a = m->color.a; } }; - - ctx.entity_set_position = [](int i, float x, float y, float z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) t->position = {x, y, z}; }; - ctx.entity_set_rotation = [](int i, float x, float y, float z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) t->rotation = {x, y, z}; }; - ctx.entity_set_scale = [](int i, float x, float y, float z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) t->scale = {x, y, z}; }; - ctx.entity_set_color = [](int i, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { if (auto* m = s_editor->scene.entities[i].get_material_component()) m->color = {r, g, b, a}; }; - ctx.entity_set_name = [](int i, const char* name) { s_editor->scene.entities[i].name = name; }; - - ctx.scene_save = []() { project_save(s_editor->project_path, s_editor->scene); }; - ctx.scene_spawn = [](const char* asset_name) -> int { + ctx->scene = &s_editor->scene; + + ctx->delta_time = GetFrameTime(); + ctx->entity_count = (int)s_editor->scene.entities.size(); + ctx->selected = &s_editor->scene.selected; + + ctx->ui_begin = [](const char* t) { return ImGui::Begin(t); }; + ctx->ui_end = []() { ImGui::End(); }; + ctx->ui_begin_menu = [](const char* label) { return ImGui::BeginMenu(label); }; + ctx->ui_end_menu = []() { ImGui::EndMenu(); }; + ctx->ui_menu_item = [](const char* label) { return ImGui::MenuItem(label); }; + ctx->ui_text = [](const char* t) { ImGui::Text("%s", t); }; + ctx->ui_button = [](const char* l) { return ImGui::Button(l); }; + ctx->ui_checkbox = [](const char* l, bool* v) { return ImGui::Checkbox(l, v); }; + ctx->ui_slider_float = [](const char* l, float* v, float mn, float mx) { return ImGui::SliderFloat(l, v, mn, mx); }; + ctx->ui_input_float = [](const char* l, float* v) { return ImGui::InputFloat(l, v); }; + ctx->ui_color_edit3 = [](const char* l, float c[3]) { return ImGui::ColorEdit3(l, c); }; + ctx->ui_separator = []() { ImGui::Separator(); }; + ctx->ui_same_line = []() { ImGui::SameLine(); }; + + ctx->entity_get_name = [](int i) -> const char* { return s_editor->scene.entities[i].name.c_str(); }; + ctx->entity_get_position = [](int i, float* x, float* y, float* z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) { *x = t->position.x; *y = t->position.y; *z = t->position.z; } }; + ctx->entity_get_rotation = [](int i, float* x, float* y, float* z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) { *x = t->rotation.x; *y = t->rotation.y; *z = t->rotation.z; } }; + ctx->entity_get_scale = [](int i, float* x, float* y, float* z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) { *x = t->scale.x; *y = t->scale.y; *z = t->scale.z; } }; + ctx->entity_get_color = [](int i, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a) { if (auto* m = s_editor->scene.entities[i].get_material_component()) { *r = m->color.r; *g = m->color.g; *b = m->color.b; *a = m->color.a; } }; + + ctx->entity_set_position = [](int i, float x, float y, float z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) t->position = {x, y, z}; }; + ctx->entity_set_rotation = [](int i, float x, float y, float z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) t->rotation = {x, y, z}; }; + ctx->entity_set_scale = [](int i, float x, float y, float z) { if (auto* t = s_editor->scene.entities[i].get_transform_component()) t->scale = {x, y, z}; }; + ctx->entity_set_color = [](int i, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { if (auto* m = s_editor->scene.entities[i].get_material_component()) m->color = {r, g, b, a}; }; + ctx->entity_set_name = [](int i, const char* name) { s_editor->scene.entities[i].name = name; }; + + ctx->scene_save = []() { project_save(s_editor->project_path, s_editor->scene); }; + ctx->scene_spawn = [](const char* asset_name) -> int { for (auto& a : assets) { if (a.name == asset_name) { Entity e = make_entity_from_asset(s_editor->scene, a); @@ -121,15 +148,20 @@ static void update_plugins(PluginManager& plugin_manager, Editor& editor) { } return -1; }; - ctx.scene_delete = [](int i) { + ctx->scene_delete = [](int i) { if (i < 0 || i >= (int)s_editor->scene.entities.size()) return; s_editor->scene.entities.erase(s_editor->scene.entities.begin() + i); if (s_editor->scene.selected >= (int)s_editor->scene.entities.size()) s_editor->scene.selected = -1; }; + ctx->register_ui_callback = [](UIRegion region, PluginUICallback callback) { + if (g_plugin_manager) { + g_plugin_manager->register_ui_callback(region, callback); + } + }; - plugin_manager.update_all(ctx); - plugin_manager.draw_ui_all(ctx); + plugin_manager.update_all(*ctx); + plugin_manager.draw_ui_all(*ctx); } static Matrix compose_entity_transform(const Entity& entity) { @@ -436,6 +468,11 @@ int main(int argc, char* argv[]) { Mat4 light_view = {0}; Mat4 light_proj = {0}; + g_plugin_manager = new PluginManager(); + ctx = new PluginContext{}; + + init_plugin_context(ctx); + load_models(); load_textures(project_path); refresh_assets(project_path); @@ -446,8 +483,10 @@ int main(int argc, char* argv[]) { else project_new(project_path, editor.scene); - PluginManager plugin_manager; - plugin_manager.load_all("plugins"); + + g_plugin_manager->load_all("plugins", ctx); + editor.plugin_manager = g_plugin_manager; + std::string active_font_language = LanguageManager::get().current; while (!WindowShouldClose()) { @@ -584,9 +623,9 @@ int main(int argc, char* argv[]) { editor.handle_input(); - editor.draw_ui(shadowmap_shader, camera); + editor.draw_ui(shadowmap_shader, camera, ctx); - update_plugins(plugin_manager, editor); + update_plugins(*g_plugin_manager, editor); EndImGui(); EndDrawing(); @@ -598,7 +637,7 @@ int main(int argc, char* argv[]) { UnloadShader(shadowcaster_shader); UnloadShader(shadowmap_shader); unload_shadowmap_render_texture(shadow_map); - plugin_manager.unload_all(); + g_plugin_manager->unload_all(); ShutdownImGui(); CloseWindow(); return 0; diff --git a/src/plugins/plugin.h b/src/plugins/plugin.h index 43c3459..6bfea66 100644 --- a/src/plugins/plugin.h +++ b/src/plugins/plugin.h @@ -6,16 +6,48 @@ * * On Windows expands to `extern "C" __declspec(dllexport)`, which marks the * symbol for export from the DLL and suppresses C++ name-mangling. - * On all other platforms expands to `extern "C"` for C linkage only. + * On all other platforms expands to + * `extern "C" __attribute__((visibility("default")))`, which enables C linkage + * and explicitly exports the symbol from the shared library. + * * Apply to every symbol that must be discovered at runtime via * LoadLibrary / dlopen. */ #ifdef _WIN32 #define PLUGIN_EXPORT extern "C" __declspec(dllexport) #else - #define PLUGIN_EXPORT extern "C" + #define PLUGIN_EXPORT extern "C" __attribute__((visibility("default"))) #endif +struct PluginContext; +struct Scene; + +/** + * @enum UIRegion + * @brief Logical zones of the editor UI where plugins can inject custom UI. + * + * Used to reigster callbacks that will be called only when the corresponding + * part of the interface is being drawn. + */ +enum UIRegion { + UI_MENU_FILE, + UI_MENU_EDIT, + UI_MENU_HELP, + UI_HIERARCHY, + UI_INSPECTOR, + UI_SCENE +}; + +/** + * @typedef PluginUICallback + * @param ctx Pointer to the host-provided plugin context. + * @brief Function pointer type for UI callbacks executed in a specific UIRegion. + * + * Called by the host when rendering a registered UI region. Receives the + * current PluginContext for accessing UI and engine state. + */ +using PluginUICallback = void(*)(PluginContext*); + /** * @struct PluginContext * @brief Per-frame host state passed into every plugin callback. @@ -58,6 +90,27 @@ struct PluginContext { */ void (*ui_end)(); + /** + * @brief Begins a menu section in the UI (e.g. top menu bar entry). + * @param label Menu name. + * @return true if the menu is open and its items should be drawn. + */ + bool (*ui_begin_menu)(const char* label); + + /** + * @brief Ends the most recently opened menu section. + */ + void (*ui_end_menu)(); + + /** + * @brief Creates a clickable item inside a menu. + * @param label Item text. + * @return true when the item is clicked. + */ + bool (*ui_menu_item)(const char* label); + + void (*register_ui_callback)(UIRegion region, PluginUICallback callback); + /** * @brief Renders a read-only text label inside the current window. * @param text Null-terminated string to display. @@ -205,6 +258,11 @@ struct PluginContext { // Scene management // ------------------------------------------------------------------------- + /** + * @brief Current scene state owned by the host. + */ + Scene* scene; + /** * @brief Serialises the current scene to disk using the host's default * save path. Equivalent to the user pressing Ctrl-S. diff --git a/src/plugins/plugin_manager.cpp b/src/plugins/plugin_manager.cpp index c0a7cbe..938de00 100644 --- a/src/plugins/plugin_manager.cpp +++ b/src/plugins/plugin_manager.cpp @@ -70,7 +70,7 @@ void PluginManager::load(const std::string& filepath) { TraceLog(LOG_INFO, "PLUGIN: Loaded '%s' v%s", plugin->name, plugin->version); } -void PluginManager::load_all(const std::string& plugin_dir) { +void PluginManager::load_all(const std::string& plugin_dir, PluginContext* ctx) { if (!fs::exists(plugin_dir)) { fs::create_directories(plugin_dir); return; @@ -89,11 +89,10 @@ void PluginManager::load_all(const std::string& plugin_dir) { } for (auto& lp : plugins) { - PluginContext ctx; - ctx.delta_time = 0.0f; - ctx.entity_count = 0; - ctx.selected = nullptr; - if (lp.plugin->on_load) lp.plugin->on_load(&ctx); + ctx->delta_time = 0.0f; + ctx->entity_count = 0; + ctx->selected = nullptr; + if (lp.plugin->on_load) lp.plugin->on_load(ctx); } } @@ -115,4 +114,15 @@ void PluginManager::draw_ui_all(PluginContext& ctx) { for (auto& lp : plugins) { if (lp.plugin->on_draw_ui) lp.plugin->on_draw_ui(&ctx); } +} + +void PluginManager::register_ui_callback(UIRegion region, PluginUICallback callback) { + ui_callbacks.push_back({region, callback}); +} + +void PluginManager::draw_ui_region(UIRegion region, PluginContext& ctx) { + for (RegisteredUICallback cb : ui_callbacks) { + if (cb.region == region) + cb.callback(&ctx); + } } \ No newline at end of file diff --git a/src/plugins/plugin_manager.h b/src/plugins/plugin_manager.h index 783c615..9236cb2 100644 --- a/src/plugins/plugin_manager.h +++ b/src/plugins/plugin_manager.h @@ -1,16 +1,17 @@ #pragma once #include "plugin.h" #include +#include #include #ifdef _WIN32 + #define NOMINMAX #define WIN32_LEAN_AND_MEAN - - #define CloseWindow WinCloseWindow - #define ShowCursor WinShowCursor - #define Rectangle WinRectangle + #define NOGDI + #define NOUSER #include + #include #undef CloseWindow #undef ShowCursor @@ -27,16 +28,25 @@ struct LoadedPlugin { std::string filepath; }; +struct RegisteredUICallback { + UIRegion region; + PluginUICallback callback; +}; + class PluginManager { public: - void load_all(const std::string& plugin_dir); + void load_all(const std::string& plugin_dir, PluginContext* ctx); void load(const std::string& filepath); void unload_all(); void update_all(PluginContext& ctx); void draw_ui_all(PluginContext& ctx); + + void register_ui_callback(UIRegion region, PluginUICallback callback); + void draw_ui_region(UIRegion region, PluginContext& ctx); const std::vector& get_plugins() const { return plugins; } private: std::vector plugins; + std::vector ui_callbacks; }; \ No newline at end of file