From 50a7f9c75c57a8217d878575e507b9bd9b82f634 Mon Sep 17 00:00:00 2001 From: Andrew Story Date: Fri, 7 Jan 2022 15:08:25 -0600 Subject: [PATCH] UI: Add API to create simple browser docks --- UI/api-interface.cpp | 26 +++++ UI/obs-frontend-api/obs-frontend-api.cpp | 14 +++ UI/obs-frontend-api/obs-frontend-api.h | 5 + UI/obs-frontend-api/obs-frontend-internal.hpp | 4 + UI/window-basic-main.cpp | 33 ++++-- UI/window-basic-main.hpp | 9 ++ UI/window-extra-browsers.cpp | 104 ++++++++++++++---- docs/sphinx/reference-frontend-api.rst | 26 ++++- 8 files changed, 193 insertions(+), 28 deletions(-) diff --git a/UI/api-interface.cpp b/UI/api-interface.cpp index 3d6980b733c76a..061c7980e21fc8 100644 --- a/UI/api-interface.cpp +++ b/UI/api-interface.cpp @@ -370,6 +370,32 @@ struct OBSStudioAPI : obs_frontend_callbacks { return (void *)main->AddDockWidget((QDockWidget *)dock); } + void *obs_frontend_add_browser_dock(const char *id, const char *title, + const char *url) override + { +#ifdef BROWSER_AVAILABLE + QString qstrID = QT_UTF8(id); + QString qstrTitle = QT_UTF8(title); + QString qstrURL = QT_UTF8(url); + + return main->AddPluginBrowserDock(qstrID, qstrTitle, qstrURL); +#else + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(title); + UNUSED_PARAMETER(url); + return nullptr; +#endif + } + + void obs_frontend_remove_browser_dock(void *dock) override + { +#ifdef BROWSER_AVAILABLE + main->RemovePluginBrowserDock((QDockWidget *)dock); +#else + UNUSED_PARAMETER(dock); +#endif + } + void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) override { diff --git a/UI/obs-frontend-api/obs-frontend-api.cpp b/UI/obs-frontend-api/obs-frontend-api.cpp index 8231e9a17a1685..8164c2481ff2ef 100644 --- a/UI/obs-frontend-api/obs-frontend-api.cpp +++ b/UI/obs-frontend-api/obs-frontend-api.cpp @@ -324,6 +324,20 @@ void *obs_frontend_add_dock(void *dock) return !!callbacks_valid() ? c->obs_frontend_add_dock(dock) : nullptr; } +void *obs_frontend_add_browser_dock(const char *id, const char *title, + const char *url) +{ + return !!callbacks_valid() + ? c->obs_frontend_add_browser_dock(id, title, url) + : nullptr; +} + +void obs_frontend_remove_browser_dock(void *dock) +{ + if (callbacks_valid()) + c->obs_frontend_remove_browser_dock(dock); +} + void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) { diff --git a/UI/obs-frontend-api/obs-frontend-api.h b/UI/obs-frontend-api/obs-frontend-api.h index 2fdbe03674b1a3..ebab6637d07c63 100644 --- a/UI/obs-frontend-api/obs-frontend-api.h +++ b/UI/obs-frontend-api/obs-frontend-api.h @@ -135,6 +135,11 @@ EXPORT void obs_frontend_add_tools_menu_item(const char *name, /* takes QDockWidget and returns QAction */ EXPORT void *obs_frontend_add_dock(void *dock); +/* returns QDockWidget */ +EXPORT void *obs_frontend_add_browser_dock(const char *id, const char *title, + const char *url); +/* takes QDockWidget, calls delete on dock and its corresponding QAction */ +EXPORT void obs_frontend_remove_browser_dock(void *dock); typedef void (*obs_frontend_event_cb)(enum obs_frontend_event event, void *private_data); diff --git a/UI/obs-frontend-api/obs-frontend-internal.hpp b/UI/obs-frontend-api/obs-frontend-internal.hpp index f0d002b926c30a..a0d39ee516fff7 100644 --- a/UI/obs-frontend-api/obs-frontend-internal.hpp +++ b/UI/obs-frontend-api/obs-frontend-internal.hpp @@ -64,6 +64,10 @@ struct obs_frontend_callbacks { void *private_data) = 0; virtual void *obs_frontend_add_dock(void *dock) = 0; + virtual void *obs_frontend_add_browser_dock(const char *id, + const char *title, + const char *url) = 0; + virtual void obs_frontend_remove_browser_dock(void *dock) = 0; virtual void obs_frontend_add_event_callback(obs_frontend_event_cb callback, diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 386c75bdfbb0ac..c0ef7b3579d187 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1923,6 +1923,8 @@ void OBSBasic::OBSInit() #ifdef BROWSER_AVAILABLE if (cef) { + LoadPluginBrowserDocks(); + QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." "CustomBrowserDocks"), this); @@ -9530,8 +9532,30 @@ void OBSBasic::ResizeOutputSizeOfSource() QAction *OBSBasic::AddDockWidget(QDockWidget *dock) { + bool hasDock = false; + QString dockUUID = dock->property("uuid").toString(); + /* prune deleted docks and prevent adding the same dock again */ + for (int i = extraDocks.size() - 1; i >= 0; i--) { + if (!extraDocks[i]) { + extraDocks.removeAt(i); + continue; + } + if (!hasDock && + extraDocks[i]->property("uuid").toString() == dockUUID) { + hasDock = true; + } + } + if (hasDock) { + /* Return the existing action */ + for (const auto action : ui->menuDocks->actions()) { + if (action->property("uuid").toString() == dockUUID) { + return action; + } + } + } + QAction *action = ui->menuDocks->addAction(dock->windowTitle()); - action->setProperty("uuid", dock->property("uuid").toString()); + action->setProperty("uuid", dockUUID); action->setCheckable(true); assignDockToggle(dock, action); extraDocks.push_back(dock); @@ -9545,13 +9569,6 @@ QAction *OBSBasic::AddDockWidget(QDockWidget *dock) dock->setFeatures(features); - /* prune deleted docks */ - for (int i = extraDocks.size() - 1; i >= 0; i--) { - if (!extraDocks[i]) { - extraDocks.removeAt(i); - } - } - return action; } diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 41a5e6fb9f19a9..86b69a3ade8251 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -544,6 +544,15 @@ class OBSBasic : public OBSMainWindow { void ManageExtraBrowserDocks(); void AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate); + + QStringList pluginBrowserDockTargets; + QList> pluginBrowserDocks; + + void LoadPluginBrowserDocks(); + void *AddPluginBrowserDock(const QString &id, const QString &title, + const QString &url); + void RemovePluginBrowserDock(QDockWidget *dock); + #endif QIcon imageIcon; diff --git a/UI/window-extra-browsers.cpp b/UI/window-extra-browsers.cpp index 8f57b13755490e..2ecc0f566e250c 100644 --- a/UI/window-extra-browsers.cpp +++ b/UI/window-extra-browsers.cpp @@ -458,6 +458,9 @@ void OBSExtraBrowsers::on_apply_clicked() /* ------------------------------------------------------------------------- */ +static QAction *PrepareBrowserDockWindow(OBSBasic *api, BrowserDock *dock, + QString url, bool firstCreate); + void OBSBasic::ClearExtraBrowserDocks() { extraBrowserDockTargets.clear(); @@ -524,11 +527,6 @@ void OBSBasic::ManageExtraBrowserDocks() void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate) { - static int panel_version = -1; - if (panel_version == -1) { - panel_version = obs_browser_qcef_version(); - } - BrowserDock *dock = new BrowserDock(); QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid); bId.replace(QRegularExpression("[{}-]"), ""); @@ -539,6 +537,84 @@ void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, dock->setWindowTitle(title); dock->setAllowedAreas(Qt::AllDockWidgetAreas); + QAction *action = + PrepareBrowserDockWindow(this, dock, url, firstCreate); + if (!action) { + return; + } + + if (firstCreate) { + dock->setVisible(true); + action->blockSignals(true); + action->setChecked(true); + action->blockSignals(false); + } + + extraBrowserDocks.push_back(QSharedPointer(dock)); + extraBrowserDockActions.push_back(QSharedPointer(action)); + extraBrowserDockTargets.push_back(url); +} + +void OBSBasic::LoadPluginBrowserDocks() +{ + /* Deferred loading of plugin browser docks since CEF is initialized + at this point */ + for (int i = 0; i < pluginBrowserDocks.size(); ++i) { + auto dock = static_cast( + pluginBrowserDocks[i].data()); + (void)PrepareBrowserDockWindow( + this, dock, pluginBrowserDockTargets[i], true); + } +} + +void *OBSBasic::AddPluginBrowserDock(const QString &id, const QString &title, + const QString &url) +{ + + BrowserDock *dock = new BrowserDock(); + QString bId(QUuid::createUuid().toString()); + bId.replace(QRegularExpression("[{}-]"), ""); + dock->setProperty("uuid", bId); + dock->setObjectName(id + "_pluginBrowser"); + dock->resize(460, 600); + dock->setMinimumSize(80, 80); + dock->setWindowTitle(title); + dock->setAllowedAreas(Qt::AllDockWidgetAreas); + dock->setVisible(false); + + pluginBrowserDocks.push_back(dock); + pluginBrowserDockTargets.push_back(url); + + if (cef) { + (void)PrepareBrowserDockWindow(this, dock, url, true); + } + + return static_cast(dock); +} + +void OBSBasic::RemovePluginBrowserDock(QDockWidget *dock) +{ + QString dockUUID = dock->property("uuid").toString(); + for (const auto action : ui->menuDocks->actions()) { + if (action && action->property("uuid").toString() == dockUUID) { + delete action; + } + } + delete dock; +} + +/* fills a browser dock window with CEF and adds compatibility shims */ +static QAction *PrepareBrowserDockWindow(OBSBasic *api, BrowserDock *dock, + QString url, bool firstCreate) +{ + if (!dock) { + return nullptr; + } + static int panel_version = -1; + if (panel_version == -1) { + panel_version = obs_browser_qcef_version(); + } + QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr); if (browser && panel_version >= 1) @@ -564,30 +640,20 @@ void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, } } - addDockWidget(Qt::RightDockWidgetArea, dock); + api->addDockWidget(Qt::RightDockWidgetArea, dock); if (firstCreate) { dock->setFloating(true); - QPoint curPos = pos(); - QSize wSizeD2 = size() / 2; + QPoint curPos = api->pos(); + QSize wSizeD2 = api->size() / 2; QSize dSizeD2 = dock->size() / 2; curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width()); curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height()); dock->move(curPos); - dock->setVisible(true); } - QAction *action = AddDockWidget(dock); - if (firstCreate) { - action->blockSignals(true); - action->setChecked(true); - action->blockSignals(false); - } - - extraBrowserDocks.push_back(QSharedPointer(dock)); - extraBrowserDockActions.push_back(QSharedPointer(action)); - extraBrowserDockTargets.push_back(url); + return api->AddDockWidget(dock); } diff --git a/docs/sphinx/reference-frontend-api.rst b/docs/sphinx/reference-frontend-api.rst index 7bce5c5808f94b..e8342c0f95560d 100644 --- a/docs/sphinx/reference-frontend-api.rst +++ b/docs/sphinx/reference-frontend-api.rst @@ -79,7 +79,7 @@ Structures/Enumerations - **OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED** Triggered when the transition duration has been changed by the - user. + user. - **OBS_FRONTEND_EVENT_TBAR_VALUE_CHANGED** @@ -423,6 +423,30 @@ Functions --------------------------------------- +.. function:: void *obs_frontend_add_browser_dock(const char *id, const char *title, const char *url) + + Adds a plugin-controlled browser dock to the UI. The dock will automatically + be added to the Docks menu, however in order to retrieve the associated + QAction, you must call :c:func:`obs_frontend_add_dock()` with the returned + pointer. + + :param id: A unique ID used to identify this dock + :param title: Name of the dock to create + :param url: URL of page to show in the new dock + :return: A pointer to a new QDockWidget + +--------------------------------------- + +.. function:: void obs_frontend_remove_browser_dock(void *dock) + + Deletes a browser dock and any associated QActions. This is an alternative + to calling delete directly on the returned pointer for non-C++ plugins. + + :param dock: A pointer to a QDockWidget returned from + :c:func:`obs_frontend_add_browser_dock()` + +--------------------------------------- + .. function:: void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) Adds a callback that will be called when a frontend event occurs.