From 0ecea8ad838de7039b3f74e3982d5d1ed9316f3b Mon Sep 17 00:00:00 2001 From: fourtf Date: Thu, 22 Oct 2020 23:17:31 +0200 Subject: [PATCH] added category navigation to general settings --- chatterino.pro | 3 + resources/qss/settings.qss | 3 - src/util/LayoutHelper.hpp | 17 +- src/widgets/helper/SignalLabel.cpp | 4 +- src/widgets/helper/SignalLabel.hpp | 4 +- src/widgets/settingspages/GeneralPage.cpp | 228 +-------------- src/widgets/settingspages/GeneralPage.hpp | 176 +----------- src/widgets/settingspages/GeneralPageView.cpp | 270 ++++++++++++++++++ src/widgets/settingspages/GeneralPageView.hpp | 204 +++++++++++++ 9 files changed, 512 insertions(+), 397 deletions(-) create mode 100644 src/widgets/settingspages/GeneralPageView.cpp create mode 100644 src/widgets/settingspages/GeneralPageView.hpp diff --git a/chatterino.pro b/chatterino.pro index dd4a2a670..af25e7a36 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -224,6 +224,7 @@ SOURCES += \ src/util/IncognitoBrowser.cpp \ src/util/InitUpdateButton.cpp \ src/util/JsonQuery.cpp \ + src/util/LayoutHelper.cpp \ src/util/NuulsUploader.cpp \ src/util/RapidjsonHelpers.cpp \ src/util/StreamerMode.cpp \ @@ -283,6 +284,7 @@ SOURCES += \ src/widgets/settingspages/ExternalToolsPage.cpp \ src/widgets/settingspages/FiltersPage.cpp \ src/widgets/settingspages/GeneralPage.cpp \ + src/widgets/settingspages/GeneralPageView.cpp \ src/widgets/settingspages/HighlightingPage.cpp \ src/widgets/settingspages/IgnoresPage.cpp \ src/widgets/settingspages/KeyboardSettingsPage.cpp \ @@ -534,6 +536,7 @@ HEADERS += \ src/widgets/settingspages/ExternalToolsPage.hpp \ src/widgets/settingspages/FiltersPage.hpp \ src/widgets/settingspages/GeneralPage.hpp \ + src/widgets/settingspages/GeneralPageView.hpp \ src/widgets/settingspages/HighlightingPage.hpp \ src/widgets/settingspages/IgnoresPage.hpp \ src/widgets/settingspages/KeyboardSettingsPage.hpp \ diff --git a/resources/qss/settings.qss b/resources/qss/settings.qss index 5801dd980..3643d7b3a 100644 --- a/resources/qss/settings.qss +++ b/resources/qss/settings.qss @@ -16,7 +16,6 @@ QScrollArea { } QScrollArea QFrame { - background: #222; border: none; } @@ -27,8 +26,6 @@ QComboBox QFrame { chatterino--SettingsPage { background: #222; - /*border: 1px solid #555; - border-left: none;*/ } chatterino--PageHeader { diff --git a/src/util/LayoutHelper.hpp b/src/util/LayoutHelper.hpp index 11157f0fa..9d998681d 100644 --- a/src/util/LayoutHelper.hpp +++ b/src/util/LayoutHelper.hpp @@ -1,13 +1,18 @@ #pragma once #include -#include #include +class QWidget; +class QScrollArea; + namespace chatterino { using LayoutItem = boost::variant; +QWidget *wrapLayout(QLayout *layout); +QScrollArea *makeScrollArea(LayoutItem item); + template T *makeLayout(std::initializer_list items) { @@ -26,9 +31,19 @@ T *makeLayout(std::initializer_list items) } } + t->setContentsMargins(0, 0, 0, 0); + return t; } +template +T *makeStretchingLayout(std::initializer_list items) +{ + auto layout = makeLayout(items); + layout->addStretch(1); + return layout; +} + template T *makeWidget(With with) { diff --git a/src/widgets/helper/SignalLabel.cpp b/src/widgets/helper/SignalLabel.cpp index 46fd0d80c..4fa24ab15 100644 --- a/src/widgets/helper/SignalLabel.cpp +++ b/src/widgets/helper/SignalLabel.cpp @@ -16,7 +16,7 @@ void SignalLabel::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - emit mouseDown(); + emit leftMouseDown(); } event->ignore(); @@ -26,7 +26,7 @@ void SignalLabel::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - emit mouseUp(); + emit leftMouseUp(); } event->ignore(); diff --git a/src/widgets/helper/SignalLabel.hpp b/src/widgets/helper/SignalLabel.hpp index 382840265..48c3af323 100644 --- a/src/widgets/helper/SignalLabel.hpp +++ b/src/widgets/helper/SignalLabel.hpp @@ -18,8 +18,8 @@ public: signals: void mouseDoubleClick(QMouseEvent *ev); - void mouseDown(); - void mouseUp(); + void leftMouseDown(); + void leftMouseUp(); void mouseMove(QMouseEvent *event); protected: diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index cfc97806b..b9ca01b74 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -15,9 +15,8 @@ #include "util/IncognitoBrowser.hpp" #include "util/StreamerMode.hpp" #include "widgets/BaseWindow.hpp" -#include "widgets/dialogs/ColorPickerDialog.hpp" -#include "widgets/helper/ColorButton.hpp" #include "widgets/helper/Line.hpp" +#include "widgets/settingspages/GeneralPageView.hpp" #define CHROME_EXTENSION_LINK \ "https://chrome.google.com/webstore/detail/chatterino-native-host/" \ @@ -36,7 +35,7 @@ namespace chatterino { namespace { - void addKeyboardModifierSetting(SettingsLayout &layout, + void addKeyboardModifierSetting(GeneralPageView &layout, const QString &title, EnumSetting &setting) { @@ -76,239 +75,32 @@ namespace { } } // namespace -TitleLabel *SettingsLayout::addTitle(const QString &title) -{ - // space - if (!this->groups_.empty()) - this->addWidget(this->groups_.back().space = new Space); - - // title - auto label = new TitleLabel(title + ":"); - this->addWidget(label); - - // groups - this->groups_.push_back(Group{title, label, nullptr, {}}); - - return label; -} - -QCheckBox *SettingsLayout::addCheckbox(const QString &text, - BoolSetting &setting, bool inverse) -{ - auto check = new QCheckBox(text); - - // update when setting changes - setting.connect( - [inverse, check](const bool &value, auto) { - check->setChecked(inverse ^ value); - }, - this->managedConnections_); - - // update setting on toggle - QObject::connect( - check, &QCheckBox::toggled, this, - [&setting, inverse](bool state) { setting = inverse ^ state; }); - - this->addWidget(check); - - // groups - this->groups_.back().widgets.push_back({check, {text}}); - - return check; -} - -ComboBox *SettingsLayout::addDropdown(const QString &text, - const QStringList &list) -{ - auto layout = new QHBoxLayout; - auto combo = new ComboBox; - combo->setFocusPolicy(Qt::StrongFocus); - combo->addItems(list); - - auto label = new QLabel(text + ":"); - layout->addWidget(label); - layout->addStretch(1); - layout->addWidget(combo); - - this->addLayout(layout); - - // groups - this->groups_.back().widgets.push_back({combo, {text}}); - this->groups_.back().widgets.push_back({label, {text}}); - - return combo; -} - -ComboBox *SettingsLayout::addDropdown( - const QString &text, const QStringList &items, - pajlada::Settings::Setting &setting, bool editable) -{ - auto combo = this->addDropdown(text, items); - - if (editable) - combo->setEditable(true); - - // update when setting changes - setting.connect( - [combo](const QString &value, auto) { combo->setCurrentText(value); }, - this->managedConnections_); - - QObject::connect(combo, &QComboBox::currentTextChanged, - [&setting](const QString &newValue) { - setting = newValue; - getApp()->windows->forceLayoutChannelViews(); - }); - - return combo; -} - -ColorButton *SettingsLayout::addColorButton( - const QString &text, const QColor &color, - pajlada::Settings::Setting &setting) -{ - auto colorButton = new ColorButton(color); - auto layout = new QHBoxLayout(); - auto label = new QLabel(text + ":"); - layout->addWidget(label); - layout->addStretch(1); - layout->addWidget(colorButton); - this->addLayout(layout); - QObject::connect( - colorButton, &ColorButton::clicked, [&setting, colorButton]() { - auto dialog = new ColorPickerDialog(QColor(setting)); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); - dialog->closed.connect([&setting, colorButton, &dialog] { - QColor selected = dialog->selectedColor(); - - if (selected.isValid()) - { - setting = selected.name(QColor::HexArgb); - colorButton->setColor(selected); - } - }); - }); - - this->groups_.back().widgets.push_back({label, {text}}); - this->groups_.back().widgets.push_back({colorButton, {text}}); - - return colorButton; -} - -DescriptionLabel *SettingsLayout::addDescription(const QString &text) -{ - auto label = new DescriptionLabel(text); - - label->setTextInteractionFlags(Qt::TextBrowserInteraction | - Qt::LinksAccessibleByKeyboard); - label->setOpenExternalLinks(true); - label->setWordWrap(true); - - this->addWidget(label); - - // groups - this->groups_.back().widgets.push_back({label, {text}}); - - return label; -} - -void SettingsLayout::addSeperator() -{ - this->addWidget(new Line(false)); -} - -bool SettingsLayout::filterElements(const QString &query) -{ - bool any{}; - - for (auto &&group : this->groups_) - { - // if a description in a group matches `query` then show the entire group - bool descriptionMatches{}; - for (auto &&widget : group.widgets) - { - if (auto x = dynamic_cast(widget.element); x) - { - if (x->text().contains(query, Qt::CaseInsensitive)) - { - descriptionMatches = true; - break; - } - } - } - - // if group name matches then all should be visible - if (group.name.contains(query, Qt::CaseInsensitive) || - descriptionMatches) - { - for (auto &&widget : group.widgets) - widget.element->show(); - group.title->show(); - any = true; - } - // check if any match - else - { - auto groupAny = false; - - for (auto &&widget : group.widgets) - { - for (auto &&keyword : widget.keywords) - { - if (keyword.contains(query, Qt::CaseInsensitive)) - { - widget.element->show(); - groupAny = true; - } - else - { - widget.element->hide(); - } - } - } - - if (group.space) - group.space->setVisible(groupAny); - group.title->setVisible(groupAny); - any |= groupAny; - } - } - - return any; -} - GeneralPage::GeneralPage() { auto y = new QVBoxLayout; - auto scroll = new QScrollArea; - scroll->setWidgetResizable(true); - y->addWidget(scroll); auto x = new QHBoxLayout; - auto layout = new SettingsLayout; - this->settingsLayout_ = layout; - x->addLayout(layout, 0); - x->addStretch(1); + auto view = new GeneralPageView; + this->view_ = view; + x->addWidget(view); auto z = new QFrame; z->setLayout(x); - scroll->setWidget(z); + y->addWidget(z); this->setLayout(y); - this->initLayout(*layout); - - layout->addStretch(1); + this->initLayout(*view); this->initExtra(); } bool GeneralPage::filterElements(const QString &query) { - if (this->settingsLayout_) - return this->settingsLayout_->filterElements(query) || query.isEmpty(); + if (this->view_) + return this->view_->filterElements(query) || query.isEmpty(); else return false; } -void GeneralPage::initLayout(SettingsLayout &layout) +void GeneralPage::initLayout(GeneralPageView &layout) { auto &s = *getSettings(); diff --git a/src/widgets/settingspages/GeneralPage.hpp b/src/widgets/settingspages/GeneralPage.hpp index a74dbb823..53468d37f 100644 --- a/src/widgets/settingspages/GeneralPage.hpp +++ b/src/widgets/settingspages/GeneralPage.hpp @@ -1,15 +1,5 @@ #pragma once -#include -#include -#include - -#include "Application.hpp" -#include "boost/variant.hpp" -#include "pajlada/signals/signal.hpp" -#include "singletons/Settings.hpp" -#include "singletons/WindowManager.hpp" -#include "widgets/helper/ColorButton.hpp" #include "widgets/settingspages/SettingsPage.hpp" class QLabel; @@ -18,165 +8,9 @@ class QComboBox; namespace chatterino { -class Space : public QLabel -{ - Q_OBJECT -}; - -class TitleLabel : public QLabel -{ - Q_OBJECT - -public: - TitleLabel(const QString &text) - : QLabel(text) - { - } -}; - -class DescriptionLabel : public QLabel -{ - Q_OBJECT - -public: - DescriptionLabel(const QString &text) - : QLabel(text) - { - } -}; - -struct DropdownArgs { - QString value; - int index; - QComboBox *combobox; -}; - -class ComboBox : public QComboBox -{ - Q_OBJECT - - void wheelEvent(QWheelEvent *event) override - { - } -}; - -class SettingsLayout : public QVBoxLayout -{ - Q_OBJECT - -public: - TitleLabel *addTitle(const QString &text); - /// @param inverse Inverses true to false and vice versa - QCheckBox *addCheckbox(const QString &text, BoolSetting &setting, - bool inverse = false); - ComboBox *addDropdown(const QString &text, const QStringList &items); - ComboBox *addDropdown(const QString &text, const QStringList &items, - pajlada::Settings::Setting &setting, - bool editable = false); - ColorButton *addColorButton(const QString &text, const QColor &color, - pajlada::Settings::Setting &setting); - - template - QPushButton *makeButton(const QString &text, OnClick onClick) - { - auto button = new QPushButton(text); - this->groups_.back().widgets.push_back({button, {text}}); - QObject::connect(button, &QPushButton::clicked, onClick); - return button; - } - - template - QPushButton *addButton(const QString &text, OnClick onClick) - { - auto button = makeButton(text, onClick); - auto layout = new QHBoxLayout(); - layout->addWidget(button); - layout->addStretch(1); - this->addLayout(layout); - return button; - } - - template - ComboBox *addDropdown( - const QString &text, const QStringList &items, - pajlada::Settings::Setting &setting, - std::function(T)> getValue, - std::function setValue, bool editable = true) - { - auto items2 = items; - auto selected = getValue(setting.getValue()); - - if (selected.which() == 1) - { - // QString - if (!editable && !items2.contains(boost::get(selected))) - items2.insert(0, boost::get(selected)); - } - - auto combo = this->addDropdown(text, items2); - if (editable) - combo->setEditable(true); - - if (selected.which() == 0) - { - // int - auto value = boost::get(selected); - if (value >= 0 && value < items2.size()) - combo->setCurrentIndex(value); - } - else if (selected.which() == 1) - { - // QString - combo->setEditText(boost::get(selected)); - } - - setting.connect( - [getValue = std::move(getValue), combo](const T &value, auto) { - auto var = getValue(value); - if (var.which() == 0) - combo->setCurrentIndex(boost::get(var)); - else - { - combo->setCurrentText(boost::get(var)); - combo->setEditText(boost::get(var)); - } - }, - this->managedConnections_); - - QObject::connect( - combo, - QOverload::of(&QComboBox::currentIndexChanged), - // &QComboBox::editTextChanged, - [combo, &setting, - setValue = std::move(setValue)](const QString &newValue) { - setting = setValue( - DropdownArgs{newValue, combo->currentIndex(), combo}); - getApp()->windows->forceLayoutChannelViews(); - }); - - return combo; - } - DescriptionLabel *addDescription(const QString &text); - - void addSeperator(); - bool filterElements(const QString &query); - -private: - struct Widget { - QWidget *element; - QStringList keywords; - }; - - struct Group { - QString name; - QWidget *title{}; - Space *space{}; - std::vector widgets; - }; - - std::vector groups_; - std::vector managedConnections_; -}; +class GeneralPageView; +class DescriptionLabel; +struct DropdownArgs; class GeneralPage : public SettingsPage { @@ -188,13 +22,13 @@ public: bool filterElements(const QString &query); private: - void initLayout(SettingsLayout &layout); + void initLayout(GeneralPageView &layout); void initExtra(); QString getFont(const DropdownArgs &args) const; DescriptionLabel *cachePath_{}; - SettingsLayout *settingsLayout_{}; + GeneralPageView *view_{}; }; } // namespace chatterino diff --git a/src/widgets/settingspages/GeneralPageView.cpp b/src/widgets/settingspages/GeneralPageView.cpp new file mode 100644 index 000000000..e38d8b9f7 --- /dev/null +++ b/src/widgets/settingspages/GeneralPageView.cpp @@ -0,0 +1,270 @@ +#include "GeneralPageView.hpp" +#include +#include "singletons/Settings.hpp" +#include "singletons/WindowManager.hpp" +#include "util/LayoutHelper.hpp" +#include "widgets/dialogs/ColorPickerDialog.hpp" +#include "widgets/helper/ColorButton.hpp" +#include "widgets/helper/Line.hpp" + +namespace chatterino { + +GeneralPageView::GeneralPageView(QWidget *parent) + : QWidget(parent) +{ + auto scrollArea = this->contentScrollArea_ = + makeScrollArea(this->contentLayout_ = new QVBoxLayout); + + auto navigation = + wrapLayout(this->navigationLayout_ = makeLayout({})); + + this->setLayout(makeLayout({scrollArea, navigation})); + + QObject::connect(scrollArea->verticalScrollBar(), &QScrollBar::valueChanged, + this, [=] { this->updateNavigationHighlighting(); }); +} + +void GeneralPageView::addWidget(QWidget *widget) +{ + this->contentLayout_->addWidget(widget); +} + +void GeneralPageView::addLayout(QLayout *layout) +{ + this->contentLayout_->addLayout(layout); +} + +TitleLabel *GeneralPageView::addTitle(const QString &title) +{ + // space + if (!this->groups_.empty()) + this->addWidget(this->groups_.back().space = new Space); + + // title + auto label = new TitleLabel(title + ":"); + this->addWidget(label); + + // navigation item + auto navLabel = new NavigationLabel(title); + navLabel->setCursor(Qt::PointingHandCursor); + this->navigationLayout_->addWidget(navLabel); + + QObject::connect(navLabel, &NavigationLabel::leftMouseUp, label, [=] { + this->contentScrollArea_->verticalScrollBar()->setValue(label->y()); + }); + + // groups + this->groups_.push_back(Group{title, label, navLabel, nullptr, {}}); + + if (this->groups_.size() == 1) + this->updateNavigationHighlighting(); + + return label; +} + +QCheckBox *GeneralPageView::addCheckbox(const QString &text, + BoolSetting &setting, bool inverse) +{ + auto check = new QCheckBox(text); + + // update when setting changes + setting.connect( + [inverse, check](const bool &value, auto) { + check->setChecked(inverse ^ value); + }, + this->managedConnections_); + + // update setting on toggle + QObject::connect( + check, &QCheckBox::toggled, this, + [&setting, inverse](bool state) { setting = inverse ^ state; }); + + this->addWidget(check); + + // groups + this->groups_.back().widgets.push_back({check, {text}}); + + return check; +} + +ComboBox *GeneralPageView::addDropdown(const QString &text, + const QStringList &list) +{ + auto layout = new QHBoxLayout; + auto combo = new ComboBox; + combo->setFocusPolicy(Qt::StrongFocus); + combo->addItems(list); + + auto label = new QLabel(text + ":"); + layout->addWidget(label); + layout->addStretch(1); + layout->addWidget(combo); + + this->addLayout(layout); + + // groups + this->groups_.back().widgets.push_back({combo, {text}}); + this->groups_.back().widgets.push_back({label, {text}}); + + return combo; +} + +ComboBox *GeneralPageView::addDropdown( + const QString &text, const QStringList &items, + pajlada::Settings::Setting &setting, bool editable) +{ + auto combo = this->addDropdown(text, items); + + if (editable) + combo->setEditable(true); + + // update when setting changes + setting.connect( + [combo](const QString &value, auto) { combo->setCurrentText(value); }, + this->managedConnections_); + + QObject::connect(combo, &QComboBox::currentTextChanged, + [&setting](const QString &newValue) { + setting = newValue; + getApp()->windows->forceLayoutChannelViews(); + }); + + return combo; +} + +ColorButton *GeneralPageView::addColorButton( + const QString &text, const QColor &color, + pajlada::Settings::Setting &setting) +{ + auto colorButton = new ColorButton(color); + auto layout = new QHBoxLayout(); + auto label = new QLabel(text + ":"); + layout->addWidget(label); + layout->addStretch(1); + layout->addWidget(colorButton); + this->addLayout(layout); + QObject::connect( + colorButton, &ColorButton::clicked, [&setting, colorButton]() { + auto dialog = new ColorPickerDialog(QColor(setting)); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); + dialog->closed.connect([&setting, colorButton, &dialog] { + QColor selected = dialog->selectedColor(); + + if (selected.isValid()) + { + setting = selected.name(QColor::HexArgb); + colorButton->setColor(selected); + } + }); + }); + + this->groups_.back().widgets.push_back({label, {text}}); + this->groups_.back().widgets.push_back({colorButton, {text}}); + + return colorButton; +} + +DescriptionLabel *GeneralPageView::addDescription(const QString &text) +{ + auto label = new DescriptionLabel(text); + + label->setTextInteractionFlags(Qt::TextBrowserInteraction | + Qt::LinksAccessibleByKeyboard); + label->setOpenExternalLinks(true); + label->setWordWrap(true); + + this->addWidget(label); + + // groups + this->groups_.back().widgets.push_back({label, {text}}); + + return label; +} + +void GeneralPageView::addSeperator() +{ + this->addWidget(new Line(false)); +} + +bool GeneralPageView::filterElements(const QString &query) +{ + bool any{}; + + for (auto &&group : this->groups_) + { + // if a description in a group matches `query` then show the entire group + bool descriptionMatches{}; + for (auto &&widget : group.widgets) + { + if (auto x = dynamic_cast(widget.element); x) + { + if (x->text().contains(query, Qt::CaseInsensitive)) + { + descriptionMatches = true; + break; + } + } + } + + // if group name matches then all should be visible + if (group.name.contains(query, Qt::CaseInsensitive) || + descriptionMatches) + { + for (auto &&widget : group.widgets) + widget.element->show(); + group.title->show(); + any = true; + } + // check if any match + else + { + auto groupAny = false; + + for (auto &&widget : group.widgets) + { + for (auto &&keyword : widget.keywords) + { + if (keyword.contains(query, Qt::CaseInsensitive)) + { + widget.element->show(); + groupAny = true; + } + else + { + widget.element->hide(); + } + } + } + + if (group.space) + group.space->setVisible(groupAny); + group.title->setVisible(groupAny); + any |= groupAny; + } + } + + return any; +} + +void GeneralPageView::updateNavigationHighlighting() +{ + auto scrollY = this->contentScrollArea_->verticalScrollBar()->value(); + auto first = true; + + for (auto &&group : this->groups_) + { + if (first && (group.title->geometry().bottom() > scrollY || + &group == &this->groups_.back())) + { + first = false; + group.navigationLink->setStyleSheet("color: #4FC3F7"); + } + else + { + group.navigationLink->setStyleSheet(""); + } + } +} + +} // namespace chatterino diff --git a/src/widgets/settingspages/GeneralPageView.hpp b/src/widgets/settingspages/GeneralPageView.hpp new file mode 100644 index 000000000..680374709 --- /dev/null +++ b/src/widgets/settingspages/GeneralPageView.hpp @@ -0,0 +1,204 @@ +#pragma once + +#include +#include +#include "common/ChatterinoSetting.hpp" +#include "widgets/helper/SignalLabel.hpp" + +class QScrollArea; + +namespace chatterino { + +class ColorButton; + +class Space : public QLabel +{ + Q_OBJECT +}; + +class TitleLabel : public QLabel +{ + Q_OBJECT + +public: + TitleLabel(const QString &text) + : QLabel(text) + { + } +}; + +class NavigationLabel : public SignalLabel +{ + Q_OBJECT + +public: + NavigationLabel(const QString &text) + : SignalLabel() + { + this->setText(text); + } +}; + +class DescriptionLabel : public QLabel +{ + Q_OBJECT + +public: + DescriptionLabel(const QString &text) + : QLabel(text) + { + } +}; + +class ComboBox : public QComboBox +{ + Q_OBJECT + + void wheelEvent(QWheelEvent *event) override + { + } +}; + +struct DropdownArgs { + QString value; + int index; + QComboBox *combobox; +}; + +class GeneralPageView : public QWidget +{ + Q_OBJECT + +public: + GeneralPageView(QWidget *parent = nullptr); + + void addWidget(QWidget *widget); + void addLayout(QLayout *layout); + + TitleLabel *addTitle(const QString &text); + /// @param inverse Inverses true to false and vice versa + QCheckBox *addCheckbox(const QString &text, BoolSetting &setting, + bool inverse = false); + ComboBox *addDropdown(const QString &text, const QStringList &items); + ComboBox *addDropdown(const QString &text, const QStringList &items, + pajlada::Settings::Setting &setting, + bool editable = false); + ColorButton *addColorButton(const QString &text, const QColor &color, + pajlada::Settings::Setting &setting); + + template + QPushButton *makeButton(const QString &text, OnClick onClick) + { + auto button = new QPushButton(text); + this->groups_.back().widgets.push_back({button, {text}}); + QObject::connect(button, &QPushButton::clicked, onClick); + return button; + } + + template + QPushButton *addButton(const QString &text, OnClick onClick) + { + auto button = makeButton(text, onClick); + auto layout = new QHBoxLayout(); + layout->addWidget(button); + layout->addStretch(1); + this->addLayout(layout); + return button; + } + + template + ComboBox *addDropdown( + const QString &text, const QStringList &items, + pajlada::Settings::Setting &setting, + std::function(T)> getValue, + std::function setValue, bool editable = true) + { + auto items2 = items; + auto selected = getValue(setting.getValue()); + + if (selected.which() == 1) + { + // QString + if (!editable && !items2.contains(boost::get(selected))) + items2.insert(0, boost::get(selected)); + } + + auto combo = this->addDropdown(text, items2); + if (editable) + combo->setEditable(true); + + if (selected.which() == 0) + { + // int + auto value = boost::get(selected); + if (value >= 0 && value < items2.size()) + combo->setCurrentIndex(value); + } + else if (selected.which() == 1) + { + // QString + combo->setEditText(boost::get(selected)); + } + + setting.connect( + [getValue = std::move(getValue), combo](const T &value, auto) { + auto var = getValue(value); + if (var.which() == 0) + combo->setCurrentIndex(boost::get(var)); + else + { + combo->setCurrentText(boost::get(var)); + combo->setEditText(boost::get(var)); + } + }, + this->managedConnections_); + + QObject::connect( + combo, + QOverload::of(&QComboBox::currentIndexChanged), + // &QComboBox::editTextChanged, + [combo, &setting, + setValue = std::move(setValue)](const QString &newValue) { + setting = setValue( + DropdownArgs{newValue, combo->currentIndex(), combo}); + getApp()->windows->forceLayoutChannelViews(); + }); + + return combo; + } + DescriptionLabel *addDescription(const QString &text); + + void addSeperator(); + bool filterElements(const QString &query); + +protected: + void resizeEvent(QResizeEvent *ev) override + { + qDebug() << ev->size(); + } + +private: + void updateNavigationHighlighting(); + + struct Widget { + QWidget *element; + QStringList keywords; + }; + + struct Group { + QString name; + QWidget *title{}; + QWidget *navigationLink{}; + Space *space{}; + std::vector widgets; + }; + + QScrollArea *contentScrollArea_; + QVBoxLayout *contentLayout_; + QVBoxLayout *navigationLayout_; + + std::vector groups_; + std::vector managedConnections_; +}; + +} // namespace chatterino