diff options
Diffstat (limited to 'archived/projt-launcher/launcher/ui/widgets/ProjectItem.cpp')
| -rw-r--r-- | archived/projt-launcher/launcher/ui/widgets/ProjectItem.cpp | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/ui/widgets/ProjectItem.cpp b/archived/projt-launcher/launcher/ui/widgets/ProjectItem.cpp new file mode 100644 index 0000000000..073d951533 --- /dev/null +++ b/archived/projt-launcher/launcher/ui/widgets/ProjectItem.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2026 Project Tick +// SPDX-FileContributor: Project Tick Team +/* + * ProjT Launcher - Minecraft Launcher + * Copyright (C) 2026 Project Tick + * + * 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, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "ProjectItem.h" + +#include <QApplication> + +#include <QDebug> +#include <QIcon> +#include <QPainter> +#include "Common.h" + +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 isInstalled = index.data(UserDataTypes::INSTALLED).toBool(); + auto isChecked = opt.checkState == Qt::Checked; + auto isSelected = option.state & QStyle::State_Selected; + + const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + + auto rect = opt.rect; + + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); + + if (isSelected && style->objectName() != "windowsvista") + painter->setPen(opt.palette.highlightedText().color()); + + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) + { + QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); + style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &checkboxOpt, painter, opt.widget); + + rect.setX(checkboxOpt.rect.right()); + } + + if (!isSelected && !isChecked && isInstalled) + { + painter->setOpacity(0.4); // Fade out the entire item + } + // The default icon size will be a square (and height is usually the lower value). + auto icon_width = rect.height(), icon_height = rect.height(); + int icon_x_margin = (rect.height() - icon_width) / 2; + int icon_y_margin = (rect.height() - icon_height) / 2; + + if (!opt.icon.isNull()) + { // Icon painting + { + auto icon_size = opt.decorationSize; + icon_width = icon_size.width(); + icon_height = icon_size.height(); + + icon_y_margin = (rect.height() - icon_height) / 2; + icon_x_margin = icon_y_margin; // use same margins for consistency + } + + // Centralize icon with a margin to separate from the other elements + int x = rect.x() + icon_x_margin; + int y = rect.y() + icon_y_margin; + + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) + rect.translate(icon_x_margin / 2, 0); + + // Prevent 'scaling null pixmap' warnings + if (icon_width > 0 && icon_height > 0) + opt.icon.paint(painter, x, y, icon_width, icon_height); + } + + // Change the rect so that funther painting is easier + 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(); + + painter->save(); + + auto font = opt.font; + if (isChecked) + { + font.setBold(true); + } + if (isInstalled) + { + title = tr("%1 [installed]").arg(title); + } + + 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() + title_height, title); + + painter->restore(); + } + + { // Description painting + auto description = index.data(UserDataTypes::DESCRIPTION).toString().simplified(); + + 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; + auto num_lines = 1; + + // Get second line, elided if needed + if (cut_text.size() > 1) + { + // 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(description_x, + description_y, + remaining_width, + cut_text.size() * opt.fontMetrics.height(), + Qt::TextWordWrap, + description); + } + + painter->restore(); +} + +bool ProjectItemDelegate::editorEvent(QEvent* event, + QAbstractItemModel* model, + const QStyleOptionViewItem& option, + const QModelIndex& index) +{ + if (!(event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonPress + || event->type() == QEvent::MouseButtonDblClick)) + return false; + + auto mouseEvent = (QMouseEvent*)event; + + if (mouseEvent->button() != Qt::LeftButton) + return false; + + QStyleOptionViewItem opt(option); + initStyleOption(&opt, index); + + const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + + const QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); + + if (!checkboxOpt.rect.contains(mouseEvent->pos().x(), mouseEvent->pos().y())) + return false; + + // swallow other events + // (prevents item being selected or double click action triggering) + if (event->type() != QEvent::MouseButtonRelease) + return true; + + emit checkboxClicked(index); + return true; +} + +QStyleOptionViewItem ProjectItemDelegate::makeCheckboxStyleOption(const QStyleOptionViewItem& opt, + const QStyle* style) const +{ + QStyleOptionViewItem checkboxOpt = opt; + + checkboxOpt.state &= ~QStyle::State_HasFocus; + + if (checkboxOpt.checkState == Qt::Checked) + checkboxOpt.state |= QStyle::State_On; + else + checkboxOpt.state |= QStyle::State_Off; + + QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); + // 5px is the typical top margin for image + // we don't want the checkboxes to be all over the place :) + checkboxOpt.rect = QRect(opt.rect.x() + 5, + opt.rect.y() + (opt.rect.height() / 2 - checkboxRect.height() / 2), + checkboxRect.width(), + checkboxRect.height()); + + return checkboxOpt; +} |
