diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index e80b3eaf..7313a2e0 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -187,6 +187,10 @@ using CallbackRequest = std::variant +#include "bitsery/ext/std_optional.h" #include "../../common.h" #include "../base.h" +#include "../context-menu-target.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -105,19 +107,19 @@ class YaContextMenu : public Steinberg::Vst::IContextMenu { Steinberg::Vst::IContextMenuItem item; /** - * Will be true if the plugin passed a `target` pointer. I'm not sure if - * this is optional since there are no implementations for this - * interface to be found, but I can imagine that this could be optional - * for disabled menu items or for group starts/ends. + * Will be a nullopt if the plugin does not pass a `target` pointer. I'm + * not sure if this is optional since there are no implementations for + * this interface to be found, but I can imagine that this could be + * optional for disabled menu items or for group starts/ends. */ - bool has_target; + std::optional target; template void serialize(S& s) { s.value8b(owner_instance_id); s.value8b(context_menu_id); s.object(item); - s.value1b(has_target); + s.ext(target, bitsery::ext::StdOptional{}); } }; @@ -186,7 +188,7 @@ namespace Steinberg { namespace Vst { template void serialize(S& s, IContextMenuItem& item) { - s.text2b(item.name, std::extent_v); + s.text2b(item.name); s.value4b(item.tag); s.value4b(item.flags); } diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 4d493359..5e3b7d9b 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -18,6 +18,10 @@ #include "plug-view-proxy.h" +Vst3PluginProxyImpl::ContextMenu::ContextMenu( + Steinberg::IPtr menu) + : menu(menu) {} + Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge, Vst3PluginProxy::ConstructArgs&& args) : Vst3PluginProxy(std::move(args)), bridge(bridge) { diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index c68bdffd..4298736c 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -258,6 +258,26 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { */ Vst3PlugViewProxyImpl* last_created_plug_view = nullptr; + /** + * A pointer to a context menu returned by the host as a response to a call + * to `IComponentHandler3::createContextMenu`, as well as all targets we've + * created for it. This way we can drop both all at once. + */ + struct ContextMenu { + ContextMenu(Steinberg::IPtr menu); + + Steinberg::IPtr menu; + + /** + * All targets we pass to `IContextMenu::addItem`. We'll store them per + * item tag, so we can drop them together with the menu. We probably + * don't have to use smart pointers for this, but the docs are missing a + * lot of details o how this should be implemented and there's no + * example implementation around. + */ + std::map> targets; + }; + /** * All context menus created by this object through * `IComponentHandler3::createContextMenu()`. We'll generate a unique @@ -269,8 +289,7 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { * @see Vst3PluginProxyImpl::register_context_menu * @see Vst3PluginProxyImpl::unregister_context_menu */ - std::map> - context_menus; + std::map context_menus; std::mutex context_menus_mutex; // The following pointers are cast from `host_context` if diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 6906af23..cb09563f 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -17,6 +17,7 @@ #include "vst3.h" #include "src/common/serialization/vst3.h" +#include "vst3-impls/context-menu-target.h" #include "vst3-impls/plugin-factory.h" #include "vst3-impls/plugin-proxy.h" @@ -148,6 +149,57 @@ Vst3PluginBridge::Vst3PluginBridge() .context_menu_args = std::nullopt}; } }, + [&](const YaContextMenu::GetItemCount& request) + -> YaContextMenu::GetItemCount::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .context_menus.at(request.context_menu_id) + .menu->getItemCount(); + }, + [&](YaContextMenu::AddItem& request) + -> YaContextMenu::AddItem::Response { + Vst3PluginProxyImpl::ContextMenu& context_menu = + plugin_proxies.at(request.owner_instance_id) + .get() + .context_menus.at(request.context_menu_id); + + if (request.target) { + context_menu.targets[request.item.tag] = + Steinberg::owned(new YaContextMenuTargetImpl( + *this, std::move(*request.target))); + + return context_menu.menu->addItem( + request.item, + context_menu.targets[request.item.tag]); + } else { + return context_menu.menu->addItem(request.item, + nullptr); + } + }, + [&](const YaContextMenu::RemoveItem& request) + -> YaContextMenu::RemoveItem::Response { + Vst3PluginProxyImpl::ContextMenu& context_menu = + plugin_proxies.at(request.owner_instance_id) + .get() + .context_menus.at(request.context_menu_id); + + if (const auto it = + context_menu.targets.find(request.item.tag); + it != context_menu.targets.end()) { + return context_menu.menu->removeItem(request.item, + it->second); + } else { + return context_menu.menu->removeItem(request.item, + nullptr); + } + }, + [&](const YaContextMenu::Popup& request) + -> YaContextMenu::Popup::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .context_menus.at(request.context_menu_id) + .menu->popup(request.x, request.y); + }, [&](YaConnectionPoint::Notify& request) -> YaConnectionPoint::Notify::Response { return plugin_proxies.at(request.instance_id)