diff options
Diffstat (limited to 'launcher/ui/widgets')
-rw-r--r-- | launcher/ui/widgets/Common.cpp | 22 | ||||
-rw-r--r-- | launcher/ui/widgets/Common.h | 9 | ||||
-rw-r--r-- | launcher/ui/widgets/InfoFrame.cpp | 261 | ||||
-rw-r--r-- | launcher/ui/widgets/InfoFrame.h (renamed from launcher/ui/widgets/MCModInfoFrame.h) | 38 | ||||
-rw-r--r-- | launcher/ui/widgets/InfoFrame.ui (renamed from launcher/ui/widgets/MCModInfoFrame.ui) | 42 | ||||
-rw-r--r-- | launcher/ui/widgets/MCModInfoFrame.cpp | 168 | ||||
-rw-r--r-- | launcher/ui/widgets/ModFilterWidget.cpp | 65 | ||||
-rw-r--r-- | launcher/ui/widgets/ModFilterWidget.h | 16 | ||||
-rw-r--r-- | launcher/ui/widgets/PageContainer.cpp | 9 | ||||
-rw-r--r-- | launcher/ui/widgets/PageContainer.h | 4 | ||||
-rw-r--r-- | launcher/ui/widgets/ProgressWidget.cpp | 94 | ||||
-rw-r--r-- | launcher/ui/widgets/ProgressWidget.h | 48 | ||||
-rw-r--r-- | launcher/ui/widgets/ProjectItem.cpp | 78 | ||||
-rw-r--r-- | launcher/ui/widgets/ProjectItem.h | 25 |
14 files changed, 620 insertions, 259 deletions
diff --git a/launcher/ui/widgets/Common.cpp b/launcher/ui/widgets/Common.cpp index f72f3596..097bb6d4 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<std::pair<qreal, QString>> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height) { - QStringList lines; + QList<std::pair<qreal, QString>> 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 b3fbe1a0..b3dd5ca8 100644 --- a/launcher/ui/widgets/Common.h +++ b/launcher/ui/widgets/Common.h @@ -1,6 +1,9 @@ #pragma once -#include <QStringList> + #include <QTextLayout> -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<std::pair<qreal, QString>> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height); diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp new file mode 100644 index 00000000..9e0553f8 --- /dev/null +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -0,0 +1,261 @@ +// 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/>. +* +* This file incorporates work covered by the following copyright and +* permission notice: +* +* Copyright 2013-2021 MultiMC Contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include <QMessageBox> + +#include "InfoFrame.h" +#include "ui_InfoFrame.h" + +#include "ui/dialogs/CustomMessageBox.h" + +InfoFrame::InfoFrame(QWidget *parent) : + QFrame(parent), + ui(new Ui::InfoFrame) +{ + ui->setupUi(this); + ui->descriptionLabel->setHidden(true); + ui->nameLabel->setHidden(true); + updateHiddenState(); +} + +InfoFrame::~InfoFrame() +{ + delete ui; +} + +void InfoFrame::updateWithMod(Mod const& m) +{ + if (m.type() == ResourceType::FOLDER) + { + clear(); + return; + } + + QString text = ""; + QString name = ""; + if (m.name().isEmpty()) + name = m.internal_id(); + else + name = m.name(); + + if (m.homeurl().isEmpty()) + text = name; + else + text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>"; + if (!m.authors().isEmpty()) + text += " by " + m.authors().join(", "); + + setName(text); + + if (m.description().isEmpty()) + { + setDescription(QString()); + } + else + { + setDescription(m.description()); + } + + setImage(); +} + +void InfoFrame::updateWithResource(const Resource& resource) +{ + setName(resource.name()); + setImage(); +} + +// https://www.sportskeeda.com/minecraft-wiki/color-codes +static const QMap<QChar, QString> s_value_to_color = { + {'0', "#000000"}, {'1', "#0000AA"}, {'2', "#00AA00"}, {'3', "#00AAAA"}, {'4', "#AA0000"}, + {'5', "#AA00AA"}, {'6', "#FFAA00"}, {'7', "#AAAAAA"}, {'8', "#555555"}, {'9', "#5555FF"}, + {'a', "#55FF55"}, {'b', "#55FFFF"}, {'c', "#FF5555"}, {'d', "#FF55FF"}, {'e', "#FFFF55"}, + {'f', "#FFFFFF"} +}; + +void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) +{ + setName(resource_pack.name()); + + // We have to manually set the colors for use. + // + // A color is set using §x, with x = a hex number from 0 to f. + // + // We traverse the description and, when one of those is found, we create + // a span element with that color set. + // + // TODO: Make the same logic for font formatting too. + // TODO: Wrap links inside <a> tags + + auto description = resource_pack.description(); + + QString description_parsed("<html>"); + bool in_div = false; + + auto desc_it = description.constBegin(); + while (desc_it != description.constEnd()) { + if (*desc_it == u'§') { + if (in_div) + description_parsed += "</span>"; + + auto const& num = *(++desc_it); + description_parsed += QString("<span style=\"color: %1;\">").arg(s_value_to_color.constFind(num).value()); + + in_div = true; + + desc_it++; + } + + description_parsed += *desc_it; + desc_it++; + } + + if (in_div) + description_parsed += "</span>"; + description_parsed += "</html>"; + + description_parsed.replace("\n", "<br>"); + + setDescription(description_parsed); + setImage(resource_pack.image({64, 64})); +} + +void InfoFrame::clear() +{ + setName(); + setDescription(); + setImage(); +} + +void InfoFrame::updateHiddenState() +{ + if(ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden()) + { + setHidden(true); + } + else + { + setHidden(false); + } +} + +void InfoFrame::setName(QString text) +{ + if(text.isEmpty()) + { + ui->nameLabel->setHidden(true); + } + else + { + ui->nameLabel->setText(text); + ui->nameLabel->setHidden(false); + } + updateHiddenState(); +} + +void InfoFrame::setDescription(QString text) +{ + if(text.isEmpty()) + { + ui->descriptionLabel->setHidden(true); + updateHiddenState(); + return; + } + else + { + ui->descriptionLabel->setHidden(false); + updateHiddenState(); + } + ui->descriptionLabel->setToolTip(""); + QString intermediatetext = text.trimmed(); + bool prev(false); + QChar rem('\n'); + QString finaltext; + finaltext.reserve(intermediatetext.size()); + foreach(const QChar& c, intermediatetext) + { + if(c == rem && prev){ + continue; + } + prev = c == rem; + finaltext += c; + } + QString labeltext; + labeltext.reserve(300); + if(finaltext.length() > 290) + { + ui->descriptionLabel->setOpenExternalLinks(false); + ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); + m_description = text; + // This allows injecting HTML here. + labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); + QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); + } + else + { + ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText); + labeltext.append(finaltext); + } + ui->descriptionLabel->setText(labeltext); +} + +void InfoFrame::setImage(QPixmap img) +{ + if (img.isNull()) { + ui->iconLabel->setHidden(true); + } else { + ui->iconLabel->setHidden(false); + ui->iconLabel->setPixmap(img); + } +} + +void InfoFrame::descriptionEllipsisHandler(QString link) +{ + if(!m_current_box) + { + m_current_box = CustomMessageBox::selectable(this, "", m_description); + connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed); + m_current_box->show(); + } + else + { + m_current_box->setText(m_description); + } +} + +void InfoFrame::boxClosed(int result) +{ + m_current_box = nullptr; +} diff --git a/launcher/ui/widgets/MCModInfoFrame.h b/launcher/ui/widgets/InfoFrame.h index 0b7ef537..70d15b1e 100644 --- a/launcher/ui/widgets/MCModInfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -16,37 +16,41 @@ #pragma once #include <QFrame> + #include "minecraft/mod/Mod.h" +#include "minecraft/mod/ResourcePack.h" namespace Ui { -class MCModInfoFrame; +class InfoFrame; } -class MCModInfoFrame : public QFrame -{ +class InfoFrame : public QFrame { Q_OBJECT -public: - explicit MCModInfoFrame(QWidget *parent = 0); - ~MCModInfoFrame(); + public: + InfoFrame(QWidget* parent = nullptr); + ~InfoFrame() override; - void setModText(QString text); - void setModDescription(QString text); + void setName(QString text = {}); + void setDescription(QString text = {}); + void setImage(QPixmap img = {}); - void updateWithMod(Mod &m); void clear(); -public slots: - void modDescEllipsisHandler(const QString& link ); + void updateWithMod(Mod const& m); + void updateWithResource(Resource const& resource); + void updateWithResourcePack(ResourcePack& rp); + + public slots: + void descriptionEllipsisHandler(QString link); void boxClosed(int result); -private: + private: void updateHiddenState(); -private: - Ui::MCModInfoFrame *ui; - QString desc; - class QMessageBox * currentBox = nullptr; + private: + Ui::InfoFrame* ui; + QString m_description; + class QMessageBox* m_current_box = nullptr; }; - diff --git a/launcher/ui/widgets/MCModInfoFrame.ui b/launcher/ui/widgets/InfoFrame.ui index 5ef33379..9e407ce9 100644 --- a/launcher/ui/widgets/MCModInfoFrame.ui +++ b/launcher/ui/widgets/InfoFrame.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>MCModInfoFrame</class> - <widget class="QFrame" name="MCModInfoFrame"> + <class>InfoFrame</class> + <widget class="QFrame" name="InfoFrame"> <property name="geometry"> <rect> <x>0</x> @@ -22,10 +22,7 @@ <height>120</height> </size> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="spacing"> - <number>6</number> - </property> + <layout class="QGridLayout" name="gridLayout"> <property name="leftMargin"> <number>0</number> </property> @@ -38,8 +35,8 @@ <property name="bottomMargin"> <number>0</number> </property> - <item> - <widget class="QLabel" name="label_ModText"> + <item row="0" column="1"> + <widget class="QLabel" name="nameLabel"> <property name="text"> <string notr="true"/> </property> @@ -60,8 +57,8 @@ </property> </widget> </item> - <item> - <widget class="QLabel" name="label_ModDescription"> + <item row="1" column="1"> + <widget class="QLabel" name="descriptionLabel"> <property name="toolTip"> <string notr="true"/> </property> @@ -85,6 +82,31 @@ </property> </widget> </item> + <item row="0" column="0" rowspan="2"> + <widget class="QLabel" name="iconLabel"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> </layout> </widget> <resources/> diff --git a/launcher/ui/widgets/MCModInfoFrame.cpp b/launcher/ui/widgets/MCModInfoFrame.cpp deleted file mode 100644 index 7d78006b..00000000 --- a/launcher/ui/widgets/MCModInfoFrame.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <QMessageBox> -#include <QtGui> - -#include "MCModInfoFrame.h" -#include "ui_MCModInfoFrame.h" - -#include "ui/dialogs/CustomMessageBox.h" - -void MCModInfoFrame::updateWithMod(Mod &m) -{ - if (m.type() == m.MOD_FOLDER) - { - clear(); - return; - } - - QString text = ""; - QString name = ""; - if (m.name().isEmpty()) - name = m.internal_id(); - else - name = m.name(); - - if (m.homeurl().isEmpty()) - text = name; - else - text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>"; - if (!m.authors().isEmpty()) - text += " by " + m.authors().join(", "); - - setModText(text); - - if (m.description().isEmpty()) - { - setModDescription(QString()); - } - else - { - setModDescription(m.description()); - } -} - -void MCModInfoFrame::clear() -{ - setModText(QString()); - setModDescription(QString()); -} - -MCModInfoFrame::MCModInfoFrame(QWidget *parent) : - QFrame(parent), - ui(new Ui::MCModInfoFrame) -{ - ui->setupUi(this); - ui->label_ModDescription->setHidden(true); - ui->label_ModText->setHidden(true); - updateHiddenState(); -} - -MCModInfoFrame::~MCModInfoFrame() -{ - delete ui; -} - -void MCModInfoFrame::updateHiddenState() -{ - if(ui->label_ModDescription->isHidden() && ui->label_ModText->isHidden()) - { - setHidden(true); - } - else - { - setHidden(false); - } -} - -void MCModInfoFrame::setModText(QString text) -{ - if(text.isEmpty()) - { - ui->label_ModText->setHidden(true); - } - else - { - ui->label_ModText->setText(text); - ui->label_ModText->setHidden(false); - } - updateHiddenState(); -} - -void MCModInfoFrame::setModDescription(QString text) -{ - if(text.isEmpty()) - { - ui->label_ModDescription->setHidden(true); - updateHiddenState(); - return; - } - else - { - ui->label_ModDescription->setHidden(false); - updateHiddenState(); - } - ui->label_ModDescription->setToolTip(""); - QString intermediatetext = text.trimmed(); - bool prev(false); - QChar rem('\n'); - QString finaltext; - finaltext.reserve(intermediatetext.size()); - foreach(const QChar& c, intermediatetext) - { - if(c == rem && prev){ - continue; - } - prev = c == rem; - finaltext += c; - } - QString labeltext; - labeltext.reserve(300); - if(finaltext.length() > 290) - { - ui->label_ModDescription->setOpenExternalLinks(false); - ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); - desc = text; - // This allows injecting HTML here. - labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); - QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); - } - else - { - ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText); - labeltext.append(finaltext); - } - ui->label_ModDescription->setText(labeltext); -} - -void MCModInfoFrame::modDescEllipsisHandler(const QString &link) -{ - if(!currentBox) - { - currentBox = CustomMessageBox::selectable(this, QString(), desc); - connect(currentBox, &QMessageBox::finished, this, &MCModInfoFrame::boxClosed); - currentBox->show(); - } - else - { - currentBox->setText(desc); - } -} - -void MCModInfoFrame::boxClosed(int result) -{ - currentBox = nullptr; -} diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 4ab34375..ea052c41 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -1,6 +1,39 @@ #include "ModFilterWidget.h" #include "ui_ModFilterWidget.h" +#include "Application.h" + +unique_qobject_ptr<ModFilterWidget> ModFilterWidget::create(Version default_version, QWidget* parent) +{ + auto filter_widget = new ModFilterWidget(default_version, parent); + + if (!filter_widget->versionList()->isLoaded()) { + QEventLoop load_version_list_loop; + + QTimer time_limit_for_list_load; + time_limit_for_list_load.setTimerType(Qt::TimerType::CoarseTimer); + time_limit_for_list_load.setSingleShot(true); + time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit); + time_limit_for_list_load.start(4000); + + auto task = filter_widget->versionList()->getLoadTask(); + + connect(task.get(), &Task::failed, [filter_widget]{ + filter_widget->disableVersionButton(VersionButtonID::Major, tr("failed to get version index")); + }); + connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit); + + if (!task->isRunning()) + task->start(); + + load_version_list_loop.exec(); + if (time_limit_for_list_load.isActive()) + time_limit_for_list_load.stop(); + } + + return unique_qobject_ptr<ModFilterWidget>(filter_widget); +} + ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget) { @@ -16,6 +49,7 @@ ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) m_filter->versions.push_front(def); + m_version_list = APPLICATION->metadataIndex()->get("net.minecraft"); setHidden(true); } @@ -51,24 +85,30 @@ auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter> return m_filter; } -void ModFilterWidget::disableVersionButton(VersionButtonID id) +void ModFilterWidget::disableVersionButton(VersionButtonID id, QString reason) { + QAbstractButton* btn = nullptr; + switch(id){ case(VersionButtonID::Strict): - ui->strictVersionButton->setEnabled(false); + btn = ui->strictVersionButton; break; case(VersionButtonID::Major): - ui->majorVersionButton->setEnabled(false); + btn = ui->majorVersionButton; break; case(VersionButtonID::All): - ui->allVersionsButton->setEnabled(false); + btn = ui->allVersionsButton; break; case(VersionButtonID::Between): - // ui->betweenVersionsButton->setEnabled(false); - break; default: break; } + + if (btn) { + btn->setEnabled(false); + if (!reason.isEmpty()) + btn->setText(btn->text() + QString(" (%1)").arg(reason)); + } } void ModFilterWidget::onVersionFilterChanged(int id) @@ -76,7 +116,7 @@ void ModFilterWidget::onVersionFilterChanged(int id) //ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between); //ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between); - int index = 0; + int index = 1; auto cast_id = (VersionButtonID) id; if (cast_id != m_version_id) { @@ -93,10 +133,15 @@ void ModFilterWidget::onVersionFilterChanged(int id) break; case(VersionButtonID::Major): { auto versionSplit = mcVersionStr().split("."); - for(auto i = Version(QString("%1.%2").arg(versionSplit[0], versionSplit[1])); i <= mcVersion(); index++){ - m_filter->versions.push_front(i); - i = Version(QString("%1.%2.%3").arg(versionSplit[0], versionSplit[1], QString("%1").arg(index))); + + auto major_version = QString("%1.%2").arg(versionSplit[0], versionSplit[1]); + QString version_str = major_version; + + while (m_version_list->hasVersion(version_str)) { + m_filter->versions.emplace_back(version_str); + version_str = QString("%1.%2").arg(major_version, QString::number(index++)); } + break; } case(VersionButtonID::All): diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 334fc672..958a1e2b 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -4,6 +4,10 @@ #include <QButtonGroup> #include "Version.h" + +#include "meta/Index.h" +#include "meta/VersionList.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -34,18 +38,22 @@ public: std::shared_ptr<Filter> m_filter; public: - explicit ModFilterWidget(Version def, QWidget* parent = nullptr); + static unique_qobject_ptr<ModFilterWidget> create(Version default_version, QWidget* parent = nullptr); ~ModFilterWidget(); void setInstance(MinecraftInstance* instance); /// By default all buttons are enabled - void disableVersionButton(VersionButtonID); + void disableVersionButton(VersionButtonID, QString reason = {}); auto getFilter() -> std::shared_ptr<Filter>; auto changed() const -> bool { return m_last_version_id != m_version_id; } + Meta::VersionListPtr versionList() { return m_version_list; } + private: + ModFilterWidget(Version def, QWidget* parent = nullptr); + inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; } inline auto mcVersion() const -> Version { return { mcVersionStr() }; } @@ -61,8 +69,12 @@ private: MinecraftInstance* m_instance = nullptr; + +/* Version stuff */ QButtonGroup m_mcVersion_buttons; + Meta::VersionListPtr m_version_list; + /* Used to tell if the filter was changed since the last getFilter() call */ VersionButtonID m_last_version_id = VersionButtonID::Strict; VersionButtonID m_version_id = VersionButtonID::Strict; diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 419ccb66..8d606820 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -244,7 +244,14 @@ void PageContainer::help() void PageContainer::currentChanged(const QModelIndex ¤t) { - showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1); + int selected_index = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; + + auto* selected = m_model->pages().at(selected_index); + auto* previous = m_currentPage; + + emit selectedPageChanged(previous, selected); + + showPage(selected_index); } bool PageContainer::prepareToClose() diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 86f549eb..80d87a9b 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -95,6 +95,10 @@ private: public slots: void help(); +signals: + /** Emitted when the currently selected page is changed */ + void selectedPageChanged(BasePage* previous, BasePage* selected); + private slots: void currentChanged(const QModelIndex ¤t); void showPage(int row); diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index 911e555d..b60d9a7a 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -1,66 +1,104 @@ // Licensed under the Apache-2.0 license. See README.md for details. #include "ProgressWidget.h" -#include <QProgressBar> +#include <QEventLoop> #include <QLabel> +#include <QProgressBar> #include <QVBoxLayout> -#include <QEventLoop> #include "tasks/Task.h" -ProgressWidget::ProgressWidget(QWidget *parent) - : QWidget(parent) +ProgressWidget::ProgressWidget(QWidget* parent, bool show_label) : QWidget(parent) { - m_label = new QLabel(this); - m_label->setWordWrap(true); + auto* layout = new QVBoxLayout(this); + + if (show_label) { + m_label = new QLabel(this); + m_label->setWordWrap(true); + layout->addWidget(m_label); + } + m_bar = new QProgressBar(this); m_bar->setMinimum(0); m_bar->setMaximum(100); - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(m_label); layout->addWidget(m_bar); - layout->addStretch(); + setLayout(layout); } -void ProgressWidget::start(std::shared_ptr<Task> task) +void ProgressWidget::reset() +{ + m_bar->reset(); +} + +void ProgressWidget::progressFormat(QString format) +{ + if (format.isEmpty()) + m_bar->setTextVisible(false); + else + m_bar->setFormat(format); +} + +void ProgressWidget::watch(Task* task) { + if (!task) + return; + if (m_task) - { - disconnect(m_task.get(), 0, this, 0); - } + disconnect(m_task, nullptr, this, nullptr); + m_task = task; - connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish); - connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus); - connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress); - connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed); + + connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish); + connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus); + connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress); + connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed); + + show(); +} + +void ProgressWidget::start(Task* task) +{ + watch(task); if (!m_task->isRunning()) - { - QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection); - } + QMetaObject::invokeMethod(m_task, "start", Qt::QueuedConnection); } + bool ProgressWidget::exec(std::shared_ptr<Task> task) { QEventLoop loop; + connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); - start(task); + + start(task.get()); + if (task->isRunning()) - { loop.exec(); - } + return task->wasSuccessful(); } +void ProgressWidget::show() +{ + setHidden(false); +} +void ProgressWidget::hide() +{ + setHidden(true); +} + void ProgressWidget::handleTaskFinish() { - if (!m_task->wasSuccessful()) - { + if (!m_task->wasSuccessful() && m_label) m_label->setText(m_task->failReason()); - } + + if (m_hide_if_inactive) + hide(); } -void ProgressWidget::handleTaskStatus(const QString &status) +void ProgressWidget::handleTaskStatus(const QString& status) { - m_label->setText(status); + if (m_label) + m_label->setText(status); } void ProgressWidget::handleTaskProgress(qint64 current, qint64 total) { diff --git a/launcher/ui/widgets/ProgressWidget.h b/launcher/ui/widgets/ProgressWidget.h index fa67748a..4d9097b8 100644 --- a/launcher/ui/widgets/ProgressWidget.h +++ b/launcher/ui/widgets/ProgressWidget.h @@ -9,24 +9,48 @@ class Task; class QProgressBar; class QLabel; -class ProgressWidget : public QWidget -{ +class ProgressWidget : public QWidget { Q_OBJECT -public: - explicit ProgressWidget(QWidget *parent = nullptr); + public: + explicit ProgressWidget(QWidget* parent = nullptr, bool show_label = true); -public slots: - void start(std::shared_ptr<Task> task); + /** Whether to hide the widget automatically if it's watching no running task. */ + void hideIfInactive(bool hide) { m_hide_if_inactive = hide; } + + /** Reset the displayed progress to 0 */ + void reset(); + + /** The text that shows up in the middle of the progress bar. + * By default it's '%p%', with '%p' being the total progress in percentage. + */ + void progressFormat(QString); + + public slots: + /** Watch the progress of a task. */ + void watch(Task* task); + + /** Watch the progress of a task, and start it if needed */ + void start(Task* task); + + /** Blocking way of waiting for a task to finish. */ bool exec(std::shared_ptr<Task> task); -private slots: + /** Un-hide the widget if needed. */ + void show(); + + /** Make the widget invisible. */ + void hide(); + + private slots: void handleTaskFinish(); - void handleTaskStatus(const QString &status); + void handleTaskStatus(const QString& status); void handleTaskProgress(qint64 current, qint64 total); void taskDestroyed(); -private: - QLabel *m_label; - QProgressBar *m_bar; - std::shared_ptr<Task> m_task; + private: + QLabel* m_label = nullptr; + QProgressBar* m_bar = nullptr; + Task* m_task = nullptr; + + bool m_hide_if_inactive = false; }; diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp new file mode 100644 index 00000000..56ae35fb --- /dev/null +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -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(); +} diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h new file mode 100644 index 00000000..f668edf6 --- /dev/null +++ b/launcher/ui/widgets/ProjectItem.h @@ -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; + +}; |