diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini
index b2892163f963c2..5dc11081b856b9 100644
--- a/UI/data/locale/en-US.ini
+++ b/UI/data/locale/en-US.ini
@@ -141,6 +141,8 @@ TwitchAuth.Feed="Twitch Activity Feed"
TwitchAuth.TwoFactorFail.Title="Could not query stream key"
TwitchAuth.TwoFactorFail.Text="OBS was unable to connect to your Twitch account. Please make sure two-factor authentication is set up in your Twitch security settings as this is required to stream."
RestreamAuth.Channels="Restream Channels"
+SetFloating="Set Floating"
+SetDocked="Set Docked"
# copy filters
Copy.Filters="Copy Filters"
diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss
index 54e91b2cc5416d..d4a8eef8eee5e9 100644
--- a/UI/data/themes/Acri.qss
+++ b/UI/data/themes/Acri.qss
@@ -154,10 +154,6 @@ QListView QLineEdit {
}
/* Dock stuff */
-QDockWidget {
- titlebar-close-icon: url('./Dark/close.svg');
- titlebar-normal-icon: url('./Dark/popout.svg');
-}
QDockWidget {
background: palette(window);
@@ -167,7 +163,7 @@ QDockWidget {
border-bottom: 2px solid rgb(47,47,47);
}
-QDockWidget::title {
+QFrame#dockTitleBar {
border-bottom: 2px solid rgb(47,47,47);
margin-left: 5px;
margin-right: 5px;
@@ -181,20 +177,16 @@ QDockWidget::title {
background-repeat: none;
}
-QDockWidget::close-button,
-QDockWidget::float-button {
- icon-size: 20px;
- subcontrol-position: top right;
- subcontrol-origin: padding;
- right: 0px;
- margin: 0px;
+QFrame#dockTitleBar > QLabel {
+ font-weight: bold;
+ font-size: 12px;
+ background: transparent;
}
-QDockWidget::float-button {
- right: 20px;
+* [themeID="dotsIcon"] {
+ qproperty-icon: url(./Dark/dots.svg);
}
-
QListView#scenes,
SourceListWidget {
border: none;
diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss
index fa1e939392abff..679b00ad100f87 100644
--- a/UI/data/themes/Dark.qss
+++ b/UI/data/themes/Dark.qss
@@ -119,28 +119,34 @@ SourceTree QLineEdit {
/* Dock Widget */
-QDockWidget {
- titlebar-close-icon: url('./Dark/close.svg');
- titlebar-normal-icon: url('./Dark/popout.svg');
+QFrame#dockTitleBar {
+ background-color: rgb(70,69,70);
+}
+
+QFrame#dockTitleBar > QLabel {
+ font-weight: bold;
+ font-size: 12px;
+ background: transparent;
}
-QDockWidget::title {
- text-align: center;
+QPushButton#dockTitleBarButton::flat {
background-color: rgb(70,69,70);
}
-QDockWidget::close-button, QDockWidget::float-button {
- border: 1px solid transparent;
- background: transparent;
- padding: 0px;
+QPushButton#dockTitleBarButton::flat:hover {
+ background-color: rgb(122,121,122); /* light */
}
-QDockWidget::close-button:hover, QDockWidget::float-button:hover {
- background: transparent;
+QPushButton#dockTitleBarButton::flat:pressed {
+ background-color: palette(base);
+}
+
+QPushButton#dockTitleBarButton::flat:disabled {
+ background-color: rgb(46,45,46);
}
-QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
- padding: 1px -1px -1px 1px;
+* [themeID="dotsIcon"] {
+ qproperty-icon: url(./Dark/dots.svg);
}
/* Group Box */
diff --git a/UI/data/themes/Dark/dots.svg b/UI/data/themes/Dark/dots.svg
new file mode 100644
index 00000000000000..137d22b7dc1ba9
--- /dev/null
+++ b/UI/data/themes/Dark/dots.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss
index b0f367ae7be816..f57ef0b6bce238 100644
--- a/UI/data/themes/Rachni.qss
+++ b/UI/data/themes/Rachni.qss
@@ -188,38 +188,45 @@ QListView::item:hover:!active {
/* --- Dock widget --- */
/***********************/
-QDockWidget {
- titlebar-close-icon: url('./Dark/close.svg');
- titlebar-normal-icon: url('./Dark/popout.svg');
-}
-
QDockWidget {
background: palette(window);
border: 1px solid rgb(58, 64, 69); /* Light Blue-gray */
}
-QDockWidget::title {
- text-align: left;
+QFrame#dockTitleBar {
background: palette(shadow);
padding-left: 5px;
}
-
-QDockWidget::close-button, QDockWidget::float-button {
- border: 1px solid transparent;
- border-radius: 2px;
+QFrame#dockTitleBar > QLabel {
+ font-weight: bold;
+ font-size: 12px;
background: transparent;
}
-QDockWidget::close-button:hover, QDockWidget::float-button:hover {
- background: rgba(255, 148, 194, 0.25); /* Light Pink (Secondary Light) */
+QPushButton#dockTitleBarButton::flat {
+ background-color: palette(shadow);
+ border: none;
+ outline: none;
+}
+
+QPushButton#dockTitleBarButton::flat:hover {
+ background-color: rgba(240, 98, 146, 0.5); /* Pink (Secondary) */
+ margin: 0;
+ padding: 0;
+ border-radius: 2px;
+ border: none;
+ outline: none;
}
-QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
- padding: 1px -1px -1px 1px;
- background: rgba(255, 148, 194, 0.25); /* Light Pink (Secondary Light) */
+QPushButton#dockTitleBarButton::flat:pressed {
+ background-color: rgb(240, 98, 146); /* Pink (Secondary) */
+ border: 1px solid rgb(240, 98, 146); /* Pink (Secondary) */
}
+* [themeID="dotsIcon"] {
+ qproperty-icon: url(./Dark/dots.svg);
+}
/***********************/
/* --- Group Boxes --- */
diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss
index 82dfbc42de9a44..d7a01fbcd0df75 100644
--- a/UI/data/themes/System.qss
+++ b/UI/data/themes/System.qss
@@ -357,4 +357,15 @@ QCalendarWidget #qt_calendar_nextmonth {
padding: 2px;
qproperty-icon: url(./Dark/expand.svg);
icon-size: 16px, 16px;
+}
+
+/* Dock Title Bar */
+#dockTitleBar > QLabel {
+ font-weight: bold;
+ font-size: 12px;
+ background: transparent;
+}
+
+* [themeID="dotsIcon"] {
+ qproperty-icon: url(:/res/images/dots.svg);
}
\ No newline at end of file
diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui
index be96c96a8d3a0d..4d94a10cd4ab76 100644
--- a/UI/forms/OBSBasic.ui
+++ b/UI/forms/OBSBasic.ui
@@ -667,7 +667,6 @@
-
@@ -800,24 +799,6 @@
- -
-
-
-
- 16
- 16
-
-
-
- false
-
-
-
-
-
-
-
-
-
@@ -935,25 +916,6 @@
- -
-
-
-
- 16
- 16
-
-
-
- false
-
-
-
-
-
-
-
-
-
-
diff --git a/UI/forms/images/dots.svg b/UI/forms/images/dots.svg
new file mode 100644
index 00000000000000..cd0c79abb12a35
--- /dev/null
+++ b/UI/forms/images/dots.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc
index e6769cc2611253..8f67b249bfcc48 100644
--- a/UI/forms/obs.qrc
+++ b/UI/forms/obs.qrc
@@ -56,6 +56,7 @@
images/media/media_restart.svg
images/media/media_stop.svg
images/interact.svg
+ images/dots.svg
images/settings/output.svg
diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp
index 7228e54ae8709f..243158c3f0c4c8 100644
--- a/UI/window-basic-main.cpp
+++ b/UI/window-basic-main.cpp
@@ -267,14 +267,12 @@ OBSBasic::OBSBasic(QWidget *parent)
statsDock->setFeatures(QDockWidget::DockWidgetClosable |
QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable);
- statsDock->setWindowTitle(QTStr("Basic.Stats"));
+ statsDock->SetTitle(QTStr("Basic.Stats"));
addDockWidget(Qt::BottomDockWidgetArea, statsDock);
statsDock->setVisible(false);
statsDock->setFloating(true);
statsDock->resize(700, 200);
- copyActionsDynamicProperties();
-
char styleSheetPath[512];
int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath),
"stylesheet.qss");
@@ -573,26 +571,6 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
return saveData;
}
-void OBSBasic::copyActionsDynamicProperties()
-{
- // Themes need the QAction dynamic properties
- for (QAction *x : ui->scenesToolbar->actions()) {
- QWidget *temp = ui->scenesToolbar->widgetForAction(x);
-
- for (QByteArray &y : x->dynamicPropertyNames()) {
- temp->setProperty(y, x->property(y));
- }
- }
-
- for (QAction *x : ui->sourcesToolbar->actions()) {
- QWidget *temp = ui->sourcesToolbar->widgetForAction(x);
-
- for (QByteArray &y : x->dynamicPropertyNames()) {
- temp->setProperty(y, x->property(y));
- }
- }
-}
-
void OBSBasic::UpdateVolumeControlsDecayRate()
{
double meterDecayRate =
@@ -1923,6 +1901,8 @@ void OBSBasic::OBSInit()
sysTrayEnabled &&
(opt_minimize_tray || sysTrayWhenStarted);
+ SetupDockTitleBars();
+
#ifdef _WIN32
SetWin32DropStyle(this);
@@ -8892,15 +8872,9 @@ void OBSBasic::on_lockUI_toggled(bool lock)
extraDocks[i]->setFeatures(features);
}
}
-}
-void OBSBasic::on_toggleListboxToolbars_toggled(bool visible)
-{
- ui->sourcesToolbar->setVisible(visible);
- ui->scenesToolbar->setVisible(visible);
-
- config_set_bool(App()->GlobalConfig(), "BasicWindow",
- "ShowListboxToolbars", visible);
+ QList list = findChildren();
+ foreach(OBSDock * dock, list) { dock->EnableControlMenu(lock); }
}
void OBSBasic::ShowContextBar()
@@ -10140,3 +10114,37 @@ void OBSBasic::SetDisplayAffinity(QWindow *window)
UNUSED_PARAMETER(hideFromCapture);
#endif
}
+
+void OBSBasic::SetupDockTitleBars()
+{
+ ui->scenesDock->SetTitle(QTStr("Basic.Main.Scenes"));
+ ui->scenesDock->AddButton("addIconSmall", QTStr("Add"), this,
+ SLOT(on_actionAddScene_triggered()));
+ ui->scenesDock->AddButton("removeIconSmall", QTStr("Remove"), this,
+ SLOT(on_actionRemoveScene_triggered()));
+ ui->scenesDock->AddSeparator();
+ ui->scenesDock->AddButton("upArrowIconSmall", QTStr("MoveUp"), this,
+ SLOT(on_actionSceneUp_triggered()));
+ ui->scenesDock->AddButton("downArrowIconSmall", QTStr("MoveDown"), this,
+ SLOT(on_actionSceneDown_triggered()));
+
+ ui->sourcesDock->SetTitle(QTStr("Basic.Main.Sources"));
+ ui->sourcesDock->AddButton("addIconSmall", QTStr("Add"), this,
+ SLOT(on_actionAddSource_triggered()));
+ ui->sourcesDock->AddButton("removeIconSmall", QTStr("Remove"), this,
+ SLOT(on_actionRemoveSource_triggered()));
+ ui->sourcesDock->AddButton("configIconSmall", QTStr("Properties"), this,
+ SLOT(on_actionSourceProperties_triggered()));
+ ui->sourcesDock->AddSeparator();
+ ui->sourcesDock->AddButton("upArrowIconSmall", QTStr("MoveUp"), this,
+ SLOT(on_actionSourceUp_triggered()));
+ ui->sourcesDock->AddButton("downArrowIconSmall", QTStr("MoveDown"),
+ this, SLOT(on_actionSourceDown_triggered()));
+
+ ui->mixerDock->SetTitle(QTStr("Mixer"));
+ ui->transitionsDock->SetTitle(QTStr("Basic.SceneTransitions"));
+ ui->controlsDock->SetTitle(QTStr("Basic.Main.Controls"));
+
+ QList list = findChildren();
+ foreach(OBSDock * dock, list) { dock->AddControlMenu(); }
+}
diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp
index 06a335007687df..bfa73a7791f4d2 100644
--- a/UI/window-basic-main.hpp
+++ b/UI/window-basic-main.hpp
@@ -185,6 +185,7 @@ class OBSBasic : public OBSMainWindow {
friend class OBSYoutubeActions;
friend struct BasicOutputHandler;
friend struct OBSStudioAPI;
+ friend class OBSDock;
enum class MoveDir { Up, Down, Left, Right };
@@ -240,7 +241,7 @@ class OBSBasic : public OBSMainWindow {
QPointer transformWindow;
QPointer advAudioWindow;
QPointer filters;
- QPointer statsDock;
+ QPointer statsDock;
QPointer about;
QPointer missDialog;
QPointer logView;
@@ -538,6 +539,8 @@ class OBSBasic : public OBSMainWindow {
void ReceivedIntroJson(const QString &text);
void ShowWhatsNew(const QString &url);
+ void SetupDockTitleBars();
+
#ifdef BROWSER_AVAILABLE
QList> extraBrowserDocks;
QList> extraBrowserDockActions;
@@ -804,7 +807,6 @@ private slots:
void AddSource(const char *id);
QMenu *CreateAddSourcePopupMenu();
void AddSourcePopupMenu(const QPoint &pos);
- void copyActionsDynamicProperties();
static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed);
@@ -1062,7 +1064,6 @@ private slots:
void on_actionAlwaysOnTop_triggered();
- void on_toggleListboxToolbars_toggled(bool visible);
void on_toggleContextBar_toggled(bool visible);
void on_toggleStatusBar_toggled(bool visible);
void on_toggleSourceIcons_toggled(bool visible);
diff --git a/UI/window-dock.cpp b/UI/window-dock.cpp
index ed8e928609c0eb..2acde52da731a8 100644
--- a/UI/window-dock.cpp
+++ b/UI/window-dock.cpp
@@ -3,6 +3,31 @@
#include
#include
+#include
+#include
+#include
+#include
+
+OBSDock::OBSDock(QWidget *parent) : QDockWidget(parent)
+{
+ QFrame *titleBar = new QFrame(this);
+
+ titleBar->setObjectName("dockTitleBar");
+
+ title = new QLabel(titleBar);
+ title->setAlignment(Qt::AlignVCenter);
+
+ layout = new QHBoxLayout(titleBar);
+ layout->setContentsMargins(4, 2, 4, 2);
+ layout->setSpacing(4);
+
+ layout->addWidget(title);
+ layout->addStretch();
+
+ titleBar->setLayout(layout);
+
+ setTitleBarWidget(titleBar);
+}
void OBSDock::closeEvent(QCloseEvent *event)
{
@@ -34,3 +59,75 @@ void OBSDock::closeEvent(QCloseEvent *event)
QDockWidget::closeEvent(event);
}
+
+void OBSDock::ToggleFloating()
+{
+ if (isFloating())
+ setFloating(false);
+ else
+ setFloating(true);
+}
+
+void OBSDock::ShowControlMenu()
+{
+ QMenu popup;
+
+ QDockWidget::DockWidgetFeatures dockFeatures = features();
+
+ if ((QDockWidget::DockWidgetClosable & dockFeatures) != 0)
+ popup.addAction(QTStr("Hide"), this, SLOT(close()));
+
+ popup.addAction(isFloating() ? QTStr("SetDocked")
+ : QTStr("SetFloating"),
+ this, SLOT(ToggleFloating()));
+
+ popup.exec(QCursor::pos());
+}
+
+void OBSDock::EnableControlMenu(bool lock)
+{
+ menuButton->setVisible(!lock);
+}
+
+void OBSDock::AddControlMenu()
+{
+ menuButton = new QPushButton(this);
+ menuButton->setFlat(true);
+ menuButton->setObjectName("dockTitleBarButton");
+ menuButton->setFixedSize(16, 16);
+ menuButton->setIconSize(QSize(12, 12));
+ menuButton->setProperty("themeID", "dotsIcon");
+
+ connect(menuButton, SIGNAL(clicked()), this, SLOT(ShowControlMenu()));
+
+ layout->addWidget(menuButton);
+}
+
+void OBSDock::SetTitle(const QString &newTitle)
+{
+ title->setText(newTitle);
+}
+
+void OBSDock::AddButton(const QString &themeID, const QString &toolTip,
+ const QObject *receiver, const char *slot)
+{
+ QPushButton *button = new QPushButton(this);
+ button->setFlat(true);
+ button->setObjectName("dockTitleBarButton");
+ button->setFixedSize(16, 16);
+ button->setIconSize(QSize(12, 12));
+ button->setProperty("themeID", themeID);
+ button->setToolTip(toolTip);
+
+ connect(button, SIGNAL(clicked()), receiver, slot);
+
+ layout->addWidget(button);
+}
+
+void OBSDock::AddSeparator()
+{
+ QFrame *line = new QFrame(this);
+ line->setFrameShape(QFrame::VLine);
+ line->setFixedWidth(1);
+ layout->addWidget(line);
+}
diff --git a/UI/window-dock.hpp b/UI/window-dock.hpp
index ccb1cf0ae5199c..541f27ac3ec839 100644
--- a/UI/window-dock.hpp
+++ b/UI/window-dock.hpp
@@ -2,11 +2,31 @@
#include
+class QHBoxLayout;
+class QLabel;
+class QPushButton;
+
class OBSDock : public QDockWidget {
Q_OBJECT
+private:
+ QLabel *title = nullptr;
+ QHBoxLayout *layout = nullptr;
+ QPushButton *menuButton = nullptr;
+
+private slots:
+ void ShowControlMenu();
+ void ToggleFloating();
+
public:
- inline OBSDock(QWidget *parent = nullptr) : QDockWidget(parent) {}
+ void EnableControlMenu(bool lock);
+ void AddControlMenu();
+
+ void SetTitle(const QString &newTitle);
+ void AddButton(const QString &themeID, const QString &toolTip,
+ const QObject *receiver, const char *slot);
+ void AddSeparator();
+ OBSDock(QWidget *parent = nullptr);
virtual void closeEvent(QCloseEvent *event);
};
diff --git a/UI/window-extra-browsers.cpp b/UI/window-extra-browsers.cpp
index 9387d0b88c7c02..539523b01ca521 100644
--- a/UI/window-extra-browsers.cpp
+++ b/UI/window-extra-browsers.cpp
@@ -534,7 +534,7 @@ void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url,
dock->setObjectName(title + OBJ_NAME_SUFFIX);
dock->resize(460, 600);
dock->setMinimumSize(80, 80);
- dock->setWindowTitle(title);
+ dock->SetTitle(title);
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
QCefWidget *browser =