Browse Source

Merge pull request #7278 from tytan652/control_dock_separation

Separation of the controls dock from the main window
Ryan Foster 1 year ago
parent
commit
93d51918b0

+ 329 - 0
UI/basic-controls.cpp

@@ -0,0 +1,329 @@
+#include "basic-controls.hpp"
+
+#include "window-basic-main.hpp"
+
+OBSBasicControls::OBSBasicControls(OBSBasic *main)
+	: QFrame(nullptr),
+	  ui(new Ui::OBSBasicControls)
+{
+	/* Create UI elements */
+	ui->setupUi(this);
+
+	streamButtonMenu.reset(new QMenu());
+	startStreamAction =
+		streamButtonMenu->addAction(QTStr("Basic.Main.StartStreaming"));
+	stopStreamAction =
+		streamButtonMenu->addAction(QTStr("Basic.Main.StopStreaming"));
+	QAction *forceStopStreamAction = streamButtonMenu->addAction(
+		QTStr("Basic.Main.ForceStopStreaming"));
+
+	/* Transfer buttons signals as OBSBasicControls signals */
+	connect(
+		ui->streamButton, &QPushButton::clicked, this,
+		[this]() { emit this->StreamButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->broadcastButton, &QPushButton::clicked, this,
+		[this]() { emit this->BroadcastButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->recordButton, &QPushButton::clicked, this,
+		[this]() { emit this->RecordButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->pauseRecordButton, &QPushButton::clicked, this,
+		[this]() { emit this->PauseRecordButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->replayBufferButton, &QPushButton::clicked, this,
+		[this]() { emit this->ReplayBufferButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->saveReplayButton, &QPushButton::clicked, this,
+		[this]() { emit this->SaveReplayBufferButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->virtualCamButton, &QPushButton::clicked, this,
+		[this]() { emit this->VirtualCamButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->virtualCamConfigButton, &QPushButton::clicked, this,
+		[this]() { emit this->VirtualCamConfigButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->modeSwitch, &QPushButton::clicked, this,
+		[this]() { emit this->StudioModeButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->settingsButton, &QPushButton::clicked, this,
+		[this]() { emit this->SettingsButtonClicked(); },
+		Qt::DirectConnection);
+	connect(
+		ui->exitButton, &QPushButton::clicked, this,
+		[this]() { emit this->ExitButtonClicked(); },
+		Qt::DirectConnection);
+
+	/* Transfer menu actions signals as OBSBasicControls signals */
+	connect(
+		startStreamAction.get(), &QAction::triggered, this,
+		[this]() { emit this->StartStreamMenuActionClicked(); },
+		Qt::DirectConnection);
+	connect(
+		stopStreamAction.get(), &QAction::triggered, this,
+		[this]() { emit this->StopStreamMenuActionClicked(); },
+		Qt::DirectConnection);
+	connect(
+		forceStopStreamAction, &QAction::triggered, this,
+		[this]() { emit this->ForceStopStreamMenuActionClicked(); },
+		Qt::DirectConnection);
+
+	/* Set up default visibilty */
+	ui->broadcastButton->setVisible(false);
+	ui->pauseRecordButton->setVisible(false);
+	ui->replayBufferButton->setVisible(false);
+	ui->saveReplayButton->setVisible(false);
+	ui->virtualCamButton->setVisible(false);
+	ui->virtualCamConfigButton->setVisible(false);
+
+	/* Set up state update connections */
+	connect(main, &OBSBasic::StreamingPreparing, this,
+		&OBSBasicControls::StreamingPreparing);
+	connect(main, &OBSBasic::StreamingStarting, this,
+		&OBSBasicControls::StreamingStarting);
+	connect(main, &OBSBasic::StreamingStarted, this,
+		&OBSBasicControls::StreamingStarted);
+	connect(main, &OBSBasic::StreamingStopping, this,
+		&OBSBasicControls::StreamingStopping);
+	connect(main, &OBSBasic::StreamingStopped, this,
+		&OBSBasicControls::StreamingStopped);
+
+	connect(main, &OBSBasic::BroadcastStreamReady, this,
+		&OBSBasicControls::BroadcastStreamReady);
+	connect(main, &OBSBasic::BroadcastStreamActive, this,
+		&OBSBasicControls::BroadcastStreamActive);
+	connect(main, &OBSBasic::BroadcastStreamStarted, this,
+		&OBSBasicControls::BroadcastStreamStarted);
+
+	connect(main, &OBSBasic::RecordingStarted, this,
+		&OBSBasicControls::RecordingStarted);
+	connect(main, &OBSBasic::RecordingPaused, this,
+		&OBSBasicControls::RecordingPaused);
+	connect(main, &OBSBasic::RecordingUnpaused, this,
+		&OBSBasicControls::RecordingUnpaused);
+	connect(main, &OBSBasic::RecordingStopping, this,
+		&OBSBasicControls::RecordingStopping);
+	connect(main, &OBSBasic::RecordingStopped, this,
+		&OBSBasicControls::RecordingStopped);
+
+	connect(main, &OBSBasic::ReplayBufStarted, this,
+		&OBSBasicControls::ReplayBufferStarted);
+	connect(main, &OBSBasic::ReplayBufferStopping, this,
+		&OBSBasicControls::ReplayBufferStopping);
+	connect(main, &OBSBasic::ReplayBufStopped, this,
+		&OBSBasicControls::ReplayBufferStopped);
+
+	connect(main, &OBSBasic::VirtualCamStarted, this,
+		&OBSBasicControls::VirtualCamStarted);
+	connect(main, &OBSBasic::VirtualCamStopped, this,
+		&OBSBasicControls::VirtualCamStopped);
+
+	connect(main, &OBSBasic::PreviewProgramModeChanged, this,
+		&OBSBasicControls::UpdateStudioModeState);
+
+	/* Set up enablement connection */
+	connect(main, &OBSBasic::BroadcastFlowEnabled, this,
+		&OBSBasicControls::EnableBroadcastFlow);
+	connect(main, &OBSBasic::ReplayBufEnabled, this,
+		&OBSBasicControls::EnableReplayBufferButtons);
+	connect(main, &OBSBasic::VirtualCamEnabled, this,
+		&OBSBasicControls::EnableVirtualCamButtons);
+}
+
+void OBSBasicControls::StreamingPreparing()
+{
+	ui->streamButton->setEnabled(false);
+	ui->streamButton->setText(QTStr("Basic.Main.PreparingStream"));
+}
+
+void OBSBasicControls::StreamingStarting(bool broadcastAutoStart)
+{
+	ui->streamButton->setText(QTStr("Basic.Main.Connecting"));
+
+	if (!broadcastAutoStart) {
+		// well, we need to disable button while stream is not active
+		ui->broadcastButton->setEnabled(false);
+
+		ui->broadcastButton->setText(
+			QTStr("Basic.Main.StartBroadcast"));
+
+		ui->broadcastButton->setProperty("broadcastState", "ready");
+		ui->broadcastButton->style()->unpolish(ui->broadcastButton);
+		ui->broadcastButton->style()->polish(ui->broadcastButton);
+	}
+}
+
+void OBSBasicControls::StreamingStarted(bool withDelay)
+{
+	ui->streamButton->setEnabled(true);
+	ui->streamButton->setChecked(true);
+	ui->streamButton->setText(QTStr("Basic.Main.StopStreaming"));
+
+	if (withDelay) {
+		ui->streamButton->setMenu(streamButtonMenu.get());
+		startStreamAction->setVisible(false);
+		stopStreamAction->setVisible(true);
+	}
+}
+
+void OBSBasicControls::StreamingStopping()
+{
+	ui->streamButton->setText(QTStr("Basic.Main.StoppingStreaming"));
+}
+
+void OBSBasicControls::StreamingStopped(bool withDelay)
+{
+	ui->streamButton->setEnabled(true);
+	ui->streamButton->setChecked(false);
+	ui->streamButton->setText(QTStr("Basic.Main.StartStreaming"));
+
+	if (withDelay) {
+		if (!ui->streamButton->menu())
+			ui->streamButton->setMenu(streamButtonMenu.get());
+
+		startStreamAction->setVisible(true);
+		stopStreamAction->setVisible(false);
+	} else {
+		ui->streamButton->setMenu(nullptr);
+	}
+}
+
+void OBSBasicControls::BroadcastStreamReady(bool ready)
+{
+	ui->broadcastButton->setChecked(ready);
+}
+
+void OBSBasicControls::BroadcastStreamActive()
+{
+	ui->broadcastButton->setEnabled(true);
+}
+
+void OBSBasicControls::BroadcastStreamStarted(bool autoStop)
+{
+	ui->broadcastButton->setText(
+		QTStr(autoStop ? "Basic.Main.AutoStopEnabled"
+			       : "Basic.Main.StopBroadcast"));
+	if (autoStop)
+		ui->broadcastButton->setEnabled(false);
+
+	ui->broadcastButton->setProperty("broadcastState", "active");
+	ui->broadcastButton->style()->unpolish(ui->broadcastButton);
+	ui->broadcastButton->style()->polish(ui->broadcastButton);
+}
+
+void OBSBasicControls::RecordingStarted(bool pausable)
+{
+	ui->recordButton->setChecked(true);
+	ui->recordButton->setText(QTStr("Basic.Main.StopRecording"));
+
+	if (pausable) {
+		ui->pauseRecordButton->setVisible(pausable);
+		RecordingUnpaused();
+	}
+}
+
+void OBSBasicControls::RecordingPaused()
+{
+	QString text = QTStr("Basic.Main.UnpauseRecording");
+
+	ui->pauseRecordButton->setChecked(true);
+	ui->pauseRecordButton->setAccessibleName(text);
+	ui->pauseRecordButton->setToolTip(text);
+
+	ui->saveReplayButton->setEnabled(false);
+}
+
+void OBSBasicControls::RecordingUnpaused()
+{
+	QString text = QTStr("Basic.Main.PauseRecording");
+
+	ui->pauseRecordButton->setChecked(false);
+	ui->pauseRecordButton->setAccessibleName(text);
+	ui->pauseRecordButton->setToolTip(text);
+
+	ui->saveReplayButton->setEnabled(true);
+}
+
+void OBSBasicControls::RecordingStopping()
+{
+	ui->recordButton->setText(QTStr("Basic.Main.StoppingRecording"));
+}
+
+void OBSBasicControls::RecordingStopped()
+{
+	ui->recordButton->setChecked(false);
+	ui->recordButton->setText(QTStr("Basic.Main.StartRecording"));
+
+	ui->pauseRecordButton->setVisible(false);
+}
+
+void OBSBasicControls::ReplayBufferStarted()
+{
+	ui->replayBufferButton->setChecked(true);
+	ui->replayBufferButton->setText(QTStr("Basic.Main.StopReplayBuffer"));
+
+	ui->saveReplayButton->setVisible(true);
+}
+
+void OBSBasicControls::ReplayBufferStopping()
+{
+	ui->replayBufferButton->setText(
+		QTStr("Basic.Main.StoppingReplayBuffer"));
+}
+
+void OBSBasicControls::ReplayBufferStopped()
+{
+	ui->replayBufferButton->setChecked(false);
+	ui->replayBufferButton->setText(QTStr("Basic.Main.StartReplayBuffer"));
+
+	ui->saveReplayButton->setVisible(false);
+}
+
+void OBSBasicControls::VirtualCamStarted()
+{
+	ui->virtualCamButton->setChecked(true);
+	ui->virtualCamButton->setText(QTStr("Basic.Main.StopVirtualCam"));
+}
+
+void OBSBasicControls::VirtualCamStopped()
+{
+	ui->virtualCamButton->setChecked(false);
+	ui->virtualCamButton->setText(QTStr("Basic.Main.StartVirtualCam"));
+}
+
+void OBSBasicControls::UpdateStudioModeState(bool enabled)
+{
+	ui->modeSwitch->setChecked(enabled);
+}
+
+void OBSBasicControls::EnableBroadcastFlow(bool enabled)
+{
+	ui->broadcastButton->setVisible(enabled);
+	ui->broadcastButton->setEnabled(enabled);
+
+	ui->broadcastButton->setText(QTStr("Basic.Main.SetupBroadcast"));
+
+	ui->broadcastButton->setProperty("broadcastState", "idle");
+	ui->broadcastButton->style()->unpolish(ui->broadcastButton);
+	ui->broadcastButton->style()->polish(ui->broadcastButton);
+}
+
+void OBSBasicControls::EnableReplayBufferButtons(bool enabled)
+{
+	ui->replayBufferButton->setVisible(enabled);
+}
+
+void OBSBasicControls::EnableVirtualCamButtons()
+{
+	ui->virtualCamButton->setVisible(true);
+	ui->virtualCamConfigButton->setVisible(true);
+}

+ 72 - 0
UI/basic-controls.hpp

@@ -0,0 +1,72 @@
+#pragma once
+
+#include <memory>
+
+#include <QFrame>
+#include <QPointer>
+#include <QScopedPointer>
+
+class OBSBasic;
+
+#include "ui_OBSBasicControls.h"
+
+class OBSBasicControls : public QFrame {
+	Q_OBJECT
+
+	std::unique_ptr<Ui::OBSBasicControls> ui;
+
+	QScopedPointer<QMenu> streamButtonMenu;
+	QPointer<QAction> startStreamAction;
+	QPointer<QAction> stopStreamAction;
+
+private slots:
+	void StreamingPreparing();
+	void StreamingStarting(bool broadcastAutoStart);
+	void StreamingStarted(bool withDelay);
+	void StreamingStopping();
+	void StreamingStopped(bool withDelay);
+
+	void BroadcastStreamReady(bool ready);
+	void BroadcastStreamActive();
+	void BroadcastStreamStarted(bool autoStop);
+
+	void RecordingStarted(bool pausable);
+	void RecordingPaused();
+	void RecordingUnpaused();
+	void RecordingStopping();
+	void RecordingStopped();
+
+	void ReplayBufferStarted();
+	void ReplayBufferStopping();
+	void ReplayBufferStopped();
+
+	void VirtualCamStarted();
+	void VirtualCamStopped();
+
+	void UpdateStudioModeState(bool enabled);
+
+	void EnableBroadcastFlow(bool enabled);
+	void EnableReplayBufferButtons(bool enabled);
+	void EnableVirtualCamButtons();
+
+public:
+	OBSBasicControls(OBSBasic *main);
+	inline ~OBSBasicControls() {}
+
+signals:
+	void StreamButtonClicked();
+	void BroadcastButtonClicked();
+	void RecordButtonClicked();
+	void PauseRecordButtonClicked();
+	void ReplayBufferButtonClicked();
+	void SaveReplayBufferButtonClicked();
+	void VirtualCamButtonClicked();
+	void VirtualCamConfigButtonClicked();
+	void StudioModeButtonClicked();
+	void SettingsButtonClicked();
+	void ExitButtonClicked();
+
+	void StartStreamMenuActionClicked();
+	void StopStreamMenuActionClicked();
+	void ForceStopStreamMenuActionClicked();
+};

+ 4 - 2
UI/cmake/legacy.cmake

@@ -101,6 +101,7 @@ target_sources(
           forms/OBSAbout.ui
           forms/OBSAdvAudio.ui
           forms/OBSBasic.ui
+          forms/OBSBasicControls.ui
           forms/OBSBasicFilters.ui
           forms/OBSBasicInteraction.ui
           forms/OBSBasicProperties.ui
@@ -167,6 +168,8 @@ target_sources(
           audio-encoders.cpp
           audio-encoders.hpp
           balance-slider.hpp
+          basic-controls.cpp
+          basic-controls.hpp
           clickable-label.hpp
           double-slider.cpp
           double-slider.hpp
@@ -189,13 +192,12 @@ target_sources(
           menu-button.cpp
           menu-button.hpp
           mute-checkbox.hpp
+          noncheckable-button.hpp
           plain-text-edit.cpp
           plain-text-edit.hpp
           properties-view.cpp
           properties-view.hpp
           properties-view.moc.hpp
-          record-button.cpp
-          record-button.hpp
           remote-text.cpp
           remote-text.hpp
           scene-tree.cpp

+ 3 - 2
UI/cmake/ui-elements.cmake

@@ -38,6 +38,8 @@ target_sources(
           audio-encoders.cpp
           audio-encoders.hpp
           balance-slider.hpp
+          basic-controls.cpp
+          basic-controls.hpp
           context-bar-controls.cpp
           context-bar-controls.hpp
           focus-list.cpp
@@ -55,8 +57,7 @@ target_sources(
           menu-button.cpp
           menu-button.hpp
           mute-checkbox.hpp
-          record-button.cpp
-          record-button.hpp
+          noncheckable-button.hpp
           remote-text.cpp
           remote-text.hpp
           scene-tree.cpp

+ 1 - 0
UI/cmake/ui-qt.cmake

@@ -34,6 +34,7 @@ set(_qt_sources
     forms/OBSAbout.ui
     forms/OBSAdvAudio.ui
     forms/OBSBasic.ui
+    forms/OBSBasicControls.ui
     forms/OBSBasicFilters.ui
     forms/OBSBasicInteraction.ui
     forms/OBSBasicProperties.ui

+ 13 - 5
UI/data/themes/Yami.obt

@@ -985,16 +985,24 @@ QDoubleSpinBox::down-arrow {
 
 #streamButton,
 #recordButton,
-QPushButton[themeID="replayBufferButton"],
+#replayBufferButton,
 #broadcastButton {
     padding: var(--padding_large);
 }
 
+#pauseRecordButton,
+#saveReplayButton,
+#virtualCamConfigButton {
+    padding: var(--padding_large) var(--padding_large);
+    width: var(--input_height);
+    max-width: var(--input_height);
+}
+
 /* Primary Control Button Checked Coloring */
 #streamButton:!hover:!pressed:checked,
 #recordButton:!hover:!pressed:checked,
-QPushButton[themeID="replayBufferButton"]:!hover:!pressed:checked,
-QPushButton[themeID="vcamButton"]:!hover:!pressed:checked,
+#replayBufferButton:!hover:!pressed:checked,
+#virtualCamButton:!hover:!pressed:checked,
 #modeSwitch:!hover:!pressed:checked,
 #broadcastButton:!hover:!pressed:checked {
     background: var(--primary);
@@ -1003,8 +1011,8 @@ QPushButton[themeID="vcamButton"]:!hover:!pressed:checked,
 /* Primary Control Button Hover Coloring */
 #streamButton:hover:!pressed:checked,
 #recordButton:hover:!pressed:checked,
-QPushButton[themeID="replayBufferButton"]:!pressed:checked,
-QPushButton[themeID="vcamButton"]:!pressed:checked,
+#replayBufferButton:!pressed:checked,
+#virtualCamButton:!pressed:checked,
 #modeSwitch:hover:!pressed:checked,
 #broadcastButton:hover:!pressed:checked {
     background: var(--primary_light);

+ 6 - 6
UI/data/themes/Yami_Acri.ovt

@@ -147,8 +147,8 @@ QTabBar QToolButton {
 /* Primary Control Button Checked Coloring */
 #streamButton:!hover:!pressed:checked,
 #recordButton:!hover:!pressed:checked,
-QPushButton[themeID="replayBufferButton"]:!hover:!pressed:checked,
-QPushButton[themeID="vcamButton"]:!hover:!pressed:checked,
+#replayBufferButton:!hover:!pressed:checked,
+#virtualCamButton:!hover:!pressed:checked,
 #modeSwitch:!hover:!pressed:checked,
 #broadcastButton:!hover:!pressed:checked {
     background: var(--button_bg_red);
@@ -158,8 +158,8 @@ QPushButton[themeID="vcamButton"]:!hover:!pressed:checked,
 /* Primary Control Button Hover Coloring */
 #streamButton:hover:!pressed:checked,
 #recordButton:hover:!pressed:checked,
-QPushButton[themeID="replayBufferButton"]:!pressed:checked,
-QPushButton[themeID="vcamButton"]:!pressed:checked,
+#replayBufferButton:hover:!pressed:checked,
+#virtualCamButton:hover:!pressed:checked,
 #modeSwitch:hover:!pressed:checked,
 #broadcastButton:hover:!pressed:checked {
     background: var(--button_bg_red_hover);
@@ -168,8 +168,8 @@ QPushButton[themeID="vcamButton"]:!pressed:checked,
 /* Primary Control Button Checked + Pressed Coloring */
 #streamButton:pressed:checked,
 #recordButton:pressed:checked,
-QPushButton[themeID="replayBufferButton"]:pressed:checked,
-QPushButton[themeID="vcamButton"]:pressed:checked,
+#replayBufferButton:pressed:checked,
+#virtualCamButton:pressed:checked,
 #modeSwitch:pressed:checked,
 #broadcastButton:pressed:checked {
     background: var(--button_bg_red_down);

+ 0 - 243
UI/forms/OBSBasic.ui

@@ -1399,228 +1399,6 @@
     </layout>
    </widget>
   </widget>
-  <widget class="OBSDock" name="controlsDock">
-   <property name="features">
-    <set>QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
-   </property>
-   <property name="windowTitle">
-    <string>Basic.Main.Controls</string>
-   </property>
-   <attribute name="dockWidgetArea">
-    <number>8</number>
-   </attribute>
-   <widget class="QWidget" name="controlsDockContents">
-    <layout class="QVBoxLayout" name="verticalLayout_9">
-     <property name="spacing">
-      <number>0</number>
-     </property>
-     <property name="leftMargin">
-      <number>1</number>
-     </property>
-     <property name="topMargin">
-      <number>0</number>
-     </property>
-     <property name="rightMargin">
-      <number>1</number>
-     </property>
-     <property name="bottomMargin">
-      <number>1</number>
-     </property>
-     <item>
-      <widget class="QFrame" name="controlsFrame">
-       <layout class="QVBoxLayout" name="buttonsVLayout">
-        <property name="spacing">
-         <number>0</number>
-        </property>
-        <property name="leftMargin">
-         <number>0</number>
-        </property>
-        <property name="topMargin">
-         <number>0</number>
-        </property>
-        <property name="rightMargin">
-         <number>0</number>
-        </property>
-        <property name="bottomMargin">
-         <number>0</number>
-        </property>
-        <item>
-         <layout class="QHBoxLayout" name="horizontalLayout_7">
-          <item>
-           <widget class="QPushButton" name="streamButton">
-            <property name="enabled">
-             <bool>true</bool>
-            </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="minimumSize">
-             <size>
-              <width>150</width>
-              <height>0</height>
-             </size>
-            </property>
-            <property name="text">
-             <string>Basic.Main.StartStreaming</string>
-            </property>
-            <property name="checkable">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QPushButton" name="broadcastButton">
-            <property name="enabled">
-             <bool>true</bool>
-            </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="minimumSize">
-             <size>
-              <width>150</width>
-              <height>0</height>
-             </size>
-            </property>
-            <property name="text">
-             <string>Basic.Main.StartBroadcast</string>
-            </property>
-            <property name="checkable">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
-        </item>
-        <item>
-         <layout class="QHBoxLayout" name="recordingLayout">
-          <property name="spacing">
-           <number>2</number>
-          </property>
-          <property name="leftMargin">
-           <number>0</number>
-          </property>
-          <property name="topMargin">
-           <number>0</number>
-          </property>
-          <property name="rightMargin">
-           <number>0</number>
-          </property>
-          <property name="bottomMargin">
-           <number>0</number>
-          </property>
-          <item>
-           <widget class="RecordButton" name="recordButton">
-            <property name="enabled">
-             <bool>true</bool>
-            </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="minimumSize">
-             <size>
-              <width>0</width>
-              <height>0</height>
-             </size>
-            </property>
-            <property name="text">
-             <string>Basic.Main.StartRecording</string>
-            </property>
-            <property name="checkable">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
-        </item>
-        <item>
-         <widget class="QPushButton" name="modeSwitch">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>150</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>Basic.TogglePreviewProgramMode</string>
-          </property>
-          <property name="checkable">
-           <bool>true</bool>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QPushButton" name="settingsButton">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>150</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>Settings</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QPushButton" name="exitButton">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>150</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>Exit</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <spacer name="expVSpacer">
-          <property name="orientation">
-           <enum>Qt::Vertical</enum>
-          </property>
-          <property name="sizeHint" stdset="0">
-           <size>
-            <width>0</width>
-            <height>0</height>
-           </size>
-          </property>
-         </spacer>
-        </item>
-       </layout>
-      </widget>
-     </item>
-    </layout>
-   </widget>
-  </widget>
   <action name="actionAddScene">
    <property name="icon">
     <iconset resource="obs.qrc">
@@ -2372,11 +2150,6 @@
    <header>window-dock.hpp</header>
    <container>1</container>
   </customwidget>
-  <customwidget>
-   <class>RecordButton</class>
-   <extends>QPushButton</extends>
-   <header>record-button.hpp</header>
-  </customwidget>
  </customwidgets>
  <resources>
   <include location="obs.qrc"/>
@@ -2398,21 +2171,5 @@
     </hint>
    </hints>
   </connection>
-  <connection>
-   <sender>exitButton</sender>
-   <signal>clicked()</signal>
-   <receiver>OBSBasic</receiver>
-   <slot>close()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>976</x>
-     <y>601</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>862</x>
-     <y>-11</y>
-    </hint>
-   </hints>
-  </connection>
  </connections>
 </ui>

+ 403 - 0
UI/forms/OBSBasicControls.ui

@@ -0,0 +1,403 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OBSBasicControls</class>
+ <widget class="QWidget" name="OBSBasicControls">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>318</width>
+    <height>213</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_9">
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <property name="leftMargin">
+    <number>1</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>1</number>
+   </property>
+   <property name="bottomMargin">
+    <number>1</number>
+   </property>
+   <item>
+    <widget class="QFrame" name="controlsFrame">
+     <layout class="QVBoxLayout" name="buttonsVLayout">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_7">
+        <item>
+         <widget class="NonCheckableButton" name="streamButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>150</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>Basic.Main.StartStreaming</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="NonCheckableButton" name="broadcastButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>150</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>Basic.Main.StartBroadcast</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="recordingLayout">
+        <property name="spacing">
+         <number>2</number>
+        </property>
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <widget class="NonCheckableButton" name="recordButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>Basic.Main.StartRecording</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="pauseRecordButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="toolTip">
+           <string>Basic.Main.PauseRecording</string>
+          </property>
+          <property name="accessibleName">
+           <string>Basic.Main.PauseRecording</string>
+          </property>
+          <property name="icon">
+           <iconset resource="obs.qrc">
+            <normaloff>:/res/images/media-pause.svg</normaloff>:/res/images/media-pause.svg</iconset>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+          <property name="themeID" stdset="0">
+           <string>pauseIconSmall</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="replayBufferLayout">
+        <property name="spacing">
+         <number>2</number>
+        </property>
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <widget class="NonCheckableButton" name="replayBufferButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>Basic.Main.StartReplayBuffer</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="saveReplayButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="toolTip">
+           <string>Basic.Main.SaveReplay</string>
+          </property>
+          <property name="accessibleName">
+           <string>Basic.Main.SaveReplay</string>
+          </property>
+          <property name="icon">
+           <iconset resource="obs.qrc">
+            <normaloff>:/res/images/save.svg</normaloff>:/res/images/save.svg</iconset>
+          </property>
+          <property name="themeID" stdset="0">
+           <string>replayIconSmall</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="virtualCamLayout">
+        <property name="spacing">
+         <number>2</number>
+        </property>
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <widget class="NonCheckableButton" name="virtualCamButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="text">
+           <string>Basic.Main.StartVirtualCam</string>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="virtualCamConfigButton">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="toolTip">
+           <string>Basic.Main.VirtualCamConfig</string>
+          </property>
+          <property name="accessibleName">
+           <string>Basic.Main.VirtualCamConfig</string>
+          </property>
+          <property name="icon">
+           <iconset resource="obs.qrc">
+            <normaloff>:/settings/images/settings/general.svg</normaloff>:/settings/images/settings/general.svg</iconset>
+          </property>
+          <property name="themeID" stdset="0">
+           <string>configIconSmall</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <widget class="NonCheckableButton" name="modeSwitch">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>150</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="text">
+         <string>Basic.TogglePreviewProgramMode</string>
+        </property>
+        <property name="checkable">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="settingsButton">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>150</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="text">
+         <string>Settings</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="exitButton">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>150</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="text">
+         <string>Exit</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="expVSpacer">
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>0</width>
+          <height>0</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>NonCheckableButton</class>
+   <extends>QPushButton</extends>
+   <header>noncheckable-button.hpp</header>
+  </customwidget>
+ </customwidgets>
+ <resources>
+  <include location="obs.qrc"/>
+ </resources>
+ <connections/>
+</ui>

+ 23 - 0
UI/noncheckable-button.hpp

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <QPushButton>
+
+/* Button with its checked property not changed when clicked.
+ * Meant to be used in situations where manually changing the property
+ * is always preferred. */
+class NonCheckableButton : public QPushButton {
+	Q_OBJECT
+
+	inline void nextCheckState() override {}
+
+public:
+	inline NonCheckableButton(QWidget *parent = nullptr)
+		: QPushButton(parent)
+	{
+	}
+	inline NonCheckableButton(const QString &text,
+				  QWidget *parent = nullptr)
+		: QPushButton(text, parent)
+	{
+	}
+};

+ 0 - 159
UI/record-button.cpp

@@ -1,159 +0,0 @@
-#include "record-button.hpp"
-#include "window-basic-main.hpp"
-
-void RecordButton::resizeEvent(QResizeEvent *event)
-{
-	OBSBasic *main = OBSBasic::Get();
-	if (!main->pause)
-		return;
-
-	QSize pauseSize = main->pause->size();
-	int height = main->ui->recordButton->size().height();
-
-	if (pauseSize.height() != height || pauseSize.width() != height) {
-		main->pause->setMinimumSize(height, height);
-		main->pause->setMaximumSize(height, height);
-	}
-
-	event->accept();
-}
-
-static QWidget *firstWidget(QLayoutItem *item)
-{
-	auto widget = item->widget();
-	if (widget)
-		return widget;
-
-	auto layout = item->layout();
-	if (!layout)
-		return nullptr;
-
-	auto n = layout->count();
-	for (auto i = 0; i < n; i++) {
-		widget = firstWidget(layout->itemAt(i));
-		if (widget)
-			return widget;
-	}
-	return nullptr;
-}
-
-static QWidget *lastWidget(QLayoutItem *item)
-{
-	auto widget = item->widget();
-	if (widget)
-		return widget;
-
-	auto layout = item->layout();
-	if (!layout)
-		return nullptr;
-
-	for (auto i = layout->count(); i > 0; i--) {
-		widget = lastWidget(layout->itemAt(i - 1));
-		if (widget)
-			return widget;
-	}
-	return nullptr;
-}
-
-static QWidget *getNextWidget(QBoxLayout *container, QLayoutItem *item)
-{
-	for (auto i = 1, n = container->count(); i < n; i++) {
-		if (container->itemAt(i - 1) == item)
-			return firstWidget(container->itemAt(i));
-	}
-	return nullptr;
-}
-
-ControlsSplitButton::ControlsSplitButton(const QString &text,
-					 const QVariant &themeID,
-					 void (OBSBasic::*clicked)())
-	: QHBoxLayout()
-{
-	button.reset(new QPushButton(text));
-	button->setCheckable(true);
-	button->setProperty("themeID", themeID);
-
-	button->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
-	button->installEventFilter(this);
-
-	OBSBasic *main = OBSBasic::Get();
-	connect(button.data(), &QPushButton::clicked, main, clicked);
-
-	addWidget(button.data());
-}
-
-void ControlsSplitButton::addIcon(const QString &name, const QVariant &themeID,
-				  void (OBSBasic::*clicked)())
-{
-	icon.reset(new QPushButton());
-	icon->setAccessibleName(name);
-	icon->setToolTip(name);
-	icon->setChecked(false);
-	icon->setProperty("themeID", themeID);
-
-	QSizePolicy sp;
-	sp.setHeightForWidth(true);
-	icon->setSizePolicy(sp);
-
-	OBSBasic *main = OBSBasic::Get();
-	connect(icon.data(), &QAbstractButton::clicked, main, clicked);
-
-	addWidget(icon.data());
-	QWidget::setTabOrder(button.data(), icon.data());
-
-	auto next = getNextWidget(main->ui->buttonsVLayout, this);
-	if (next)
-		QWidget::setTabOrder(icon.data(), next);
-}
-
-void ControlsSplitButton::removeIcon()
-{
-	icon.reset();
-}
-
-void ControlsSplitButton::insert(int index)
-{
-	OBSBasic *main = OBSBasic::Get();
-	auto count = main->ui->buttonsVLayout->count();
-	if (index < 0)
-		index = 0;
-	else if (index > count)
-		index = count;
-
-	main->ui->buttonsVLayout->insertLayout(index, this);
-
-	QWidget *prev = button.data();
-
-	if (index > 0) {
-		prev = lastWidget(main->ui->buttonsVLayout->itemAt(index - 1));
-		if (prev)
-			QWidget::setTabOrder(prev, button.data());
-		prev = button.data();
-	}
-
-	if (icon) {
-		QWidget::setTabOrder(button.data(), icon.data());
-		prev = icon.data();
-	}
-
-	if (index < count) {
-		auto next = firstWidget(
-			main->ui->buttonsVLayout->itemAt(index + 1));
-		if (next)
-			QWidget::setTabOrder(prev, next);
-	}
-}
-
-bool ControlsSplitButton::eventFilter(QObject *obj, QEvent *event)
-{
-	if (event->type() == QEvent::Resize && icon) {
-		QSize iconSize = icon->size();
-		int height = button->height();
-
-		if (iconSize.height() != height || iconSize.width() != height) {
-			icon->setMinimumSize(height, height);
-			icon->setMaximumSize(height, height);
-		}
-	}
-	return QObject::eventFilter(obj, event);
-}

+ 0 - 39
UI/record-button.hpp

@@ -1,39 +0,0 @@
-#pragma once
-
-#include <QPushButton>
-#include <QBoxLayout>
-#include <QScopedPointer>
-
-class RecordButton : public QPushButton {
-	Q_OBJECT
-
-public:
-	inline RecordButton(QWidget *parent = nullptr) : QPushButton(parent) {}
-
-	virtual void resizeEvent(QResizeEvent *event) override;
-};
-
-class OBSBasic;
-
-class ControlsSplitButton : public QHBoxLayout {
-	Q_OBJECT
-
-public:
-	ControlsSplitButton(const QString &text, const QVariant &themeID,
-			    void (OBSBasic::*clicked)());
-
-	void addIcon(const QString &name, const QVariant &themeID,
-		     void (OBSBasic::*clicked)());
-	void removeIcon();
-	void insert(int index);
-
-	inline QPushButton *first() { return button.data(); }
-	inline QPushButton *second() { return icon.data(); }
-
-protected:
-	virtual bool eventFilter(QObject *obj, QEvent *event) override;
-
-private:
-	QScopedPointer<QPushButton> button;
-	QScopedPointer<QPushButton> icon;
-};

+ 2 - 2
UI/window-basic-main-transitions.cpp

@@ -976,7 +976,7 @@ int OBSBasic::GetTbarPosition()
 	return tBar->value();
 }
 
-void OBSBasic::on_modeSwitch_clicked()
+void OBSBasic::TogglePreviewProgramMode()
 {
 	SetPreviewProgramMode(!IsPreviewProgramMode());
 }
@@ -1607,8 +1607,8 @@ void OBSBasic::SetPreviewProgramMode(bool enabled)
 	if (IsPreviewProgramMode() == enabled)
 		return;
 
-	ui->modeSwitch->setChecked(enabled);
 	os_atomic_set_bool(&previewProgramMode, enabled);
+	emit PreviewProgramModeChanged(enabled);
 
 	if (IsPreviewProgramMode()) {
 		if (!previewEnabled)

File diff suppressed because it is too large
+ 201 - 270
UI/window-basic-main.cpp


+ 64 - 19
UI/window-basic-main.hpp

@@ -319,13 +319,8 @@ private:
 	QPointer<QWidget> extraBrowsers;
 	QPointer<QWidget> importer;
 
-	QPointer<QMenu> startStreamMenu;
-
 	QPointer<QPushButton> transitionButton;
-	QPointer<ControlsSplitButton> replayBufferButton;
-	QScopedPointer<QPushButton> pause;
 
-	QPointer<ControlsSplitButton> vcamButton;
 	bool vcamEnabled = false;
 	VCamConfig vcamConfig;
 
@@ -546,8 +541,6 @@ private:
 	void dragMoveEvent(QDragMoveEvent *event) override;
 	void dropEvent(QDropEvent *event) override;
 
-	void ReplayBufferClicked();
-
 	bool sysTrayMinimizeToTray();
 
 	void EnumDialogs();
@@ -677,6 +670,12 @@ private:
 
 	void UpdatePreviewOverflowSettings();
 
+	bool streamingStarting = false;
+
+	bool recordingStarted = false;
+	bool isRecordingPausable = false;
+	bool recordingPaused = false;
+
 	bool restartingVCam = false;
 
 public slots:
@@ -881,8 +880,7 @@ private:
 
 	void AutoRemux(QString input, bool no_show = false);
 
-	void UpdatePause(bool activate = true);
-	void UpdateReplayBuffer(bool activate = true);
+	void UpdateIsRecordingPausable();
 
 	bool IsFFmpegOutputToURL() const;
 	bool OutputPathValid();
@@ -926,7 +924,6 @@ public:
 	int ResetVideo();
 	bool ResetAudio();
 
-	void AddVCamButton();
 	void ResetOutputs();
 
 	void RefreshVolumeColors();
@@ -1125,11 +1122,6 @@ private slots:
 	void on_actionScaleCanvas_triggered();
 	void on_actionScaleOutput_triggered();
 
-	void on_streamButton_clicked();
-	void on_recordButton_clicked();
-	void VCamButtonClicked();
-	void VCamConfigButtonClicked();
-	void on_settingsButton_clicked();
 	void Screenshot(OBSSource source_ = nullptr);
 	void ScreenshotSelectedSource();
 	void ScreenshotProgram();
@@ -1177,8 +1169,6 @@ private slots:
 	void ShowTransitionProperties();
 	void HideTransitionProperties();
 
-	void on_modeSwitch_clicked();
-
 	// Source Context Buttons
 	void on_sourcePropertiesButton_clicked();
 	void on_sourceFiltersButton_clicked();
@@ -1193,8 +1183,6 @@ private slots:
 	void on_multiviewProjectorWindowed_triggered();
 	void on_sideDocks_toggled(bool side);
 
-	void PauseToggled();
-
 	void logUploadFinished(const QString &text, const QString &error);
 	void crashUploadFinished(const QString &text, const QString &error);
 	void openLogDialog(const QString &text, const bool crash);
@@ -1236,6 +1224,26 @@ private slots:
 	void RepairOldExtraDockName();
 	void RepairCustomExtraDockName();
 
+	/* Stream action (start/stop) slot */
+	void StreamActionTriggered();
+
+	/* Record action (start/stop) slot */
+	void RecordActionTriggered();
+
+	/* Record pause (pause/unpause) slot */
+	void RecordPauseToggled();
+
+	/* Replay Buffer action (start/stop) slot */
+	void ReplayBufferActionTriggered();
+
+	/* Virtual Cam action (start/stop) slots */
+	void VirtualCamActionTriggered();
+
+	void OpenVirtualCamConfig();
+
+	/* Studio Mode toggle slot */
+	void TogglePreviewProgramMode();
+
 public slots:
 	void on_actionResetTransform_triggered();
 
@@ -1249,9 +1257,46 @@ public slots:
 	void UpdateContextBarDeferred(bool force = false);
 	void UpdateContextBarVisibility();
 
+signals:
+	/* Streaming signals */
+	void StreamingPreparing();
+	void StreamingStarting(bool broadcastAutoStart);
+	void StreamingStarted(bool withDelay = false);
+	void StreamingStopping();
+	void StreamingStopped(bool withDelay = false);
+
+	/* Broadcast Flow signals */
+	void BroadcastFlowEnabled(bool enabled);
+	void BroadcastStreamReady(bool ready);
+	void BroadcastStreamActive();
+	void BroadcastStreamStarted(bool autoStop);
+
+	/* Recording signals */
+	void RecordingStarted(bool pausable = false);
+	void RecordingPaused();
+	void RecordingUnpaused();
+	void RecordingStopping();
+	void RecordingStopped();
+
+	/* Replay Buffer signals */
+	void ReplayBufEnabled(bool enabled);
+	void ReplayBufStarted();
+	void ReplayBufStopping();
+	void ReplayBufStopped();
+
+	/* Virtual Camera signals */
+	void VirtualCamEnabled();
+	void VirtualCamStarted();
+	void VirtualCamStopped();
+
+	/* Studio Mode signal */
+	void PreviewProgramModeChanged(bool enabled);
+
 private:
 	std::unique_ptr<Ui::OBSBasic> ui;
 
+	QPointer<OBSDock> controlsDock;
+
 public:
 	/* `undo_s` needs to be declared after `ui` to prevent an uninitialized
 	 * warning for `ui` while initializing `undo_s`. */

Some files were not shown because too many files changed in this diff