From 74308fcaa5cfc97a291710316f0f15b4b22fee2d Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 24 Nov 2025 15:23:18 +0100 Subject: [PATCH 1/4] add category selector to icon picker dialog it uses some regex shenanigans for this, probably not ideal, idk if theres a good way to filter the icons without adding extra metadata or storing them in subfolders Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 50 +++++++++++++++++--- launcher/ui/dialogs/IconPickerDialog.h | 9 ++++ launcher/ui/dialogs/IconPickerDialog.ui | 59 +++++++++++++++++++++++- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index b56e95dba..dfeea20a1 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -35,9 +35,25 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui ui->setupUi(this); setWindowModality(Qt::WindowModal); - searchBar = new QLineEdit(this); - searchBar->setPlaceholderText(tr("Search...")); - ui->verticalLayout->insertWidget(0, searchBar); + static const QString context_text[] = { + tr("All"), + tr("Modern"), + tr("Legacy"), + tr("Modpacks"), + }; + static const Context context_id[] = { + Any, + Modern, + Legacy, + Modpacks, + }; + const int cnt = sizeof(context_text) / sizeof(context_text[0]); + for (int i = 0; i < cnt; ++i) { + ui->contextCombo->addItem(context_text[i], context_id[i]); + if (i == 0) { + ui->contextCombo->insertSeparator(i + 1); + } + } proxyModel = new QSortFilterProxyModel(this); proxyModel->setSourceModel(APPLICATION->icons()); @@ -45,11 +61,8 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui ui->iconView->setModel(proxyModel); auto contentsWidget = ui->iconView; - contentsWidget->setViewMode(QListView::IconMode); contentsWidget->setFlow(QListView::LeftToRight); contentsWidget->setIconSize(QSize(48, 48)); - contentsWidget->setMovement(QListView::Static); - contentsWidget->setResizeMode(QListView::Adjust); contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); contentsWidget->setSpacing(5); contentsWidget->setWordWrap(false); @@ -86,7 +99,11 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); - connect(searchBar, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); + connect(ui->searchLine, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); + connect(ui->contextCombo, &QComboBox::currentIndexChanged, this, [this](int index) { + Context category = static_cast(ui->contextCombo->itemData(index).toInt()); + filterIconsByCategory(category); + }); // Prevent incorrect indices from e.g. filesystem changes connect(APPLICATION->icons(), &IconList::iconUpdated, this, [this]() { proxyModel->invalidate(); }); } @@ -182,3 +199,22 @@ void IconPickerDialog::filterIcons(const QString& query) { proxyModel->setFilterFixedString(query); } + +void IconPickerDialog::filterIconsByCategory(Context category) +{ + switch (category) { + default: + case Any: + proxyModel->setFilterRegularExpression(""); + break; + case Modern: + proxyModel->setFilterRegularExpression("^(?:ftb_logo|(?!.*_legacy$)(?!^(?:curseforge_|modrinth_|ftb_|technic_|atl_))[A-Za-z0-9._-]+)$"); + break; + case Legacy: + proxyModel->setFilterRegularExpression("^(?:[A-Za-z0-9._-]+_legacy|ftb_glow)$"); + break; + case Modpacks: + proxyModel->setFilterRegularExpression("^(?!(?:ftb_glow|ftb_logo))(?:curseforge_|modrinth_|ftb_|technic_|atl_)[A-Za-z0-9._-]*$"); + break; + } +} diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index db1315338..58a94bb9b 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -41,6 +41,14 @@ class IconPickerDialog : public QDialog { QLineEdit* searchBar; QSortFilterProxyModel* proxyModel; + enum Context { + Any, + Modern, + Legacy, + Modpacks, + }; + Q_ENUM(Context) + private slots: void selectionChanged(QItemSelection, QItemSelection); void activated(QModelIndex); @@ -49,4 +57,5 @@ class IconPickerDialog : public QDialog { void removeSelectedIcon(); void openFolder(); void filterIcons(const QString& text); + void filterIconsByCategory(Context); }; diff --git a/launcher/ui/dialogs/IconPickerDialog.ui b/launcher/ui/dialogs/IconPickerDialog.ui index c548edfb7..948e7043f 100644 --- a/launcher/ui/dialogs/IconPickerDialog.ui +++ b/launcher/ui/dialogs/IconPickerDialog.ui @@ -15,7 +15,64 @@ - + + + + + + + Icon category + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Search Icons... + + + true + + + + + + + + + + + + 60 + 60 + + + + QListView::Static + + + QListView::Adjust + + + QListView::IconMode + + + true + + From 0c4c8703a363d3a0c8dd1f5095330eb04b50f626 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 24 Nov 2025 18:06:09 +0100 Subject: [PATCH 2/4] rename IconPickerCategory and make public Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 6 +++--- launcher/ui/dialogs/IconPickerDialog.h | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index dfeea20a1..49b396b5e 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -41,7 +41,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui tr("Legacy"), tr("Modpacks"), }; - static const Context context_id[] = { + static const IconPickerCategory context_id[] = { Any, Modern, Legacy, @@ -101,7 +101,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); connect(ui->searchLine, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); connect(ui->contextCombo, &QComboBox::currentIndexChanged, this, [this](int index) { - Context category = static_cast(ui->contextCombo->itemData(index).toInt()); + IconPickerCategory category = static_cast(ui->contextCombo->itemData(index).toInt()); filterIconsByCategory(category); }); // Prevent incorrect indices from e.g. filesystem changes @@ -200,7 +200,7 @@ void IconPickerDialog::filterIcons(const QString& query) proxyModel->setFilterFixedString(query); } -void IconPickerDialog::filterIconsByCategory(Context category) +void IconPickerDialog::filterIconsByCategory(IconPickerCategory category) { switch (category) { default: diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index 58a94bb9b..b2d707ae8 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -32,6 +32,14 @@ class IconPickerDialog : public QDialog { int execWithSelection(QString selection); QString selectedIconKey; + enum IconPickerCategory { + Any, + Modern, + Legacy, + Modpacks, + }; + Q_ENUM(IconPickerCategory) + protected: virtual bool eventFilter(QObject*, QEvent*); @@ -41,14 +49,6 @@ class IconPickerDialog : public QDialog { QLineEdit* searchBar; QSortFilterProxyModel* proxyModel; - enum Context { - Any, - Modern, - Legacy, - Modpacks, - }; - Q_ENUM(Context) - private slots: void selectionChanged(QItemSelection, QItemSelection); void activated(QModelIndex); @@ -57,5 +57,5 @@ class IconPickerDialog : public QDialog { void removeSelectedIcon(); void openFolder(); void filterIcons(const QString& text); - void filterIconsByCategory(Context); + void filterIconsByCategory(IconPickerCategory); }; From bbd8c1e745916bea65343dc1ac1f012902b0f0e1 Mon Sep 17 00:00:00 2001 From: Tayou Date: Tue, 25 Nov 2025 15:27:18 +0100 Subject: [PATCH 3/4] use custom QSortFilterProxyModel impl Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 83 +++++++++++++++++++----- launcher/ui/dialogs/IconPickerDialog.h | 1 + 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index 49b396b5e..051d8495f 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -30,6 +30,71 @@ #include "icons/IconList.h" #include "icons/IconUtils.h" +class IconProxyModel : public QSortFilterProxyModel +{ +public: + explicit IconProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) + { + } + + void setCategory(IconPickerDialog::IconPickerCategory category) + { + if (m_category == category) + return; + m_category = category; + invalidateFilter(); + } + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + if (!QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent)) + return false; + + if (m_category == IconPickerDialog::Any) + return true; + + auto model = static_cast(sourceModel()); + QModelIndex index = model->index(source_row, 0, source_parent); + QString key = model->data(index, Qt::UserRole).toString(); + const MMCIcon* icon = model->icon(key); + + if (!icon) + return false; + + bool isModpack = false; + bool isBuiltin = icon->isBuiltIn(); + bool isLegacy = isBuiltin && icon->name().endsWith("_legacy", Qt::CaseInsensitive); + + if (!isBuiltin) { + const QString& name = icon->name(); + if (name.startsWith("curseforge_", Qt::CaseInsensitive) || + name.startsWith("modrinth_", Qt::CaseInsensitive) || + name.startsWith("ftb_", Qt::CaseInsensitive) || + name.startsWith("technic_", Qt::CaseInsensitive) || + name.startsWith("atl_", Qt::CaseInsensitive)) { + isModpack = true; + } + } + + switch (m_category) { + case IconPickerDialog::Legacy: + return isBuiltin && isLegacy; + case IconPickerDialog::Modpacks: + return isModpack; + case IconPickerDialog::Modern: + return isBuiltin && !isLegacy; + case IconPickerDialog::Custom: + return !isBuiltin && !isModpack; + default: + return true; + } + } + +private: + IconPickerDialog::IconPickerCategory m_category = IconPickerDialog::Any; +}; + IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::IconPickerDialog) { ui->setupUi(this); @@ -40,12 +105,14 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui tr("Modern"), tr("Legacy"), tr("Modpacks"), + tr("Custom"), }; static const IconPickerCategory context_id[] = { Any, Modern, Legacy, Modpacks, + Custom, }; const int cnt = sizeof(context_text) / sizeof(context_text[0]); for (int i = 0; i < cnt; ++i) { @@ -202,19 +269,5 @@ void IconPickerDialog::filterIcons(const QString& query) void IconPickerDialog::filterIconsByCategory(IconPickerCategory category) { - switch (category) { - default: - case Any: - proxyModel->setFilterRegularExpression(""); - break; - case Modern: - proxyModel->setFilterRegularExpression("^(?:ftb_logo|(?!.*_legacy$)(?!^(?:curseforge_|modrinth_|ftb_|technic_|atl_))[A-Za-z0-9._-]+)$"); - break; - case Legacy: - proxyModel->setFilterRegularExpression("^(?:[A-Za-z0-9._-]+_legacy|ftb_glow)$"); - break; - case Modpacks: - proxyModel->setFilterRegularExpression("^(?!(?:ftb_glow|ftb_logo))(?:curseforge_|modrinth_|ftb_|technic_|atl_)[A-Za-z0-9._-]*$"); - break; - } + static_cast(proxyModel)->setCategory(category); } diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index b2d707ae8..063f9b905 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -37,6 +37,7 @@ class IconPickerDialog : public QDialog { Modern, Legacy, Modpacks, + Custom, }; Q_ENUM(IconPickerCategory) From 502a5175e2fc6ca386e4f8e855a2413f6e10dbfe Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 4 Jun 2026 19:07:24 +0200 Subject: [PATCH 4/4] fix category filtering I have no idea how this worked at all when I made the commit originally, but it works now, just as well as it did on the prior commit. Further improvements, using subfolders and other metadata will be in another PR. Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index 051d8495f..b2269cc70 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -122,7 +122,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui } } - proxyModel = new QSortFilterProxyModel(this); + proxyModel = new IconProxyModel(this); proxyModel->setSourceModel(APPLICATION->icons()); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); ui->iconView->setModel(proxyModel);