diff --git a/frontend/data/themes/Yami.obt b/frontend/data/themes/Yami.obt index e1f26ef14606ad..4089f95c48ddff 100644 --- a/frontend/data/themes/Yami.obt +++ b/frontend/data/themes/Yami.obt @@ -221,7 +221,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); @@ -2489,25 +2488,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; @@ -2517,11 +2516,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 { @@ -2529,17 +2529,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); } @@ -2636,7 +2640,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); @@ -2673,35 +2677,54 @@ 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--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--CollapsibleRow idian--PropertiesList idian--ToggleSwitch { +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); } @@ -2717,7 +2740,7 @@ idian--ExpandButton { idian--ExpandButton::indicator { background: var(--grey5); - border-radius: var(--action_row_collapse_radius); + border-radius: var(--border_radius); padding: var(--padding_large); image: url(theme:Dark/down.svg); border: var(--highlight_width) solid var(--grey5); @@ -2728,32 +2751,13 @@ idian--ExpandButton::indicator:checked { } idian--ExpandButton.keyFocus, -idian--ExpandButton.keyFocus::indicator { +idian--Row.keyFocus idian--ExpandButton::indicator { border-color: var(--highlight_color); } -idian--RowFrame .btn-frame { - background: var(--grey5); - padding: var(--action_row_padding) var(--action_row_padding_x); -} - -idian--RowFrame.hover .btn-frame { - background: var(--grey4); -} - -idian--RowFrame.hover idian--Row { +idian--ExpandButton.hover::indicator, +idian--Row.hover idian--ExpandButton.row-buddy::indicator { 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); } diff --git a/frontend/dialogs/OBSIdianPlayground.cpp b/frontend/dialogs/OBSIdianPlayground.cpp index c28db477443e16..da45f0aab8fdb5 100644 --- a/frontend/dialogs/OBSIdianPlayground.cpp +++ b/frontend/dialogs/OBSIdianPlayground.cpp @@ -17,7 +17,10 @@ #include "OBSIdianPlayground.hpp" +#include #include +#include +#include #include @@ -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 DoubleSpinBox()); + 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); } diff --git a/frontend/widgets/OBSBasic.hpp b/frontend/widgets/OBSBasic.hpp index 4343e16054c4da..64bcf0ca4d7d4e 100644 --- a/frontend/widgets/OBSBasic.hpp +++ b/frontend/widgets/OBSBasic.hpp @@ -595,6 +595,10 @@ private slots: QPointer colorWidgetAction; QPointer colorSelect; +#ifdef ENABLE_IDIAN_PLAYGROUND + QPointer playground; +#endif + QList visDialogs; QList modalDialogs; QList visMsgBoxes; diff --git a/frontend/widgets/OBSBasic_MainControls.cpp b/frontend/widgets/OBSBasic_MainControls.cpp index 40b3aef444b418..6b089586db6c4c 100644 --- a/frontend/widgets/OBSBasic_MainControls.cpp +++ b/frontend/widgets/OBSBasic_MainControls.cpp @@ -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 } diff --git a/shared/qt/idian/CMakeLists.txt b/shared/qt/idian/CMakeLists.txt index 7ed39f3526f309..88e3d7c445d746 100644 --- a/shared/qt/idian/CMakeLists.txt +++ b/shared/qt/idian/CMakeLists.txt @@ -11,24 +11,30 @@ target_sources( components/CheckBox.cpp components/ComboBox.cpp components/DoubleSpinBox.cpp + components/ExpandButton.cpp + components/RowInfo.cpp components/SpinBox.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/ListHeader.hpp include/Idian/Row.hpp + include/Idian/RowInfo.hpp + include/Idian/RowList.hpp include/Idian/SpinBox.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) diff --git a/shared/qt/idian/components/ExpandButton.cpp b/shared/qt/idian/components/ExpandButton.cpp new file mode 100644 index 00000000000000..29bba1ea16d63b --- /dev/null +++ b/shared/qt/idian/components/ExpandButton.cpp @@ -0,0 +1,52 @@ +/****************************************************************************** + Copyright (C) 2026 by Taylor Giampaolo + + 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 . +******************************************************************************/ + +#include +#include + +#include +#include + +namespace idian { +ExpandButton::ExpandButton(QWidget *parent) : QAbstractButton(parent) +{ + widgetUtils = new Utils(this); + widgetUtils->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); +} +} // namespace idian diff --git a/shared/qt/idian/components/RowInfo.cpp b/shared/qt/idian/components/RowInfo.cpp new file mode 100644 index 00000000000000..7991012747a040 --- /dev/null +++ b/shared/qt/idian/components/RowInfo.cpp @@ -0,0 +1,93 @@ +/****************************************************************************** + Copyright (C) 2026 by Taylor Giampaolo + + 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 . +******************************************************************************/ + +#include +#include + +#include +#include + +namespace idian { +RowInfo::RowInfo(QWidget *parent) : QWidget(parent) +{ + widgetUtils = new idian::Utils(this); + + 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); + widgetUtils->addClass(nameLabel, "title"); + + descriptionLabel = new QLabel(); + descriptionLabel->setVisible(false); + widgetUtils->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 diff --git a/shared/qt/idian/components/ToggleSwitch.cpp b/shared/qt/idian/components/ToggleSwitch.cpp index 7fda7ba973dd7a..042ad19218b0c0 100644 --- a/shared/qt/idian/components/ToggleSwitch.cpp +++ b/shared/qt/idian/components/ToggleSwitch.cpp @@ -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); diff --git a/shared/qt/idian/include/Idian/CollapsibleGroup.hpp b/shared/qt/idian/include/Idian/CollapsibleGroup.hpp new file mode 100644 index 00000000000000..d0fc92050be904 --- /dev/null +++ b/shared/qt/idian/include/Idian/CollapsibleGroup.hpp @@ -0,0 +1,67 @@ +/****************************************************************************** + Copyright (C) 2026 by Taylor Giampaolo + + 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 . +******************************************************************************/ + +#pragma once + +#include + +#include + +class QPixmap; +class QVBoxLayout; + +namespace idian { +class Row; +class RowInfo; +class ExpandButton; +class RowList; +class ToggleSwitch; + +class CollapsibleGroup : public QFrame, public Utils { + 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 diff --git a/shared/qt/idian/include/Idian/ExpandButton.hpp b/shared/qt/idian/include/Idian/ExpandButton.hpp new file mode 100644 index 00000000000000..e9368efd99c5c3 --- /dev/null +++ b/shared/qt/idian/include/Idian/ExpandButton.hpp @@ -0,0 +1,44 @@ +/****************************************************************************** + Copyright (C) 2026 by Taylor Giampaolo + + 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 . +******************************************************************************/ + +#pragma once + +#include + +class QPixmap; + +namespace idian { +class Utils; + +class ExpandButton : public QAbstractButton { + Q_OBJECT + +public: + explicit ExpandButton(QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *) override; + +private: + Utils *widgetUtils; + + QPixmap extendDown; + QPixmap extendUp; + + friend class CollapsibleGroup; +}; +} // namespace idian diff --git a/shared/qt/idian/include/Idian/Idian.hpp b/shared/qt/idian/include/Idian/Idian.hpp index c69add2d88daa7..aa8dc3fb6a46fc 100644 --- a/shared/qt/idian/include/Idian/Idian.hpp +++ b/shared/qt/idian/include/Idian/Idian.hpp @@ -24,8 +24,7 @@ #include #include #include -#include -#include +#include #include #include #include diff --git a/shared/qt/idian/include/Idian/Group.hpp b/shared/qt/idian/include/Idian/ListHeader.hpp similarity index 69% rename from shared/qt/idian/include/Idian/Group.hpp rename to shared/qt/idian/include/Idian/ListHeader.hpp index 030361995f07c0..9c90d03d6e5f47 100644 --- a/shared/qt/idian/include/Idian/Group.hpp +++ b/shared/qt/idian/include/Idian/ListHeader.hpp @@ -17,8 +17,7 @@ #pragma once -#include -#include +#include #include #include @@ -28,15 +27,13 @@ namespace idian { -class Group : public QFrame, public Utils { +class ListHeader : public QFrame, public Utils { 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,17 @@ class Group : public QFrame, public Utils { 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; + Utils *widgetUtils; - 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 diff --git a/shared/qt/idian/include/Idian/Row.hpp b/shared/qt/idian/include/Idian/Row.hpp index 4f324957dc0696..20875a8408e8b1 100644 --- a/shared/qt/idian/include/Idian/Row.hpp +++ b/shared/qt/idian/include/Idian/Row.hpp @@ -17,10 +17,7 @@ #pragma once -#include -#include -#include -#include +#include #include #include @@ -33,53 +30,38 @@ #include 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); + void setChangeCursor(bool change); - virtual void setTitle(const QString &title) override; - virtual void setDescription(const QString &description) override; + QHBoxLayout *layout() { return rowLayout; } - void showTitle(bool visible); - void showDescription(bool visible); + // Convenience function to add a widget to the rows layout. + void addWidget(QWidget *widget) { layout()->addWidget(widget); } + // Convenience function to add a widget to the rows 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); - void setChangeCursor(bool change); - signals: void clicked(); @@ -88,96 +70,13 @@ class Row : public GenericRow { 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 diff --git a/shared/qt/idian/include/Idian/RowInfo.hpp b/shared/qt/idian/include/Idian/RowInfo.hpp new file mode 100644 index 00000000000000..010d35d188fd75 --- /dev/null +++ b/shared/qt/idian/include/Idian/RowInfo.hpp @@ -0,0 +1,54 @@ +/****************************************************************************** + Copyright (C) 2026 by Taylor Giampaolo + + 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 . +******************************************************************************/ + +#pragma once + +#include + +class QLabel; +class QVBoxLayout; + +namespace idian { +class Utils; + +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: + Utils *widgetUtils; + + QVBoxLayout *layout_ = nullptr; + + QLabel *nameLabel = nullptr; + QLabel *descriptionLabel = nullptr; +}; +} // namespace idian diff --git a/shared/qt/idian/include/Idian/PropertiesList.hpp b/shared/qt/idian/include/Idian/RowList.hpp similarity index 75% rename from shared/qt/idian/include/Idian/PropertiesList.hpp rename to shared/qt/idian/include/Idian/RowList.hpp index b0fe840d65fcfe..518e2cef3c427e 100644 --- a/shared/qt/idian/include/Idian/PropertiesList.hpp +++ b/shared/qt/idian/include/Idian/RowList.hpp @@ -17,6 +17,7 @@ #pragma once +#include #include #include @@ -24,32 +25,29 @@ #include 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 rows() const { return rowsList; } - private: - GenericRow *first = nullptr; - GenericRow *last = nullptr; + QWidget *first = nullptr; + QWidget *last = nullptr; QVBoxLayout *layout; - QList 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); } diff --git a/shared/qt/idian/include/Idian/Utils.cpp b/shared/qt/idian/include/Idian/Utils.cpp index f1ecd4068222d6..890eb3beebd98f 100644 --- a/shared/qt/idian/include/Idian/Utils.cpp +++ b/shared/qt/idian/include/Idian/Utils.cpp @@ -52,6 +52,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)); diff --git a/shared/qt/idian/widgets/CollapsibleGroup.cpp b/shared/qt/idian/widgets/CollapsibleGroup.cpp new file mode 100644 index 00000000000000..223cac9715187c --- /dev/null +++ b/shared/qt/idian/widgets/CollapsibleGroup.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + Copyright (C) 2026 by Taylor Giampaolo + + 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 . +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace idian { +CollapsibleGroup::CollapsibleGroup(QWidget *parent) : QFrame(parent), Utils(this) +{ + 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("expanded", expand); + Utils::repolish(rowWidget); + + propertyList->setVisible(expand); + expandButton->setChecked(expand); +} +} // namespace idian diff --git a/shared/qt/idian/widgets/Group.cpp b/shared/qt/idian/widgets/Group.cpp deleted file mode 100644 index 89b14e4b9e0085..00000000000000 --- a/shared/qt/idian/widgets/Group.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/****************************************************************************** - Copyright (C) 2023 by Dennis Sädtler - - 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 . -******************************************************************************/ - -#include - -#include - -#include - -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(); - } -} diff --git a/shared/qt/idian/widgets/ListHeader.cpp b/shared/qt/idian/widgets/ListHeader.cpp new file mode 100644 index 00000000000000..b0047c46f30072 --- /dev/null +++ b/shared/qt/idian/widgets/ListHeader.cpp @@ -0,0 +1,101 @@ +/****************************************************************************** + Copyright (C) 2023 by Dennis Sädtler + + 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 . +******************************************************************************/ + +#include +#include + +#include + +using idian::ListHeader; + +ListHeader::ListHeader(QWidget *parent) : QFrame(parent), Utils(this) +{ + widgetUtils = new idian::Utils(this); + + 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); + widgetUtils->addClass(nameLabel, "title"); + + descriptionLabel = new QLabel(); + descriptionLabel->setVisible(false); + widgetUtils->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(); + } +} diff --git a/shared/qt/idian/widgets/Row.cpp b/shared/qt/idian/widgets/Row.cpp index 96d195794d8dcf..56bd0983651067 100644 --- a/shared/qt/idian/widgets/Row.cpp +++ b/shared/qt/idian/widgets/Row.cpp @@ -15,7 +15,14 @@ along with this program. If not, see . ******************************************************************************/ +#include +#include +#include +#include #include +#include +#include +#include #include #include @@ -25,129 +32,19 @@ #include namespace idian { - -Row::Row(QWidget *parent) : GenericRow(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); - 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) +Row::Row(QWidget *parent) : QFrame(parent), Utils(this) { - 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); -} + rowLayout = new QHBoxLayout(this); + rowLayout->setContentsMargins(0, 0, 0, 0); -void Row::showDescription(bool visible) -{ - descriptionLabel->setVisible(visible); + setLayout(rowLayout); } void Row::setBuddy(QWidget *widget) { buddyWidget = widget; Utils::addClass(widget, "row-buddy"); + connectBuddyWidget(widget); } void Row::setChangeCursor(bool change) @@ -170,11 +67,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 +76,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,14 +97,15 @@ 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(widget); if (obsToggle && obsToggle->isCheckable()) { setChangeCursor(true); + widget->setFocusProxy(this); connect(this, &Row::clicked, obsToggle, &ToggleSwitch::click); return; @@ -225,6 +115,7 @@ void Row::connectBuddyWidget(QWidget *widget) QAbstractButton *button = dynamic_cast(widget); if (button && button->isCheckable()) { setChangeCursor(true); + widget->setFocusProxy(this); connect(this, &Row::clicked, button, &QAbstractButton::click); return; @@ -234,158 +125,10 @@ void Row::connectBuddyWidget(QWidget *widget) ComboBox *obsCombo = dynamic_cast(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 diff --git a/shared/qt/idian/widgets/PropertiesList.cpp b/shared/qt/idian/widgets/RowList.cpp similarity index 70% rename from shared/qt/idian/widgets/PropertiesList.cpp rename to shared/qt/idian/widgets/RowList.cpp index d28935ece89499..fae414e53f8d54 100644 --- a/shared/qt/idian/widgets/PropertiesList.cpp +++ b/shared/qt/idian/widgets/RowList.cpp @@ -15,42 +15,47 @@ along with this program. If not, see . ******************************************************************************/ -#include - -#include +#include #include -#include +#include -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(); - 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();