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 <flowlnlnln@gmail.com>
This commit is contained in:
parent
0f61f5ba03
commit
4a13dbe3bb
@ -884,6 +884,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/widgets/PageContainer.cpp
|
ui/widgets/PageContainer.cpp
|
||||||
ui/widgets/PageContainer.h
|
ui/widgets/PageContainer.h
|
||||||
ui/widgets/PageContainer_p.h
|
ui/widgets/PageContainer_p.h
|
||||||
|
ui/widgets/ProjectItem.h
|
||||||
|
ui/widgets/ProjectItem.cpp
|
||||||
ui/widgets/VersionListView.cpp
|
ui/widgets/VersionListView.cpp
|
||||||
ui/widgets/VersionListView.h
|
ui/widgets/VersionListView.h
|
||||||
ui/widgets/VersionSelectWidget.cpp
|
ui/widgets/VersionSelectWidget.cpp
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
#include "ui/dialogs/ModDownloadDialog.h"
|
#include "ui/dialogs/ModDownloadDialog.h"
|
||||||
|
|
||||||
|
#include "ui/widgets/ProjectItem.h"
|
||||||
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
namespace ModPlatform {
|
namespace ModPlatform {
|
||||||
@ -39,9 +41,6 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
|
|||||||
|
|
||||||
ModPlatform::IndexedPack pack = modpacks.at(pos);
|
ModPlatform::IndexedPack pack = modpacks.at(pos);
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole: {
|
|
||||||
return pack.name;
|
|
||||||
}
|
|
||||||
case Qt::ToolTipRole: {
|
case Qt::ToolTipRole: {
|
||||||
if (pack.description.length() > 100) {
|
if (pack.description.length() > 100) {
|
||||||
// some magic to prevent to long tooltips and replace html linebreaks
|
// 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);
|
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
case Qt::SizeHintRole:
|
||||||
|
return QSize(0, 58);
|
||||||
case Qt::UserRole: {
|
case Qt::UserRole: {
|
||||||
QVariant v;
|
QVariant v;
|
||||||
v.setValue(pack);
|
v.setValue(pack);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
case Qt::FontRole: {
|
// Custom data
|
||||||
QFont font;
|
case UserDataTypes::TITLE:
|
||||||
if (m_parent->getDialog()->isModSelected(pack.name)) {
|
return pack.name;
|
||||||
font.setBold(true);
|
case UserDataTypes::DESCRIPTION:
|
||||||
font.setUnderline(true);
|
return pack.description;
|
||||||
}
|
case UserDataTypes::SELECTED:
|
||||||
|
return m_parent->getDialog()->isModSelected(pack.name);
|
||||||
return font;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
#include "modplatform/ModAPI.h"
|
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
#include "ui/dialogs/ModDownloadDialog.h"
|
#include "ui/dialogs/ModDownloadDialog.h"
|
||||||
|
#include "ui/widgets/ProjectItem.h"
|
||||||
|
|
||||||
ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
|
ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
|
||||||
: QWidget(dialog)
|
: QWidget(dialog)
|
||||||
@ -71,6 +72,8 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
|
|||||||
connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{
|
connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{
|
||||||
ui->searchButton->setStyleSheet("text-decoration: none");
|
ui->searchButton->setStyleSheet("text-decoration: none");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
ModPage::~ModPage()
|
ModPage::~ModPage()
|
||||||
|
@ -1,27 +1,33 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
|
||||||
// Origin: Qt
|
// Origin: Qt
|
||||||
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
|
// More specifically, this is a trimmed down version on the algorithm in:
|
||||||
qreal &widthUsed)
|
// https://code.woboq.org/qt5/qtbase/src/widgets/styles/qcommonstyle.cpp.html#846
|
||||||
|
QList<std::pair<qreal, QString>> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height)
|
||||||
{
|
{
|
||||||
QStringList lines;
|
QList<std::pair<qreal, QString>> lines;
|
||||||
height = 0;
|
height = 0;
|
||||||
widthUsed = 0;
|
|
||||||
textLayout.beginLayout();
|
textLayout.beginLayout();
|
||||||
|
|
||||||
QString str = textLayout.text();
|
QString str = textLayout.text();
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
|
||||||
QTextLine line = textLayout.createLine();
|
QTextLine line = textLayout.createLine();
|
||||||
|
|
||||||
if (!line.isValid())
|
if (!line.isValid())
|
||||||
break;
|
break;
|
||||||
if (line.textLength() == 0)
|
if (line.textLength() == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
line.setLineWidth(lineWidth);
|
line.setLineWidth(lineWidth);
|
||||||
line.setPosition(QPointF(0, height));
|
line.setPosition(QPointF(0, height));
|
||||||
|
|
||||||
height += line.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();
|
textLayout.endLayout();
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QStringList>
|
|
||||||
#include <QTextLayout>
|
#include <QTextLayout>
|
||||||
|
|
||||||
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
|
/** Cuts out the text in textLayout into smaller pieces, according to the lineWidth.
|
||||||
qreal &widthUsed);
|
* 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<std::pair<qreal, QString>> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height);
|
||||||
|
78
launcher/ui/widgets/ProjectItem.cpp
Normal file
78
launcher/ui/widgets/ProjectItem.cpp
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#include "ProjectItem.h"
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
25
launcher/ui/widgets/ProjectItem.h
Normal file
25
launcher/ui/widgets/ProjectItem.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user