aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/ui/widgets')
-rw-r--r--launcher/ui/widgets/Common.cpp22
-rw-r--r--launcher/ui/widgets/Common.h9
-rw-r--r--launcher/ui/widgets/InfoFrame.cpp261
-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.cpp168
-rw-r--r--launcher/ui/widgets/ModFilterWidget.cpp65
-rw-r--r--launcher/ui/widgets/ModFilterWidget.h16
-rw-r--r--launcher/ui/widgets/PageContainer.cpp9
-rw-r--r--launcher/ui/widgets/PageContainer.h4
-rw-r--r--launcher/ui/widgets/ProgressWidget.cpp94
-rw-r--r--launcher/ui/widgets/ProgressWidget.h48
-rw-r--r--launcher/ui/widgets/ProjectItem.cpp78
-rw-r--r--launcher/ui/widgets/ProjectItem.h25
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 &current)
{
- 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 &current);
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;
+
+};