frontend: Refactor Idian widgets

The idian widgets were built when we had a much lower
understanding of how Qt expects you to build custom
complex widgets and without a direct use-case.

They were also built far too rigid for what every attempted
use of them thus far has necessitated. These changes break
them into simpler 'pieces' and provide getters for internals
that are intended to be accessed.
This commit is contained in:
Warchamp7
2026-09-18 15:24:10 -04:00
committed by Ryan Foster
parent cba17c1d81
commit d129eeb303
27 changed files with 798 additions and 722 deletions
+71 -56
View File
@@ -245,7 +245,6 @@
--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);
@@ -2516,25 +2515,25 @@ SourceSelectButton:pressed:hover {
}
/* Idian Widgets */
idian--Group {
idian--ListHeader {
border-radius: var(--border_radius);
font-weight: bold;
margin: 0 0 var(--spacing_base);
padding: 0 var(--padding_base);
min-width: 300px;
max-width: 600px;
}
idian--Group .header .title {
idian--ListHeader .title {
font-weight: bold;
padding: var(--padding_large) 0;
}
idian--Group .header .description {
idian--ListHeader .description {
color: var(--text_muted);
padding: var(--spacing_small) 0;
}
idian--PropertiesList {
idian--RowList {
border-width: 0;
padding: 0;
margin: var(--spacing_base) 0;
@@ -2544,11 +2543,12 @@ idian--Row {
background: var(--grey5);
margin: 0;
padding: var(--action_row_padding) var(--action_row_padding_x);
border-radius: var(--border_radius);
}
idian--Row.keyFocus {
background: var(--grey4);
border: var(--highlight_width) solid var(--grey4);
border: var(--highlight_width) solid var(--highlight_color);
}
idian--Row.cursor-pointer.hover {
@@ -2556,17 +2556,21 @@ idian--Row.cursor-pointer.hover {
border: var(--highlight_width) solid var(--grey1);
}
idian--Row.first {
idian--RowList idian--Row {
border-radius: 0;
}
idian--RowList idian--Row.first {
border-top-left-radius: var(--border_radius);
border-top-right-radius: var(--border_radius);
}
idian--Row.last {
idian--RowList idian--Row.last {
border-bottom-left-radius: var(--border_radius);
border-bottom-right-radius: var(--border_radius);
}
idian--Row > QLabel.description {
idian--RowInfo QLabel.description {
font-size: var(--font_small);
color: var(--text_muted);
}
@@ -2663,7 +2667,7 @@ idian--Row QDoubleSpinBox {
padding: var(--padding_base) var(--action_row_padding_x);
}
idian--PropertiesListSpacer {
idian--RowListSpacer {
max-height: var(--spacing_small);
min-height: var(--spacing_small);
background-color: var(--bg_window);
@@ -2700,88 +2704,99 @@ idian--CheckBox.keyFocus.checked::indicator {
border-color: var(--highlight_color);
}
idian--CollapsibleRow {
idian--CollapsibleGroup {
margin: 0;
padding: 0;
border: none;
}
idian--CollapsibleRow.keyFocus {
idian--CollapsibleGroup.expanded > idian--Row {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
idian--CollapsibleGroup idian--Row.keyFocus {
border: var(--highlight_width) solid var(--highlight_color);
}
idian--CollapsibleRow idian--PropertiesList {
idian--CollapsibleGroup idian--RowList {
border-radius: 0;
border-left: 1px solid var(--grey5);
border-right: 1px solid var(--grey5);
border-bottom: 1px solid var(--grey5);
border-bottom: 1px solid var(--grey5);
margin: var(--spacing_small) 0px 0px;
}
idian--CollapsibleRow idian--PropertiesList idian--Row {
idian--CollapsibleGroup idian--RowList idian--Row {
background-color: var(--grey6);
padding-left: var(--action_row_padding_nested);
}
idian--CollapsibleRow idian--Row.first,
idian--CollapsibleRow idian--Row.last {
idian--CollapsibleGroup idian--RowList idian--Row.first {
border-radius: 0;
}
idian--CollapsibleRow idian--PropertiesList idian--ToggleSwitch {
idian--CollapsibleGroup idian--RowList idian--Row.last {
border-bottom-left-radius: var(--border_radius);
border-bottom-right-radius: var(--border_radius);
}
idian--RowList idian--CollapsibleGroup idian--Row.last {
border-radius: 0;
}
idian--RowList idian--CollapsibleGroup.last idian--Row.last {
border-bottom-left-radius: var(--border_radius);
border-bottom-right-radius: var(--border_radius);
}
idian--CollapsibleGroup idian--RowList idian--ToggleSwitch {
qproperty-background: var(--grey7);
qproperty-background_hover: var(--grey6);
}
idian--ExpandButton {
idian--InlineButton,
idian--Row idian--InlineButton {
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;
}
idian--ExpandButton::indicator {
background: var(--grey5);
border-radius: var(--action_row_collapse_radius);
min-width: var(--input_height);
min-height: var(--input_height);
border-radius: var(--border_radius);
padding: var(--padding_large);
image: url(theme:Dark/down.svg);
border: var(--highlight_width) solid var(--grey5);
margin: 0 var(--spacing_base);
border: var(--highlight_width) solid transparent;
}
idian--ExpandButton::indicator:checked {
image: url(theme:Dark/up.svg);
idian--InlineButton:hover {
background-color: var(--button_bg_hover);
border-color: var(--input_border_hover);
}
idian--ExpandButton.keyFocus,
idian--ExpandButton.keyFocus::indicator {
idian--InlineButton:hover,
idian--Row.hover idian--InlineButton.row-buddy {
border-color: var(--input_border_hover);
}
idian--InlineButton:pressed {
background-color: var(--button_bg_down);
}
idian--InlineButton:focus,
idian--InlineButton.keyFocus,
idian--Row.keyFocus idian--InlineButton,
idian--Row.keyFocus idian--InlineButton.checked {
border-color: var(--highlight_color);
}
idian--RowFrame .btn-frame {
background: var(--grey5);
padding: var(--action_row_padding) var(--action_row_padding_x);
idian--ExpandButton {
qproperty-icon: url(theme:Dark/down.svg);
}
idian--RowFrame.hover .btn-frame {
background: var(--grey4);
}
idian--RowFrame.hover idian--Row {
background: var(--grey4);
border: var(--highlight_width) solid var(--grey1);
border-right: none;
}
idian--RowFrame.hover .row-buddy {
background: var(--grey4);
border: var(--highlight_width) solid var(--grey1);
border-left: none;
}
idian--RowFrame.hover idian--ExpandButton::indicator {
border-color: var(--grey1);
idian--ExpandButton.checked {
qproperty-icon: url(theme:Dark/up.svg);
background: transparent;
border-color: transparent;
}
AlignmentSelector {
+53 -49
View File
@@ -17,7 +17,10 @@
#include "OBSIdianPlayground.hpp"
#include <Idian/CollapsibleGroup.hpp>
#include <Idian/Idian.hpp>
#include <Idian/ListHeader.hpp>
#include <Idian/RowInfo.hpp>
#include <QTimer>
@@ -31,7 +34,9 @@ OBSIdianPlayground::OBSIdianPlayground(QWidget *parent) : QDialog(parent), ui(ne
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
Group *test;
QLayout *contents = ui->scrollAreaWidgetContents->layout();
RowList *rowList;
Row *tmp;
ComboBox *cbox = new ComboBox;
@@ -39,31 +44,29 @@ OBSIdianPlayground::OBSIdianPlayground(QWidget *parent) : QDialog(parent), ui(ne
cbox->addItem("Test 2");
// Group box 1
test = new Group(this);
rowList = new RowList(this);
tmp = new Row();
tmp->setTitle("Row with a dropdown");
tmp->setSuffix(cbox);
test->properties()->addRow(tmp);
tmp->addWidget(new RowInfo(tmp, "Row with a dropdown"));
tmp->addBuddy(cbox);
rowList->addRow(tmp);
cbox = new ComboBox;
cbox->addItem("Test 3");
cbox->addItem("Test 4");
tmp = new Row();
tmp->setTitle("Row with a dropdown");
tmp->setDescription("And a subtitle!");
tmp->setSuffix(cbox);
test->properties()->addRow(tmp);
tmp->addWidget(new RowInfo(tmp, "Row with a dropdown", "And a subtitle!"));
tmp->addBuddy(cbox);
rowList->addRow(tmp);
tmp = new Row();
tmp->setTitle("Toggle Switch");
tmp->setSuffix(new ToggleSwitch());
test->properties()->addRow(tmp);
ui->scrollAreaWidgetContents->layout()->addWidget(test);
tmp->addWidget(new RowInfo(tmp, "Toggle Switch"));
tmp->addBuddy(new ToggleSwitch());
rowList->addRow(tmp);
contents->addWidget(rowList);
tmp = new Row();
tmp->setTitle("Delayed toggle switch");
tmp->setDescription("The state can be set separately");
tmp->addWidget(new RowInfo(tmp, "Delayed toggle switch", "The state can be set separately"));
auto tswitch = new ToggleSwitch;
tswitch->setDelayed(true);
connect(tswitch, &ToggleSwitch::pendingChecked, this, [=]() {
@@ -74,64 +77,65 @@ OBSIdianPlayground::OBSIdianPlayground(QWidget *parent) : QDialog(parent), ui(ne
// Do async disable stuff, then set toggle status when complete
QTimer::singleShot(1000, [=]() { tswitch->setStatus(false); });
});
tmp->setSuffix(tswitch);
test->properties()->addRow(tmp);
tmp->addBuddy(tswitch);
rowList->addRow(tmp);
// Group box 2
test = new Group();
test->setTitle("Just a few checkboxes");
contents->addWidget(new ListHeader(rowList, "Just a few checkboxes"));
rowList = new RowList();
tmp = new Row();
tmp->setTitle("Box 1");
tmp->setPrefix(new CheckBox);
test->properties()->addRow(tmp);
tmp->addBuddy(new CheckBox);
tmp->addWidget(new RowInfo(tmp, "Box 1"));
rowList->addRow(tmp);
tmp = new Row();
tmp->setTitle("Box 2");
tmp->setPrefix(new CheckBox);
test->properties()->addRow(tmp);
tmp->addBuddy(new CheckBox);
tmp->addWidget(new RowInfo(tmp, "Box 2"));
rowList->addRow(tmp);
ui->scrollAreaWidgetContents->layout()->addWidget(test);
ui->scrollAreaWidgetContents->layout()->addWidget(rowList);
// Group box 2
test = new Group();
test->setTitle("Another Group");
test->setDescription("With a subtitle");
rowList = new RowList();
contents->addWidget(new ListHeader(rowList, "Another Group", "With a subtitle"));
tmp = new Row();
tmp->setTitle("Placeholder");
tmp->setSuffix(new ToggleSwitch);
test->properties()->addRow(tmp);
tmp->addWidget(new RowInfo(tmp, "Placeholder"));
tmp->addBuddy(new ToggleSwitch);
rowList->addRow(tmp);
CollapsibleRow *tmp2 = new CollapsibleRow(this);
tmp2->setTitle("A Collapsible row!");
CollapsibleGroup *tmp2 = new CollapsibleGroup(this);
tmp2->row()->layout()->insertWidget(0, new RowInfo(tmp, "A Collapsible row!"));
tmp2->setCheckable(true);
test->addRow(tmp2);
rowList->addRow(tmp2);
tmp = new Row();
tmp->setTitle("Spin box demo");
tmp->setSuffix(new DoubleSpinBox());
tmp2->addRow(tmp);
tmp->addWidget(new RowInfo(tmp, "Spin box demo"));
tmp->addBuddy(new RowDoubleSpinBox());
tmp2->list()->addRow(tmp);
tmp = new Row();
tmp->setTitle("Just another placeholder");
tmp->setSuffix(new ToggleSwitch(true));
tmp2->addRow(tmp);
tmp->addWidget(new RowInfo(tmp, "Just another placeholder"));
tmp->addBuddy(new ToggleSwitch(true));
tmp2->list()->addRow(tmp);
tmp = new Row();
tmp->setTitle("Placeholder 2");
tmp->setSuffix(new ToggleSwitch);
test->properties()->addRow(tmp);
tmp->addWidget(new RowInfo(tmp, "Placeholder 2"));
tmp->addBuddy(new ToggleSwitch);
rowList->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()->addWidget(rowList);
ui->scrollAreaWidgetContents->layout()->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
// Test Checkable Group
Group *test2 = new Group();
test2->setTitle("Checkable Group");
test2->setDescription("Description goes here");
test2->setCheckable(true);
auto *test2 = new RowList(this);
auto *header = new ListHeader(this, "Checkable Group", "Description goes here");
header->setCheckable(true);
test2->addHeader(header);
ui->scrollAreaWidgetContents->layout()->addWidget(test2);
}
+4
View File
@@ -596,6 +596,10 @@ private:
QPointer<QWidgetAction> colorWidgetAction;
QPointer<ColorSelect> colorSelect;
#ifdef ENABLE_IDIAN_PLAYGROUND
QPointer<QDialog> playground;
#endif
QList<QDialog *> visDialogs;
QList<QDialog *> modalDialogs;
QList<QMessageBox *> visMsgBoxes;
+5 -4
View File
@@ -670,10 +670,11 @@ void OBSBasic::on_stats_triggered()
void OBSBasic::on_idianPlayground_triggered()
{
#ifdef ENABLE_IDIAN_PLAYGROUND
OBSIdianPlayground playground(this);
playground.setModal(true);
playground.show();
playground.exec();
if (!playground) {
playground = new OBSIdianPlayground(this);
playground->setAttribute(Qt::WA_DeleteOnClose);
playground->show();
}
#endif
}
+16 -8
View File
@@ -10,25 +10,33 @@ target_sources(
PRIVATE
components/CheckBox.cpp
components/ComboBox.cpp
components/DoubleSpinBox.cpp
components/SpinBox.cpp
components/ExpandButton.cpp
components/InlineButton.cpp
components/RowDoubleSpinBox.cpp
components/RowInfo.cpp
components/RowSpinBox.cpp
components/ToggleSwitch.cpp
include/Idian/CheckBox.hpp
include/Idian/CollapsibleGroup.hpp
include/Idian/ComboBox.hpp
include/Idian/DoubleSpinBox.hpp
include/Idian/Group.hpp
include/Idian/ExpandButton.hpp
include/Idian/Idian.hpp
include/Idian/PropertiesList.hpp
include/Idian/InlineButton.hpp
include/Idian/ListHeader.hpp
include/Idian/Row.hpp
include/Idian/SpinBox.hpp
include/Idian/RowDoubleSpinBox.hpp
include/Idian/RowInfo.hpp
include/Idian/RowList.hpp
include/Idian/RowSpinBox.hpp
include/Idian/StateEventFilter.cpp
include/Idian/StateEventFilter.hpp
include/Idian/ToggleSwitch.hpp
include/Idian/Utils.cpp
include/Idian/Utils.hpp
widgets/Group.cpp
widgets/PropertiesList.cpp
widgets/CollapsibleGroup.cpp
widgets/ListHeader.cpp
widgets/Row.cpp
widgets/RowList.cpp
)
target_sources(idian PUBLIC include/Idian/Idian.hpp)
@@ -0,0 +1,30 @@
/******************************************************************************
Copyright (C) 2026 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 <Idian/ExpandButton.hpp>
#include <QPainter>
#include <QStyleOptionButton>
namespace idian {
ExpandButton::ExpandButton(QWidget *parent) : InlineButton(parent)
{
Utils::applyStateStylingEventFilter(this);
setCheckable(true);
}
} // namespace idian
@@ -0,0 +1,25 @@
/******************************************************************************
Copyright (C) 2026 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 <Idian/InlineButton.hpp>
namespace idian {
InlineButton::InlineButton(QWidget *parent) : QPushButton(parent), Utils(this)
{
Utils::applyStateStylingEventFilter(this);
}
} // namespace idian
@@ -15,13 +15,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <Idian/DoubleSpinBox.hpp>
#include <Idian/RowDoubleSpinBox.hpp>
#include <Idian/moc_DoubleSpinBox.cpp>
#include <Idian/moc_RowDoubleSpinBox.cpp>
using idian::DoubleSpinBox;
using idian::RowDoubleSpinBox;
DoubleSpinBox::DoubleSpinBox(QWidget *parent) : QFrame(parent)
RowDoubleSpinBox::RowDoubleSpinBox(QWidget *parent) : QFrame(parent)
{
layout = new QHBoxLayout();
setLayout(layout);
+91
View File
@@ -0,0 +1,91 @@
/******************************************************************************
Copyright (C) 2026 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 <Idian/RowInfo.hpp>
#include <Idian/Utils.hpp>
#include <QLabel>
#include <QVBoxLayout>
namespace idian {
RowInfo::RowInfo(QWidget *parent) : QWidget(parent)
{
layout_ = new QVBoxLayout();
layout_->setSpacing(0);
layout_->setContentsMargins(0, 0, 0, 0);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
setLayout(layout_);
nameLabel = new QLabel();
nameLabel->setVisible(false);
Utils::addClass(nameLabel, "title");
descriptionLabel = new QLabel();
descriptionLabel->setVisible(false);
Utils::addClass(descriptionLabel, "description");
layout_->addWidget(nameLabel);
layout_->addWidget(descriptionLabel);
}
RowInfo::RowInfo(QWidget *parent, QString title) : RowInfo(parent)
{
setTitle(title);
}
RowInfo::RowInfo(QWidget *parent, QString title, QString description) : RowInfo(parent)
{
setTitle(title);
setDescription(description);
}
void RowInfo::setTitle(const QString &name)
{
if (name.isEmpty()) {
showTitle(false);
return;
}
nameLabel->setText(name);
setAccessibleName(name);
showTitle(true);
}
void RowInfo::setDescription(const QString &description)
{
if (description.isEmpty()) {
showDescription(false);
return;
}
descriptionLabel->setText(description);
setAccessibleDescription(description);
showDescription(true);
}
void RowInfo::showTitle(bool visible)
{
nameLabel->setVisible(visible);
}
void RowInfo::showDescription(bool visible)
{
descriptionLabel->setVisible(visible);
}
} // namespace idian
@@ -15,13 +15,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <Idian/SpinBox.hpp>
#include <Idian/RowSpinBox.hpp>
#include <Idian/moc_SpinBox.cpp>
#include <Idian/moc_RowSpinBox.cpp>
using idian::SpinBox;
using idian::RowSpinBox;
SpinBox::SpinBox(QWidget *parent) : QFrame(parent)
RowSpinBox::RowSpinBox(QWidget *parent) : QFrame(parent)
{
layout = new QHBoxLayout();
setLayout(layout);
@@ -50,6 +50,8 @@ ToggleSwitch::ToggleSwitch(QWidget *parent)
installEventFilter(this);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
connect(this, &ToggleSwitch::clicked, this, &ToggleSwitch::onClicked);
connect(animHandle, &QVariantAnimation::valueChanged, this, &ToggleSwitch::updateBackgroundColor);
@@ -0,0 +1,65 @@
/******************************************************************************
Copyright (C) 2026 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 <QFrame>
class QPixmap;
class QVBoxLayout;
namespace idian {
class Row;
class RowInfo;
class ExpandButton;
class RowList;
class ToggleSwitch;
class CollapsibleGroup : public QFrame {
Q_OBJECT
public:
CollapsibleGroup(QWidget *parent = nullptr);
void setCheckable(bool check);
bool isCheckable() { return checkable; }
void setChecked(bool checked);
bool isChecked();
void setExpanded(bool expand = true);
Row *row() { return rowWidget; }
RowList *list() { return propertyList; }
protected:
void toggleVisibility();
QVBoxLayout *mainLayout;
Row *rowWidget;
ExpandButton *expandButton;
RowList *propertyList;
ToggleSwitch *toggleSwitch = nullptr;
bool checkable = false;
signals:
void toggled(bool checked);
};
} // namespace idian
@@ -0,0 +1,34 @@
/******************************************************************************
Copyright (C) 2026 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 <Idian/InlineButton.hpp>
#include <QAbstractButton>
class QPixmap;
namespace idian {
class ExpandButton : public InlineButton {
Q_OBJECT
public:
explicit ExpandButton(QWidget *parent = nullptr);
};
} // namespace idian
+3 -4
View File
@@ -23,9 +23,8 @@
#include <Idian/CheckBox.hpp>
#include <Idian/ComboBox.hpp>
#include <Idian/DoubleSpinBox.hpp>
#include <Idian/Group.hpp>
#include <Idian/PropertiesList.hpp>
#include <Idian/Row.hpp>
#include <Idian/SpinBox.hpp>
#include <Idian/RowDoubleSpinBox.hpp>
#include <Idian/RowList.hpp>
#include <Idian/RowSpinBox.hpp>
#include <Idian/ToggleSwitch.hpp>
@@ -0,0 +1,35 @@
/******************************************************************************
Copyright (C) 2026 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 <Idian/Utils.hpp>
#include <QPushButton>
class QPixmap;
namespace idian {
class Utils;
class InlineButton : public QPushButton, public Utils {
Q_OBJECT
public:
explicit InlineButton(QWidget *parent = nullptr);
};
} // namespace idian
@@ -17,8 +17,7 @@
#pragma once
#include <Idian/PropertiesList.hpp>
#include <Idian/Row.hpp>
#include <Idian/RowList.hpp>
#include <Idian/ToggleSwitch.hpp>
#include <QLabel>
@@ -28,15 +27,13 @@
namespace idian {
class Group : public QFrame, public Utils {
class ListHeader : public QFrame {
Q_OBJECT
public:
Group(QWidget *parent = nullptr);
PropertiesList *properties() const { return propertyList; }
void addRow(GenericRow *row) const;
ListHeader(QWidget *parent = nullptr);
ListHeader(QWidget *parent, QString title);
ListHeader(QWidget *parent, QString title, QString description);
void setTitle(QString name);
void setDescription(QString desc);
@@ -48,24 +45,15 @@ public:
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;
QHBoxLayout *layout_ = nullptr;
QLabel *nameLabel = nullptr;
QLabel *descriptionLabel = nullptr;
PropertiesList *propertyList = nullptr;
ToggleSwitch *toggleSwitch = nullptr;
bool checkable = false;
signals:
void toggled(bool enable);
};
} // namespace idian
+25 -126
View File
@@ -17,10 +17,7 @@
#pragma once
#include <Idian/ComboBox.hpp>
#include <Idian/DoubleSpinBox.hpp>
#include <Idian/PropertiesList.hpp>
#include <Idian/SpinBox.hpp>
#include <Idian/RowInfo.hpp>
#include <Idian/ToggleSwitch.hpp>
#include <Idian/Utils.hpp>
@@ -33,53 +30,38 @@
#include <QWidget>
namespace idian {
// Base class mostly so adding stuff to a list is easier
class GenericRow : public QFrame, public Utils {
Q_OBJECT
public:
GenericRow(QWidget *parent = nullptr) : QFrame(parent), Utils(this)
{
setAttribute(Qt::WA_Hover, true);
applyStateStylingEventFilter(this);
setAccessibleName("");
};
virtual void setTitle(const QString &title) = 0;
virtual void setDescription(const QString &description) = 0;
};
class ExpandButton;
class RowList;
class RowInfo;
// Row widget containing one or more controls
class Row : public GenericRow {
class Row : public QFrame, public Utils {
Q_OBJECT
public:
Row(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);
virtual void setTitle(const QString &title) override;
virtual void setDescription(const QString &description) override;
void showTitle(bool visible);
void showDescription(bool visible);
void setBuddy(QWidget *w);
void setChangeCursor(bool change);
QHBoxLayout *layout() { return rowLayout; }
// Convenience function to add a widget to the row's layout.
void addWidget(QWidget *widget) { layout()->addWidget(widget); }
// Convenience function to add a widget to the row's layout and then set it as the buddy.
void addBuddy(QWidget *widget)
{
layout()->addWidget(widget);
setBuddy(widget);
}
// Sets this widget as the buddy of the Row.
// The idian StateEventFilter on Row means certain style-able classes are applied to it based on interaction
// events. The buddy widget will be repolished when these events happen. This allows the buddy widget to be
// styled differently based on the state of the row. For certain widgets, a slot will also be triggered
// on the buddy widget.
void setBuddy(QWidget *w);
signals:
void clicked();
@@ -88,96 +70,13 @@ protected:
void leaveEvent(QEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
void keyReleaseEvent(QKeyEvent *) override;
bool hasDescription() const { return descriptionLabel != nullptr; }
private:
QGridLayout *layout;
QVBoxLayout *labelLayout = nullptr;
QLabel *nameLabel = nullptr;
QLabel *descriptionLabel = nullptr;
QWidget *prefix_ = nullptr;
QWidget *suffix_ = nullptr;
QHBoxLayout *rowLayout;
QWidget *buddyWidget = nullptr;
void connectBuddyWidget(QWidget *widget);
bool changeCursor = false;
};
// Collapsible row expand button
class ExpandButton : public QAbstractButton, public Utils {
Q_OBJECT
private:
QPixmap extendDown;
QPixmap extendUp;
friend class CollapsibleRow;
protected:
explicit ExpandButton(QWidget *parent = nullptr);
void paintEvent(QPaintEvent *) override;
};
class RowFrame : protected QFrame, protected Utils {
Q_OBJECT
signals:
void clicked();
protected:
explicit RowFrame(QWidget *parent = nullptr);
void enterEvent(QEnterEvent *) override;
void leaveEvent(QEvent *) override;
private:
friend class CollapsibleRow;
};
// Collapsible Generic OBS property container
class CollapsibleRow : public GenericRow {
Q_OBJECT
public:
CollapsibleRow(QWidget *parent = nullptr);
void setCheckable(bool check);
bool isCheckable() { return checkable; }
void setChecked(bool checked);
bool isChecked() { return toggleSwitch->isChecked(); };
virtual void setTitle(const QString &title) override;
virtual void setDescription(const QString &description) override;
void addRow(GenericRow *actionRow);
signals:
void toggled(bool checked);
private:
void toggleVisibility();
QPixmap extendDown;
QPixmap extendUp;
QVBoxLayout *layout;
RowFrame *rowWidget;
QHBoxLayout *rowLayout;
Row *actionRow;
QFrame *expandFrame;
QHBoxLayout *btnLayout;
ExpandButton *expandButton;
PropertiesList *propertyList;
ToggleSwitch *toggleSwitch = nullptr;
bool checkable = false;
};
} // namespace idian
@@ -24,11 +24,11 @@
namespace idian {
class DoubleSpinBox : public QFrame {
class RowDoubleSpinBox : public QFrame {
Q_OBJECT;
public:
DoubleSpinBox(QWidget *parent = nullptr);
RowDoubleSpinBox(QWidget *parent = nullptr);
QDoubleSpinBox *spinBox() const { return sbox; }
+51
View File
@@ -0,0 +1,51 @@
/******************************************************************************
Copyright (C) 2026 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>
class QLabel;
class QVBoxLayout;
namespace idian {
class RowInfo : public QWidget {
Q_OBJECT
public:
RowInfo(QWidget *parent);
RowInfo(QWidget *parent, QString title);
RowInfo(QWidget *parent, QString title, QString description);
~RowInfo() = default;
void setTitle(const QString &title);
void setDescription(const QString &description);
void showTitle(bool visible);
void showDescription(bool visible);
QLabel *title() { return nameLabel; }
QLabel *description() { return descriptionLabel; }
private:
QVBoxLayout *layout_ = nullptr;
QLabel *nameLabel = nullptr;
QLabel *descriptionLabel = nullptr;
};
} // namespace idian
@@ -17,6 +17,7 @@
#pragma once
#include <Idian/Row.hpp>
#include <Idian/Utils.hpp>
#include <QFrame>
@@ -24,32 +25,29 @@
#include <QWidget>
namespace idian {
class GenericRow;
class PropertiesList : public QFrame {
class RowList : public QFrame {
Q_OBJECT
public:
PropertiesList(QWidget *parent = nullptr);
RowList(QWidget *parent = nullptr);
void addRow(GenericRow *row);
void addHeader(QWidget *widget);
void addRow(QWidget *row);
void clear();
QList<GenericRow *> rows() const { return rowsList; }
private:
GenericRow *first = nullptr;
GenericRow *last = nullptr;
QWidget *first = nullptr;
QWidget *last = nullptr;
QVBoxLayout *layout;
QList<GenericRow *> rowsList;
QVBoxLayout *rowLayout;
};
// Spacer with only cosmetic functionality
class PropertiesListSpacer : public QFrame {
class RowListSpacer : public QFrame {
Q_OBJECT
public:
PropertiesListSpacer(QWidget *parent = nullptr) : QFrame(parent)
RowListSpacer(QWidget *parent = nullptr) : QFrame(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
}
@@ -24,11 +24,11 @@
namespace idian {
class SpinBox : public QFrame {
class RowSpinBox : public QFrame {
Q_OBJECT;
public:
SpinBox(QWidget *parent = nullptr);
RowSpinBox(QWidget *parent = nullptr);
QSpinBox *spinBox() const { return sbox; }
+3
View File
@@ -66,6 +66,9 @@ QPixmap Utils::recolorPixmap(const QPixmap &src, const QColor &color)
return QPixmap::fromImage(img);
}
// Updates the dynamic property 'class' on a widget with values in response to certain interaction events.
// Ex. `hover` when the widget is hovered.
// Widgets can then be styled via CSS class-style rules like .hover.
void Utils::applyStateStylingEventFilter(QWidget *widget)
{
widget->installEventFilter(new StateEventFilter(this, widget));
@@ -0,0 +1,107 @@
/******************************************************************************
Copyright (C) 2026 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 <Idian/CollapsibleGroup.hpp>
#include <Idian/ExpandButton.hpp>
#include <Idian/Row.hpp>
#include <Idian/RowInfo.hpp>
#include <Idian/RowList.hpp>
#include <Idian/ToggleSwitch.hpp>
#include <Idian/Utils.hpp>
#include <QPixmap>
#include <QVBoxLayout>
namespace idian {
CollapsibleGroup::CollapsibleGroup(QWidget *parent) : QFrame(parent)
{
mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
setLayout(mainLayout);
rowWidget = new Row(this);
mainLayout->addWidget(rowWidget);
auto *headerInfo = new RowInfo(rowWidget);
rowWidget->layout()->addWidget(headerInfo);
expandButton = new ExpandButton(this);
rowWidget->layout()->addWidget(expandButton);
rowWidget->setBuddy(expandButton);
propertyList = new RowList(this);
propertyList->setVisible(false);
mainLayout->addWidget(propertyList);
connect(expandButton, &QAbstractButton::clicked, this, &CollapsibleGroup::toggleVisibility);
}
void CollapsibleGroup::setCheckable(bool check)
{
checkable = check;
if (checkable && !toggleSwitch) {
propertyList->setEnabled(false);
Utils::polishChildren(propertyList);
toggleSwitch = new ToggleSwitch(false);
rowWidget->layout()->insertWidget(rowWidget->layout()->count() - 1, toggleSwitch);
connect(toggleSwitch, &ToggleSwitch::toggled, propertyList, &RowList::setEnabled);
connect(toggleSwitch, &ToggleSwitch::toggled, this, &CollapsibleGroup::toggled);
}
if (!checkable && toggleSwitch) {
propertyList->setEnabled(true);
Utils::polishChildren(propertyList);
toggleSwitch->deleteLater();
}
}
void CollapsibleGroup::setChecked(bool checked)
{
if (!isCheckable()) {
throw std::logic_error("Called setChecked on a non-checkable collapse row.");
}
toggleSwitch->setChecked(checked);
}
bool CollapsibleGroup::isChecked()
{
return toggleSwitch->isChecked();
}
void CollapsibleGroup::toggleVisibility()
{
bool visible = !propertyList->isVisible();
setExpanded(visible);
}
void CollapsibleGroup::setExpanded(bool expand)
{
Utils::toggleClass(this, "expanded", expand);
Utils::repolish(rowWidget);
propertyList->setVisible(expand);
expandButton->setChecked(expand);
}
} // namespace idian
-126
View File
@@ -1,126 +0,0 @@
/******************************************************************************
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 <Idian/Group.hpp>
#include <Idian/Utils.hpp>
#include <Idian/moc_Group.cpp>
using idian::Group;
Group::Group(QWidget *parent) : QFrame(parent), Utils(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);
Utils::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);
Utils::addClass(contentsContainer, "contents");
layout->addWidget(headerContainer);
layout->addWidget(contentsContainer);
propertyList = new PropertiesList(this);
setLayout(layout);
contentsLayout->addWidget(propertyList);
nameLabel = new QLabel();
Utils::addClass(nameLabel, "title");
nameLabel->setVisible(false);
labelLayout->addWidget(nameLabel);
descriptionLabel = new QLabel();
Utils::addClass(descriptionLabel, "description");
descriptionLabel->setVisible(false);
labelLayout->addWidget(descriptionLabel);
}
void Group::addRow(GenericRow *row) const
{
propertyList->addRow(row);
}
void Group::setTitle(QString name)
{
nameLabel->setText(name);
setAccessibleName(name);
showTitle(true);
}
void Group::setDescription(QString desc)
{
descriptionLabel->setText(desc);
setAccessibleDescription(desc);
showDescription(true);
}
void Group::showTitle(bool visible)
{
nameLabel->setVisible(visible);
}
void Group::showDescription(bool visible)
{
descriptionLabel->setVisible(visible);
}
void Group::setCheckable(bool check)
{
checkable = check;
if (checkable && !toggleSwitch) {
toggleSwitch = new ToggleSwitch(true);
controlLayout->addWidget(toggleSwitch);
connect(toggleSwitch, &ToggleSwitch::toggled, this,
[this](bool checked) { propertyList->setEnabled(checked); });
}
if (!checkable && toggleSwitch) {
controlLayout->removeWidget(toggleSwitch);
toggleSwitch->deleteLater();
}
}
+99
View File
@@ -0,0 +1,99 @@
/******************************************************************************
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 <Idian/ListHeader.hpp>
#include <Idian/Utils.hpp>
#include <Idian/moc_ListHeader.cpp>
using idian::ListHeader;
ListHeader::ListHeader(QWidget *parent) : QFrame(parent)
{
layout_ = new QHBoxLayout();
layout_->setSpacing(0);
layout_->setContentsMargins(0, 0, 0, 0);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
setLayout(layout_);
auto *textLayout = new QVBoxLayout();
nameLabel = new QLabel();
nameLabel->setVisible(false);
Utils::addClass(nameLabel, "title");
descriptionLabel = new QLabel();
descriptionLabel->setVisible(false);
Utils::addClass(descriptionLabel, "description");
textLayout->addWidget(nameLabel);
textLayout->addWidget(descriptionLabel);
layout_->addLayout(textLayout);
}
ListHeader::ListHeader(QWidget *parent, QString title) : ListHeader(parent)
{
setTitle(title);
}
ListHeader::ListHeader(QWidget *parent, QString title, QString description) : ListHeader(parent)
{
setTitle(title);
setDescription(description);
}
void ListHeader::setTitle(QString name)
{
nameLabel->setText(name);
setAccessibleName(name);
showTitle(true);
}
void ListHeader::setDescription(QString desc)
{
descriptionLabel->setText(desc);
setAccessibleDescription(desc);
showDescription(true);
}
void ListHeader::showTitle(bool visible)
{
nameLabel->setVisible(visible);
}
void ListHeader::showDescription(bool visible)
{
descriptionLabel->setVisible(visible);
}
void ListHeader::setCheckable(bool check)
{
checkable = check;
if (checkable && !toggleSwitch) {
toggleSwitch = new ToggleSwitch(true);
layout_->addWidget(toggleSwitch);
connect(toggleSwitch, &ToggleSwitch::toggled, this, [this](bool checked) { emit toggled(checked); });
}
if (!checkable && toggleSwitch) {
layout_->removeWidget(toggleSwitch);
toggleSwitch->deleteLater();
}
}
+23 -281
View File
@@ -15,7 +15,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <Idian/ComboBox.hpp>
#include <Idian/Row.hpp>
#include <Idian/ToggleSwitch.hpp>
#include <QApplication>
#include <QComboBox>
@@ -25,129 +27,23 @@
#include <Idian/moc_Row.cpp>
namespace idian {
Row::Row(QWidget *parent) : GenericRow(parent)
Row::Row(QWidget *parent) : QFrame(parent), Utils(this)
{
layout = new QGridLayout(this);
layout->setVerticalSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
rowLayout = new QHBoxLayout(this);
rowLayout->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);
Utils::addClass(nameLabel, "title");
descriptionLabel = new QLabel();
descriptionLabel->setVisible(false);
Utils::addClass(descriptionLabel, "description");
labelLayout->addWidget(nameLabel);
labelLayout->addWidget(descriptionLabel);
layout->addLayout(labelLayout, 0, 1, Qt::AlignLeft);
}
void Row::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 Row::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 Row::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 Row::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 Row::setTitle(const QString &name)
{
nameLabel->setText(name);
setAccessibleName(name);
showTitle(true);
}
void Row::setDescription(const QString &description)
{
descriptionLabel->setText(description);
setAccessibleDescription(description);
showDescription(true);
}
void Row::showTitle(bool visible)
{
nameLabel->setVisible(visible);
}
void Row::showDescription(bool visible)
{
descriptionLabel->setVisible(visible);
setLayout(rowLayout);
}
void Row::setBuddy(QWidget *widget)
{
if (buddyWidget) {
return;
}
buddyWidget = widget;
Utils::addClass(widget, "row-buddy");
connectBuddyWidget(widget);
}
void Row::setChangeCursor(bool change)
@@ -170,11 +66,7 @@ void Row::enterEvent(QEnterEvent *event)
Utils::repolish(buddyWidget);
}
if (hasPrefix() || hasSuffix()) {
Utils::polishChildren();
}
GenericRow::enterEvent(event);
QFrame::enterEvent(event);
}
void Row::leaveEvent(QEvent *event)
@@ -183,11 +75,7 @@ void Row::leaveEvent(QEvent *event)
Utils::repolish(buddyWidget);
}
if (hasPrefix() || hasSuffix()) {
Utils::polishChildren();
}
GenericRow::leaveEvent(event);
QFrame::leaveEvent(event);
}
void Row::mouseReleaseEvent(QMouseEvent *event)
@@ -208,184 +96,38 @@ void Row::keyReleaseEvent(QKeyEvent *event)
void Row::connectBuddyWidget(QWidget *widget)
{
setAccessibleName(nameLabel->text());
setFocusProxy(widget);
setBuddy(widget);
setAttribute(Qt::WA_Hover, true);
setFocusPolicy(Qt::StrongFocus);
applyStateStylingEventFilter(this);
// If element is a ToggleSwitch and checkable, forward clicks to the widget
ToggleSwitch *obsToggle = dynamic_cast<ToggleSwitch *>(widget);
ToggleSwitch *obsToggle = qobject_cast<ToggleSwitch *>(widget);
if (obsToggle && obsToggle->isCheckable()) {
setChangeCursor(true);
widget->setFocusProxy(this);
connect(this, &Row::clicked, obsToggle, &ToggleSwitch::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()) {
QAbstractButton *button = qobject_cast<QAbstractButton *>(widget);
if (button) {
setChangeCursor(true);
widget->setFocusProxy(this);
connect(this, &Row::clicked, button, &QAbstractButton::click);
return;
}
// If element is an ComboBox, clicks toggle the dropdown.
ComboBox *obsCombo = dynamic_cast<ComboBox *>(widget);
ComboBox *obsCombo = qobject_cast<ComboBox *>(widget);
if (obsCombo) {
setChangeCursor(true);
widget->setFocusProxy(this);
connect(this, &Row::clicked, obsCombo, &ComboBox::togglePopup);
return;
}
}
// Button for expanding a collapsible ActionRow
ExpandButton::ExpandButton(QWidget *parent) : QAbstractButton(parent), Utils(this)
{
Utils::applyStateStylingEventFilter(this);
setCheckable(true);
}
void ExpandButton::paintEvent(QPaintEvent *)
{
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);
}
// Row variant that can be expanded to show another properties list
CollapsibleRow::CollapsibleRow(QWidget *parent) : GenericRow(parent)
{
layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
setLayout(layout);
rowWidget = new RowFrame();
rowLayout = new QHBoxLayout();
rowLayout->setContentsMargins(0, 0, 0, 0);
rowLayout->setSpacing(0);
rowWidget->setLayout(rowLayout);
actionRow = new Row();
actionRow->setChangeCursor(false);
rowLayout->addWidget(actionRow);
propertyList = new PropertiesList(this);
propertyList->setVisible(false);
expandFrame = new QFrame();
btnLayout = new QHBoxLayout();
btnLayout->setContentsMargins(0, 0, 0, 0);
btnLayout->setSpacing(0);
expandFrame->setLayout(btnLayout);
Utils::addClass(expandFrame, "btn-frame");
actionRow->setBuddy(expandFrame);
expandButton = new ExpandButton(this);
btnLayout->addWidget(expandButton);
rowLayout->addWidget(expandFrame);
layout->addWidget(rowWidget);
layout->addWidget(propertyList);
actionRow->setFocusProxy(expandButton);
connect(expandButton, &QAbstractButton::clicked, this, &CollapsibleRow::toggleVisibility);
connect(actionRow, &Row::clicked, expandButton, &QAbstractButton::click);
}
void CollapsibleRow::setCheckable(bool check)
{
checkable = check;
if (checkable && !toggleSwitch) {
propertyList->setEnabled(false);
Utils::polishChildren(propertyList);
toggleSwitch = new ToggleSwitch(false);
actionRow->setSuffix(toggleSwitch, false);
connect(toggleSwitch, &ToggleSwitch::toggled, propertyList, &PropertiesList::setEnabled);
connect(toggleSwitch, &ToggleSwitch::toggled, this, &CollapsibleRow::toggled);
}
if (!checkable && toggleSwitch) {
propertyList->setEnabled(true);
Utils::polishChildren(propertyList);
actionRow->suffix()->deleteLater();
}
}
void CollapsibleRow::setChecked(bool checked)
{
if (!isCheckable()) {
throw std::logic_error("Called setChecked on a non-checkable row.");
}
toggleSwitch->setChecked(checked);
}
void CollapsibleRow::setTitle(const QString &name)
{
actionRow->setTitle(name);
}
void CollapsibleRow::setDescription(const QString &description)
{
actionRow->setDescription(description);
}
void CollapsibleRow::toggleVisibility()
{
bool visible = !propertyList->isVisible();
propertyList->setVisible(visible);
expandButton->setChecked(visible);
}
void CollapsibleRow::addRow(GenericRow *actionRow)
{
propertyList->addRow(actionRow);
}
RowFrame::RowFrame(QWidget *parent) : QFrame(parent), Utils(this)
{
setAttribute(Qt::WA_Hover, true);
Utils::applyStateStylingEventFilter(this);
}
void RowFrame::enterEvent(QEnterEvent *event)
{
setCursor(Qt::PointingHandCursor);
Utils::polishChildren();
QWidget::enterEvent(event);
}
void RowFrame::leaveEvent(QEvent *event)
{
Utils::polishChildren();
QWidget::leaveEvent(event);
}
} // namespace idian
@@ -15,42 +15,47 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <Idian/PropertiesList.hpp>
#include <Idian/Row.hpp>
#include <Idian/RowList.hpp>
#include <QStyle>
#include <Idian/moc_PropertiesList.cpp>
#include <Idian/moc_RowList.cpp>
using idian::PropertiesList;
using idian::RowList;
PropertiesList::PropertiesList(QWidget *parent) : QFrame(parent)
RowList::RowList(QWidget *parent) : QFrame(parent)
{
layout = new QVBoxLayout();
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
rowsList = QList<GenericRow *>();
setLayout(layout);
rowLayout = new QVBoxLayout();
layout->addLayout(rowLayout);
}
void idian::RowList::addHeader(QWidget *widget)
{
layout->insertWidget(layout->indexOf(rowLayout) - 1, widget);
}
// 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 PropertiesList::addRow(GenericRow *row)
void RowList::addRow(QWidget *widget)
{
// Add custom spacer once more than one element exists
if (layout->count() > 0) {
layout->addWidget(new PropertiesListSpacer(this));
// Add custom spacer when more than one row exists
if (rowLayout->count() > 0) {
rowLayout->addWidget(new RowListSpacer(this));
}
// Custom properties to work around :first and :last not existing.
if (!first) {
Utils::addClass(row, "first");
first = row;
Utils::addClass(widget, "first");
first = widget;
}
// Remove last property from existing last item
@@ -59,21 +64,18 @@ void PropertiesList::addRow(GenericRow *row)
}
// Most recently added item is also always last
Utils::addClass(row, "last");
last = row;
Utils::addClass(widget, "last");
last = widget;
row->setParent(this);
rowsList.append(row);
layout->addWidget(row);
rowLayout->addWidget(widget);
adjustSize();
}
void PropertiesList::clear()
void RowList::clear()
{
rowsList.clear();
first = nullptr;
last = nullptr;
QLayoutItem *item = layout->takeAt(0);
QLayoutItem *item = rowLayout->takeAt(0);
while (item) {
if (item->widget()) {
@@ -81,7 +83,7 @@ void PropertiesList::clear()
}
delete item;
item = layout->takeAt(0);
item = rowLayout->takeAt(0);
}
adjustSize();