diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 39d638200..df4d26450 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -46,6 +46,7 @@ configure_file( set(CMAKE_INCLUDE_CURRENT_DIR TRUE) set(CMAKE_AUTOMOC TRUE) +find_package(Qt5Widgets ${FIND_MODE}) find_package(Qt5Svg ${FIND_MODE}) find_package(Qt5Xml ${FIND_MODE}) @@ -214,6 +215,7 @@ set(obs_SOURCES window-basic-preview.cpp window-basic-about.cpp window-importer.cpp + media-controls.cpp window-namedialog.cpp window-log-reply.cpp window-projector.cpp @@ -234,6 +236,7 @@ set(obs_SOURCES volume-control.cpp adv-audio-control.cpp item-widget-helpers.cpp + context-bar-controls.cpp horizontal-scroll-area.cpp vertical-scroll-area.cpp visibility-item-widget.cpp @@ -245,7 +248,11 @@ set(obs_SOURCES remote-text.cpp audio-encoders.cpp qt-wrappers.cpp - log-viewer.cpp) + log-viewer.cpp + obs-proxy-style.cpp + locked-checkbox.cpp + visibility-checkbox.cpp + media-slider.cpp) set(obs_HEADERS ${obs_PLATFORM_HEADERS} @@ -270,6 +277,7 @@ set(obs_HEADERS window-basic-transform.hpp window-basic-preview.hpp window-importer.hpp + media-controls.hpp window-namedialog.hpp window-log-reply.hpp window-projector.hpp @@ -296,6 +304,7 @@ set(obs_HEADERS adv-audio-control.hpp item-widget-helpers.hpp visibility-checkbox.hpp + context-bar-controls.hpp locked-checkbox.hpp horizontal-scroll-area.hpp expand-checkbox.hpp @@ -310,7 +319,10 @@ set(obs_HEADERS audio-encoders.hpp qt-wrappers.hpp clickable-label.hpp - log-viewer.hpp) + log-viewer.hpp + obs-proxy-style.hpp + obs-proxy-style.hpp + media-slider.hpp) set(obs_importers_HEADERS importers/importers.hpp) @@ -326,6 +338,13 @@ source_group("importers\\Source Files" FILES ${obs_importers_SOURCES}) source_group("importers\\Header Files" FILES ${obs_importers_HEADERS}) set(obs_UI + forms/source-toolbar/browser-source-toolbar.ui + forms/source-toolbar/device-select-toolbar.ui + forms/source-toolbar/game-capture-toolbar.ui + forms/source-toolbar/image-source-toolbar.ui + forms/source-toolbar/color-source-toolbar.ui + forms/source-toolbar/text-source-toolbar.ui + forms/source-toolbar/media-controls.ui forms/NameDialog.ui forms/AutoConfigStartPage.ui forms/AutoConfigVideoPage.ui diff --git a/UI/context-bar-controls.cpp b/UI/context-bar-controls.cpp new file mode 100644 index 000000000..5583c55fb --- /dev/null +++ b/UI/context-bar-controls.cpp @@ -0,0 +1,631 @@ +#include "context-bar-controls.hpp" +#include "qt-wrappers.hpp" +#include "obs-app.hpp" + +#include +#include +#include + +#include "ui_browser-source-toolbar.h" +#include "ui_device-select-toolbar.h" +#include "ui_game-capture-toolbar.h" +#include "ui_image-source-toolbar.h" +#include "ui_color-source-toolbar.h" +#include "ui_text-source-toolbar.h" + +#ifdef _WIN32 +#define get_os_module(win, mac, linux) obs_get_module(win) +#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, win) +#elif __APPLE__ +#define get_os_module(win, mac, linux) obs_get_module(mac) +#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, mac) +#else +#define get_os_module(win, mac, linux) obs_get_module(linux) +#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, linux) +#endif + +/* ========================================================================= */ + +SourceToolbar::SourceToolbar(QWidget *parent, OBSSource source) + : QWidget(parent), + weakSource(OBSGetWeakRef(source)), + props(obs_source_properties(source), obs_properties_destroy) +{ + obs_data_t *settings = obs_source_get_settings(source); + obs_properties_apply_settings(props.get(), settings); + obs_data_release(settings); +} + +/* ========================================================================= */ + +BrowserToolbar::BrowserToolbar(QWidget *parent, OBSSource source) + : SourceToolbar(parent, source), ui(new Ui_BrowserSourceToolbar) +{ + ui->setupUi(this); +} + +BrowserToolbar::~BrowserToolbar() +{ + delete ui; +} + +void BrowserToolbar::on_refresh_clicked() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + obs_property_t *p = obs_properties_get(props.get(), "refreshnocache"); + obs_property_button_clicked(p, source.Get()); +} + +/* ========================================================================= */ + +ComboSelectToolbar::ComboSelectToolbar(QWidget *parent, OBSSource source) + : SourceToolbar(parent, source), ui(new Ui_DeviceSelectToolbar) +{ + ui->setupUi(this); +} + +ComboSelectToolbar::~ComboSelectToolbar() +{ + delete ui; +} + +static int FillPropertyCombo(QComboBox *c, obs_property_t *p, + const std::string &cur_id, bool is_int = false) +{ + size_t count = obs_property_list_item_count(p); + int cur_idx = -1; + + for (size_t i = 0; i < count; i++) { + const char *name = obs_property_list_item_name(p, i); + std::string id; + + if (is_int) { + id = std::to_string(obs_property_list_item_int(p, i)); + } else { + const char *val = obs_property_list_item_string(p, i); + id = val ? val : ""; + } + + if (cur_id == id) + cur_idx = (int)i; + + c->addItem(name, id.c_str()); + } + + return cur_idx; +} + +static void SetComboItemDisabled(QComboBox *c, int idx) +{ + QStandardItemModel *model = + dynamic_cast(c->model()); + QStandardItem *item = model->item(idx); + item->setFlags(Qt::NoItemFlags); +} + +void ComboSelectToolbar::Init() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + std::string cur_id; + + obs_data_t *settings = obs_source_get_settings(source); + if (is_int) { + cur_id = std::to_string(obs_data_get_int(settings, prop_name)); + } else { + cur_id = obs_data_get_string(settings, prop_name); + } + obs_data_release(settings); + + ui->device->blockSignals(true); + + obs_property_t *p = obs_properties_get(props.get(), prop_name); + int cur_idx = FillPropertyCombo(ui->device, p, cur_id, is_int); + + if (cur_idx == -1 || obs_property_list_item_disabled(p, cur_idx)) { + if (cur_idx == -1) { + ui->device->insertItem( + 0, + QTStr("Basic.Settings.Audio.UnknownAudioDevice")); + cur_idx = 0; + } + + SetComboItemDisabled(ui->device, cur_idx); + } else { + ui->device->setCurrentIndex(cur_idx); + } + + ui->device->blockSignals(false); +} + +void ComboSelectToolbar::UpdateActivateButtonName() +{ + obs_property_t *p = obs_properties_get(props.get(), "activate"); + ui->activateButton->setText(obs_property_description(p)); +} + +void ComboSelectToolbar::on_device_currentIndexChanged(int idx) +{ + OBSSource source = GetSource(); + if (idx == -1 || !source) { + return; + } + + QString id = ui->device->itemData(idx).toString(); + + obs_data_t *settings = obs_data_create(); + if (is_int) { + obs_data_set_int(settings, prop_name, id.toInt()); + } else { + obs_data_set_string(settings, prop_name, QT_TO_UTF8(id)); + } + obs_source_update(source, settings); + obs_data_release(settings); +} + +void ComboSelectToolbar::on_activateButton_clicked() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + obs_property_t *p = obs_properties_get(props.get(), "activate"); + if (!p) { + return; + } + + obs_property_button_clicked(p, source.Get()); + UpdateActivateButtonName(); +} + +AudioCaptureToolbar::AudioCaptureToolbar(QWidget *parent, OBSSource source) + : ComboSelectToolbar(parent, source) +{ +} + +void AudioCaptureToolbar::Init() +{ + delete ui->activateButton; + ui->activateButton = nullptr; + + obs_module_t *mod = + get_os_module("win-wasapi", "mac-capture", "linux-pulseaudio"); + const char *device_str = + get_os_text(mod, "Device", "CoreAudio.Device", "Device"); + ui->deviceLabel->setText(device_str); + + prop_name = "device_id"; + + ComboSelectToolbar::Init(); +} + +WindowCaptureToolbar::WindowCaptureToolbar(QWidget *parent, OBSSource source) + : ComboSelectToolbar(parent, source) +{ +} + +void WindowCaptureToolbar::Init() +{ + delete ui->activateButton; + ui->activateButton = nullptr; + + obs_module_t *mod = + get_os_module("win-capture", "mac-capture", "linux-capture"); + const char *device_str = get_os_text(mod, "WindowCapture.Window", + "WindowUtils.Window", "Window"); + ui->deviceLabel->setText(device_str); + +#if !defined(_WIN32) && !defined(__APPLE__) //linux + prop_name = "capture_window"; +#else + prop_name = "window"; +#endif + +#ifdef __APPLE__ + is_int = true; +#endif + + ComboSelectToolbar::Init(); +} + +DisplayCaptureToolbar::DisplayCaptureToolbar(QWidget *parent, OBSSource source) + : ComboSelectToolbar(parent, source) +{ +} + +void DisplayCaptureToolbar::Init() +{ + delete ui->activateButton; + ui->activateButton = nullptr; + + obs_module_t *mod = + get_os_module("win-capture", "mac-capture", "linux-capture"); + const char *device_str = + get_os_text(mod, "Monitor", "DisplayCapture.Display", "Screen"); + ui->deviceLabel->setText(device_str); + is_int = true; + +#ifdef _WIN32 + prop_name = "monitor"; +#elif __APPLE__ + prop_name = "display"; +#else + prop_name = "screen"; +#endif + + ComboSelectToolbar::Init(); +} + +DeviceCaptureToolbar::DeviceCaptureToolbar(QWidget *parent, OBSSource source) + : ComboSelectToolbar(parent, source) +{ +} + +void DeviceCaptureToolbar::Init() +{ +#ifndef _WIN32 + delete ui->activateButton; + ui->activateButton = nullptr; +#endif + + obs_module_t *mod = + get_os_module("win-dshow", "mac-avcapture", "linux-v4l2"); + const char *device_str = obs_module_get_locale_text(mod, "Device"); + ui->deviceLabel->setText(device_str); + +#ifdef _WIN32 + prop_name = "video_device_id"; +#elif __APPLE__ + prop_name = "device"; +#else + prop_name = "device_id"; +#endif + +#ifdef _WIN32 + UpdateActivateButtonName(); +#endif + + ComboSelectToolbar::Init(); +} + +/* ========================================================================= */ + +GameCaptureToolbar::GameCaptureToolbar(QWidget *parent, OBSSource source) + : SourceToolbar(parent, source), ui(new Ui_GameCaptureToolbar) +{ + obs_property_t *p; + int cur_idx; + + ui->setupUi(this); + + obs_module_t *mod = obs_get_module("win-capture"); + ui->modeLabel->setText(obs_module_get_locale_text(mod, "Mode")); + ui->windowLabel->setText( + obs_module_get_locale_text(mod, "WindowCapture.Window")); + + obs_data_t *settings = obs_source_get_settings(source); + std::string cur_mode = obs_data_get_string(settings, "capture_mode"); + std::string cur_window = obs_data_get_string(settings, "window"); + obs_data_release(settings); + + ui->mode->blockSignals(true); + p = obs_properties_get(props.get(), "capture_mode"); + cur_idx = FillPropertyCombo(ui->mode, p, cur_mode); + ui->mode->setCurrentIndex(cur_idx); + ui->mode->blockSignals(false); + + ui->window->blockSignals(true); + p = obs_properties_get(props.get(), "window"); + cur_idx = FillPropertyCombo(ui->window, p, cur_window); + ui->window->setCurrentIndex(cur_idx); + ui->window->blockSignals(false); + + if (cur_idx != -1 && obs_property_list_item_disabled(p, cur_idx)) { + SetComboItemDisabled(ui->window, cur_idx); + } + + UpdateWindowVisibility(); +} + +GameCaptureToolbar::~GameCaptureToolbar() +{ + delete ui; +} + +void GameCaptureToolbar::UpdateWindowVisibility() +{ + QString mode = ui->mode->currentData().toString(); + bool is_window = (mode == "window"); + ui->windowLabel->setVisible(is_window); + ui->window->setVisible(is_window); + ui->empty->setVisible(!is_window); +} + +void GameCaptureToolbar::on_mode_currentIndexChanged(int idx) +{ + OBSSource source = GetSource(); + if (idx == -1 || !source) { + return; + } + + QString id = ui->mode->itemData(idx).toString(); + + obs_data_t *settings = obs_data_create(); + obs_data_set_string(settings, "capture_mode", QT_TO_UTF8(id)); + obs_source_update(source, settings); + obs_data_release(settings); + + UpdateWindowVisibility(); +} + +void GameCaptureToolbar::on_window_currentIndexChanged(int idx) +{ + OBSSource source = GetSource(); + if (idx == -1 || !source) { + return; + } + + QString id = ui->window->itemData(idx).toString(); + + obs_data_t *settings = obs_data_create(); + obs_data_set_string(settings, "window", QT_TO_UTF8(id)); + obs_source_update(source, settings); + obs_data_release(settings); +} + +/* ========================================================================= */ + +ImageSourceToolbar::ImageSourceToolbar(QWidget *parent, OBSSource source) + : SourceToolbar(parent, source), ui(new Ui_ImageSourceToolbar) +{ + ui->setupUi(this); + + obs_module_t *mod = obs_get_module("image-source"); + ui->pathLabel->setText(obs_module_get_locale_text(mod, "File")); + + obs_data_t *settings = obs_source_get_settings(source); + std::string file = obs_data_get_string(settings, "file"); + obs_data_release(settings); + + ui->path->setText(file.c_str()); +} + +ImageSourceToolbar::~ImageSourceToolbar() +{ + delete ui; +} + +void ImageSourceToolbar::on_browse_clicked() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + obs_property_t *p = obs_properties_get(props.get(), "file"); + const char *desc = obs_property_description(p); + const char *filter = obs_property_path_filter(p); + const char *default_path = obs_property_path_default_path(p); + + QString path = OpenFile(this, desc, default_path, filter); + if (path.isEmpty()) { + return; + } + + ui->path->setText(path); + + obs_data_t *settings = obs_data_create(); + obs_data_set_string(settings, "file", QT_TO_UTF8(path)); + obs_source_update(source, settings); + obs_data_release(settings); +} + +/* ========================================================================= */ + +static inline QColor color_from_int(long long val) +{ + return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, + (val >> 24) & 0xff); +} + +static inline long long color_to_int(QColor color) +{ + auto shift = [&](unsigned val, int shift) { + return ((val & 0xff) << shift); + }; + + return shift(color.red(), 0) | shift(color.green(), 8) | + shift(color.blue(), 16) | shift(color.alpha(), 24); +} + +ColorSourceToolbar::ColorSourceToolbar(QWidget *parent, OBSSource source) + : SourceToolbar(parent, source), ui(new Ui_ColorSourceToolbar) +{ + ui->setupUi(this); + + obs_data_t *settings = obs_source_get_settings(source); + unsigned int val = (unsigned int)obs_data_get_int(settings, "color"); + obs_data_release(settings); + + color = color_from_int(val); + UpdateColor(); +} + +ColorSourceToolbar::~ColorSourceToolbar() +{ + delete ui; +} + +void ColorSourceToolbar::UpdateColor() +{ + color.setAlpha(255); + + QPalette palette = QPalette(color); + ui->color->setFrameStyle(QFrame::Sunken | QFrame::Panel); + ui->color->setText(color.name(QColor::HexRgb)); + ui->color->setPalette(palette); + ui->color->setStyleSheet( + QString("background-color :%1; color: %2;") + .arg(palette.color(QPalette::Window) + .name(QColor::HexRgb)) + .arg(palette.color(QPalette::WindowText) + .name(QColor::HexRgb))); + ui->color->setAutoFillBackground(true); + ui->color->setAlignment(Qt::AlignCenter); +} + +void ColorSourceToolbar::on_choose_clicked() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + obs_property_t *p = obs_properties_get(props.get(), "color"); + const char *desc = obs_property_description(p); + + QColorDialog::ColorDialogOptions options; + +#ifndef _WIN32 + options |= QColorDialog::DontUseNativeDialog; +#endif + + color = QColorDialog::getColor(color, this, desc, options); + UpdateColor(); + + obs_data_t *settings = obs_data_create(); + obs_data_set_int(settings, "color", color_to_int(color)); + obs_source_update(source, settings); + obs_data_release(settings); +} + +/* ========================================================================= */ + +extern void MakeQFont(obs_data_t *font_obj, QFont &font, bool limit = false); + +TextSourceToolbar::TextSourceToolbar(QWidget *parent, OBSSource source) + : SourceToolbar(parent, source), ui(new Ui_TextSourceToolbar) +{ + ui->setupUi(this); + + obs_data_t *settings = obs_source_get_settings(source); + + const char *id = obs_source_get_unversioned_id(source); + bool ft2 = strcmp(id, "text_ft2_source") == 0; + bool read_from_file = obs_data_get_bool( + settings, ft2 ? "from_file" : "read_from_file"); + + obs_data_t *font_obj = obs_data_get_obj(settings, "font"); + MakeQFont(font_obj, font); + obs_data_release(font_obj); + + unsigned int val = (unsigned int)obs_data_get_int(settings, "color"); + color = color_from_int(val); + + const char *text = obs_data_get_string(settings, "text"); + + bool single_line = !read_from_file && + (!text || (strchr(text, '\n') == nullptr)); + ui->emptySpace->setVisible(!single_line); + ui->text->setVisible(single_line); + if (single_line) + ui->text->setText(text); + + obs_data_release(settings); +} + +TextSourceToolbar::~TextSourceToolbar() +{ + delete ui; +} + +void TextSourceToolbar::on_selectFont_clicked() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + QFontDialog::FontDialogOptions options; + uint32_t flags; + bool success; + +#ifndef _WIN32 + options = QFontDialog::DontUseNativeDialog; +#endif + + font = QFontDialog::getFont(&success, font, this, "Pick a Font", + options); + if (!success) { + return; + } + + obs_data_t *font_obj = obs_data_create(); + + obs_data_set_string(font_obj, "face", QT_TO_UTF8(font.family())); + obs_data_set_string(font_obj, "style", QT_TO_UTF8(font.styleName())); + obs_data_set_int(font_obj, "size", font.pointSize()); + flags = font.bold() ? OBS_FONT_BOLD : 0; + flags |= font.italic() ? OBS_FONT_ITALIC : 0; + flags |= font.underline() ? OBS_FONT_UNDERLINE : 0; + flags |= font.strikeOut() ? OBS_FONT_STRIKEOUT : 0; + obs_data_set_int(font_obj, "flags", flags); + + obs_data_t *settings = obs_data_create(); + + obs_data_set_obj(settings, "font", font_obj); + obs_data_release(font_obj); + + obs_source_update(source, settings); + obs_data_release(settings); +} + +void TextSourceToolbar::on_selectColor_clicked() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + obs_property_t *p = obs_properties_get(props.get(), "color"); + const char *desc = obs_property_description(p); + + QColorDialog::ColorDialogOptions options; + +#ifndef _WIN32 + options |= QColorDialog::DontUseNativeDialog; +#endif + + QColor newColor = QColorDialog::getColor(color, this, desc, options); + if (!newColor.isValid()) { + return; + } + + color = newColor; + + obs_data_t *settings = obs_data_create(); + obs_data_set_int(settings, "color", color_to_int(color)); + obs_source_update(source, settings); + obs_data_release(settings); +} + +void TextSourceToolbar::on_text_textChanged() +{ + OBSSource source = GetSource(); + if (!source) { + return; + } + + obs_data_t *settings = obs_data_create(); + obs_data_set_string(settings, "text", QT_TO_UTF8(ui->text->text())); + obs_source_update(source, settings); + obs_data_release(settings); +} diff --git a/UI/context-bar-controls.hpp b/UI/context-bar-controls.hpp new file mode 100644 index 000000000..1c03beca6 --- /dev/null +++ b/UI/context-bar-controls.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include + +class Ui_BrowserSourceToolbar; +class Ui_DeviceSelectToolbar; +class Ui_GameCaptureToolbar; +class Ui_ImageSourceToolbar; +class Ui_ColorSourceToolbar; +class Ui_TextSourceToolbar; + +class SourceToolbar : public QWidget { + Q_OBJECT + + OBSWeakSource weakSource; + +protected: + using properties_delete_t = decltype(&obs_properties_destroy); + using properties_t = + std::unique_ptr; + + properties_t props; + +public: + SourceToolbar(QWidget *parent, OBSSource source); + + OBSSource GetSource() { return OBSGetStrongRef(weakSource); } + +public slots: + virtual void Update() {} +}; + +class BrowserToolbar : public SourceToolbar { + Q_OBJECT + + Ui_BrowserSourceToolbar *ui; + +public: + BrowserToolbar(QWidget *parent, OBSSource source); + ~BrowserToolbar(); + +public slots: + void on_refresh_clicked(); +}; + +class ComboSelectToolbar : public SourceToolbar { + Q_OBJECT + +protected: + Ui_DeviceSelectToolbar *ui; + const char *prop_name; + bool is_int = false; + + void UpdateActivateButtonName(); + +public: + ComboSelectToolbar(QWidget *parent, OBSSource source); + ~ComboSelectToolbar(); + virtual void Init(); + +public slots: + void on_device_currentIndexChanged(int idx); + void on_activateButton_clicked(); +}; + +class AudioCaptureToolbar : public ComboSelectToolbar { + Q_OBJECT + +public: + AudioCaptureToolbar(QWidget *parent, OBSSource source); + void Init() override; +}; + +class WindowCaptureToolbar : public ComboSelectToolbar { + Q_OBJECT + +public: + WindowCaptureToolbar(QWidget *parent, OBSSource source); + void Init() override; +}; + +class DisplayCaptureToolbar : public ComboSelectToolbar { + Q_OBJECT + +public: + DisplayCaptureToolbar(QWidget *parent, OBSSource source); + void Init() override; +}; + +class DeviceCaptureToolbar : public ComboSelectToolbar { + Q_OBJECT + +public: + DeviceCaptureToolbar(QWidget *parent, OBSSource source); + void Init() override; +}; + +class GameCaptureToolbar : public SourceToolbar { + Q_OBJECT + + Ui_GameCaptureToolbar *ui; + + void UpdateWindowVisibility(); + +public: + GameCaptureToolbar(QWidget *parent, OBSSource source); + ~GameCaptureToolbar(); + +public slots: + void on_mode_currentIndexChanged(int idx); + void on_window_currentIndexChanged(int idx); +}; + +class ImageSourceToolbar : public SourceToolbar { + Q_OBJECT + + Ui_ImageSourceToolbar *ui; + +public: + ImageSourceToolbar(QWidget *parent, OBSSource source); + ~ImageSourceToolbar(); + +public slots: + void on_browse_clicked(); +}; + +class ColorSourceToolbar : public SourceToolbar { + Q_OBJECT + + Ui_ColorSourceToolbar *ui; + QColor color; + + void UpdateColor(); + +public: + ColorSourceToolbar(QWidget *parent, OBSSource source); + ~ColorSourceToolbar(); + +public slots: + void on_choose_clicked(); +}; + +class TextSourceToolbar : public SourceToolbar { + Q_OBJECT + + Ui_TextSourceToolbar *ui; + QFont font; + QColor color; + +public: + TextSourceToolbar(QWidget *parent, OBSSource source); + ~TextSourceToolbar(); + +public slots: + void on_selectFont_clicked(); + void on_selectColor_clicked(); + void on_text_textChanged(); +}; diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 62fa9862e..d3f7392af 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -92,6 +92,7 @@ Calculating="Calculating..." Fullscreen="Fullscreen" Windowed="Windowed" Percent="Percent" +RefreshBrowser="Refresh" AspectRatio="Aspect Ratio %1:%2" LockVolume="Lock Volume" LogViewer="Log Viewer" @@ -538,6 +539,8 @@ Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..." Basic.Main.StopStreaming="Stop Streaming" Basic.Main.StoppingStreaming="Stopping Stream..." Basic.Main.ForceStopStreaming="Stop Streaming (discard delay)" +Basic.Main.ShowContextBar="Show Source Toolbar" +Basic.Main.HideContextBar="Hide Source Toolbar" Basic.Main.StopVirtualCam="Stop Virtual Camera" Basic.Main.Group="Group %1" Basic.Main.GroupItems="Group Selected Items" @@ -598,6 +601,7 @@ Basic.MainMenu.View.Docks.ResetUI="Reset UI" Basic.MainMenu.View.Docks.LockUI="Lock UI" Basic.MainMenu.View.Docks.CustomBrowserDocks="Custom Browser Docks..." Basic.MainMenu.View.ListboxToolbars="Scene/Source List Buttons" +Basic.MainMenu.View.ContextBar="Source Toolbar" Basic.MainMenu.View.SceneTransitions="S&cene Transitions" Basic.MainMenu.View.SourceIcons="Source &Icons" Basic.MainMenu.View.StatusBar="&Status Bar" @@ -1033,3 +1037,18 @@ XSplitBroadcaster="XSplit Broadcaster" # OBS restart Restart="Restart" NeedsRestart="OBS Studio needs to be restarted. Do you want to restart now?" + +# Context Bar +ContextBar.NoSelectedSource="No source selected" +ContextBar.ResetTransform="Reset Transform" +ContextBar.FitToCanvas="Fit to Canvas" + +# Context Bar Media Controls +ContextBar.MediaControls.PlayMedia="Play Media" +ContextBar.MediaControls.PauseMedia="Pause Media" +ContextBar.MediaControls.StopMedia="Stop Media" +ContextBar.MediaControls.RestartMedia="Restart Media" +ContextBar.MediaControls.PlaylistNext="Next in Playlist" +ContextBar.MediaControls.PlaylistPrevious="Previous in Playlist" +ContextBar.MediaControls.MediaProperties="Media Properties" +ContextBar.MediaControls.BlindSeek="Media Seek Widget" diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index 5a6e867fc..8a6fdf7bb 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -311,6 +311,24 @@ QScrollBar::left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBa color: none; } +/* Source Context */ +#contextContainer { + min-height: 40px; + max-height: 40px; +} + +#contextContainer QPushButton[themeID2=contextBarButton] { + padding: 0px; +} + +QPushButton#sourcePropertiesButton { + qproperty-icon: url(./Dark/settings/general.svg); +} + +QPushButton#sourceFiltersButton { + qproperty-icon: url(./Dark/filter.svg); +} + /* Scenes and Sources toolbar */ QToolBar { @@ -1108,3 +1126,29 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { height: 28px; margin: -28px 0px; } + +/* Media icons */ + +* [themeID="playIcon"] { + qproperty-icon: url(./Dark/media/media_play.svg); +} + +* [themeID="pauseIcon"] { + qproperty-icon: url(./Dark/media/media_pause.svg); +} + +* [themeID="restartIcon"] { + qproperty-icon: url(./Dark/media/media_restart.svg); +} + +* [themeID="stopIcon"] { + qproperty-icon: url(./Dark/media/media_stop.svg); +} + +* [themeID="nextIcon"] { + qproperty-icon: url(./Dark/media/media_next.svg); +} + +* [themeID="previousIcon"] { + qproperty-icon: url(./Dark/media/media_previous.svg); +} diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index cad2422e2..b9ab60a5a 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -220,6 +220,19 @@ QScrollBar::left-arrow:horizontal, QScrollBar::right-arrow:horizontal, QScrollBa color: none; } +/* Source Context */ +#contextContainer QPushButton[themeID2=contextBarButton] { + padding: 3px; + margin: 0px; +} + +#contextContainer QPushButton#sourcePropertiesButton { + qproperty-icon: url(./Dark/settings/general.svg); +} + +#contextContainer QPushButton#sourceFiltersButton { + qproperty-icon: url(./Dark/filter.svg); +} /* Scenes and Sources toolbar */ @@ -839,3 +852,29 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { height: 24px; margin: -24px 0px; } + +/* Media icons */ + +* [themeID="playIcon"] { + qproperty-icon: url(./Dark/media/media_play.svg); +} + +* [themeID="pauseIcon"] { + qproperty-icon: url(./Dark/media/media_pause.svg); +} + +* [themeID="restartIcon"] { + qproperty-icon: url(./Dark/media/media_restart.svg); +} + +* [themeID="stopIcon"] { + qproperty-icon: url(./Dark/media/media_stop.svg); +} + +* [themeID="nextIcon"] { + qproperty-icon: url(./Dark/media/media_next.svg); +} + +* [themeID="previousIcon"] { + qproperty-icon: url(./Dark/media/media_previous.svg); +} diff --git a/UI/data/themes/Dark/filter.svg b/UI/data/themes/Dark/filter.svg new file mode 100644 index 000000000..00a41f532 --- /dev/null +++ b/UI/data/themes/Dark/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/UI/data/themes/Dark/media/media_next.svg b/UI/data/themes/Dark/media/media_next.svg new file mode 100644 index 000000000..afaed6b76 --- /dev/null +++ b/UI/data/themes/Dark/media/media_next.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Dark/media/media_pause.svg b/UI/data/themes/Dark/media/media_pause.svg new file mode 100644 index 000000000..a741b4d38 --- /dev/null +++ b/UI/data/themes/Dark/media/media_pause.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Dark/media/media_play.svg b/UI/data/themes/Dark/media/media_play.svg new file mode 100644 index 000000000..df4659d21 --- /dev/null +++ b/UI/data/themes/Dark/media/media_play.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Dark/media/media_previous.svg b/UI/data/themes/Dark/media/media_previous.svg new file mode 100644 index 000000000..dea2b2252 --- /dev/null +++ b/UI/data/themes/Dark/media/media_previous.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Dark/media/media_restart.svg b/UI/data/themes/Dark/media/media_restart.svg new file mode 100644 index 000000000..da17ac7a7 --- /dev/null +++ b/UI/data/themes/Dark/media/media_restart.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Dark/media/media_stop.svg b/UI/data/themes/Dark/media/media_stop.svg new file mode 100644 index 000000000..fc9926824 --- /dev/null +++ b/UI/data/themes/Dark/media/media_stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 2e9e16905..0807c25a2 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -981,6 +981,12 @@ QPushButton[themeID="removeIconSmall"], QPushButton[themeID="configIconSmall"], QPushButton[themeID="trashIcon"], QPushButton[themeID="revertIcon"], +QPushButton[themeID="playIcon"], +QPushButton[themeID="pauseIcon"], +QPushButton[themeID="restartIcon"], +QPushButton[themeID="stopIcon"], +QPushButton[themeID="nextIcon"], +QPushButton[themeID="previousIcon"], QPushButton#transitionRemove, QPushButton#moveAsyncFilterUp, QPushButton#moveAsyncFilterDown, @@ -996,6 +1002,12 @@ QPushButton:hover[themeID="removeIconSmall"], QPushButton:hover[themeID="configIconSmall"], QPushButton:hover[themeID="trashIcon"], QPushButton:hover[themeID="revertIcon"], +QPushButton:hover[themeID="playIcon"], +QPushButton:hover[themeID="pauseIcon"], +QPushButton:hover[themeID="restartIcon"], +QPushButton:hover[themeID="stopIcon"], +QPushButton:hover[themeID="nextIcon"], +QPushButton:hover[themeID="previousIcon"], QPushButton:hover#transitionRemove, QPushButton:hover#moveAsyncFilterUp, QPushButton:hover#moveAsyncFilterDown, @@ -1409,3 +1421,40 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { height: 24px; margin: -24px 0px; } + +/* Source Context */ +#contextContainer QPushButton[themeID2=contextBarButton] { + padding: 0px; + background: transparent; + border: none; +} + +QPushButton#sourcePropertiesButton { + qproperty-icon: url(./Dark/settings/general.svg); +} + +QPushButton#sourceFiltersButton { + qproperty-icon: url(./Dark/filter.svg); +} + +/* Media icons */ + +* [themeID="playIcon"] { + qproperty-icon: url(./Dark/media/media_play.svg); +} + +* [themeID="pauseIcon"] { + qproperty-icon: url(./Dark/media/media_pause.svg); +} + +* [themeID="restartIcon"] { + qproperty-icon: url(./Dark/media/media_restart.svg); +} + +* [themeID="stopIcon"] { + qproperty-icon: url(./Dark/media/media_stop.svg); +} + +* [themeID="nextIcon"] { + qproperty-icon: url(./Dark/media/media_next.svg); +} diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index 97f03ab40..125c5089e 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -265,3 +265,34 @@ QSlider::handle:horizontal[themeID="tBarSlider"] { height: 24px; margin: -24px 0px; } + +/* Source Context */ +#contextContainer QPushButton[themeID2=contextBarButton] { + padding: 0px; +} + +/* Media icons */ + +* [themeID="playIcon"] { + qproperty-icon: url(:res/images/media/media_play.svg); +} + +* [themeID="pauseIcon"] { + qproperty-icon: url(:/res/images/media/media_pause.svg); +} + +* [themeID="restartIcon"] { + qproperty-icon: url(:/res/images/media/media_restart.svg); +} + +* [themeID="stopIcon"] { + qproperty-icon: url(:/res/images/media/media_stop.svg); +} + +* [themeID="nextIcon"] { + qproperty-icon: url(:/res/images/media/media_next.svg); +} + +* [themeID="previousIcon"] { + qproperty-icon: url(./Dark/media/media_previous.svg); +} diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index c288be5d3..6a6d87bdc 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -8,7 +8,7 @@ 0 0 1079 - 730 + 729 @@ -195,6 +195,187 @@ + + + + + 0 + 0 + + + + + 0 + 32 + + + + + 16777215 + 32 + + + + + 4 + + + QLayout::SetDefaultConstraint + + + 6 + + + 4 + + + 6 + + + 4 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 150 + 0 + + + + + 150 + 16 + + + + TextLabel + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + Properties + + + Properties + + + + :/settings/images/settings/general.svg:/settings/images/settings/general.svg + + + true + + + contextBarButton + + + + + + + Filters + + + Filters + + + + :/res/images/filter.svg:/res/images/filter.svg + + + true + + + contextBarButton + + + + + + + Qt::Vertical + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + @@ -402,6 +583,7 @@ + @@ -747,7 +929,7 @@ 0 0 - 82 + 92 16 @@ -801,7 +983,7 @@ 0 0 16 - 16 + 28 @@ -1762,6 +1944,25 @@ Basic.MainMenu.View.SourceIcons + + + true + + + Basic.MainMenu.View.Toolbars.Context + + + + + true + + + true + + + Basic.MainMenu.View.ContextBar + + diff --git a/UI/forms/images/filter.svg b/UI/forms/images/filter.svg new file mode 100644 index 000000000..6d3272bc0 --- /dev/null +++ b/UI/forms/images/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/UI/forms/images/media/media_next.svg b/UI/forms/images/media/media_next.svg new file mode 100644 index 000000000..df2129919 --- /dev/null +++ b/UI/forms/images/media/media_next.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/UI/forms/images/media/media_pause.svg b/UI/forms/images/media/media_pause.svg new file mode 100644 index 000000000..a74159e41 --- /dev/null +++ b/UI/forms/images/media/media_pause.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/UI/forms/images/media/media_play.svg b/UI/forms/images/media/media_play.svg new file mode 100644 index 000000000..54cebff91 --- /dev/null +++ b/UI/forms/images/media/media_play.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/UI/forms/images/media/media_previous.svg b/UI/forms/images/media/media_previous.svg new file mode 100644 index 000000000..78ac71476 --- /dev/null +++ b/UI/forms/images/media/media_previous.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/UI/forms/images/media/media_restart.svg b/UI/forms/images/media/media_restart.svg new file mode 100644 index 000000000..92611c5d1 --- /dev/null +++ b/UI/forms/images/media/media_restart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/UI/forms/images/media/media_stop.svg b/UI/forms/images/media/media_stop.svg new file mode 100644 index 000000000..af6f4f481 --- /dev/null +++ b/UI/forms/images/media/media_stop.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc index 040e9af58..97118eba7 100644 --- a/UI/forms/obs.qrc +++ b/UI/forms/obs.qrc @@ -2,6 +2,7 @@ images/save.svg images/media-pause.svg + images/filter.svg images/mute.svg images/refresh.svg images/no_sources.svg @@ -40,6 +41,12 @@ images/recording-pause-inactive.svg images/streaming-active.svg images/streaming-inactive.svg + images/media/media_play.svg + images/media/media_pause.svg + images/media/media_next.svg + images/media/media_previous.svg + images/media/media_restart.svg + images/media/media_stop.svg images/settings/output.svg diff --git a/UI/forms/source-toolbar/browser-source-toolbar.ui b/UI/forms/source-toolbar/browser-source-toolbar.ui new file mode 100644 index 000000000..4190213b9 --- /dev/null +++ b/UI/forms/source-toolbar/browser-source-toolbar.ui @@ -0,0 +1,75 @@ + + + BrowserSourceToolbar + + + + 0 + 0 + 628 + 38 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + RefreshBrowser + + + + :/res/images/refresh.svg:/res/images/refresh.svg + + + true + + + refreshIconSmall + + + contextBarButton + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/UI/forms/source-toolbar/color-source-toolbar.ui b/UI/forms/source-toolbar/color-source-toolbar.ui new file mode 100644 index 000000000..31e679ef5 --- /dev/null +++ b/UI/forms/source-toolbar/color-source-toolbar.ui @@ -0,0 +1,69 @@ + + + ColorSourceToolbar + + + + 0 + 0 + 565 + 37 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 80 + 0 + + + + color here + + + Qt::AlignCenter + + + + + + + Basic.PropertiesWindow.SelectColor + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/UI/forms/source-toolbar/device-select-toolbar.ui b/UI/forms/source-toolbar/device-select-toolbar.ui new file mode 100644 index 000000000..d52f27039 --- /dev/null +++ b/UI/forms/source-toolbar/device-select-toolbar.ui @@ -0,0 +1,88 @@ + + + DeviceSelectToolbar + + + + 0 + 0 + 665 + 43 + + + + Form + + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Device + + + device + + + + + + + + 0 + 0 + + + + + + + QComboBox::AdjustToMinimumContentsLength + + + + + + + Activate + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/UI/forms/source-toolbar/game-capture-toolbar.ui b/UI/forms/source-toolbar/game-capture-toolbar.ui new file mode 100644 index 000000000..a040e161a --- /dev/null +++ b/UI/forms/source-toolbar/game-capture-toolbar.ui @@ -0,0 +1,98 @@ + + + GameCaptureToolbar + + + + 0 + 0 + 650 + 29 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Mode + + + mode + + + + + + + + + + + + + + + 0 + 0 + + + + Window + + + window + + + + + + + + 0 + 0 + + + + + + + QComboBox::AdjustToMinimumContentsLength + + + + + + + + 0 + 0 + + + + + + + + + diff --git a/UI/forms/source-toolbar/image-source-toolbar.ui b/UI/forms/source-toolbar/image-source-toolbar.ui new file mode 100644 index 000000000..5fda7c945 --- /dev/null +++ b/UI/forms/source-toolbar/image-source-toolbar.ui @@ -0,0 +1,100 @@ + + + ImageSourceToolbar + + + + 0 + 0 + 580 + 41 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Image File + + + path + + + + + + + + 100 + 0 + + + + + 600 + 16777215 + + + + + + + true + + + + + + + + 0 + 0 + + + + Browse + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + diff --git a/UI/forms/source-toolbar/media-controls.ui b/UI/forms/source-toolbar/media-controls.ui new file mode 100644 index 000000000..9cb3b9912 --- /dev/null +++ b/UI/forms/source-toolbar/media-controls.ui @@ -0,0 +1,305 @@ + + + MediaControls + + + + 0 + 0 + 888 + 28 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 24 + 24 + + + + + 24 + 24 + + + + ContextBar.MediaControls.RestartMedia + + + + + + + :/res/images/media/media_restart.svg:/res/images/media/media_restart.svg + + + + 20 + 20 + + + + Space + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + ContextBar.MediaControls.PlaylistPrevious + + + + + + + :/res/images/media/media_previous.svg:/res/images/media/media_previous.svg + + + + 20 + 20 + + + + P + + + true + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + + 24 + 24 + + + + ContextBar.MediaControls.StopMedia + + + + + + + :/res/images/media/media_stop.svg:/res/images/media/media_stop.svg + + + + 20 + 20 + + + + S + + + true + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + + 24 + 24 + + + + ContextBar.MediaControls.PlaylistNext + + + + + + + :/res/images/media/media_next.svg:/res/images/media/media_next.svg + + + + 20 + 20 + + + + N + + + true + + + + + + + ContextBar.MediaControls.BlindSeek + + + 1024 + + + false + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 6 + 20 + + + + + + + + --:--:-- + + + + + + + / + + + + + + + --:--:-- + + + + + + + + 0 + 0 + + + + + + + + + MediaSlider + QSlider +
media-slider.hpp
+
+ + ClickableLabel + QLabel +
clickable-label.hpp
+
+
+ + + + +
diff --git a/UI/forms/source-toolbar/text-source-toolbar.ui b/UI/forms/source-toolbar/text-source-toolbar.ui new file mode 100644 index 000000000..9db30e0ff --- /dev/null +++ b/UI/forms/source-toolbar/text-source-toolbar.ui @@ -0,0 +1,60 @@ + + + TextSourceToolbar + + + + 0 + 0 + 726 + 49 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Basic.PropertiesWindow.SelectFont + + + + + + + Basic.PropertiesWindow.SelectColor + + + + + + + + 0 + 0 + + + + + + + + + + + + diff --git a/UI/locked-checkbox.cpp b/UI/locked-checkbox.cpp new file mode 100644 index 000000000..bd88a2aa1 --- /dev/null +++ b/UI/locked-checkbox.cpp @@ -0,0 +1,5 @@ +#include "locked-checkbox.hpp" + +LockedCheckBox::LockedCheckBox() {} + +LockedCheckBox::LockedCheckBox(QWidget *parent) : QCheckBox(parent) {} diff --git a/UI/locked-checkbox.hpp b/UI/locked-checkbox.hpp index 0eb2d82ab..e5405f076 100644 --- a/UI/locked-checkbox.hpp +++ b/UI/locked-checkbox.hpp @@ -4,4 +4,8 @@ class LockedCheckBox : public QCheckBox { Q_OBJECT + +public: + LockedCheckBox(); + explicit LockedCheckBox(QWidget *parent); }; diff --git a/UI/media-controls.cpp b/UI/media-controls.cpp new file mode 100644 index 000000000..0248ade81 --- /dev/null +++ b/UI/media-controls.cpp @@ -0,0 +1,428 @@ +#include "window-basic-main.hpp" +#include "media-controls.hpp" +#include "obs-app.hpp" +#include +#include +#include + +#include "ui_media-controls.h" + +void MediaControls::OBSMediaStopped(void *data, calldata_t *) +{ + MediaControls *media = static_cast(data); + QMetaObject::invokeMethod(media, "SetRestartState"); +} + +void MediaControls::OBSMediaPlay(void *data, calldata_t *) +{ + MediaControls *media = static_cast(data); + QMetaObject::invokeMethod(media, "SetPlayingState"); +} + +void MediaControls::OBSMediaPause(void *data, calldata_t *) +{ + MediaControls *media = static_cast(data); + QMetaObject::invokeMethod(media, "SetPausedState"); +} + +void MediaControls::OBSMediaStarted(void *data, calldata_t *) +{ + MediaControls *media = static_cast(data); + QMetaObject::invokeMethod(media, "SetPlayingState"); +} + +MediaControls::MediaControls(QWidget *parent) + : QWidget(parent), ui(new Ui::MediaControls) +{ + ui->setupUi(this); + ui->playPauseButton->setProperty("themeID", "playIcon"); + ui->previousButton->setProperty("themeID", "previousIcon"); + ui->nextButton->setProperty("themeID", "nextIcon"); + ui->stopButton->setProperty("themeID", "stopIcon"); + + connect(&mediaTimer, SIGNAL(timeout()), this, + SLOT(SetSliderPosition())); + connect(&seekTimer, SIGNAL(timeout()), this, SLOT(SeekTimerCallback())); + connect(ui->slider, SIGNAL(sliderPressed()), this, + SLOT(MediaSliderClicked())); + connect(ui->slider, SIGNAL(mediaSliderHovered(int)), this, + SLOT(MediaSliderHovered(int))); + connect(ui->slider, SIGNAL(sliderReleased()), this, + SLOT(MediaSliderReleased())); + connect(ui->slider, SIGNAL(sliderMoved(int)), this, + SLOT(MediaSliderMoved(int))); + + countDownTimer = config_get_bool(App()->GlobalConfig(), "BasicWindow", + "MediaControlsCountdownTimer"); + + QAction *restartAction = new QAction(this); + restartAction->setShortcut({Qt::Key_R}); + connect(restartAction, SIGNAL(triggered()), this, SLOT(RestartMedia())); + addAction(restartAction); +} + +MediaControls::~MediaControls() +{ + delete ui; +} + +bool MediaControls::MediaPaused() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (!source) { + return false; + } + + obs_media_state state = obs_source_media_get_state(source); + return state == OBS_MEDIA_STATE_PAUSED; +} + +int64_t MediaControls::GetSliderTime(int val) +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (!source) { + return 0; + } + + float percent = (float)val / (float)ui->slider->maximum(); + float duration = (float)obs_source_media_get_duration(source); + int64_t seekTo = (int64_t)(percent * duration); + + return seekTo; +} + +void MediaControls::MediaSliderClicked() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (!source) { + return; + } + + obs_media_state state = obs_source_media_get_state(source); + + if (state == OBS_MEDIA_STATE_PAUSED) { + prevPaused = true; + } else if (state == OBS_MEDIA_STATE_PLAYING) { + prevPaused = false; + PauseMedia(); + mediaTimer.stop(); + } + + seek = ui->slider->value(); + seekTimer.start(100); +} + +void MediaControls::MediaSliderReleased() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (!source) { + return; + } + + if (seekTimer.isActive()) { + seekTimer.stop(); + if (lastSeek != seek) { + obs_source_media_set_time(source, GetSliderTime(seek)); + } + + seek = lastSeek = -1; + } + + if (!prevPaused) { + PlayMedia(); + mediaTimer.start(1000); + } +} + +void MediaControls::MediaSliderHovered(int val) +{ + float seconds = ((float)GetSliderTime(val) / 1000.0f); + QToolTip::showText(QCursor::pos(), FormatSeconds((int)seconds), this); +} + +void MediaControls::MediaSliderMoved(int val) +{ + if (seekTimer.isActive()) { + seek = val; + } +} + +void MediaControls::SeekTimerCallback() +{ + if (lastSeek != seek) { + OBSSource source = OBSGetStrongRef(weakSource); + if (source) { + obs_source_media_set_time(source, GetSliderTime(seek)); + } + lastSeek = seek; + } +} + +void MediaControls::StartMediaTimer() +{ + if (isSlideshow) + return; + + if (!mediaTimer.isActive()) + mediaTimer.start(1000); +} + +void MediaControls::StopMediaTimer() +{ + if (mediaTimer.isActive()) + mediaTimer.stop(); +} + +void MediaControls::SetPlayingState() +{ + ui->slider->setEnabled(true); + ui->playPauseButton->setProperty("themeID", "pauseIcon"); + ui->playPauseButton->style()->unpolish(ui->playPauseButton); + ui->playPauseButton->style()->polish(ui->playPauseButton); + ui->playPauseButton->setToolTip( + QTStr("ContextBar.MediaControls.PauseMedia")); + + prevPaused = false; + + StartMediaTimer(); +} + +void MediaControls::SetPausedState() +{ + ui->playPauseButton->setProperty("themeID", "playIcon"); + ui->playPauseButton->style()->unpolish(ui->playPauseButton); + ui->playPauseButton->style()->polish(ui->playPauseButton); + ui->playPauseButton->setToolTip( + QTStr("ContextBar.MediaControls.PlayMedia")); + + StopMediaTimer(); +} + +void MediaControls::SetRestartState() +{ + ui->playPauseButton->setProperty("themeID", "restartIcon"); + ui->playPauseButton->style()->unpolish(ui->playPauseButton); + ui->playPauseButton->style()->polish(ui->playPauseButton); + ui->playPauseButton->setToolTip( + QTStr("ContextBar.MediaControls.RestartMedia")); + + ui->slider->setValue(0); + ui->timerLabel->setText("--:--:--"); + ui->durationLabel->setText("--:--:--"); + ui->slider->setEnabled(false); + + StopMediaTimer(); +} + +void MediaControls::RefreshControls() +{ + OBSSource source; + source = OBSGetStrongRef(weakSource); + + uint32_t flags = 0; + const char *id = nullptr; + + if (source) { + flags = obs_source_get_output_flags(source); + id = obs_source_get_unversioned_id(source); + } + + if (!source || !(flags & OBS_SOURCE_CONTROLLABLE_MEDIA)) { + SetRestartState(); + setEnabled(false); + hide(); + return; + } else { + setEnabled(true); + show(); + } + + bool has_playlist = strcmp(id, "ffmpeg_source") != 0; + ui->previousButton->setVisible(has_playlist); + ui->nextButton->setVisible(has_playlist); + + isSlideshow = strcmp(id, "slideshow") == 0; + ui->slider->setVisible(!isSlideshow); + ui->timerLabel->setVisible(!isSlideshow); + ui->label->setVisible(!isSlideshow); + ui->durationLabel->setVisible(!isSlideshow); + ui->emptySpaceAgain->setVisible(isSlideshow); + + obs_media_state state = obs_source_media_get_state(source); + + switch (state) { + case OBS_MEDIA_STATE_STOPPED: + case OBS_MEDIA_STATE_ENDED: + SetRestartState(); + break; + case OBS_MEDIA_STATE_PLAYING: + SetPlayingState(); + break; + case OBS_MEDIA_STATE_PAUSED: + SetPausedState(); + break; + default: + break; + } + + SetSliderPosition(); +} + +OBSSource MediaControls::GetSource() +{ + return OBSGetStrongRef(weakSource); +} + +void MediaControls::SetSource(OBSSource source) +{ + sigs.clear(); + + if (source) { + weakSource = OBSGetWeakRef(source); + signal_handler_t *sh = obs_source_get_signal_handler(source); + sigs.emplace_back(sh, "media_play", OBSMediaPlay, this); + sigs.emplace_back(sh, "media_pause", OBSMediaPause, this); + sigs.emplace_back(sh, "media_restart", OBSMediaPlay, this); + sigs.emplace_back(sh, "media_stopped", OBSMediaStopped, this); + sigs.emplace_back(sh, "media_started", OBSMediaStarted, this); + sigs.emplace_back(sh, "media_ended", OBSMediaStopped, this); + } else { + weakSource = nullptr; + } + + RefreshControls(); +} + +void MediaControls::SetSliderPosition() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (!source) { + return; + } + + float time = (float)obs_source_media_get_time(source); + float duration = (float)obs_source_media_get_duration(source); + + float sliderPosition = (time / duration) * (float)ui->slider->maximum(); + + ui->slider->setValue((int)sliderPosition); + + ui->timerLabel->setText(FormatSeconds((int)(time / 1000.0f))); + + if (!countDownTimer) + ui->durationLabel->setText( + FormatSeconds((int)(duration / 1000.0f))); + else + ui->durationLabel->setText( + QString("-") + + FormatSeconds((int)((duration - time) / 1000.0f))); +} + +QString MediaControls::FormatSeconds(int totalSeconds) +{ + int seconds = totalSeconds % 60; + int totalMinutes = totalSeconds / 60; + int minutes = totalMinutes % 60; + int hours = totalMinutes / 60; + + return QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds); +} + +void MediaControls::on_playPauseButton_clicked() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (!source) { + return; + } + + obs_media_state state = obs_source_media_get_state(source); + + switch (state) { + case OBS_MEDIA_STATE_STOPPED: + case OBS_MEDIA_STATE_ENDED: + RestartMedia(); + break; + case OBS_MEDIA_STATE_PLAYING: + PauseMedia(); + break; + case OBS_MEDIA_STATE_PAUSED: + PlayMedia(); + break; + default: + break; + } +} + +void MediaControls::RestartMedia() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (source) { + obs_source_media_restart(source); + } +} + +void MediaControls::PlayMedia() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (source) { + obs_source_media_play_pause(source, false); + } +} + +void MediaControls::PauseMedia() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (source) { + obs_source_media_play_pause(source, true); + } +} + +void MediaControls::StopMedia() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (source) { + obs_source_media_stop(source); + } +} + +void MediaControls::PlaylistNext() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (source) { + obs_source_media_next(source); + } +} + +void MediaControls::PlaylistPrevious() +{ + OBSSource source = OBSGetStrongRef(weakSource); + if (source) { + obs_source_media_previous(source); + } +} + +void MediaControls::on_stopButton_clicked() +{ + StopMedia(); +} + +void MediaControls::on_nextButton_clicked() +{ + PlaylistNext(); +} + +void MediaControls::on_previousButton_clicked() +{ + PlaylistPrevious(); +} + +void MediaControls::on_durationLabel_clicked() +{ + countDownTimer = !countDownTimer; + + config_set_bool(App()->GlobalConfig(), "BasicWindow", + "MediaControlsCountdownTimer", countDownTimer); + + if (MediaPaused()) + SetSliderPosition(); +} diff --git a/UI/media-controls.hpp b/UI/media-controls.hpp new file mode 100644 index 000000000..8ba56f577 --- /dev/null +++ b/UI/media-controls.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include "qt-wrappers.hpp" + +class Ui_MediaControls; + +class MediaControls : public QWidget { + Q_OBJECT + +private: + std::vector sigs; + OBSWeakSource weakSource = nullptr; + QTimer mediaTimer; + QTimer seekTimer; + int seek; + int lastSeek; + bool prevPaused = false; + bool countDownTimer = false; + bool isSlideshow = false; + + QString FormatSeconds(int totalSeconds); + void StartMediaTimer(); + void StopMediaTimer(); + void RefreshControls(); + void SetScene(OBSScene scene); + int64_t GetSliderTime(int val); + + static void OBSMediaStopped(void *data, calldata_t *calldata); + static void OBSMediaPlay(void *data, calldata_t *calldata); + static void OBSMediaPause(void *data, calldata_t *calldata); + static void OBSMediaStarted(void *data, calldata_t *calldata); + + Ui_MediaControls *ui; + +private slots: + void on_playPauseButton_clicked(); + void on_stopButton_clicked(); + void on_nextButton_clicked(); + void on_previousButton_clicked(); + void on_durationLabel_clicked(); + + void MediaSliderClicked(); + void MediaSliderReleased(); + void MediaSliderHovered(int val); + void MediaSliderMoved(int val); + void SetSliderPosition(); + void SetPlayingState(); + void SetPausedState(); + void SetRestartState(); + void RestartMedia(); + void StopMedia(); + void PlaylistNext(); + void PlaylistPrevious(); + + void SeekTimerCallback(); + +public slots: + void PlayMedia(); + void PauseMedia(); + +public: + MediaControls(QWidget *parent = nullptr); + ~MediaControls(); + + OBSSource GetSource(); + void SetSource(OBSSource newSource); + bool MediaPaused(); +}; diff --git a/UI/media-slider.cpp b/UI/media-slider.cpp new file mode 100644 index 000000000..6eb88d4ee --- /dev/null +++ b/UI/media-slider.cpp @@ -0,0 +1,34 @@ +#include "slider-absoluteset-style.hpp" +#include "media-slider.hpp" +#include + +MediaSlider::MediaSlider(QWidget *parent) : QSlider(parent) +{ + setMouseTracking(true); + + QString styleName = style()->objectName(); + QStyle *style; + style = QStyleFactory::create(styleName); + if (!style) { + style = new SliderAbsoluteSetStyle(); + } else { + style = new SliderAbsoluteSetStyle(style); + } + + style->setParent(this); + this->setStyle(style); +} + +void MediaSlider::mouseMoveEvent(QMouseEvent *event) +{ + int val = minimum() + ((maximum() - minimum()) * event->x()) / width(); + + if (val > maximum()) + val = maximum(); + else if (val < minimum()) + val = minimum(); + + emit mediaSliderHovered(val); + event->accept(); + QSlider::mouseMoveEvent(event); +} diff --git a/UI/media-slider.hpp b/UI/media-slider.hpp new file mode 100644 index 000000000..360403382 --- /dev/null +++ b/UI/media-slider.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +class MediaSlider : public QSlider { + Q_OBJECT + +public: + MediaSlider(QWidget *parent = nullptr); + +signals: + void mediaSliderHovered(int value); + +protected: + virtual void mouseMoveEvent(QMouseEvent *event) override; +}; diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 5861aae3b..aee14ba51 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -43,6 +43,7 @@ #include "window-basic-settings.hpp" #include "crash-report.hpp" #include "platform.hpp" +#include "obs-proxy-style.hpp" #include @@ -483,6 +484,10 @@ bool OBSApp::InitGlobalConfigDefaults() config_set_default_bool(globalConfig, "Video", "ResetOSXVSyncOnExit", true); #endif + + config_set_default_bool(globalConfig, "BasicWindow", + "MediaControlsCountdownTimer", true); + return true; } @@ -1082,6 +1087,7 @@ bool OBSApp::SetTheme(std::string name, std::string path) QString mpath = QString("file:///") + path.c_str(); setPalette(defaultPalette); setStyleSheet(mpath); + setStyle(new OBSProxyStyle); ParseExtraThemeData(path.c_str()); emit StyleChanged(); diff --git a/UI/obs-proxy-style.cpp b/UI/obs-proxy-style.cpp new file mode 100644 index 000000000..72b124881 --- /dev/null +++ b/UI/obs-proxy-style.cpp @@ -0,0 +1,77 @@ +#include "obs-proxy-style.hpp" +#include + +static inline uint qt_intensity(uint r, uint g, uint b) +{ + /* 30% red, 59% green, 11% blue */ + return (77 * r + 150 * g + 28 * b) / 255; +} + +/* The constants in the default QT styles don't dim the icons enough in + * disabled mode + * + * https://code.woboq.org/qt5/qtbase/src/widgets/styles/qcommonstyle.cpp.html#6429 + */ +QPixmap OBSProxyStyle::generatedIconPixmap(QIcon::Mode iconMode, + const QPixmap &pixmap, + const QStyleOption *option) const +{ + if (iconMode == QIcon::Disabled) { + QImage im = + pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + + /* Create a colortable based on the background + * (black -> bg -> white) */ + + QColor bg = option->palette.color(QPalette::Disabled, + QPalette::Window); + int red = bg.red(); + int green = bg.green(); + int blue = bg.blue(); + uchar reds[256], greens[256], blues[256]; + + for (int i = 0; i < 128; ++i) { + reds[i] = uchar((red * (i << 1)) >> 8); + greens[i] = uchar((green * (i << 1)) >> 8); + blues[i] = uchar((blue * (i << 1)) >> 8); + } + for (int i = 0; i < 128; ++i) { + reds[i + 128] = uchar(qMin(red + (i << 1), 255)); + greens[i + 128] = uchar(qMin(green + (i << 1), 255)); + blues[i + 128] = uchar(qMin(blue + (i << 1), 255)); + } + + /* High intensity colors needs dark shifting in the color + * table, while low intensity colors needs light shifting. This + * is to increase the perceived contrast. */ + + int intensity = qt_intensity(red, green, blue); + const int factor = 191; + + if ((red - factor > green && red - factor > blue) || + (green - factor > red && green - factor > blue) || + (blue - factor > red && blue - factor > green)) + qMin(255, intensity + 20); + else if (intensity <= 128) + intensity += 100; + + for (int y = 0; y < im.height(); ++y) { + QRgb *scanLine = (QRgb *)im.scanLine(y); + for (int x = 0; x < im.width(); ++x) { + QRgb pixel = *scanLine; + /* Calculate color table index, taking + * intensity adjustment and a magic offset into + * account. */ + uint ci = uint(qGray(pixel) / 3 + + (130 - intensity / 3)); + *scanLine = qRgba(reds[ci], greens[ci], + blues[ci], qAlpha(pixel)); + ++scanLine; + } + } + + return QPixmap::fromImage(im); + } + + return QProxyStyle::generatedIconPixmap(iconMode, pixmap, option); +} diff --git a/UI/obs-proxy-style.hpp b/UI/obs-proxy-style.hpp new file mode 100644 index 000000000..29ddd57ef --- /dev/null +++ b/UI/obs-proxy-style.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +class OBSProxyStyle : public QProxyStyle { +public: + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *option) const override; +}; diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index 6e70b5962..849dd4825 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -684,7 +684,7 @@ void OBSPropertiesView::AddColor(obs_property_t *prop, QFormLayout *layout, layout->addRow(label, subLayout); } -static void MakeQFont(obs_data_t *font_obj, QFont &font, bool limit = false) +void MakeQFont(obs_data_t *font_obj, QFont &font, bool limit = false) { const char *face = obs_data_get_string(font_obj, "face"); const char *style = obs_data_get_string(font_obj, "style"); diff --git a/UI/source-tree.cpp b/UI/source-tree.cpp index dc11827b3..d23477856 100644 --- a/UI/source-tree.cpp +++ b/UI/source-tree.cpp @@ -528,11 +528,13 @@ void SourceTreeItem::ExpandClicked(bool checked) void SourceTreeItem::Select() { tree->SelectItem(sceneitem, true); + OBSBasic::Get()->UpdateContextBar(); } void SourceTreeItem::Deselect() { tree->SelectItem(sceneitem, false); + OBSBasic::Get()->UpdateContextBar(); } /* ========================================================================= */ diff --git a/UI/visibility-checkbox.cpp b/UI/visibility-checkbox.cpp new file mode 100644 index 000000000..85cce6fbc --- /dev/null +++ b/UI/visibility-checkbox.cpp @@ -0,0 +1,5 @@ +#include "visibility-checkbox.hpp" + +VisibilityCheckBox::VisibilityCheckBox() {} + +VisibilityCheckBox::VisibilityCheckBox(QWidget *parent) : QCheckBox(parent) {} diff --git a/UI/visibility-checkbox.hpp b/UI/visibility-checkbox.hpp index ff21df2c4..e2f1f62ca 100644 --- a/UI/visibility-checkbox.hpp +++ b/UI/visibility-checkbox.hpp @@ -4,4 +4,8 @@ class VisibilityCheckBox : public QCheckBox { Q_OBJECT + +public: + VisibilityCheckBox(); + explicit VisibilityCheckBox(QWidget *parent); }; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index cf00bcd6f..94d6ac029 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -52,10 +52,12 @@ #include "window-projector.hpp" #include "window-remux.hpp" #include "qt-wrappers.hpp" +#include "context-bar-controls.hpp" #include "display-helpers.hpp" #include "volume-control.hpp" #include "remote-text.hpp" #include "ui-validation.hpp" +#include "media-controls.hpp" #include #include @@ -355,6 +357,8 @@ OBSBasic::OBSBasic(QWidget *parent) QPoint curPos; + UpdateContextBar(); + //restore parent window geometry const char *geometry = config_get_string(App()->GlobalConfig(), "BasicWindow", "geometry"); @@ -1721,6 +1725,18 @@ void OBSBasic::OBSInit() GetGlobalConfig(), "BasicWindow", "ShowSourceIcons"); ui->toggleSourceIcons->setChecked(sourceIconsVisible); + if (config_has_user_value(App()->GlobalConfig(), "BasicWindow", + "ShowContextToolbars")) { + bool visible = config_get_bool(App()->GlobalConfig(), + "BasicWindow", + "ShowContextToolbars"); + ui->toggleContextBar->setChecked(visible); + ui->contextContainer->setVisible(visible); + } else { + ui->toggleContextBar->setChecked(true); + ui->contextContainer->setVisible(true); + } + { ProfileScope("OBSBasic::Load"); disableSaving--; @@ -1803,6 +1819,7 @@ void OBSBasic::OBSInit() const char *dockStateStr = config_get_string( App()->GlobalConfig(), "BasicWindow", "DockState"); + if (!dockStateStr) { on_resetUI_triggered(); } else { @@ -2314,6 +2331,17 @@ void OBSBasic::CreateHotkeys() this, this); LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); + + contextBarHotkeys = obs_hotkey_pair_register_frontend( + "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), + "OBSBasic.HideContextBar", Str("Basic.Main.HideContextBar"), + MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), + basic.ShowContextBar, "Showing Context Bar"), + MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), + basic.HideContextBar, "Hiding Context Bar"), + this, this); + LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", + "OBSBasic.HideContextBar"); #undef MAKE_CALLBACK auto togglePreviewProgram = [](void *data, obs_hotkey_id, @@ -2844,6 +2872,107 @@ void OBSBasic::RenameSources(OBSSource source, QString newName, obs_scene_t *scene = obs_scene_from_source(source); if (scene) OBSProjector::UpdateMultiviewProjectors(); + + UpdateContextBar(); +} + +void OBSBasic::UpdateContextBar() +{ + OBSSceneItem item = GetCurrentSceneItem(); + + QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); + if (la) { + delete la->widget(); + ui->emptySpace->layout()->removeItem(la); + } + + if (item) { + obs_source_t *source = obs_sceneitem_get_source(item); + const char *id = obs_source_get_unversioned_id(source); + uint32_t flags = obs_source_get_output_flags(source); + + if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { + MediaControls *mediaControls = + new MediaControls(ui->emptySpace); + mediaControls->SetSource(source); + + ui->emptySpace->layout()->addWidget(mediaControls); + } + + if (strcmp(id, "browser_source") == 0) { + BrowserToolbar *c = + new BrowserToolbar(ui->emptySpace, source); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "wasapi_input_capture") == 0 || + strcmp(id, "wasapi_output_capture") == 0 || + strcmp(id, "coreaudio_input_capture") == 0 || + strcmp(id, "coreaudio_output_capture") == 0 || + strcmp(id, "pulse_input_capture") == 0 || + strcmp(id, "pulse_output_capture") == 0 || + strcmp(id, "alsa_input_capture") == 0) { + AudioCaptureToolbar *c = + new AudioCaptureToolbar(ui->emptySpace, source); + c->Init(); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "window_capture") == 0 || + strcmp(id, "xcomposite_input") == 0) { + WindowCaptureToolbar *c = new WindowCaptureToolbar( + ui->emptySpace, source); + c->Init(); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "monitor_capture") == 0 || + strcmp(id, "display_capture") == 0 || + strcmp(id, "xshm_input") == 0) { + DisplayCaptureToolbar *c = new DisplayCaptureToolbar( + ui->emptySpace, source); + c->Init(); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "dshow_input") == 0 || + strcmp(id, "av_capture_input") == 0 || + strcmp(id, "v4l2_input") == 0) { + DeviceCaptureToolbar *c = new DeviceCaptureToolbar( + ui->emptySpace, source); + c->Init(); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "game_capture") == 0) { + GameCaptureToolbar *c = + new GameCaptureToolbar(ui->emptySpace, source); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "image_source") == 0) { + ImageSourceToolbar *c = + new ImageSourceToolbar(ui->emptySpace, source); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "color_source") == 0) { + ColorSourceToolbar *c = + new ColorSourceToolbar(ui->emptySpace, source); + ui->emptySpace->layout()->addWidget(c); + + } else if (strcmp(id, "text_ft2_source") == 0 || + strcmp(id, "text_gdiplus") == 0) { + TextSourceToolbar *c = + new TextSourceToolbar(ui->emptySpace, source); + ui->emptySpace->layout()->addWidget(c); + } + + const char *name = obs_source_get_name(source); + ui->contextSourceLabel->setText(name); + + ui->sourceFiltersButton->setEnabled(true); + ui->sourcePropertiesButton->setEnabled(true); + } else { + ui->contextSourceLabel->setText( + QTStr("ContextBar.NoSelectedSource")); + + ui->sourceFiltersButton->setEnabled(false); + ui->sourcePropertiesButton->setEnabled(false); + } } static inline bool SourceMixerHidden(obs_source_t *source) @@ -4221,6 +4350,8 @@ void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, if (api) api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); + UpdateContextBar(); + UNUSED_PARAMETER(prev); } @@ -7146,6 +7277,23 @@ void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) "ShowListboxToolbars", visible); } +void OBSBasic::ShowContextBar() +{ + on_toggleContextBar_toggled(true); +} + +void OBSBasic::HideContextBar() +{ + on_toggleContextBar_toggled(false); +} + +void OBSBasic::on_toggleContextBar_toggled(bool visible) +{ + config_set_bool(App()->GlobalConfig(), "BasicWindow", + "ShowContextToolbars", visible); + this->ui->contextContainer->setVisible(visible); +} + void OBSBasic::on_toggleStatusBar_toggled(bool visible) { ui->statusbar->setVisible(visible); @@ -8125,3 +8273,13 @@ void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) for (size_t i = 0; i < projectors.size(); i++) SetAlwaysOnTop(projectors[i], top); } + +void OBSBasic::on_sourcePropertiesButton_clicked() +{ + on_actionSourceProperties_triggered(); +} + +void OBSBasic::on_sourceFiltersButton_clicked() +{ + OpenFilters(); +} diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index d65f7dc19..e6d4db6d1 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -379,7 +379,8 @@ private: QModelIndexList GetAllSelectedSourceItems(); obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, - replayBufHotkeys, vcamHotkeys, togglePreviewHotkeys; + replayBufHotkeys, vcamHotkeys, togglePreviewHotkeys, + contextBarHotkeys; obs_hotkey_id forceStreamingStopHotkey; void InitDefaultTransitions(); @@ -585,6 +586,8 @@ public slots: void UpdatePatronJson(const QString &text, const QString &error); + void ShowContextBar(); + void HideContextBar(); void PauseRecording(); void UnpauseRecording(); @@ -928,6 +931,7 @@ 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); @@ -938,6 +942,10 @@ private slots: void on_modeSwitch_clicked(); + // Source Context Buttons + void on_sourcePropertiesButton_clicked(); + void on_sourceFiltersButton_clicked(); + void on_autoConfigure_triggered(); void on_stats_triggered(); @@ -999,6 +1007,8 @@ public slots: bool RecordingActive(); bool ReplayBufferActive(); + void UpdateContextBar(); + public: explicit OBSBasic(QWidget *parent = 0); virtual ~OBSBasic(); diff --git a/UI/window-basic-properties.cpp b/UI/window-basic-properties.cpp index 689534da3..52dd19fad 100644 --- a/UI/window-basic-properties.cpp +++ b/UI/window-basic-properties.cpp @@ -205,6 +205,7 @@ OBSBasicProperties::~OBSBasicProperties() } obs_source_dec_showing(source); main->SaveProject(); + main->UpdateContextBar(); } void OBSBasicProperties::AddPreviewButton()