frontend: Add new Idian widgets

Co-Authored-By: derrod <rodney@rodney.io>
This commit is contained in:
Warchamp7
2025-03-18 19:56:16 -07:00
committed by Ryan Foster
parent 50cdabbb5f
commit e8f6143769
33 changed files with 2519 additions and 11 deletions

View File

@@ -63,6 +63,7 @@ include(cmake/feature-twitch.cmake)
include(cmake/feature-restream.cmake)
include(cmake/feature-youtube.cmake)
include(cmake/feature-whatsnew.cmake)
include(cmake/feature-idian-playground.cmake)
add_subdirectory(plugins)

View File

@@ -0,0 +1,9 @@
option(ENABLE_WIDGET_PLAYGROUND "Enable building custom widget demo window" OFF)
if(ENABLE_WIDGET_PLAYGROUND)
target_sources(
obs-studio
PRIVATE forms/OBSIdianPlayground.ui dialogs/OBSIdianPlayground.hpp dialogs/OBSIdianPlayground.cpp
)
target_enable_feature(obs-studio "Widget Playground" ENABLE_WIDGET_PLAYGROUND)
endif()

View File

@@ -81,4 +81,22 @@ target_sources(
components/VolumeSlider.hpp
components/WindowCaptureToolbar.cpp
components/WindowCaptureToolbar.hpp
components/idian/OBSActionRow.cpp
components/idian/OBSActionRow.hpp
components/idian/OBSCheckBox.cpp
components/idian/OBSCheckBox.hpp
components/idian/OBSComboBox.cpp
components/idian/OBSComboBox.hpp
components/idian/OBSDoubleSpinBox.cpp
components/idian/OBSDoubleSpinBox.hpp
components/idian/OBSGroupBox.cpp
components/idian/OBSGroupBox.hpp
components/idian/OBSIdianWidget.hpp
components/idian/OBSPropertiesList.cpp
components/idian/OBSPropertiesList.hpp
components/idian/OBSSpinBox.cpp
components/idian/OBSSpinBox.hpp
components/idian/OBSToggleSwitch.cpp
components/idian/OBSToggleSwitch.hpp
components/idian/obs-widgets.hpp
)

View File

@@ -37,4 +37,6 @@ target_sources(
dialogs/OBSRemux.hpp
dialogs/OBSWhatsNew.cpp
dialogs/OBSWhatsNew.hpp
dialogs/OBSIdianPlayground.cpp
dialogs/OBSIdianPlayground.hpp
)

View File

@@ -0,0 +1,372 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <QComboBox>
#include <QApplication>
#include <QSizePolicy>
#include "OBSActionRow.hpp"
#include <util/base.h>
#include <QSvgRenderer>
OBSActionRowWidget::OBSActionRowWidget(QWidget *parent) : OBSActionRow(parent)
{
layout = new QGridLayout(this);
layout->setVerticalSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
labelLayout = new QVBoxLayout();
labelLayout->setSpacing(0);
labelLayout->setContentsMargins(0, 0, 0, 0);
setFocusPolicy(Qt::StrongFocus);
setLayout(layout);
layout->setColumnMinimumWidth(0, 0);
layout->setColumnStretch(0, 0);
layout->setColumnStretch(1, 40);
layout->setColumnStretch(2, 55);
nameLabel = new QLabel();
nameLabel->setVisible(false);
OBSIdianUtils::addClass(nameLabel, "title");
descriptionLabel = new QLabel();
descriptionLabel->setVisible(false);
OBSIdianUtils::addClass(descriptionLabel, "description");
labelLayout->addWidget(nameLabel);
labelLayout->addWidget(descriptionLabel);
layout->addLayout(labelLayout, 0, 1, Qt::AlignLeft);
}
void OBSActionRowWidget::setPrefix(QWidget *w, bool auto_connect)
{
setSuffixEnabled(false);
_prefix = w;
if (auto_connect)
this->connectBuddyWidget(w);
_prefix->setParent(this);
layout->addWidget(_prefix, 0, 0, Qt::AlignLeft);
layout->setColumnStretch(0, 3);
}
void OBSActionRowWidget::setSuffix(QWidget *w, bool auto_connect)
{
setPrefixEnabled(false);
_suffix = w;
if (auto_connect)
this->connectBuddyWidget(w);
_suffix->setParent(this);
layout->addWidget(_suffix, 0, 2, Qt::AlignRight | Qt::AlignVCenter);
}
void OBSActionRowWidget::setPrefixEnabled(bool enabled)
{
if (!_prefix)
return;
if (enabled)
setSuffixEnabled(false);
if (enabled == _prefix->isEnabled() && enabled == _prefix->isVisible())
return;
layout->setColumnStretch(0, enabled ? 3 : 0);
_prefix->setEnabled(enabled);
_prefix->setVisible(enabled);
}
void OBSActionRowWidget::setSuffixEnabled(bool enabled)
{
if (!_suffix)
return;
if (enabled)
setPrefixEnabled(false);
if (enabled == _suffix->isEnabled() && enabled == _suffix->isVisible())
return;
_suffix->setEnabled(enabled);
_suffix->setVisible(enabled);
}
void OBSActionRowWidget::setTitle(QString name)
{
nameLabel->setText(name);
setAccessibleName(name);
showTitle(true);
}
void OBSActionRowWidget::setDescription(QString description)
{
descriptionLabel->setText(description);
setAccessibleDescription(description);
showDescription(true);
}
void OBSActionRowWidget::showTitle(bool visible)
{
nameLabel->setVisible(visible);
}
void OBSActionRowWidget::showDescription(bool visible)
{
descriptionLabel->setVisible(visible);
}
void OBSActionRowWidget::setBuddy(QWidget *widget)
{
buddyWidget = widget;
OBSIdianUtils::addClass(widget, "row-buddy");
}
void OBSActionRowWidget::setChangeCursor(bool change)
{
changeCursor = change;
OBSIdianUtils::toggleClass("cursor-pointer", change);
}
void OBSActionRowWidget::enterEvent(QEnterEvent *event)
{
if (!isEnabled())
return;
if (changeCursor) {
setCursor(Qt::PointingHandCursor);
}
OBSIdianUtils::addClass("hover");
if (buddyWidget)
OBSIdianUtils::repolish(buddyWidget);
if (hasPrefix() || hasSuffix()) {
OBSIdianUtils::polishChildren();
}
OBSActionRow::enterEvent(event);
}
void OBSActionRowWidget::leaveEvent(QEvent *event)
{
OBSIdianUtils::removeClass("hover");
if (buddyWidget)
OBSIdianUtils::repolish(buddyWidget);
if (hasPrefix() || hasSuffix()) {
OBSIdianUtils::polishChildren();
}
OBSActionRow::leaveEvent(event);
}
void OBSActionRowWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() & Qt::LeftButton) {
emit clicked();
}
QFrame::mouseReleaseEvent(event);
}
void OBSActionRowWidget::keyReleaseEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_Enter) {
emit clicked();
}
QFrame::keyReleaseEvent(event);
}
void OBSActionRowWidget::connectBuddyWidget(QWidget *widget)
{
setAccessibleName(nameLabel->text());
setFocusProxy(widget);
setBuddy(widget);
/* If element is an OBSToggleSwitch and checkable, forward
* clicks to the widget */
OBSToggleSwitch *obsToggle = dynamic_cast<OBSToggleSwitch *>(widget);
if (obsToggle && obsToggle->isCheckable()) {
setChangeCursor(true);
connect(this, &OBSActionRowWidget::clicked, obsToggle, &OBSToggleSwitch::click);
return;
}
/* If element is any other QAbstractButton subclass,
* and checkable, forward clicks to the widget. */
QAbstractButton *button = dynamic_cast<QAbstractButton *>(widget);
if (button && button->isCheckable()) {
setChangeCursor(true);
connect(this, &OBSActionRowWidget::clicked, button, &QAbstractButton::click);
return;
}
/* If element is an OBSComboBox, clicks toggle the dropdown. */
OBSComboBox *obsCombo = dynamic_cast<OBSComboBox *>(widget);
if (obsCombo) {
setChangeCursor(true);
connect(this, &OBSActionRowWidget::clicked, obsCombo, &OBSComboBox::togglePopup);
return;
}
}
/*
* Button for expanding a collapsible ActionRow
*/
OBSActionRowExpandButton::OBSActionRowExpandButton(QWidget *parent) : QAbstractButton(parent), OBSIdianUtils(this)
{
setCheckable(true);
}
void OBSActionRowExpandButton::paintEvent(QPaintEvent *event)
{
UNUSED_PARAMETER(event);
QStyleOptionButton opt;
opt.initFrom(this);
QPainter p(this);
bool checked = isChecked();
opt.state.setFlag(QStyle::State_On, checked);
opt.state.setFlag(QStyle::State_Off, !checked);
opt.state.setFlag(QStyle::State_Sunken, checked);
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, &p, this);
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &p, this);
}
/*
* ActionRow variant that can be expanded to show another properties list
*/
OBSCollapsibleRowWidget::OBSCollapsibleRowWidget(const QString &name, QWidget *parent) : OBSActionRow(parent)
{
layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
setLayout(layout);
rowWidget = new OBSCollapsibleRowFrame();
rowLayout = new QHBoxLayout();
rowLayout->setContentsMargins(0, 0, 0, 0);
rowLayout->setSpacing(0);
rowWidget->setLayout(rowLayout);
actionRow = new OBSActionRowWidget();
actionRow->setTitle(name);
actionRow->setChangeCursor(false);
rowLayout->addWidget(actionRow);
propertyList = new OBSPropertiesList(this);
propertyList->setVisible(false);
expandFrame = new QFrame();
btnLayout = new QHBoxLayout();
btnLayout->setContentsMargins(0, 0, 0, 0);
btnLayout->setSpacing(0);
expandFrame->setLayout(btnLayout);
OBSIdianUtils::addClass(expandFrame, "btn-frame");
actionRow->setBuddy(expandFrame);
expandButton = new OBSActionRowExpandButton(this);
btnLayout->addWidget(expandButton);
rowLayout->addWidget(expandFrame);
layout->addWidget(rowWidget);
layout->addWidget(propertyList);
actionRow->setFocusProxy(expandButton);
connect(expandButton, &QAbstractButton::clicked, this, &OBSCollapsibleRowWidget::toggleVisibility);
connect(actionRow, &OBSActionRowWidget::clicked, expandButton, &QAbstractButton::click);
}
OBSCollapsibleRowWidget::OBSCollapsibleRowWidget(const QString &name, const QString &desc, QWidget *parent)
: OBSCollapsibleRowWidget(name, parent)
{
actionRow->setDescription(desc);
}
void OBSCollapsibleRowWidget::setCheckable(bool check)
{
checkable = check;
if (checkable && !toggleSwitch) {
propertyList->setEnabled(false);
OBSIdianUtils::polishChildren(propertyList);
toggleSwitch = new OBSToggleSwitch(false);
actionRow->setSuffix(toggleSwitch, false);
connect(toggleSwitch, &OBSToggleSwitch::toggled, propertyList, &OBSPropertiesList::setEnabled);
}
if (!checkable && toggleSwitch) {
propertyList->setEnabled(true);
OBSIdianUtils::polishChildren(propertyList);
actionRow->suffix()->deleteLater();
}
}
void OBSCollapsibleRowWidget::toggleVisibility()
{
bool visible = !propertyList->isVisible();
propertyList->setVisible(visible);
expandButton->setChecked(visible);
}
void OBSCollapsibleRowWidget::addRow(OBSActionRow *actionRow)
{
propertyList->addRow(actionRow);
}
OBSCollapsibleRowFrame::OBSCollapsibleRowFrame(QWidget *parent) : QFrame(parent), OBSIdianUtils(this) {}
void OBSCollapsibleRowFrame::enterEvent(QEnterEvent *event)
{
setCursor(Qt::PointingHandCursor);
OBSIdianUtils::addClass("hover");
OBSIdianUtils::polishChildren();
QWidget::enterEvent(event);
}
void OBSCollapsibleRowFrame::leaveEvent(QEvent *event)
{
OBSIdianUtils::removeClass("hover");
OBSIdianUtils::polishChildren();
QWidget::leaveEvent(event);
}

View File

@@ -0,0 +1,215 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QWidget>
#include <QFrame>
#include <QLabel>
#include <QLayout>
#include <QScrollArea>
#include <QMouseEvent>
#include <QCheckBox>
#include "OBSIdianWidget.hpp"
#include "OBSPropertiesList.hpp"
#include "OBSToggleSwitch.hpp"
#include "OBSComboBox.hpp"
#include "OBSSpinBox.hpp"
#include "OBSDoubleSpinBox.hpp"
/**
* Base class mostly so adding stuff to a list is easier
*/
class OBSActionRow : public QFrame, public OBSIdianUtils {
Q_OBJECT
public:
OBSActionRow(QWidget *parent = nullptr) : QFrame(parent), OBSIdianUtils(this) { setAccessibleName(""); };
};
/**
* Proxy for QScrollArea for QSS styling
*/
class OBSScrollArea : public QScrollArea {
Q_OBJECT
public:
OBSScrollArea(QWidget *parent = nullptr) : QScrollArea(parent) {}
};
/**
* Generic OBS row widget containing one or more controls
*/
class OBSActionRowWidget : public OBSActionRow {
Q_OBJECT
public:
OBSActionRowWidget(QWidget *parent = nullptr);
void setPrefix(QWidget *w, bool autoConnect = true);
void setSuffix(QWidget *w, bool autoConnect = true);
bool hasPrefix() { return _prefix; }
bool hasSuffix() { return _suffix; }
QWidget *prefix() const { return _prefix; }
QWidget *suffix() const { return _suffix; }
void setPrefixEnabled(bool enabled);
void setSuffixEnabled(bool enabled);
void setTitle(QString name);
void setDescription(QString description);
void showTitle(bool visible);
void showDescription(bool visible);
void setBuddy(QWidget *w);
void setChangeCursor(bool change);
signals:
void clicked();
protected:
void enterEvent(QEnterEvent *) override;
void leaveEvent(QEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
void keyReleaseEvent(QKeyEvent *) override;
bool hasDescription() const { return descriptionLabel != nullptr; }
void focusInEvent(QFocusEvent *event) override
{
OBSIdianUtils::showKeyFocused(event);
QFrame::focusInEvent(event);
}
void focusOutEvent(QFocusEvent *event) override
{
OBSIdianUtils::hideKeyFocused(event);
QFrame::focusOutEvent(event);
}
private:
QGridLayout *layout;
QVBoxLayout *labelLayout = nullptr;
QLabel *nameLabel = nullptr;
QLabel *descriptionLabel = nullptr;
QWidget *_prefix = nullptr;
QWidget *_suffix = nullptr;
QWidget *buddyWidget = nullptr;
void connectBuddyWidget(QWidget *widget);
bool changeCursor = false;
};
/**
* Collapsible row expand button
*/
class OBSActionRowExpandButton : public QAbstractButton, public OBSIdianUtils {
Q_OBJECT
private:
QPixmap extendDown;
QPixmap extendUp;
friend class OBSCollapsibleRowWidget;
protected:
explicit OBSActionRowExpandButton(QWidget *parent = nullptr);
void paintEvent(QPaintEvent *) override;
void focusInEvent(QFocusEvent *event) override
{
OBSIdianUtils::showKeyFocused(event);
QAbstractButton::focusInEvent(event);
}
void focusOutEvent(QFocusEvent *event) override
{
OBSIdianUtils::hideKeyFocused(event);
QAbstractButton::focusOutEvent(event);
}
};
class OBSCollapsibleRowFrame : protected QFrame, protected OBSIdianUtils {
Q_OBJECT
signals:
void clicked();
protected:
explicit OBSCollapsibleRowFrame(QWidget *parent = nullptr);
void enterEvent(QEnterEvent *) override;
void leaveEvent(QEvent *) override;
void focusInEvent(QFocusEvent *event) override
{
OBSIdianUtils::showKeyFocused(event);
QWidget::focusInEvent(event);
}
void focusOutEvent(QFocusEvent *event) override
{
OBSIdianUtils::hideKeyFocused(event);
QWidget::focusOutEvent(event);
}
private:
friend class OBSCollapsibleRowWidget;
};
/**
* Collapsible Generic OBS property container
*/
class OBSCollapsibleRowWidget : public OBSActionRow {
Q_OBJECT
public:
OBSCollapsibleRowWidget(const QString &name, QWidget *parent = nullptr);
OBSCollapsibleRowWidget(const QString &name, const QString &desc = nullptr, QWidget *parent = nullptr);
void setCheckable(bool check);
bool isCheckable() { return checkable; }
void addRow(OBSActionRow *actionRow);
private:
void toggleVisibility();
QPixmap extendDown;
QPixmap extendUp;
QVBoxLayout *layout;
OBSCollapsibleRowFrame *rowWidget;
QHBoxLayout *rowLayout;
OBSActionRowWidget *actionRow;
QFrame *expandFrame;
QHBoxLayout *btnLayout;
OBSActionRowExpandButton *expandButton;
OBSPropertiesList *propertyList;
OBSToggleSwitch *toggleSwitch = nullptr;
bool checkable = false;
};

View File

@@ -0,0 +1,20 @@
/******************************************************************************
Copyright (C) 2024 by Taylor Giampaolo <warchamp7@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSCheckBox.hpp"
OBSCheckBox::OBSCheckBox(QWidget *parent) : QCheckBox(parent), OBSIdianUtils(this) {}

View File

@@ -0,0 +1,42 @@
/******************************************************************************
Copyright (C) 2024 by Taylor Giampaolo <warchamp7@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QCheckBox>
#include "OBSIdianWidget.hpp"
class OBSCheckBox : public QCheckBox, public OBSIdianUtils {
Q_OBJECT;
public:
OBSCheckBox(QWidget *parent = nullptr);
protected:
void focusInEvent(QFocusEvent *e) override
{
OBSIdianUtils::showKeyFocused(e);
QAbstractButton::focusInEvent(e);
}
void focusOutEvent(QFocusEvent *e) override
{
OBSIdianUtils::hideKeyFocused(e);
QAbstractButton::focusOutEvent(e);
}
};

View File

@@ -0,0 +1,65 @@
/******************************************************************************
Copyright (C) 2024 by Taylor Giampaolo <warchamp7@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSActionRow.hpp"
#include "OBSComboBox.hpp"
#include <QTimer>
#include <util/base.h>
#define UNUSED_PARAMETER(param) (void)param
OBSComboBox::OBSComboBox(QWidget *parent) : QComboBox(parent), OBSIdianUtils(this) {}
void OBSComboBox::showPopup()
{
if (allowOpeningPopup) {
allowOpeningPopup = false;
QComboBox::showPopup();
}
}
void OBSComboBox::hidePopup()
{
// It would be nice to find a better way to do this.
//
// When the dropdown is closed, block attempts to open it
// again for a short time. This is so clicking a GenericRow
// with the dropdown open doesn't immediately close and re-open it.
// I have tried all sorts of things involving handling mouse events
// and event filters on both GenericRow and ComboBox.
//
// All my efforts have failed so we get this instead.
allowOpeningPopup = false;
QTimer::singleShot(120, this, [=]() { allowOpeningPopup = true; });
QComboBox::hidePopup();
}
void OBSComboBox::mousePressEvent(QMouseEvent *event)
{
blog(LOG_INFO, "OBSComboBox::mousePressEvent");
QComboBox::mousePressEvent(event);
}
void OBSComboBox::togglePopup()
{
if (view()->isVisible()) {
OBSComboBox::hidePopup();
} else {
OBSComboBox::showPopup();
}
}

View File

@@ -0,0 +1,54 @@
/******************************************************************************
Copyright (C) 2024 by Taylor Giampaolo <warchamp7@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QComboBox>
#include <QAbstractItemView>
#include "OBSIdianWidget.hpp"
class OBSComboBox : public QComboBox, public OBSIdianUtils {
Q_OBJECT
public:
OBSComboBox(QWidget *parent = nullptr);
public Q_SLOTS:
void togglePopup();
private:
bool allowOpeningPopup = true;
protected:
void showPopup() override;
void hidePopup() override;
void mousePressEvent(QMouseEvent *event) override;
void focusInEvent(QFocusEvent *e) override
{
OBSIdianUtils::showKeyFocused(e);
QComboBox::focusInEvent(e);
}
void focusOutEvent(QFocusEvent *e) override
{
OBSIdianUtils::hideKeyFocused(e);
QComboBox::focusOutEvent(e);
}
};

View File

@@ -0,0 +1,45 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSDoubleSpinBox.hpp"
OBSDoubleSpinBox::OBSDoubleSpinBox(QWidget *parent) : QFrame(parent)
{
layout = new QHBoxLayout();
setLayout(layout);
layout->setContentsMargins(0, 0, 0, 0);
decr = new QPushButton("-");
decr->setObjectName("obsSpinBoxButton");
layout->addWidget(decr);
setFocusProxy(decr);
sbox = new QDoubleSpinBox();
sbox->setObjectName("obsSpinBox");
sbox->setButtonSymbols(QAbstractSpinBox::NoButtons);
sbox->setAlignment(Qt::AlignCenter);
layout->addWidget(sbox);
incr = new QPushButton("+");
incr->setObjectName("obsSpinBoxButton");
layout->addWidget(incr);
connect(decr, &QPushButton::pressed, sbox, &QDoubleSpinBox::stepDown);
connect(incr, &QPushButton::pressed, sbox, &QDoubleSpinBox::stepUp);
}

View File

@@ -0,0 +1,38 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QFrame>
#include <QLayout>
#include <QSpinBox>
#include <QPushButton>
class OBSDoubleSpinBox : public QFrame {
Q_OBJECT;
public:
OBSDoubleSpinBox(QWidget *parent = nullptr);
QDoubleSpinBox *spinBox() const { return sbox; }
private:
QHBoxLayout *layout;
QPushButton *decr;
QPushButton *incr;
QDoubleSpinBox *sbox;
};

View File

@@ -0,0 +1,122 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSIdianWidget.hpp"
#include "OBSGroupBox.hpp"
OBSGroupBox::OBSGroupBox(QWidget *parent) : QFrame(parent), OBSIdianUtils(this)
{
layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
headerContainer = new QWidget();
headerLayout = new QHBoxLayout();
headerLayout->setSpacing(0);
headerLayout->setContentsMargins(0, 0, 0, 0);
headerContainer->setLayout(headerLayout);
OBSIdianUtils::addClass(headerContainer, "header");
labelContainer = new QWidget();
labelLayout = new QVBoxLayout();
labelLayout->setSpacing(0);
labelLayout->setContentsMargins(0, 0, 0, 0);
labelContainer->setLayout(labelLayout);
labelContainer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
controlContainer = new QWidget();
controlLayout = new QVBoxLayout();
controlLayout->setSpacing(0);
controlLayout->setContentsMargins(0, 0, 0, 0);
controlContainer->setLayout(controlLayout);
headerLayout->addWidget(labelContainer);
headerLayout->addWidget(controlContainer);
contentsContainer = new QWidget();
contentsLayout = new QVBoxLayout();
contentsLayout->setSpacing(0);
contentsLayout->setContentsMargins(0, 0, 0, 0);
contentsContainer->setLayout(contentsLayout);
OBSIdianUtils::addClass(contentsContainer, "contents");
layout->addWidget(headerContainer);
layout->addWidget(contentsContainer);
propertyList = new OBSPropertiesList(this);
setLayout(layout);
contentsLayout->addWidget(propertyList);
/*contentsContainer->setSizePolicy(policy);*/
nameLabel = new QLabel();
OBSIdianUtils::addClass(nameLabel, "title");
nameLabel->setVisible(false);
labelLayout->addWidget(nameLabel);
descriptionLabel = new QLabel();
OBSIdianUtils::addClass(descriptionLabel, "description");
descriptionLabel->setVisible(false);
labelLayout->addWidget(descriptionLabel);
}
void OBSGroupBox::addRow(OBSActionRow *actionRow) const
{
propertyList->addRow(actionRow);
}
void OBSGroupBox::setTitle(QString name)
{
nameLabel->setText(name);
setAccessibleName(name);
showTitle(true);
}
void OBSGroupBox::setDescription(QString desc)
{
descriptionLabel->setText(desc);
setAccessibleDescription(desc);
showDescription(true);
}
void OBSGroupBox::showTitle(bool visible)
{
nameLabel->setVisible(visible);
}
void OBSGroupBox::showDescription(bool visible)
{
descriptionLabel->setVisible(visible);
}
void OBSGroupBox::setCheckable(bool check)
{
checkable = check;
if (checkable && !toggleSwitch) {
toggleSwitch = new OBSToggleSwitch(true);
controlLayout->addWidget(toggleSwitch);
connect(toggleSwitch, &OBSToggleSwitch::toggled, this,
[=](bool checked) { propertyList->setEnabled(checked); });
}
if (!checkable && toggleSwitch) {
controlLayout->removeWidget(toggleSwitch);
toggleSwitch->deleteLater();
}
}

View File

@@ -0,0 +1,68 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QLayout>
#include <QLabel>
#include <QWidget>
#include <QMouseEvent>
#include "OBSActionRow.hpp"
#include "OBSPropertiesList.hpp"
#include "OBSToggleSwitch.hpp"
class OBSGroupBox : public QFrame, public OBSIdianUtils {
Q_OBJECT
public:
OBSGroupBox(QWidget *parent = nullptr);
OBSPropertiesList *properties() const { return propertyList; }
void addRow(OBSActionRow *actionRow) const;
void setTitle(QString name);
void setDescription(QString desc);
void showTitle(bool visible);
void showDescription(bool visible);
void setCheckable(bool check);
bool isCheckable() { return checkable; }
private:
QVBoxLayout *layout = nullptr;
QWidget *headerContainer = nullptr;
QHBoxLayout *headerLayout = nullptr;
QWidget *labelContainer = nullptr;
QVBoxLayout *labelLayout = nullptr;
QWidget *controlContainer = nullptr;
QVBoxLayout *controlLayout = nullptr;
QWidget *contentsContainer = nullptr;
QVBoxLayout *contentsLayout = nullptr;
QLabel *nameLabel = nullptr;
QLabel *descriptionLabel = nullptr;
OBSPropertiesList *propertyList = nullptr;
OBSToggleSwitch *toggleSwitch = nullptr;
bool checkable = false;
};

View File

@@ -0,0 +1,152 @@
/******************************************************************************
Copyright (C) 2024 by Taylor Giampaolo <warchamp7@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QWidget>
#include <QStyle>
#include <QFocusEvent>
#include <QRegularExpression>
/*
* Helpers for OBS Idian widgets
*/
static const QRegularExpression classRegex = QRegularExpression("^[a-zA-Z][a-zA-Z0-9_-]*$");
class OBSIdianUtils {
static bool classNameIsValid(const QString &name)
{
QRegularExpressionMatch match = classRegex.match(name);
return match.hasMatch();
}
public:
QWidget *parent = nullptr;
OBSIdianUtils(QWidget *w) { parent = w; }
/*
* Set a custom property whenever the widget has
* keyboard focus specifically
*/
void showKeyFocused(QFocusEvent *e)
{
if (e->reason() != Qt::MouseFocusReason && e->reason() != Qt::PopupFocusReason) {
addClass("keyFocus");
} else {
removeClass("keyFocus");
}
}
void hideKeyFocused(QFocusEvent *e)
{
if (e->reason() != Qt::PopupFocusReason) {
removeClass("keyFocus");
}
}
/*
* Force all children widgets to repaint
*/
void polishChildren() { polishChildren(parent); }
static void polishChildren(QWidget *widget)
{
for (QWidget *child : widget->findChildren<QWidget *>()) {
repolish(child);
}
}
void repolish() { repolish(parent); }
static void repolish(QWidget *widget)
{
widget->style()->unpolish(widget);
widget->style()->polish(widget);
}
/*
* Adds a style class to the widget
*/
void addClass(const QString &classname) { addClass(parent, classname); }
static void addClass(QWidget *widget, const QString &classname)
{
if (!classNameIsValid(classname)) {
return;
}
QVariant current = widget->property("class");
QStringList classList = current.toString().split(" ");
if (classList.contains(classname)) {
return;
}
classList.removeDuplicates();
classList.append(classname);
widget->setProperty("class", classList.join(" "));
repolish(widget);
}
/*
* Removes a style class from a widget
*/
void removeClass(const QString &classname) { removeClass(parent, classname); }
static void removeClass(QWidget *widget, const QString &classname)
{
if (!classNameIsValid(classname)) {
return;
}
QVariant current = widget->property("class");
if (current.isNull()) {
return;
}
QStringList classList = current.toString().split(" ");
if (!classList.contains(classname, Qt::CaseSensitive)) {
return;
}
classList.removeDuplicates();
classList.removeAll(classname);
widget->setProperty("class", classList.join(" "));
repolish(widget);
}
/*
* Forces the addition or removal of a style class from a widget
*/
void toggleClass(const QString &classname, bool toggle) { toggleClass(parent, classname, toggle); }
static void toggleClass(QWidget *widget, const QString &classname, bool toggle)
{
if (toggle) {
addClass(widget, classname);
} else {
removeClass(widget, classname);
}
}
};

View File

@@ -0,0 +1,80 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <QStyle>
#include "OBSPropertiesList.hpp"
#include "OBSActionRow.hpp"
OBSPropertiesList::OBSPropertiesList(QWidget *parent) : QFrame(parent)
{
layout = new QVBoxLayout();
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
rowsList = QList<OBSActionRow *>();
setLayout(layout);
}
/* Note: This function takes ownership of the added widget
* and it may be deleted when the properties list is destroyed
* or the clear() method is called! */
void OBSPropertiesList::addRow(OBSActionRow *actionRow)
{
// Add custom spacer once more than one element exists
if (layout->count() > 0)
layout->addWidget(new OBSPropertiesListSpacer(this));
// Custom properties to work around :first and :last not existing.
if (!first) {
OBSIdianUtils::addClass(actionRow, "first");
first = actionRow;
}
// Remove last property from existing last item
if (last)
OBSIdianUtils::removeClass(last, "last");
// Most recently added item is also always last
OBSIdianUtils::addClass(actionRow, "last");
last = actionRow;
actionRow->setParent(this);
rowsList.append(actionRow);
layout->addWidget(actionRow);
adjustSize();
}
void OBSPropertiesList::clear()
{
rowsList.clear();
first = nullptr;
last = nullptr;
QLayoutItem *item = layout->takeAt(0);
while (item) {
if (item->widget())
item->widget()->deleteLater();
delete item;
item = layout->takeAt(0);
}
adjustSize();
}

View File

@@ -0,0 +1,57 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QFrame>
#include <QWidget>
#include <QLayout>
#include "OBSIdianWidget.hpp"
class OBSActionRow;
class OBSPropertiesList : public QFrame {
Q_OBJECT
public:
OBSPropertiesList(QWidget *parent = nullptr);
void addRow(OBSActionRow *actionRow);
void clear();
QList<OBSActionRow *> rows() const { return rowsList; }
private:
OBSActionRow *first = nullptr;
OBSActionRow *last = nullptr;
QVBoxLayout *layout;
QList<OBSActionRow *> rowsList;
};
/**
* Spacer with only cosmetic functionality
*/
class OBSPropertiesListSpacer : public QFrame {
Q_OBJECT
public:
OBSPropertiesListSpacer(QWidget *parent = nullptr) : QFrame(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
};

View File

@@ -0,0 +1,42 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSSpinBox.hpp"
OBSSpinBox::OBSSpinBox(QWidget *parent) : QFrame(parent)
{
layout = new QHBoxLayout();
setLayout(layout);
layout->setContentsMargins(0, 0, 0, 0);
decr = new QPushButton("-");
decr->setObjectName("obsSpinBoxButton");
layout->addWidget(decr);
sbox = new QSpinBox();
sbox->setObjectName("obsSpinBox");
sbox->setButtonSymbols(QAbstractSpinBox::NoButtons);
layout->addWidget(sbox);
incr = new QPushButton("+");
incr->setObjectName("obsSpinBoxButton");
layout->addWidget(incr);
connect(decr, &QPushButton::pressed, sbox, &QSpinBox::stepDown);
connect(incr, &QPushButton::pressed, sbox, &QSpinBox::stepUp);
}

View File

@@ -0,0 +1,38 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QFrame>
#include <QLayout>
#include <QSpinBox>
#include <QPushButton>
class OBSSpinBox : public QFrame {
Q_OBJECT;
public:
OBSSpinBox(QWidget *parent = nullptr);
QSpinBox *spinBox() const { return sbox; }
private:
QHBoxLayout *layout;
QPushButton *decr;
QPushButton *incr;
QSpinBox *sbox;
};

View File

@@ -0,0 +1,247 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSToggleSwitch.hpp"
#include <util/base.h>
#define UNUSED_PARAMETER(param) (void)param
static QColor blendColors(const QColor &color1, const QColor &color2, float ratio)
{
int r = color1.red() * (1 - ratio) + color2.red() * ratio;
int g = color1.green() * (1 - ratio) + color2.green() * ratio;
int b = color1.blue() * (1 - ratio) + color2.blue() * ratio;
return QColor(r, g, b, 255);
}
OBSToggleSwitch::OBSToggleSwitch(QWidget *parent)
: QAbstractButton(parent),
animHandle(new QPropertyAnimation(this, "xpos", this)),
animBgColor(new QPropertyAnimation(this, "blend", this)),
OBSIdianUtils(this)
{
offPos = rect().width() / 2 - 18;
onPos = rect().width() / 2 + 18;
xPos = offPos;
margin = 3;
setCheckable(true);
setAccessibleName("ToggleSwitch");
installEventFilter(this);
connect(this, &OBSToggleSwitch::clicked, this, &OBSToggleSwitch::onClicked);
connect(animHandle, &QVariantAnimation::valueChanged, this, &OBSToggleSwitch::updateBackgroundColor);
connect(animBgColor, &QVariantAnimation::valueChanged, this, &OBSToggleSwitch::updateBackgroundColor);
}
OBSToggleSwitch::OBSToggleSwitch(bool defaultState, QWidget *parent) : OBSToggleSwitch(parent)
{
setChecked(defaultState);
if (defaultState) {
xPos = onPos;
}
}
void OBSToggleSwitch::animateHandlePosition()
{
animHandle->setStartValue(xPos);
int endPos = onPos;
if ((!isDelayed() && !isChecked()) || (isDelayed() && !pendingStatus))
endPos = offPos;
animHandle->setEndValue(endPos);
animHandle->setDuration(120);
animHandle->start();
}
void OBSToggleSwitch::updateBackgroundColor()
{
QColor offColor = underMouse() ? backgroundInactiveHover : backgroundInactive;
QColor onColor = underMouse() ? backgroundActiveHover : backgroundActive;
if (!isDelayed()) {
int offset = isChecked() ? 0 : offPos;
blend = (float)(xPos - offset) / (float)(onPos);
}
QColor bg = blendColors(offColor, onColor, blend);
if (!isEnabled())
bg = backgroundInactive;
setStyleSheet("background: " + bg.name());
}
void OBSToggleSwitch::changeEvent(QEvent *event)
{
if (event->type() == QEvent::EnabledChange) {
OBSIdianUtils::toggleClass("disabled", !isEnabled());
updateBackgroundColor();
}
}
void OBSToggleSwitch::paintEvent(QPaintEvent *e)
{
UNUSED_PARAMETER(e);
QStyleOptionButton opt;
opt.initFrom(this);
QPainter p(this);
bool showChecked = isChecked();
if (isDelayed()) {
showChecked = pendingStatus;
}
opt.state.setFlag(QStyle::State_On, showChecked);
opt.state.setFlag(QStyle::State_Off, !showChecked);
opt.state.setFlag(QStyle::State_Sunken, true);
style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, &p, this);
p.setRenderHint(QPainter::Antialiasing, true);
p.setBrush(handleColor);
p.drawEllipse(QRectF(xPos, margin, handleSize, handleSize));
}
void OBSToggleSwitch::showEvent(QShowEvent *e)
{
margin = (rect().height() - handleSize) / 2;
offPos = margin;
onPos = rect().width() - handleSize - margin;
xPos = isChecked() ? onPos : offPos;
updateBackgroundColor();
style()->polish(this);
QAbstractButton::showEvent(e);
}
void OBSToggleSwitch::click()
{
if (!isDelayed())
QAbstractButton::click();
if (isChecked() == pendingStatus)
setPending(!isChecked());
}
void OBSToggleSwitch::onClicked(bool checked)
{
if (delayed)
return;
setPending(checked);
}
void OBSToggleSwitch::setStatus(bool status)
{
if (status == isChecked() && status == pendingStatus)
return;
pendingStatus = status;
setChecked(status);
if (isChecked()) {
animBgColor->setStartValue(0.0f);
animBgColor->setEndValue(1.0f);
} else {
animBgColor->setStartValue(1.0f);
animBgColor->setEndValue(0.0f);
}
animBgColor->setEasingCurve(QEasingCurve::InOutCubic);
animBgColor->setDuration(240);
animBgColor->start();
}
void OBSToggleSwitch::setPending(bool pending)
{
pendingStatus = pending;
animateHandlePosition();
if (!isDelayed())
return;
if (pending) {
emit pendingChecked();
} else {
emit pendingUnchecked();
}
}
void OBSToggleSwitch::setDelayed(bool state)
{
delayed = state;
pendingStatus = isChecked();
}
void OBSToggleSwitch::enterEvent(QEnterEvent *e)
{
setCursor(Qt::PointingHandCursor);
updateBackgroundColor();
QAbstractButton::enterEvent(e);
}
void OBSToggleSwitch::leaveEvent(QEvent *e)
{
updateBackgroundColor();
QAbstractButton::leaveEvent(e);
}
void OBSToggleSwitch::keyReleaseEvent(QKeyEvent *e)
{
if (!isDelayed()) {
QAbstractButton::keyReleaseEvent(e);
return;
}
if (e->key() != Qt::Key_Space) {
return;
}
click();
}
void OBSToggleSwitch::mouseReleaseEvent(QMouseEvent *e)
{
if (!isDelayed()) {
QAbstractButton::mouseReleaseEvent(e);
return;
}
if (e->button() != Qt::LeftButton) {
return;
}
click();
}
QSize OBSToggleSwitch::sizeHint() const
{
return QSize(2 * handleSize, handleSize);
}

View File

@@ -0,0 +1,121 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QWidget>
#include <QEvent>
#include <QBrush>
#include <QPropertyAnimation>
#include <QPainter>
#include <QMouseEvent>
#include <QAbstractButton>
#include <QStyleOptionButton>
#include <QAccessibleWidget>
#include "OBSIdianWidget.hpp"
class OBSToggleSwitch : public QAbstractButton, public OBSIdianUtils {
Q_OBJECT
Q_PROPERTY(int xpos MEMBER xPos WRITE setPos)
Q_PROPERTY(QColor background MEMBER backgroundInactive DESIGNABLE true)
Q_PROPERTY(QColor background_hover MEMBER backgroundInactiveHover DESIGNABLE true)
Q_PROPERTY(QColor background_checked MEMBER backgroundActive DESIGNABLE true)
Q_PROPERTY(QColor background_checked_hover MEMBER backgroundActiveHover DESIGNABLE true)
Q_PROPERTY(QColor handleColor MEMBER handleColor DESIGNABLE true)
Q_PROPERTY(int handleSize MEMBER handleSize DESIGNABLE true)
Q_PROPERTY(float blend MEMBER blend WRITE setBlend DESIGNABLE false)
public:
OBSToggleSwitch(QWidget *parent = nullptr);
OBSToggleSwitch(bool defaultState, QWidget *parent = nullptr);
QSize sizeHint() const override;
void setPos(int x)
{
xPos = x;
update();
}
void setBlend(float newBlend)
{
blend = newBlend;
update();
}
void setDelayed(bool state);
bool isDelayed() { return delayed; }
void setStatus(bool status);
void setPending(bool pending);
public slots:
void click();
signals:
void pendingChecked();
void pendingUnchecked();
protected:
void changeEvent(QEvent *event) override;
void paintEvent(QPaintEvent *) override;
void showEvent(QShowEvent *) override;
void enterEvent(QEnterEvent *) override;
void leaveEvent(QEvent *) override;
void keyReleaseEvent(QKeyEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
void focusInEvent(QFocusEvent *e) override
{
OBSIdianUtils::showKeyFocused(e);
QAbstractButton::focusInEvent(e);
}
void focusOutEvent(QFocusEvent *e) override
{
OBSIdianUtils::hideKeyFocused(e);
QAbstractButton::focusOutEvent(e);
}
private slots:
void onClicked(bool checked);
private:
int xPos;
int onPos;
int offPos;
int margin;
float blend = 0.0f;
bool delayed = false;
bool pendingStatus = false;
void animateHandlePosition();
void updateBackgroundColor();
QColor backgroundInactive;
QColor backgroundInactiveHover;
QColor backgroundActive;
QColor backgroundActiveHover;
QColor handleColor;
int handleSize = 18;
QPropertyAnimation *animHandle = nullptr;
QPropertyAnimation *animBgColor = nullptr;
};

View File

@@ -0,0 +1,37 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
// Idian - A family of custom widgets for OBS implementing the "Yami" UI design.
//
// (OBS Idian, get it?)
#include "OBSActionRow.hpp"
#include "OBSCheckBox.hpp"
#include "OBSComboBox.hpp"
#include "OBSDoubleSpinBox.hpp"
#include "OBSGroupBox.hpp"
#include "OBSPropertiesList.hpp"
#include "OBSSpinBox.hpp"
#include "OBSToggleSwitch.hpp"
/// Note: This file serves as an all-in-one include for custom OBS widgets.
/// It is not intended to define any widgets by itself.
/// Note 2: These widgets are still heavily work in progress. They should not
/// yet be used outside of the demo and scene collection dialogues.

View File

@@ -379,3 +379,108 @@ QCalendarWidget #qt_calendar_nextmonth {
StatusBarWidget > QFrame {
padding: 0px 12px 8px;
}
OBSToggleSwitch {
qproperty-handle: rgb(255, 255, 255);
qproperty-backgroundActive: #284cb8;
qproperty-backgroundInactive: #3c404b;
}
OBSGroupBox {
border-radius: 4px;
font-weight: bold;
}
OBSGroupBox > QLabel.title {
font-weight: bold;
}
OBSGroupBox > QLabel.subtitle {
color: palette(button-text);
}
OBSPropertiesList {
background: palette(base);
border-radius: 4px;
border-width: 0px;
padding: 0px;
margin: 0px;
}
OBSActionRow {
margin: 0;
padding: 0;
border-left: 3px solid transparent;
min-height: 48px;
background: transparent;
}
OBSActionRow:hover {
background: palette(highlight);
border-left: 0;
}
OBSActionRow[last="true"]:hover {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
OBSActionRow[first="true"]:hover {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
OBSActionRow > QLabel {
font-weight: 500;
}
OBSActionRow > QLabel.subtitle {
font-size: 9pt;
color: palette(button-text);
}
OBSActionRow QComboBox,
OBSActionRow QPushButton {
margin: 0;
}
OBSActionRow QComboBox,
OBSActionRow QComboBox:hover,
OBSActionRow QComboBox:selected,
OBSActionRow QComboBox::on {
background: transparent;
}
OBSActionRow QComboBox::drop-down {
border: none;
}
OBSActionRow QComboBox::down-arrow {
image: url(./Light/collapse.svg);
}
OBSPropertiesListSpacer {
max-height: 1px;
min-height: 1px;
background-color: palette(midlight);
}
OBSCollapsibleActionRow {
margin: 0;
padding: 0;
border: none;
}
OBSCollapsibleActionRow OBSPropertiesList {
border-radius: 0;
background: palette(mid);
}
IdianPlayground QVBoxLayout {
background: palette(midlight);
border-radius: 4px;
padding-top: 32px;
padding-bottom: 8px;
font-weight: bold;
margin-bottom: 6px;
}

View File

@@ -97,6 +97,9 @@
--padding_base_value: var(--obsPadding);
--spacing_base_value: calc(2 + calc(var(--obsPadding) / 2));
--highlight_width: 1px;
--highlight_color: var(--primary_lighter);
/* TODO: Better Accessibility focus state */
/* TODO: Move Accessibilty Colors to Theme config system */
--border_highlight: "transparent";
@@ -172,6 +175,7 @@
--list_item_bg_hover: var(--primary_light);
--input_border: var(--grey1);
--input_border_width: 1px;
--input_border_hover: var(--grey1);
--input_border_focus: var(--primary);
@@ -182,6 +186,7 @@
--button_bg_down: var(--grey7);
--button_bg_disabled: var(--grey6);
--button_border_width: var(--input_border_width);
--button_border: var(--button_bg);
--button_border_hover: var(--grey1);
--button_border_focus: var(--grey1);
@@ -193,7 +198,7 @@
--tab_border: var(--border_color);
--tab_border_hover: var(--button_border_hover);
--tab_border_focus: var(--button_border_focus);
--tab_border_focus: var(--primary_lighter);
--tab_border_selected: var(--primary);
--tab_padding_base: calc(5px + var(--padding_base));
@@ -203,8 +208,22 @@
--separator_hover: var(--white1);
--highlight: rgb(42, 130, 218);
--highlight_inactive: rgb(25, 28, 34);
--action_row_base: calc(var(--input_height_base) * 0.75);
--action_row_height: calc(var(--action_row_base) + calc(var(--action_row_padding) * 2));
--action_row_border: 3px;
--action_row_input_width: calc(var(--action_row_base) * 4);
--action_row_collapse: calc(var(--action_row_base) + var(--padding_large));
--action_row_collapse_radius: calc(var(--action_row_collapse) / 2);
--action_row_padding: calc(var(--padding_large) * 1.5);
--action_row_padding_x: calc(var(--action_row_padding) * 2);
--action_row_padding_nested: calc(var(--action_row_padding_x) * 1.5);
--toggle_border: 1;
--toggle_margin: 3;
--toggle_width: calc(var(--action_row_base) * 1.8);
--toggle_height: calc(var(--action_row_base) * 0.9);
--toggle_handle: calc(calc(calc(var(--toggle_height) * 0.9) - calc(var(--toggle_border) * 2)) - var(--toggle_margin));
--toggle_radius: calc(var(--toggle_height) / 2);
/* Qt Palette variables can be set with the "palette_" prefix */
--palette_window: var(--bg_window);
@@ -538,6 +557,7 @@ QListView,
QListWidget,
QMenu {
padding: var(--spacing_base);
outline: none;
}
QMenu {
@@ -588,6 +608,7 @@ QMenu::item:selected,
QListView::item:selected,
QListWidget::item:selected {
background-color: var(--primary);
border-color: var(--primary);
}
QMenu::item:hover,
@@ -597,7 +618,7 @@ QMenu::item:selected:hover,
QListView::item:selected:hover,
QListWidget::item:selected:hover {
background-color: var(--primary_light);
color: var(--text);
border-color: var(--highlight_color);
}
QMenu::item:focus,
@@ -622,7 +643,7 @@ QListWidget QLineEdit {
padding: 0;
padding-bottom: 1px;
margin: 0;
border: 1px solid var(--white1);
border: var(--input_border_width) solid var(--white1);
border-radius: var(--border_radius);
}
@@ -937,6 +958,10 @@ QTabWidget::pane {
border-top: 4px solid var(--tab_bg);
}
QTabBar {
outline: none;
}
QTabWidget::tab-bar {
alignment: left;
}
@@ -1124,7 +1149,7 @@ QTextEdit:!editable:focus {
QSpinBox,
QDoubleSpinBox {
background-color: var(--input_bg);
border: 1px solid var(--input_bg);
border: var(--input_border_width) solid var(--input_bg);
border-radius: var(--border_radius);
padding: var(--input_padding) var(--input_text_padding);
height: var(--input_height);
@@ -1446,7 +1471,7 @@ QSlider::handle:disabled {
background-color: var(--button_bg);
padding: var(--padding_base_border) var(--padding_base_border);
margin: 0px;
border: 1px solid var(--button_border);
border: var(--highlight_width) solid var(--button_border);
border-radius: var(--border_radius);
icon-size: var(--icon_base);
}
@@ -1702,8 +1727,8 @@ QTableView::indicator:unchecked:disabled {
max-height: var(--icon_base);
padding: var(--padding_base);
margin-right: var(--spacing_large);
border: 1px solid transparent;
border-radius: 4px;
border: var(--highlight_width) solid transparent;
border-radius: var(--border_radius);
}
.checkbox-icon::indicator {
@@ -1753,7 +1778,7 @@ QTableView::indicator:unchecked:disabled {
background-color: var(--button_bg);
padding: var(--padding_base_border) var(--padding_base_border);
margin: 0px;
border: 1px solid var(--button_border);
border: var(--highlight_width) solid var(--button_border);
border-radius: var(--border_radius);
icon-size: var(--icon_base);
}
@@ -1763,7 +1788,7 @@ QTableView::indicator:unchecked:disabled {
background-color: var(--button_bg_hover);
padding: var(--padding_base_border) var(--padding_base_border);
margin: 0px;
border: 1px solid var(--button_border_hover);
border: var(--highlight_width) solid var(--button_border_hover);
icon-size: var(--icon_base);
}
@@ -2153,3 +2178,282 @@ OBSBasicAdvAudio #scrollAreaWidgetContents {
#previewZoomOutButton:focus {
border: 1px solid var(--input_border_hover);
}
/* Idian Widgets */
OBSGroupBox {
border-radius: var(--border_radius);
font-weight: bold;
margin: 0 0 var(--spacing_base);
min-width: 300px;
max-width: 600px;
}
OBSGroupBox .header .title {
font-weight: bold;
padding: var(--padding_large) 0;
}
OBSGroupBox .header .description {
color: var(--text_muted);
padding: var(--spacing_small) 0;
}
OBSPropertiesList {
border-width: 0;
padding: 0;
margin: var(--spacing_base) 0;
}
OBSActionRowWidget {
background: var(--grey5);
margin: 0;
padding: var(--action_row_padding) var(--action_row_padding_x);
}
OBSActionRowWidget.keyFocus {
background: var(--grey4);
border: var(--highlight_width) solid var(--grey4);
}
OBSActionRowWidget.cursor-pointer.hover {
background: var(--grey4);
border: var(--highlight_width) solid var(--grey1);
}
OBSActionRowWidget.first {
border-top-left-radius: var(--border_radius);
border-top-right-radius: var(--border_radius);
}
OBSActionRowWidget.last {
border-bottom-left-radius: var(--border_radius);
border-bottom-right-radius: var(--border_radius);
}
OBSActionRowWidget > QWidget QLabel {
background: red;
margin: 4px;
font-weight: 500;
max-height: var(--input_height);
}
OBSActionRowWidget > QLabel.description {
font-size: var(--font_small);
color: var(--text_muted);
}
OBSToggleSwitch {
qproperty-background: var(--grey6);
qproperty-background_hover: var(--grey7);
qproperty-background_checked: var(--primary);
qproperty-background_checked_hover: var(--primary_light);
min-width: var(--toggle_width);
min-height: var(--toggle_height);
border-radius: var(--toggle_radius);
qproperty-handleColor: var(--white1);
qproperty-handleSize: var(--toggle_handle);
border: var(--highlight_width) solid transparent;
}
OBSToggleSwitch:hover {
border-color: var(--grey4);
}
OBSToggleSwitch:checked:hover {
border-color: var(--white1);
}
OBSToggleSwitch.keyFocus {
border-color: var(--highlight_color);
}
OBSActionRowWidget OBSToggleSwitch:hover,
OBSActionRowWidget.hover > OBSToggleSwitch.row-buddy {
border-color: var(--grey1);
}
OBSActionRowWidget OBSToggleSwitch:checked:hover,
OBSActionRowWidget.hover OBSToggleSwitch.row-buddy:checked {
border-color: var(--white1);
}
OBSActionRowWidget QComboBox {
background-color: transparent;
min-height: var(--action_row_base);
max-height: var(--action_row_base);
min-width: var(--action_row_input_width);
border: var(--highlight_width) solid transparent;
padding: 0;
padding-left: var(--padding_xlarge);
margin: 0;
}
OBSActionRowWidget QComboBox:focus {
border-color: transparent;
}
OBSActionRowWidget QComboBox:hover {
border-color: var(--grey1);
}
OBSActionRowWidget QComboBox.keyFocus {
border-color: var(--highlight_color);
}
OBSActionRowWidget QComboBox::drop-down {
border: none;
}
OBSActionRowWidget QComboBox::down-arrow {
image: url(theme:Dark/collapse.svg);
}
OBSActionRowWidget QComboBox QAbstractItemView {
outline: none;
}
OBSActionRowWidget QComboBox QAbstractItemView::item {
background-color: var(--bg_base);
padding: var(--padding_base) var(--padding_large);
}
OBSActionRowWidget QComboBox QAbstractItemView::item:hover,
OBSActionRowWidget QComboBox QAbstractItemView::item:selected {
background-color: var(--list_item_bg_selected);
padding: var(--padding_base) var(--padding_large);
}
OBSActionRowWidget QPushButton,
OBSActionRowWidget QSpinBox,
OBSActionRowWidget QDoubleSpinBox {
margin: 0;
padding: var(--padding_base) var(--action_row_padding_x);
}
OBSPropertiesListSpacer {
max-height: var(--spacing_small);
min-height: var(--spacing_small);
background-color: var(--bg_window);
}
OBSActionRowWidget OBSCheckBox {
outline: none;
}
OBSActionRowWidget OBSCheckBox::indicator,
OBSActionRowWidget OBSCheckBox::indicator:unchecked:hover {
border: var(--highlight_width) solid transparent;
border-radius: var(--border_radius);
}
OBSActionRowWidget.hover > OBSCheckBox.row-buddy::indicator,
OBSActionRowWidget > OBSCheckBox::indicator:unchecked:hover,
OBSActionRowWidget > OBSCheckBox::indicator:hover {
border-color: var(--grey1);
}
OBSActionRowWidget.hover > OBSCheckBox.row-buddy::indicator:unchecked,
OBSCheckBox.keyFocus::indicator:unchecked {
image: url(theme:Yami/checkbox_unchecked_focus.svg);
}
OBSActionRowWidget OBSCheckBox.keyFocus::indicator,
OBSActionRowWidget.hover > OBSCheckBox::indicator {
image: url(theme:Yami/checkbox_checked_focus.svg);
}
OBSActionRowWidget OBSCheckBox.keyFocus::indicator,
OBSActionRowWidget OBSCheckBox.keyFocus::indicator:unchecked,
OBSActionRowWidget OBSCheckBox.keyFocus::indicator:hover,
OBSActionRowWidget OBSCheckBox.keyFocus::indicator:unchecked:hover {
border-color: var(--highlight_color);
}
OBSCollapsibleRowWidget {
margin: 0;
padding: 0;
border: none;
}
OBSCollapsibleRowWidget.keyFocus {
border: var(--highlight_width) solid var(--highlight_color);
}
OBSCollapsibleRowWidget OBSPropertiesList {
border-radius: 0;
border-left: 1px solid var(--grey5);
border-right: 1px solid var(--grey5);
border-bottom: 1px solid var(--grey5);
margin: var(--spacing_small) 0px 0px;
}
OBSCollapsibleRowWidget OBSPropertiesList OBSActionRowWidget {
background-color: var(--grey6);
padding-left: var(--action_row_padding_nested);
}
OBSCollapsibleRowWidget OBSActionRowWidget.first,
OBSCollapsibleRowWidget OBSActionRowWidget.last {
border-radius: 0;
}
OBSCollapsibleRowWidget OBSPropertiesList OBSToggleSwitch {
qproperty-background: var(--grey7);
qproperty-background_hover: var(--grey6);
}
OBSActionRowExpandButton {
background: transparent;
min-width: var(--action_row_collapse);
max-width: var(--action_row_collapse);
min-height: var(--action_row_collapse);
max-height: var(--action_row_collapse);
border: none;
}
OBSActionRowExpandButton::indicator {
background: var(--grey5);
border-radius: var(--action_row_collapse_radius);
padding: var(--padding_large);
image: url(theme:Dark/down.svg);
border: var(--highlight_width) solid var(--grey5);
}
OBSActionRowExpandButton::indicator:checked {
image: url(theme:Dark/up.svg);
}
OBSActionRowExpandButton.keyFocus,
OBSActionRowExpandButton.keyFocus::indicator {
border-color: var(--highlight_color);
}
OBSCollapsibleRowFrame .btn-frame {
background: var(--grey5);
padding: var(--action_row_padding) var(--action_row_padding_x);
}
OBSCollapsibleRowFrame.hover .btn-frame {
background: var(--grey4);
}
OBSCollapsibleRowFrame.hover OBSActionRowWidget,
OBSCollapsibleRowFrame.hover OBSActionRowWidget.hover {
background: var(--grey4);
border: 2px solid var(--grey1);
border-right: none;
}
OBSCollapsibleRowFrame.hover .row-buddy {
background: var(--grey4);
border: 2px solid var(--grey1);
border-left: none;
}
OBSCollapsibleRowFrame.hover OBSActionRowExpandButton::indicator {
border-color: var(--grey1);
}

View File

@@ -0,0 +1,132 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSIdianPlayground.hpp"
#include "components/idian/obs-widgets.hpp"
#include <QTimer>
OBSIdianPlayground::OBSIdianPlayground(QWidget *parent) : QDialog(parent), ui(new Ui_OBSIdianPlayground)
{
ui->setupUi(this);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
OBSGroupBox *test;
OBSActionRowWidget *tmp;
OBSComboBox *cbox = new OBSComboBox;
cbox->addItem("Test 1");
cbox->addItem("Test 2");
// Group box 1
test = new OBSGroupBox(this);
tmp = new OBSActionRowWidget();
tmp->setTitle("Row with a dropdown");
tmp->setSuffix(cbox);
test->properties()->addRow(tmp);
cbox = new OBSComboBox;
cbox->addItem("Test 3");
cbox->addItem("Test 4");
tmp = new OBSActionRowWidget();
tmp->setTitle("Row with a dropdown");
tmp->setDescription("And a subtitle!");
tmp->setSuffix(cbox);
test->properties()->addRow(tmp);
tmp = new OBSActionRowWidget();
tmp->setTitle("Toggle Switch");
tmp->setSuffix(new OBSToggleSwitch());
test->properties()->addRow(tmp);
ui->scrollAreaWidgetContents->layout()->addWidget(test);
tmp = new OBSActionRowWidget();
tmp->setTitle("Delayed toggle switch");
tmp->setDescription("The state can be set separately");
auto tswitch = new OBSToggleSwitch;
tswitch->setDelayed(true);
connect(tswitch, &OBSToggleSwitch::pendingChecked, this, [=]() {
// Do async enable stuff, then set toggle status when complete
QTimer::singleShot(1000, [=]() { tswitch->setStatus(true); });
});
connect(tswitch, &OBSToggleSwitch::pendingUnchecked, this, [=]() {
// Do async disable stuff, then set toggle status when complete
QTimer::singleShot(1000, [=]() { tswitch->setStatus(false); });
});
tmp->setSuffix(tswitch);
test->properties()->addRow(tmp);
// Group box 2
test = new OBSGroupBox();
test->setTitle("Just a few checkboxes");
tmp = new OBSActionRowWidget();
tmp->setTitle("Box 1");
tmp->setPrefix(new OBSCheckBox);
test->properties()->addRow(tmp);
tmp = new OBSActionRowWidget();
tmp->setTitle("Box 2");
tmp->setPrefix(new OBSCheckBox);
test->properties()->addRow(tmp);
ui->scrollAreaWidgetContents->layout()->addWidget(test);
// Group box 2
test = new OBSGroupBox();
test->setTitle("Another Group");
test->setDescription("With a subtitle");
tmp = new OBSActionRowWidget();
tmp->setTitle("Placeholder");
tmp->setSuffix(new OBSToggleSwitch);
test->properties()->addRow(tmp);
OBSCollapsibleRowWidget *tmp2 = new OBSCollapsibleRowWidget("A Collapsible row!", this);
tmp2->setCheckable(true);
test->addRow(tmp2);
tmp = new OBSActionRowWidget();
tmp->setTitle("Spin box demo");
tmp->setSuffix(new OBSDoubleSpinBox());
tmp2->addRow(tmp);
tmp = new OBSActionRowWidget();
tmp->setTitle("Just another placeholder");
tmp->setSuffix(new OBSToggleSwitch(true));
tmp2->addRow(tmp);
tmp = new OBSActionRowWidget();
tmp->setTitle("Placeholder 2");
tmp->setSuffix(new OBSToggleSwitch);
test->properties()->addRow(tmp);
ui->scrollAreaWidgetContents->setContentsMargins(0, 0, 0, 0);
ui->scrollAreaWidgetContents->layout()->setContentsMargins(0, 0, 0, 0);
ui->scrollAreaWidgetContents->layout()->addWidget(test);
ui->scrollAreaWidgetContents->layout()->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
// Test Checkable Group
OBSGroupBox *test2 = new OBSGroupBox();
test2->setTitle("Checkable Group");
test2->setDescription("Description goes here");
test2->setCheckable(true);
ui->scrollAreaWidgetContents->layout()->addWidget(test2);
}

View File

@@ -0,0 +1,36 @@
/******************************************************************************
Copyright (C) 2023 by Dennis Sädtler <dennis@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include "components/idian/obs-widgets.hpp"
#include <QDialog>
#include <memory>
#include <ui_OBSIdianPlayground.h>
// QDialog including a bunch of custom widgets for demoing
class OBSIdianPlayground : public QDialog {
Q_OBJECT
std::unique_ptr<Ui_OBSIdianPlayground> ui;
public:
OBSIdianPlayground(QWidget *parent);
};

View File

@@ -937,6 +937,7 @@
<string>Basic.MainMenu.Tools</string>
</property>
<addaction name="autoConfigure"/>
<addaction name="widgetPlayground"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuDocks">
@@ -2165,6 +2166,11 @@
<string>PasteDuplicate</string>
</property>
</action>
<action name="widgetPlayground">
<property name="text">
<string>Widget Playground</string>
</property>
</action>
<action name="autoConfigure2">
<property name="text">
<string>Basic.AutoConfig</string>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OBSIdianPlayground</class>
<widget class="QDialog" name="OBSIdianPlayground">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Idian Playground</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>786</width>
<height>919</height>
</rect>
</property>
<layout class="QVBoxLayout" name="scrollContentsLayout">
<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>9</number>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M3,5L3,6C3,6.277 3.113,6.527 3.293,6.707L8,11.414L12.707,6.707C12.887,6.527 13,6.277 13,6L13,5L12,5C11.723,5 11.473,5.113 11.293,5.293L8,8.586L4.707,5.293C4.527,5.113 4.277,5 4,5L3,5Z" style="fill:rgb(162,162,162);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 699 B

View File

@@ -30,6 +30,7 @@
<file>images/visible.svg</file>
<file>images/help.svg</file>
<file>images/help_light.svg</file>
<file>images/hide.svg</file>
<file>images/revert.svg</file>
<file>images/alert.svg</file>
<file>images/warning.svg</file>

View File

@@ -422,6 +422,10 @@ OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new
ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q);
#endif
#ifndef ENABLE_WIDGET_PLAYGROUND
ui->widgetPlayground->setVisible(false);
#endif
auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) {
QAction *nudge = new QAction(ui->preview);
nudge->setShortcut(seq);

View File

@@ -630,6 +630,7 @@ private slots:
void on_autoConfigure_triggered();
void on_stats_triggered();
void on_widgetPlayground_triggered();
void on_resetUI_triggered();

View File

@@ -42,6 +42,10 @@
#endif
#include <wizards/AutoConfig.hpp>
#ifdef ENABLE_WIDGET_PLAYGROUND
#include "dialogs/OBSIdianPlayground.hpp"
#endif
#include <qt-wrappers.hpp>
#include <QDesktopServices>
@@ -635,6 +639,16 @@ void OBSBasic::on_stats_triggered()
stats = statsDlg;
}
void OBSBasic::on_widgetPlayground_triggered()
{
#ifdef ENABLE_WIDGET_PLAYGROUND
OBSIdianPlayground playground(this);
playground.setModal(true);
playground.show();
playground.exec();
#endif
}
void OBSBasic::on_actionShowAbout_triggered()
{
if (about)