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 <claytong1214@gmail.com>
Co-authored-by: Jim <obs.jim@gmail.com>
master
Colin Edwards 2018-08-23 20:43:44 -05:00 committed by jp9000
parent 37767b6746
commit fddbbe259d
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()