From 4a13dbe3bb22ba47cf1ea5dfe0e9ffc9688b048a Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Jul 2022 22:23:41 -0300 Subject: [PATCH] feat: create delegate for project item views This allows us to define custom painting for list view items. In particular, this is applied to the mod downloader, in order to allow displaying both the mod name and mod description, and settings their effects (like bold or underline) independent of each other. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/ui/pages/modplatform/ModModel.cpp | 23 +++---- launcher/ui/pages/modplatform/ModModel.h | 1 - launcher/ui/pages/modplatform/ModPage.cpp | 3 + launcher/ui/widgets/Common.cpp | 22 +++--- launcher/ui/widgets/Common.h | 9 ++- launcher/ui/widgets/ProjectItem.cpp | 78 ++++++++++++++++++++++ launcher/ui/widgets/ProjectItem.h | 25 +++++++ 8 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 launcher/ui/widgets/ProjectItem.cpp create mode 100644 launcher/ui/widgets/ProjectItem.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 9c5c2ea88..492a4b9d0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -884,6 +884,8 @@ SET(LAUNCHER_SOURCES ui/widgets/PageContainer.cpp ui/widgets/PageContainer.h ui/widgets/PageContainer_p.h + ui/widgets/ProjectItem.h + ui/widgets/ProjectItem.cpp ui/widgets/VersionListView.cpp ui/widgets/VersionListView.h ui/widgets/VersionSelectWidget.cpp diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 94b1f0993..5861fcc62 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -6,6 +6,8 @@ #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" +#include "ui/widgets/ProjectItem.h" + #include namespace ModPlatform { @@ -39,9 +41,6 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant ModPlatform::IndexedPack pack = modpacks.at(pos); switch (role) { - case Qt::DisplayRole: { - return pack.name; - } case Qt::ToolTipRole: { if (pack.description.length() > 100) { // some magic to prevent to long tooltips and replace html linebreaks @@ -64,20 +63,20 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); return icon; } + case Qt::SizeHintRole: + return QSize(0, 58); case Qt::UserRole: { QVariant v; v.setValue(pack); return v; } - case Qt::FontRole: { - QFont font; - if (m_parent->getDialog()->isModSelected(pack.name)) { - font.setBold(true); - font.setUnderline(true); - } - - return font; - } + // Custom data + case UserDataTypes::TITLE: + return pack.name; + case UserDataTypes::DESCRIPTION: + return pack.description; + case UserDataTypes::SELECTED: + return m_parent->getDialog()->isModSelected(pack.name); default: break; } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index dd22407cb..de864df54 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -2,7 +2,6 @@ #include -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" #include "net/NetJob.h" diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 200fe59ec..b7fe0ffa0 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -43,6 +43,7 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" +#include "ui/widgets/ProjectItem.h" ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) : QWidget(dialog) @@ -71,6 +72,8 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{ ui->searchButton->setStyleSheet("text-decoration: none"); }); + + ui->packView->setItemDelegate(new ProjectItemDelegate(this)); } ModPage::~ModPage() diff --git a/launcher/ui/widgets/Common.cpp b/launcher/ui/widgets/Common.cpp index f72f35967..097bb6d41 100644 --- a/launcher/ui/widgets/Common.cpp +++ b/launcher/ui/widgets/Common.cpp @@ -1,27 +1,33 @@ #include "Common.h" // Origin: Qt -QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) +// More specifically, this is a trimmed down version on the algorithm in: +// https://code.woboq.org/qt5/qtbase/src/widgets/styles/qcommonstyle.cpp.html#846 +QList> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height) { - QStringList lines; + QList> lines; height = 0; - widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); - while (true) - { + while (true) { QTextLine line = textLayout.createLine(); + if (!line.isValid()) break; if (line.textLength() == 0) break; + line.setLineWidth(lineWidth); line.setPosition(QPointF(0, height)); + height += line.height(); - lines.append(str.mid(line.textStart(), line.textLength())); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); + + lines.append(std::make_pair(line.naturalTextWidth(), str.mid(line.textStart(), line.textLength()))); } + textLayout.endLayout(); + return lines; } diff --git a/launcher/ui/widgets/Common.h b/launcher/ui/widgets/Common.h index b3fbe1a08..b3dd5ca8c 100644 --- a/launcher/ui/widgets/Common.h +++ b/launcher/ui/widgets/Common.h @@ -1,6 +1,9 @@ #pragma once -#include + #include -QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed); \ No newline at end of file +/** Cuts out the text in textLayout into smaller pieces, according to the lineWidth. + * Returns a list of pairs, each containing the width of that line and that line's string, respectively. + * The total height of those lines is set in the last argument, 'height'. + */ +QList> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height); diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp new file mode 100644 index 000000000..56ae35fb3 --- /dev/null +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -0,0 +1,78 @@ +#include "ProjectItem.h" + +#include "Common.h" + +#include +#include + +ProjectItemDelegate::ProjectItemDelegate(QWidget* parent) : QStyledItemDelegate(parent) {} + +void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + painter->save(); + + QStyleOptionViewItem opt(option); + initStyleOption(&opt, index); + + auto& rect = opt.rect; + auto icon_width = rect.height(), icon_height = rect.height(); + auto remaining_width = rect.width() - icon_width; + + if (opt.state & QStyle::State_Selected) { + painter->fillRect(rect, opt.palette.highlight()); + painter->setPen(opt.palette.highlightedText().color()); + } else if (opt.state & QStyle::State_MouseOver) { + painter->fillRect(rect, opt.palette.window()); + } + + { // Icon painting + // Square-sized, occupying the left portion + opt.icon.paint(painter, rect.x(), rect.y(), icon_width, icon_height); + } + + { // Title painting + auto title = index.data(UserDataTypes::TITLE).toString(); + + painter->save(); + + auto font = opt.font; + if (index.data(UserDataTypes::SELECTED).toBool()) { + // Set nice font + font.setBold(true); + font.setUnderline(true); + } + + font.setPointSize(font.pointSize() + 2); + painter->setFont(font); + + // On the top, aligned to the left after the icon + painter->drawText(rect.x() + icon_width, rect.y() + QFontMetrics(font).height(), title); + + painter->restore(); + } + + { // Description painting + auto description = index.data(UserDataTypes::DESCRIPTION).toString(); + + QTextLayout text_layout(description, opt.font); + + qreal height = 0; + auto cut_text = viewItemTextLayout(text_layout, remaining_width, height); + + // Get first line unconditionally + description = cut_text.first().second; + // Get second line, elided if needed + if (cut_text.size() > 1) { + if (cut_text.size() > 2) + description += opt.fontMetrics.elidedText(cut_text.at(1).second, opt.textElideMode, cut_text.at(1).first); + else + description += cut_text.at(1).second; + } + + // On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare) + painter->drawText(rect.x() + icon_width, rect.y() + rect.height() - 2.2 * opt.fontMetrics.height(), remaining_width, + 2 * opt.fontMetrics.height(), Qt::TextWordWrap, description); + } + + painter->restore(); +} diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h new file mode 100644 index 000000000..f668edf66 --- /dev/null +++ b/launcher/ui/widgets/ProjectItem.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +/* Custom data types for our custom list models :) */ +enum UserDataTypes { + TITLE = 257, // QString + DESCRIPTION = 258, // QString + SELECTED = 259 // bool +}; + +/** This is an item delegate composed of: + * - An Icon on the left + * - A title + * - A description + * */ +class ProjectItemDelegate final : public QStyledItemDelegate { + Q_OBJECT + + public: + ProjectItemDelegate(QWidget* parent); + + void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; + +};