aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/ui/widgets')
-rw-r--r--launcher/ui/widgets/ProjectDescriptionPage.cpp23
-rw-r--r--launcher/ui/widgets/ProjectDescriptionPage.h32
-rw-r--r--launcher/ui/widgets/ProjectItem.cpp39
-rw-r--r--launcher/ui/widgets/VariableSizedImageObject.cpp127
-rw-r--r--launcher/ui/widgets/VariableSizedImageObject.h64
5 files changed, 278 insertions, 7 deletions
diff --git a/launcher/ui/widgets/ProjectDescriptionPage.cpp b/launcher/ui/widgets/ProjectDescriptionPage.cpp
new file mode 100644
index 00000000..c7e79a17
--- /dev/null
+++ b/launcher/ui/widgets/ProjectDescriptionPage.cpp
@@ -0,0 +1,23 @@
+#include "ProjectDescriptionPage.h"
+
+#include "VariableSizedImageObject.h"
+
+#include <QDebug>
+
+ProjectDescriptionPage::ProjectDescriptionPage(QWidget* parent) : QTextBrowser(parent), m_image_text_object(new VariableSizedImageObject)
+{
+ m_image_text_object->setParent(this);
+ document()->documentLayout()->registerHandler(QTextFormat::ImageObject, m_image_text_object.get());
+}
+
+void ProjectDescriptionPage::setMetaEntry(QString entry)
+{
+ if (m_image_text_object)
+ m_image_text_object->setMetaEntry(entry);
+}
+
+void ProjectDescriptionPage::flush()
+{
+ if (m_image_text_object)
+ m_image_text_object->flush();
+}
diff --git a/launcher/ui/widgets/ProjectDescriptionPage.h b/launcher/ui/widgets/ProjectDescriptionPage.h
new file mode 100644
index 00000000..3dd85302
--- /dev/null
+++ b/launcher/ui/widgets/ProjectDescriptionPage.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <QTextBrowser>
+
+#include "QObjectPtr.h"
+
+QT_BEGIN_NAMESPACE
+class VariableSizedImageObject;
+QT_END_NAMESPACE
+
+/** This subclasses QTextBrowser to provide additional capabilities
+ * to it, like allowing for images to be shown.
+ */
+class ProjectDescriptionPage final : public QTextBrowser {
+ Q_OBJECT
+
+ public:
+ ProjectDescriptionPage(QWidget* parent = nullptr);
+
+ void setMetaEntry(QString entry);
+
+ public slots:
+ /** Flushes the current processing happening in the page.
+ *
+ * Should be called when changing the page's content entirely, to
+ * prevent old tasks from changing the new content.
+ */
+ void flush();
+
+ private:
+ shared_qobject_ptr<VariableSizedImageObject> m_image_text_object;
+};
diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp
index 01be88d9..d1ff9dbc 100644
--- a/launcher/ui/widgets/ProjectItem.cpp
+++ b/launcher/ui/widgets/ProjectItem.cpp
@@ -51,6 +51,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
auto remaining_width = rect.width() - icon_width - 2 * icon_x_margin;
rect.setRect(rect.x() + icon_width + 2 * icon_x_margin, rect.y(), remaining_width, rect.height());
+ int title_height = 0;
+
{ // Title painting
auto title = index.data(UserDataTypes::TITLE).toString();
@@ -66,8 +68,10 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
font.setPointSize(font.pointSize() + 2);
painter->setFont(font);
+ title_height = QFontMetrics(font).height();
+
// On the top, aligned to the left after the icon
- painter->drawText(rect.x(), rect.y() + QFontMetrics(font).height(), title);
+ painter->drawText(rect.x(), rect.y() + title_height, title);
painter->restore();
}
@@ -82,17 +86,38 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
// Get first line unconditionally
description = cut_text.first().second;
+ auto num_lines = 1;
+
// 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;
+ // 2.5x so because there should be some margin left from the 2x so things don't get too squishy.
+ if (rect.height() - title_height <= 2.5 * opt.fontMetrics.height()) {
+ // If there's not enough space, show only a single line, elided.
+ description = opt.fontMetrics.elidedText(description, opt.textElideMode, cut_text.at(0).first);
+ } else {
+ 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;
+ }
+ num_lines += 1;
+ }
}
+ int description_x = rect.x();
+
+
+ // Have the y-value be set based on the number of lines in the description, to centralize the
+ // description text with the space between the base and the title.
+ int description_y = rect.y() + title_height + (rect.height() - title_height) / 2;
+ if (num_lines == 1)
+ description_y -= opt.fontMetrics.height() / 2;
+ else
+ description_y -= opt.fontMetrics.height();
+
// 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(), rect.y() + rect.height() - 2.2 * opt.fontMetrics.height(), remaining_width,
- 2 * opt.fontMetrics.height(), Qt::TextWordWrap, description);
+ painter->drawText(description_x, description_y, remaining_width,
+ cut_text.size() * opt.fontMetrics.height(), Qt::TextWordWrap, description);
}
painter->restore();
diff --git a/launcher/ui/widgets/VariableSizedImageObject.cpp b/launcher/ui/widgets/VariableSizedImageObject.cpp
new file mode 100644
index 00000000..e57f7e95
--- /dev/null
+++ b/launcher/ui/widgets/VariableSizedImageObject.cpp
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * 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, version 3.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "VariableSizedImageObject.h"
+
+#include <QAbstractTextDocumentLayout>
+#include <QDebug>
+#include <QPainter>
+#include <QTextObject>
+
+#include "Application.h"
+
+#include "net/NetJob.h"
+
+enum FormatProperties { ImageData = QTextFormat::UserProperty + 1 };
+
+QSizeF VariableSizedImageObject::intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format)
+{
+ Q_UNUSED(posInDocument);
+
+ auto image = qvariant_cast<QImage>(format.property(ImageData));
+ auto size = image.size();
+
+ // Get the width of the text content to make the image similar sized.
+ // doc->textWidth() includes the margin, so we need to remove it.
+ auto doc_width = doc->textWidth() - 2 * doc->documentMargin();
+
+ if (size.width() > doc_width)
+ size *= doc_width / (double)size.width();
+
+ return { size };
+}
+void VariableSizedImageObject::drawObject(QPainter* painter,
+ const QRectF& rect,
+ QTextDocument* doc,
+ int posInDocument,
+ const QTextFormat& format)
+{
+ if (!format.hasProperty(ImageData)) {
+ QUrl image_url{ qvariant_cast<QString>(format.property(QTextFormat::ImageName)) };
+ if (m_fetching_images.contains(image_url))
+ return;
+
+ loadImage(doc, image_url, posInDocument);
+ return;
+ }
+
+ auto image = qvariant_cast<QImage>(format.property(ImageData));
+
+ painter->setRenderHint(QPainter::RenderHint::SmoothPixmapTransform);
+ painter->drawImage(rect, image);
+}
+
+void VariableSizedImageObject::flush()
+{
+ m_fetching_images.clear();
+}
+
+void VariableSizedImageObject::parseImage(QTextDocument* doc, QImage image, int posInDocument)
+{
+ QTextCursor cursor(doc);
+ cursor.setPosition(posInDocument);
+ cursor.setKeepPositionOnInsert(true);
+
+ auto image_char_format = cursor.charFormat();
+
+ image_char_format.setObjectType(QTextFormat::ImageObject);
+ image_char_format.setProperty(ImageData, image);
+
+ // Qt doesn't allow us to modify the properties of an existing object in the document.
+ // So we remove the old one and add the new one with the ImageData property set.
+ cursor.deleteChar();
+ cursor.insertText(QString(QChar::ObjectReplacementCharacter), image_char_format);
+}
+
+void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source, int posInDocument)
+{
+ m_fetching_images.insert(source);
+
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(
+ m_meta_entry,
+ QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
+
+ auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network());
+ job->addNetAction(Net::Download::makeCached(source, entry));
+
+ auto full_entry_path = entry->getFullPath();
+ auto source_url = source;
+ connect(job, &NetJob::succeeded, [this, doc, full_entry_path, source_url, posInDocument] {
+ qDebug() << "Loaded resource at" << full_entry_path;
+
+ // If we flushed, don't proceed.
+ if (!m_fetching_images.contains(source_url))
+ return;
+
+ QImage image(full_entry_path);
+ doc->addResource(QTextDocument::ImageResource, source_url, image);
+
+ parseImage(doc, image, posInDocument);
+
+ // This size hack is needed to prevent the content from being laid out in an area smaller
+ // than the total width available (weird).
+ auto size = doc->pageSize();
+ doc->adjustSize();
+ doc->setPageSize(size);
+
+ m_fetching_images.remove(source_url);
+ });
+ connect(job, &NetJob::finished, job, &NetJob::deleteLater);
+
+ job->start();
+}
diff --git a/launcher/ui/widgets/VariableSizedImageObject.h b/launcher/ui/widgets/VariableSizedImageObject.h
new file mode 100644
index 00000000..137487ee
--- /dev/null
+++ b/launcher/ui/widgets/VariableSizedImageObject.h
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * 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, version 3.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QString>
+#include <QTextObjectInterface>
+#include <QUrl>
+
+/** Custom image text object to be used instead of the normal one in ProjectDescriptionPage.
+ *
+ * Why? Because we want to re-scale images dynamically based on the document's size, in order to
+ * not have images being weirdly cropped out in different resolutions.
+ */
+class VariableSizedImageObject final : public QObject, public QTextObjectInterface {
+ Q_OBJECT
+ Q_INTERFACES(QTextObjectInterface)
+
+ public:
+ QSizeF intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
+ void drawObject(QPainter* painter, const QRectF& rect, QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
+
+ void setMetaEntry(QString meta_entry) { m_meta_entry = meta_entry; }
+
+ public slots:
+ /** Stops all currently loading images from modifying the document.
+ *
+ * This does not stop the ongoing network tasks, it only prevents their result
+ * from impacting the document any further.
+ */
+ void flush();
+
+ private:
+ /** Adds the image to the document, in the given position.
+ */
+ void parseImage(QTextDocument* doc, QImage image, int posInDocument);
+
+ /** Loads an image from an external source, and adds it to the document.
+ *
+ * This uses m_meta_entry to cache the image.
+ */
+ void loadImage(QTextDocument* doc, const QUrl& source, int posInDocument);
+
+ private:
+ QString m_meta_entry;
+
+ QSet<QUrl> m_fetching_images;
+};