Merge pull request #939 from flowln/mod_downloader_improve
Some more UI / UX improvements to the mod downloader!
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include <QStringList>
|
||||
|
||||
#include <QTextLayout>
|
||||
|
||||
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
|
||||
qreal &widthUsed);
|
||||
/** 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);
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -1,66 +1,104 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#include "ProgressWidget.h"
|
||||
#include <QProgressBar>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QEventLoop>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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()
|
||||
{
|
||||
if (m_task)
|
||||
{
|
||||
disconnect(m_task.get(), 0, this, 0);
|
||||
}
|
||||
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);
|
||||
if (!m_task->isRunning())
|
||||
{
|
||||
QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection);
|
||||
}
|
||||
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, nullptr, this, nullptr);
|
||||
|
||||
m_task = task;
|
||||
|
||||
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, "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)
|
||||
{
|
||||
|
@ -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;
|
||||
};
|
||||
|
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;
|
||||
|
||||
};
|
Reference in New Issue
Block a user