Merge pull request #939 from flowln/mod_downloader_improve

Some more UI / UX improvements to the mod downloader!
This commit is contained in:
flow
2022-09-07 08:27:11 -03:00
committed by GitHub
28 changed files with 535 additions and 147 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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()

View File

@ -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);

View File

@ -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)
{

View File

@ -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;
};

View 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();
}

View 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;
};