From fddbbe259da6091086d457666241a13d3e9c3e23 Mon Sep 17 00:00:00 2001 From: Colin Edwards Date: Thu, 23 Aug 2018 20:43:44 -0500 Subject: [PATCH] UI: Source Toolbar The source toolbar allows quick and easy access to properties and filers, and shows common properties/features of a source type. For example, when you select a media source, VLC source, or the slideshow source, you'll get media controls to control playback of the media. If you select a text source you can edit the font, color, or text if applicable. Or if you select a capture source, you can select the display/window/etc to capture for that source. If the source toolbar is not desired and is viewed as taking up valuable space in the window, it can be disabled via the view menu. Co-authored-by: Clayton Groeneveld Co-authored-by: Jim --- UI/CMakeLists.txt | 23 +- UI/context-bar-controls.cpp | 631 ++++++++++++++++++ UI/context-bar-controls.hpp | 160 +++++ UI/data/locale/en-US.ini | 19 + UI/data/themes/Acri.qss | 44 ++ UI/data/themes/Dark.qss | 39 ++ UI/data/themes/Dark/filter.svg | 1 + UI/data/themes/Dark/media/media_next.svg | 3 + UI/data/themes/Dark/media/media_pause.svg | 3 + UI/data/themes/Dark/media/media_play.svg | 3 + UI/data/themes/Dark/media/media_previous.svg | 3 + UI/data/themes/Dark/media/media_restart.svg | 3 + UI/data/themes/Dark/media/media_stop.svg | 3 + UI/data/themes/Rachni.qss | 49 ++ UI/data/themes/System.qss | 31 + UI/forms/OBSBasic.ui | 207 +++++- UI/forms/images/filter.svg | 1 + UI/forms/images/media/media_next.svg | 3 + UI/forms/images/media/media_pause.svg | 3 + UI/forms/images/media/media_play.svg | 3 + UI/forms/images/media/media_previous.svg | 3 + UI/forms/images/media/media_restart.svg | 3 + UI/forms/images/media/media_stop.svg | 3 + UI/forms/obs.qrc | 7 + .../source-toolbar/browser-source-toolbar.ui | 75 +++ .../source-toolbar/color-source-toolbar.ui | 69 ++ .../source-toolbar/device-select-toolbar.ui | 88 +++ .../source-toolbar/game-capture-toolbar.ui | 98 +++ .../source-toolbar/image-source-toolbar.ui | 100 +++ UI/forms/source-toolbar/media-controls.ui | 305 +++++++++ .../source-toolbar/text-source-toolbar.ui | 60 ++ UI/locked-checkbox.cpp | 5 + UI/locked-checkbox.hpp | 4 + UI/media-controls.cpp | 428 ++++++++++++ UI/media-controls.hpp | 72 ++ UI/media-slider.cpp | 34 + UI/media-slider.hpp | 17 + UI/obs-app.cpp | 6 + UI/obs-proxy-style.cpp | 77 +++ UI/obs-proxy-style.hpp | 9 + UI/properties-view.cpp | 2 +- UI/source-tree.cpp | 2 + UI/visibility-checkbox.cpp | 5 + UI/visibility-checkbox.hpp | 4 + UI/window-basic-main.cpp | 158 +++++ UI/window-basic-main.hpp | 12 +- UI/window-basic-properties.cpp | 1 + 47 files changed, 2872 insertions(+), 7 deletions(-) create mode 100644 UI/context-bar-controls.cpp create mode 100644 UI/context-bar-controls.hpp create mode 100644 UI/data/themes/Dark/filter.svg create mode 100644 UI/data/themes/Dark/media/media_next.svg create mode 100644 UI/data/themes/Dark/media/media_pause.svg create mode 100644 UI/data/themes/Dark/media/media_play.svg create mode 100644 UI/data/themes/Dark/media/media_previous.svg create mode 100644 UI/data/themes/Dark/media/media_restart.svg create mode 100644 UI/data/themes/Dark/media/media_stop.svg create mode 100644 UI/forms/images/filter.svg create mode 100644 UI/forms/images/media/media_next.svg create mode 100644 UI/forms/images/media/media_pause.svg create mode 100644 UI/forms/images/media/media_play.svg create mode 100644 UI/forms/images/media/media_previous.svg create mode 100644 UI/forms/images/media/media_restart.svg create mode 100644 UI/forms/images/media/media_stop.svg create mode 100644 UI/forms/source-toolbar/browser-source-toolbar.ui create mode 100644 UI/forms/source-toolbar/color-source-toolbar.ui create mode 100644 UI/forms/source-toolbar/device-select-toolbar.ui create mode 100644 UI/forms/source-toolbar/game-capture-toolbar.ui create mode 100644 UI/forms/source-toolbar/image-source-toolbar.ui create mode 100644 UI/forms/source-toolbar/media-controls.ui create mode 100644 UI/forms/source-toolbar/text-source-toolbar.ui create mode 100644 UI/locked-checkbox.cpp create mode 100644 UI/media-controls.cpp create mode 100644 UI/media-controls.hpp create mode 100644 UI/media-slider.cpp create mode 100644 UI/media-slider.hpp create mode 100644 UI/obs-proxy-style.cpp create mode 100644 UI/obs-proxy-style.hpp create mode 100644 UI/visibility-checkbox.cpp 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()