Merge pull request #3268 from obsproject/context-bar

Add Source Toolbar
This commit is contained in:
Jim 2020-08-18 12:16:54 -07:00 committed by GitHub
commit bd512dae7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 2872 additions and 7 deletions

View File

@ -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

631
UI/context-bar-controls.cpp Normal file
View File

@ -0,0 +1,631 @@
#include "context-bar-controls.hpp"
#include "qt-wrappers.hpp"
#include "obs-app.hpp"
#include <QStandardItemModel>
#include <QColorDialog>
#include <QFontDialog>
#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<QStandardItemModel *>(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);
}

160
UI/context-bar-controls.hpp Normal file
View File

@ -0,0 +1,160 @@
#pragma once
#include <memory>
#include <obs.hpp>
#include <QWidget>
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<obs_properties_t, properties_delete_t>;
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();
};

View File

@ -92,6 +92,7 @@ Calculating="Calculating..."
Fullscreen="Fullscreen"
Windowed="Windowed"
Percent="Percent"
RefreshBrowser="Refresh"
AspectRatio="Aspect Ratio <b>%1:%2</b>"
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"

View File

@ -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);
}

View File

@ -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);
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="185.25 184.25 100 100" enable-background="new 185.25 184.25 100 100" xml:space="preserve"><g><g><g><path fill="#FFFFFF" d="M204.608,244.706l11.525,11.525c2.041,0.365,4.142,0.556,6.288,0.556c1.898,0,3.761-0.151,5.579-0.438l-22.379-22.38 c0.704-2.995,1.747-5.863,3.092-8.559l28.263,28.263c2.623-1.181,5.076-2.672,7.314-4.426l-30.99-30.99 c1.723-2.143,3.667-4.102,5.796-5.847l31.043,31.043c1.776-2.22,3.29-4.658,4.497-7.268l-28.425-28.426 c2.683-1.367,5.545-2.44,8.546-3.17l22.646,22.646c0.324-1.926,0.495-3.905,0.495-5.923c0-1.985-0.166-3.93-0.479-5.826 l-12.002-12.002c2.857,0.088,5.641,0.482,8.321,1.148c-5.969-11.183-17.752-18.795-31.315-18.795 c-19.592,0-35.475,15.883-35.475,35.475c0,13.563,7.612,25.346,18.796,31.315C205.11,250.078,204.721,247.429,204.608,244.706z"></path></g><path fill="#FFFFFF" d="M243.651,282.651c-0.88-0.002-1.77-0.034-2.644-0.095l0.587-8.494c1.239,0.086,2.503,0.097,3.757,0.035 c1.263-0.063,2.531-0.203,3.768-0.416l1.442,8.392c-1.572,0.27-3.182,0.448-4.785,0.528 C245.069,282.636,244.354,282.653,243.651,282.651z"></path><path fill="#FFFFFF" d="M231.594,280.743c-3.043-0.982-5.969-2.335-8.695-4.023l4.482-7.24c2.142,1.326,4.439,2.389,6.828,3.159L231.594,280.743z "></path><path fill="#FFFFFF" d="M259.738,279.319l-3.419-7.798c2.297-1.007,4.474-2.295,6.47-3.83l5.189,6.751 C265.437,276.395,262.665,278.036,259.738,279.319z"></path><path fill="#FFFFFF" d="M215.423,270.741c-2.244-2.286-4.208-4.839-5.837-7.587l7.325-4.341c1.28,2.16,2.824,4.166,4.588,5.964L215.423,270.741z"></path><path fill="#FFFFFF" d="M274.813,267.741l-6.647-5.322c1.575-1.967,2.909-4.12,3.965-6.398l7.725,3.58 C278.512,262.501,276.815,265.239,274.813,267.741z"></path><path fill="#FFFFFF" d="M282.803,250.48l-8.36-1.614c0.369-1.911,0.559-3.874,0.564-5.833c0.001-0.555-0.012-1.119-0.04-1.675l-0.004-0.072 l8.504-0.425l0.004,0.073c0.035,0.703,0.052,1.418,0.05,2.122C283.515,245.547,283.273,248.046,282.803,250.48z"></path><path fill="#FFFFFF" d="M273.677,233.877c-0.725-2.404-1.744-4.719-3.028-6.881l7.32-4.349c1.637,2.755,2.935,5.706,3.86,8.771L273.677,233.877z"></path><path fill="#FFFFFF" d="M266.055,221.036c-1.766-1.795-3.746-3.376-5.885-4.697l4.475-7.244c2.72,1.68,5.237,3.689,7.481,5.971L266.055,221.036z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
<path d="M0 0v6l5-3-5-3zm5 3v3h2v-6h-2v3z" transform="translate(0 1)" />
</svg>

After

Width:  |  Height:  |  Size: 177 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
<path d="M0 0v6h2v-6h-2zm4 0v6h2v-6h-2z" transform="translate(1 1)" />
</svg>

After

Width:  |  Height:  |  Size: 175 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
<path d="M0 0v6l6-3-6-3z" transform="translate(1 1)" />
</svg>

After

Width:  |  Height:  |  Size: 160 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
<path d="M0 0v6h2v-6h-2zm2 3l5 3v-6l-5 3z" transform="translate(0 1)" />
</svg>

After

Width:  |  Height:  |  Size: 177 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
<path d="M4 0c-2.2 0-4 1.8-4 4s1.8 4 4 4c1.1 0 2.12-.43 2.84-1.16l-.72-.72c-.54.54-1.29.88-2.13.88-1.66 0-3-1.34-3-3s1.34-3 3-3c.83 0 1.55.36 2.09.91l-1.09 1.09h3v-3l-1.19 1.19c-.72-.72-1.71-1.19-2.81-1.19z" />
</svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8" fill="#d2d2d2">
<path d="M0 0v6h6v-6h-6z" transform="translate(1 1)" />
</svg>

After

Width:  |  Height:  |  Size: 160 B

View File

@ -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);
}

View File

@ -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);
}

View File

@ -8,7 +8,7 @@
<x>0</x>
<y>0</y>
<width>1079</width>
<height>730</height>
<height>729</height>
</rect>
</property>
<property name="sizePolicy">
@ -195,6 +195,187 @@
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="contextContainer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>32</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout9">
<property name="spacing">
<number>4</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QWidget" name="contextSubContainer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="contextSourceLabel">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sourcePropertiesButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Properties</string>
</property>
<property name="text">
<string>Properties</string>
</property>
<property name="icon">
<iconset resource="obs.qrc">
<normaloff>:/settings/images/settings/general.svg</normaloff>:/settings/images/settings/general.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID2" stdset="0">
<string notr="true">contextBarButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sourceFiltersButton">
<property name="toolTip">
<string>Filters</string>
</property>
<property name="text">
<string>Filters</string>
</property>
<property name="icon">
<iconset resource="obs.qrc">
<normaloff>:/res/images/filter.svg</normaloff>:/res/images/filter.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID2" stdset="0">
<string notr="true">contextBarButton</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="emptySpace" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
@ -402,6 +583,7 @@
<addaction name="separator"/>
<addaction name="viewMenuDocks"/>
<addaction name="toggleListboxToolbars"/>
<addaction name="toggleContextBar"/>
<addaction name="toggleSourceIcons"/>
<addaction name="toggleStatusBar"/>
<addaction name="separator"/>
@ -747,7 +929,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>82</width>
<width>92</width>
<height>16</height>
</rect>
</property>
@ -801,7 +983,7 @@
<x>0</x>
<y>0</y>
<width>16</width>
<height>16</height>
<height>28</height>
</rect>
</property>
<property name="sizePolicy">
@ -1762,6 +1944,25 @@
<string>Basic.MainMenu.View.SourceIcons</string>
</property>
</action>
<action name="toggleContextToolbars">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Basic.MainMenu.View.Toolbars.Context</string>
</property>
</action>
<action name="toggleContextBar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Basic.MainMenu.View.ContextBar</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="185.25 184.25 100 100" enable-background="new 185.25 184.25 100 100" xml:space="preserve"><g><g><g><path d="M204.608,244.706l11.525,11.525c2.041,0.365,4.142,0.556,6.288,0.556c1.898,0,3.761-0.151,5.579-0.438l-22.379-22.38 c0.704-2.995,1.747-5.863,3.092-8.559l28.263,28.263c2.623-1.181,5.076-2.672,7.314-4.426l-30.99-30.99 c1.723-2.143,3.667-4.102,5.796-5.847l31.043,31.043c1.776-2.22,3.29-4.658,4.497-7.268l-28.425-28.426 c2.683-1.367,5.545-2.44,8.546-3.17l22.646,22.646c0.324-1.926,0.495-3.905,0.495-5.923c0-1.985-0.166-3.93-0.479-5.826 l-12.002-12.002c2.857,0.088,5.641,0.482,8.321,1.148c-5.969-11.183-17.752-18.795-31.315-18.795 c-19.592,0-35.475,15.883-35.475,35.475c0,13.563,7.612,25.346,18.796,31.315C205.11,250.078,204.721,247.429,204.608,244.706z"></path></g><path d="M243.651,282.651c-0.88-0.002-1.77-0.034-2.644-0.095l0.587-8.494c1.239,0.086,2.503,0.097,3.757,0.035 c1.263-0.063,2.531-0.203,3.768-0.416l1.442,8.392c-1.572,0.27-3.182,0.448-4.785,0.528 C245.069,282.636,244.354,282.653,243.651,282.651z"></path><path d="M231.594,280.743c-3.043-0.982-5.969-2.335-8.695-4.023l4.482-7.24c2.142,1.326,4.439,2.389,6.828,3.159L231.594,280.743z "></path><path d="M259.738,279.319l-3.419-7.798c2.297-1.007,4.474-2.295,6.47-3.83l5.189,6.751 C265.437,276.395,262.665,278.036,259.738,279.319z"></path><path d="M215.423,270.741c-2.244-2.286-4.208-4.839-5.837-7.587l7.325-4.341c1.28,2.16,2.824,4.166,4.588,5.964L215.423,270.741z"></path><path d="M274.813,267.741l-6.647-5.322c1.575-1.967,2.909-4.12,3.965-6.398l7.725,3.58 C278.512,262.501,276.815,265.239,274.813,267.741z"></path><path d="M282.803,250.48l-8.36-1.614c0.369-1.911,0.559-3.874,0.564-5.833c0.001-0.555-0.012-1.119-0.04-1.675l-0.004-0.072 l8.504-0.425l0.004,0.073c0.035,0.703,0.052,1.418,0.05,2.122C283.515,245.547,283.273,248.046,282.803,250.48z"></path><path d="M273.677,233.877c-0.725-2.404-1.744-4.719-3.028-6.881l7.32-4.349c1.637,2.755,2.935,5.706,3.86,8.771L273.677,233.877z"></path><path d="M266.055,221.036c-1.766-1.795-3.746-3.376-5.885-4.697l4.475-7.244c2.72,1.68,5.237,3.689,7.481,5.971L266.055,221.036z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M0 0v6l5-3-5-3zm5 3v3h2v-6h-2v3z" transform="translate(0 1)" />
</svg>

After

Width:  |  Height:  |  Size: 161 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M0 0v6h2v-6h-2zm4 0v6h2v-6h-2z" transform="translate(1 1)" />
</svg>

After

Width:  |  Height:  |  Size: 159 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M0 0v6l6-3-6-3z" transform="translate(1 1)" />
</svg>

After

Width:  |  Height:  |  Size: 144 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M0 0v6h2v-6h-2zm2 3l5 3v-6l-5 3z" transform="translate(0 1)" />
</svg>

After

Width:  |  Height:  |  Size: 161 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M4 0c-2.2 0-4 1.8-4 4s1.8 4 4 4c1.1 0 2.12-.43 2.84-1.16l-.72-.72c-.54.54-1.29.88-2.13.88-1.66 0-3-1.34-3-3s1.34-3 3-3c.83 0 1.55.36 2.09.91l-1.09 1.09h3v-3l-1.19 1.19c-.72-.72-1.71-1.19-2.81-1.19z" />
</svg>

After

Width:  |  Height:  |  Size: 299 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<path d="M0 0v6h6v-6h-6z" transform="translate(1 1)" />
</svg>

After

Width:  |  Height:  |  Size: 144 B

View File

@ -2,6 +2,7 @@
<qresource prefix="/res">
<file>images/save.svg</file>
<file>images/media-pause.svg</file>
<file>images/filter.svg</file>
<file>images/mute.svg</file>
<file>images/refresh.svg</file>
<file>images/no_sources.svg</file>
@ -40,6 +41,12 @@
<file>images/recording-pause-inactive.svg</file>
<file>images/streaming-active.svg</file>
<file>images/streaming-inactive.svg</file>
<file>images/media/media_play.svg</file>
<file>images/media/media_pause.svg</file>
<file>images/media/media_next.svg</file>
<file>images/media/media_previous.svg</file>
<file>images/media/media_restart.svg</file>
<file>images/media/media_stop.svg</file>
</qresource>
<qresource prefix="/settings">
<file>images/settings/output.svg</file>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BrowserSourceToolbar</class>
<widget class="QWidget" name="BrowserSourceToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>628</width>
<height>38</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="refresh">
<property name="text">
<string>RefreshBrowser</string>
</property>
<property name="icon">
<iconset resource="../obs.qrc">
<normaloff>:/res/images/refresh.svg</normaloff>:/res/images/refresh.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">refreshIconSmall</string>
</property>
<property name="themeID2" stdset="0">
<string notr="true">contextBarButton</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources>
<include location="../obs.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ColorSourceToolbar</class>
<widget class="QWidget" name="ColorSourceToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>565</width>
<height>37</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="color">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="text">
<string notr="true">color here</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="choose">
<property name="text">
<string>Basic.PropertiesWindow.SelectColor</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DeviceSelectToolbar</class>
<widget class="QWidget" name="DeviceSelectToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>665</width>
<height>43</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="deviceLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Device</string>
</property>
<property name="buddy">
<cstring>device</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="device">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentText">
<string notr="true"/>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="activateButton">
<property name="text">
<string notr="true">Activate</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GameCaptureToolbar</class>
<widget class="QWidget" name="GameCaptureToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>29</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="modeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Mode</string>
</property>
<property name="buddy">
<cstring>mode</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="mode">
<property name="currentText">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="windowLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Window</string>
</property>
<property name="buddy">
<cstring>window</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="window">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentText">
<string notr="true"/>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="empty" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImageSourceToolbar</class>
<widget class="QWidget" name="ImageSourceToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>580</width>
<height>41</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="pathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Image File</string>
</property>
<property name="buddy">
<cstring>path</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="path">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browse">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,305 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MediaControls</class>
<widget class="QWidget" name="MediaControls">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>888</width>
<height>28</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="playPauseButton">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>ContextBar.MediaControls.RestartMedia</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../obs.qrc">
<normaloff>:/res/images/media/media_restart.svg</normaloff>:/res/images/media/media_restart.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="shortcut">
<string>Space</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="previousButton">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>ContextBar.MediaControls.PlaylistPrevious</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../obs.qrc">
<normaloff>:/res/images/media/media_previous.svg</normaloff>:/res/images/media/media_previous.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="shortcut">
<string>P</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>ContextBar.MediaControls.StopMedia</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../obs.qrc">
<normaloff>:/res/images/media/media_stop.svg</normaloff>:/res/images/media/media_stop.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="shortcut">
<string>S</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="nextButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>ContextBar.MediaControls.PlaylistNext</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../obs.qrc">
<normaloff>:/res/images/media/media_next.svg</normaloff>:/res/images/media/media_next.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="shortcut">
<string>N</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="MediaSlider" name="slider">
<property name="accessibleName">
<string>ContextBar.MediaControls.BlindSeek</string>
</property>
<property name="maximum">
<number>1024</number>
</property>
<property name="tracking">
<bool>false</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>6</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="timerLabel">
<property name="text">
<string>--:--:--</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>/</string>
</property>
</widget>
</item>
<item>
<widget class="ClickableLabel" name="durationLabel">
<property name="text">
<string>--:--:--</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="emptySpaceAgain" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MediaSlider</class>
<extends>QSlider</extends>
<header>media-slider.hpp</header>
</customwidget>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>clickable-label.hpp</header>
</customwidget>
</customwidgets>
<resources>
<include location="../obs.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TextSourceToolbar</class>
<widget class="QWidget" name="TextSourceToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>726</width>
<height>49</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="selectFont">
<property name="text">
<string>Basic.PropertiesWindow.SelectFont</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="selectColor">
<property name="text">
<string>Basic.PropertiesWindow.SelectColor</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="emptySpace" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="text"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

5
UI/locked-checkbox.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "locked-checkbox.hpp"
LockedCheckBox::LockedCheckBox() {}
LockedCheckBox::LockedCheckBox(QWidget *parent) : QCheckBox(parent) {}

View File

@ -4,4 +4,8 @@
class LockedCheckBox : public QCheckBox {
Q_OBJECT
public:
LockedCheckBox();
explicit LockedCheckBox(QWidget *parent);
};

428
UI/media-controls.cpp Normal file
View File

@ -0,0 +1,428 @@
#include "window-basic-main.hpp"
#include "media-controls.hpp"
#include "obs-app.hpp"
#include <QToolTip>
#include <QStyle>
#include <QMenu>
#include "ui_media-controls.h"
void MediaControls::OBSMediaStopped(void *data, calldata_t *)
{
MediaControls *media = static_cast<MediaControls *>(data);
QMetaObject::invokeMethod(media, "SetRestartState");
}
void MediaControls::OBSMediaPlay(void *data, calldata_t *)
{
MediaControls *media = static_cast<MediaControls *>(data);
QMetaObject::invokeMethod(media, "SetPlayingState");
}
void MediaControls::OBSMediaPause(void *data, calldata_t *)
{
MediaControls *media = static_cast<MediaControls *>(data);
QMetaObject::invokeMethod(media, "SetPausedState");
}
void MediaControls::OBSMediaStarted(void *data, calldata_t *)
{
MediaControls *media = static_cast<MediaControls *>(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();
}

72
UI/media-controls.hpp Normal file
View File

@ -0,0 +1,72 @@
#pragma once
#include <QWidget>
#include <QTimer>
#include <vector>
#include <obs.hpp>
#include "qt-wrappers.hpp"
class Ui_MediaControls;
class MediaControls : public QWidget {
Q_OBJECT
private:
std::vector<OBSSignal> 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();
};

34
UI/media-slider.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "slider-absoluteset-style.hpp"
#include "media-slider.hpp"
#include <QStyleFactory>
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);
}

17
UI/media-slider.hpp Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <QSlider>
#include <QMouseEvent>
class MediaSlider : public QSlider {
Q_OBJECT
public:
MediaSlider(QWidget *parent = nullptr);
signals:
void mediaSliderHovered(int value);
protected:
virtual void mouseMoveEvent(QMouseEvent *event) override;
};

View File

@ -43,6 +43,7 @@
#include "window-basic-settings.hpp"
#include "crash-report.hpp"
#include "platform.hpp"
#include "obs-proxy-style.hpp"
#include <fstream>
@ -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();

77
UI/obs-proxy-style.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "obs-proxy-style.hpp"
#include <QStyleOptionButton>
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);
}

9
UI/obs-proxy-style.hpp Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <QProxyStyle>
class OBSProxyStyle : public QProxyStyle {
public:
QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap,
const QStyleOption *option) const override;
};

View File

@ -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");

View File

@ -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();
}
/* ========================================================================= */

View File

@ -0,0 +1,5 @@
#include "visibility-checkbox.hpp"
VisibilityCheckBox::VisibilityCheckBox() {}
VisibilityCheckBox::VisibilityCheckBox(QWidget *parent) : QCheckBox(parent) {}

View File

@ -4,4 +4,8 @@
class VisibilityCheckBox : public QCheckBox {
Q_OBJECT
public:
VisibilityCheckBox();
explicit VisibilityCheckBox(QWidget *parent);
};

View File

@ -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 <fstream>
#include <sstream>
@ -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();
}

View File

@ -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();

View File

@ -205,6 +205,7 @@ OBSBasicProperties::~OBSBasicProperties()
}
obs_source_dec_showing(source);
main->SaveProject();
main->UpdateContextBar();
}
void OBSBasicProperties::AddPreviewButton()