diff --git a/text/0047-switch-to-advanced-docking-system.md b/text/0047-switch-to-advanced-docking-system.md new file mode 100644 index 0000000..c729bd8 --- /dev/null +++ b/text/0047-switch-to-advanced-docking-system.md @@ -0,0 +1,385 @@ +# Summary + +- Switch main dock system to [ADS][1] +- Make ADS dock state per profile +- Allow the user to switch between pre-made layouts + - Plugins could register their own + - Users could save their own +- Dock added by plugins with the old method are added as "*Legacy dock*" +- Frontend API methods and events about ADS docks + +Note: The "central widget" design is kept. + +# Motivation + +Provide a better dock system to end-users. + +# Design +**DISCLAIMER**: The "central widget" design is kept, switching to a non-"central widget" one is not part of this RFC. + +## Main window dock separation +To make OBS Studio compatible with ADS, Controls, Transitions, Mixer, Sources, Scenes docks need to be separated from the main window. + +And the central widget of main window should also be separated as well to become the central dock. + +So the docks will be separated as widget class with their own UI class. + +Then those widget are inserted in OBSDock (QDockWidget) and the central widget and put as main window central widget. + +And then we switch to ADS, all those widget will become docks and added the ADS dock manager. + +The Main window heavily rely on some UI element from those docks, to resolve that when really required OBSBasic will be set as a friend class of the widget. + +Each widget class will be put as a friend class in OBSBasic to allow access to private slot and avoid moving them as public. + +### Controls widget +*Future ADS Controls dock widget* + +Some hotkeys and pause functions rely on UI buttons, so OBS Basic will be added as friend class to allow access to those to OBSBasic. + +This class will require to have OBSBasic as a friend class because: +- The streaming hotkey pair rely on the stream button enabled/disabled state. +- The recording hotkey pair rely on the record button checked state. +- The pause hotkey pair rely on the pause button pointer and checked state. +- Pause and unpause recording function rely on the pause button pointer. + + +*Note: In my [WIP implementation][2], many signals are added to OBSBasic to interract with controls dock UI through slots. Some of them happen to be used by other docks.* + +### Transistions widget +*Future ADS Transitions dock widget* + +The transition duration will become an attribute of OBSBasic, and signal and slots will be setup to synchromise it with the dock spinbox. + +This class will require to have OBSBasic as a friend class because the transition combobox store the transitions themselves. + +### Mixer widget +*Future ADS Mixer dock widget* + +Easily separable, no need to set OBSBasic as a friend class of this widget. + +### Sources widget +*Future ADS Sources dock widget* + +OBSBasic heavily rely on the SourceTree from the widget, so for now OBSBasic will access it by being a friend class of the widget. + +### Scenes widget +*Future ADS Scenes dock widget* + +OBSBasic heavily rely on the SceneTree from the widget, so for now OBSBasic will access it by being a friend class of the widget. + +### Central widget +*Future ADS Central dock widget* + +Some context bar, preview/program and nudge related code is moved to the central widget class. + +This class will have OBSBasicPreview as a friend class. + +This class will be a friend class of OBSBasic also to have access to some gs_vertbuffer_t type attributes. + +## ADS +The Advanced Docking System, require to add a Dock Manager in the main window and then add the central widget and then add other docks. + +Some work on themes CSS will be required. + +### About obs-deps + +obs-deps already build Qt for macOS, so adding a ADS script for the Qt tarball is not a problem. + +But in the case of Windows, there is some issues: +- ADS does not use debug Qt DLLs when building OBS with `Debug` config. So it may require to copy non-debug one just for ADS. +- It needs to build Qt or repackage it to include ADS. + +### About ADS translations +OBS does not use the Qt translations but a customised one which use source text without spaces only. + +So in the `OBSTranslator::translate()` function, removing spaces from source text will to translate ADS texts. + +In `OBSTranslator::translate()` function: +```c++ + if (QT_UTF8(sourceText).contains(" ")) { + QString text(QT_UTF8(sourceText).remove(" ")); + sourceText = strdup(QT_TO_UTF8(text)); + } +``` + +In `en-US.ini` file: +```ini +# ADS +CloseOthers="Close Others" +CloseTab="Close Tab" +CloseActiveTab="Close Active Tab" +ShowView="Show View" +DetachGroup="Detach Group" +CloseGroup="Close Group" +CloseOtherGroups="Close Other Groups" +ListAllTabs="List All Tabs" +``` + +### Floating docks titlebar on X11 +On X11, ADS provide two type of titlebar for floating docks: + +- Native + +[![Native titlebar under GNOME](./0047-switch-to-advanced-docking-system/linux_native_floating_dock_titlebar.png)]() + +- QWidget based (WIP CSS) + +[![QWidget titlebar](./0047-switch-to-advanced-docking-system/linux_qwidget_floating_dock_titlebar.png)]() + +The QWidget one is used by default when using KWin based Desktop Environement like Plasma. And this titlebar requires some CSS in the themes to match it. + +So the QWidget based titlebar usage will be enforced thanks to a flag for the Dock Manager to "force" theme makers to theme this bar, and not just ignore it because it's only "under Plasma X11". + +### Dock state +*The per profile dock state feature is not taken into account to describe those changes.* + +In the global config (`global.ini`). +- `"dockState"` is kept for for backward compatiblity and no longer overwitten. It will be used if the following state is not present. +- `"windowState"` is the state of the main window, since it does not store only the state of legacy docks. +- `"advDockState"` is the state of the dock manager which contain only the states of any ADS dock. + +Service integrations only save `"advDockProfile"` + +ADS dock state is compressed by default but behind the scene it's XML. + +### OBSAdvDock +OBSAdvDock is a class which inherit `ads::CDockWidget` class. + +This class has a constructor which require a QWidget and setup some things arround the widget and set some connections. + +It adds a warning message when closing a dock from a close dock button. + +It allows to reset a dock position after a UI reset or a layout change with a possibility to reset the size if set beforehand. + +### Custom Browser docks and BrowserAdvDock +The custom browser docks feature is modified to use BrowserAdvDock which inherit OBSAdvDock. + +Those docks are stored in the dock manager and their names is stored in a QStringList for browser docks to be able to get the dock from the manager to modify it like changing the URL. + +Each of those browser dock is named `extraBrowser_$UUID` where `$UUID` is replaced by the dock UUID to have a really unique name. + +### Service integration docks + +Like custom browser docks, those integration are modified to use BrowserAdvDock. But their names is stored in the QStringList for plugins extra docks. + +Those docks are named `obs-$SERVICE_$DOCK_NAME` where `$SERVICE` is the service name in lowercase and `$DOCK_NAME` the name of the dock. + +`"dockState"` will be imported from the integration config if `"windowState"` is not present. + +### Reset UI action +1. Legacy dock are hidden +2. The default state written in XML is applied +3. Each not shown OBSAdvDock/BrowserAdvDock dock have its position and size reseted. + +## Legacy dock +The frontend API method `obs_frontend_add_dock()` is put in deprecation. + +And dock added through this method are added to a sub-menu named `Legacy dock` of the Dock menu. + +[![Legacy dock menu](./0047-switch-to-advanced-docking-system/legacy_dock.png)]() + +When openning a legacy dock, a message will appear explaining that those docks will not meld wery well with "new" docks. + +The state of those are saved through `"windowState"` global config. + +## Per profile dock state +Move the `"advDockState"` from global config to the profile and integration no longer store their own state. `"windowState"` is kept global. + +If a release happen between the switch to ADS and this feature, import the one from the integration service if the profile has one set up. + +## Layouts management +Add the feature, to switch between registered/saved dock layouts: +- Though a sub-menu in the dock menu +- Through a hotkey +- Through the frontend API + +OBS Studio could provide layouts, the default count as one. + +Plugins could also register their own XML layouts through the frontend API. + +Users could be able to save their own layouts. Those layouts will have their name prefixed with`user_` to avoid name conflicts. + +Note: ADS perspective feature is not directly used because it relies heavily on QSettings. + +## Frontend API +Like said earlier, the method `obs_frontend_add_dock()` is put in deprecation. + +All add/remove methods related to ADS requiring a name will require the plugin module to be able to prefix the given name with the module name to avoid conflicts. + +### Add a dock +```c++ +/* takes QWidget */ +#define obs_frontend_add_adv_dock(title, unique_name, dock) \ + obs_frontend_add_module_adv_dock(obs_current_module(), title, \ + unique_name, widget) +EXPORT void obs_frontend_add_module_adv_dock(obs_module_t *module, + const char *title, + const char *unique_name, + void *widget); +``` + +This allow to add a OBSAdvDock with the given QWidget. Those docks are stored in the Dock Manager and their names is stored in the QStringList for plugins extra docks. + +Default height and width could be set through `"defaultHeight"` `"defaultHeight"` with the [`setProperty()`][7] method. + +Minimum sizes used by the dock are based on the widget ones. + +### Remove a dock +```c++ +#define obs_frontend_remove_adv_dock(unique_name) \ + obs_frontend_remove_module_adv_dock(obs_current_module(), unique_name) +EXPORT void obs_frontend_remove_module_adv_dock(obs_module_t *module, + const char *unique_name); +``` + +This allow the plugin to remove a dock added earlier. + +### Add a browser dock +```c++ +#define obs_frontend_add_adv_browser_dock(dock_params, browser_params) \ + obs_frontend_add_module_adv_browser_dock(obs_current_module(), \ + dock_params, browser_params) +EXPORT bool obs_frontend_add_module_adv_browser_dock(obs_module_t *module, + struct obs_frontend_browser_dock_params *dock_params, + struct obs_frontend_browser_params *browser_params) +``` + +This allow the plugin to add a dock with a QCefWidget as widget. Return false if the browser dock could not be created. + +The QCefWidget will get parameters from this structure: + +```c++ +struct obs_frontend_browser_params { + const char *url; + bool enable_cookie; + struct dstr startup_script; + DARRAY(char *) force_popup_urls; +}; +``` + +- `bool enable_cookie`: if true `panel_cookie` will be used. The plugin maker will have to remove the dock the between profile change because the cookie manager is per profile. +- `struct dstr startup_script` allow to set a startup script for the QCefWidget. +- `DARRAY(char *) force_popup_urls` allow to set a list of url forced to popup. + +And the dock itself will get parameters from this structure: + +```c++ +struct obs_frontend_browser_dock_params { + const char *unique_name; + const char *title; + int default_width; + int default_height; + int min_width; + int min_height; +}; +``` + +`obs_frontend_remove_adv_dock()` can be used to remove the browser dock. + +### Add a dock layouts +```c++ +#define obs_frontend_add_adv_dock_layout(title, unique_name, xml_layout) \ + obs_frontend_add_module_adv_dock_layout(obs_current_module(), \ + title, unique_name, xml_layout) +EXPORT void obs_frontend_add_module_adv_dock_layout(obs_module_t *module, + const char *title, + const char *unique_name, + const char *xml_layout); +``` + +This allow the plugin to add a dock layouts in XML to the UI. ADS allow to test a layout (state/perspective) without applying it, so the layout will be tested before registering it. + +### Get a list of docks layouts +```c++ +EXPORT char **obs_frontend_get_adv_dock_layouts(void); +``` + +This allow the plugin to get a list of registered dock layouts from the UI. + +### Set a registered dock layout +```c++ +EXPORT void obs_frontend_set_adv_dock_layouts(const char *layout_name); +``` + +This allow the plugin to set a registered dock layouts to the UI, the asked name should come directly from the get list method. + +### Remove a dock layouts +```c++ +#define obs_frontend_remove_adv_dock_layout(unique_name) \ + obs_frontend_remove_module_adv_dock_layout(obs_current_module(), \ + unique_name) +EXPORT void obs_frontend_remove_module_adv_dock_layout(obs_module_t *module, + const char *unique_name); +``` + +This allow the plugin to remove a dock layouts from the UI. + +### Add a entirely custom dock +```c++ +/* takes ads::CDockWidget */ +#define obs_frontend_add_custom_adv_dock(unique_name, dock) \ + obs_frontend_add_module_custom_adv_dock(obs_current_module(), \ + unique_name, dock) +EXPORT void obs_frontend_add_module_custom_adv_dock(obs_module_t *module, + const char *unique_name, + void *dock); +``` + +Some plugin like [Sources Dock][6], do not add their docks to the Dock menu. + +So this method allow to do this but requires the plugin to be link against ADS library. + +And the dock will not have OBSAdvDock features. + +`obs_frontend_remove_adv_dock()` can be used to remove the reference stored in the Dock Manager. Because their names is stored in the QStringList for plugins custom extra docks. + +### Get the XML behind a registered dock layout +*Method meant to allow a Dock Layout editor tool to exist* + +```c++ +EXPORT char *obs_frontend_get_current_profile(const char *layout_name); +``` + +This allow the plugin to get a registered dock layouts XML, the asked name should come directly from the get list method. + +### Get the XML of the actual dock state +*Method meant to allow a Dock Layout editor tool to exist* + +```c++ +EXPORT char *obs_frontend_get_current_profile(const char *layout_name); +``` + +This allow the plugin to get the dock states XML of the UI. + +### Events addition +- An event before the startup restore dock state and `OBS_FRONTEND_EVENT_FINISHED_LOADING` to allow plugins to load their docks before the restore or redo a restore if the number of extra docks has changed. This event could possibly be emitted when profile is changed before restoring profile dock state and `OBS_FRONTEND_EVENT_PROFILE_CHANGED`. + +- An event when the a dock layout is applied (reset or not) to allow plugins to reset the positions of their customs docks, if they want to. + +## About making dock states future proof +By default generated state are version 0. If one day we make a breaking change like remove the notion of central widget and so change the version. + +We could take the saved `"advDockState"` and uncompress to edit the XML, to make it compatible with the change. + +# Drawbacks +We can't convert old `"dockState"` to the new dock system. + +# Additional Information +We may require to make some changes to allow ADS to be built on FreeBSD and submit the required changes to upstream. **I'm just waiting for a build guide for OBS Studio on FreeBSD to make it.** + +About Wayland support, Qt and ADS docking system have very bad support because of Qt Wayland. The Wayland backend is apparently third-class on Qt's priorities. + +My WIP branches: +- [Main window dock separation][2] +- [Switch to ADS][3] +- [Frontend API to add ADS dock][4] +- [Frontend API to add custom ADS dock][5] + +[1]: https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System +[2]: https://github.com/tytan652/obs-studio/tree/main_win_separation +[3]: https://github.com/tytan652/obs-studio/tree/advanced_docking +[4]: https://github.com/tytan652/obs-studio/tree/ads_frontend_api +[5]: https://github.com/tytan652/obs-studio/tree/add_custom_dock +[6]: https://obsproject.com/forum/resources/source-dock.1317/ +[7]: https://doc.qt.io/qt-5/qobject.html#setProperty \ No newline at end of file diff --git a/text/0047-switch-to-advanced-docking-system/legacy_dock.png b/text/0047-switch-to-advanced-docking-system/legacy_dock.png new file mode 100644 index 0000000..4139276 Binary files /dev/null and b/text/0047-switch-to-advanced-docking-system/legacy_dock.png differ diff --git a/text/0047-switch-to-advanced-docking-system/linux_native_floating_dock_titlebar.png b/text/0047-switch-to-advanced-docking-system/linux_native_floating_dock_titlebar.png new file mode 100644 index 0000000..5447394 Binary files /dev/null and b/text/0047-switch-to-advanced-docking-system/linux_native_floating_dock_titlebar.png differ diff --git a/text/0047-switch-to-advanced-docking-system/linux_qwidget_floating_dock_titlebar.png b/text/0047-switch-to-advanced-docking-system/linux_qwidget_floating_dock_titlebar.png new file mode 100644 index 0000000..ca31c87 Binary files /dev/null and b/text/0047-switch-to-advanced-docking-system/linux_qwidget_floating_dock_titlebar.png differ