Visual Deck Editor Base (#5834)

* Visual Deck Editor.

* Lint.

* Address comments.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-04-16 14:02:53 +02:00 committed by GitHub
parent a55a287a9d
commit 42c56898d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1949 additions and 3 deletions

View file

@ -0,0 +1,81 @@
#include "card_group_display_widget.h"
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database_manager.h"
#include "../../../../../utility/card_info_comparator.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include <QResizeEvent>
CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _cardGroupCategory,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
CardSizeWidget *_cardSizeWidget)
: QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), cardGroupCategory(_cardGroupCategory),
activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria),
cardSizeWidget(_cardSizeWidget)
{
layout = new QVBoxLayout(this);
setLayout(layout);
setMinimumSize(QSize(0, 0));
banner = new BannerWidget(this, cardGroupCategory, Qt::Orientation::Vertical, bannerOpacity);
layout->addWidget(banner);
updateCardDisplays();
}
void CardGroupDisplayWidget::updateCardDisplays()
{
}
QList<CardInfoPtr> CardGroupDisplayWidget::getCardsMatchingGroup(QList<CardInfoPtr> cardsToSort)
{
cardsToSort = sortCardList(cardsToSort, activeSortCriteria, Qt::SortOrder::AscendingOrder);
QList<CardInfoPtr> activeList;
for (const CardInfoPtr &info : cardsToSort) {
if (info && info->getProperty(activeGroupCriteria) == cardGroupCategory) {
activeList.append(info);
}
}
return activeList;
}
QList<CardInfoPtr> CardGroupDisplayWidget::sortCardList(QList<CardInfoPtr> cardsToSort,
const QStringList properties,
Qt::SortOrder order = Qt::AscendingOrder)
{
CardInfoComparator comparator(properties, order);
std::sort(cardsToSort.begin(), cardsToSort.end(), comparator);
return cardsToSort;
}
void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria)
{
if (activeSortCriteria != _activeSortCriteria) {
activeSortCriteria = _activeSortCriteria;
updateCardDisplays(); // Refresh display with new sorting
}
}
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card);
}
void CardGroupDisplayWidget::onHover(CardInfoPtr card)
{
emit cardHovered(card);
}
void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}

View file

@ -0,0 +1,53 @@
#ifndef CARD_GROUP_DISPLAY_WIDGET_H
#define CARD_GROUP_DISPLAY_WIDGET_H
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database.h"
#include "../../general/display/banner_widget.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include "../card_size_widget.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
class CardGroupDisplayWidget : public QWidget
{
Q_OBJECT
public:
CardGroupDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString cardGroupCategory,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
CardSizeWidget *cardSizeWidget);
QList<CardInfoPtr> getCardsMatchingGroup(QList<CardInfoPtr> cardsToSort);
void resizeEvent(QResizeEvent *event) override;
DeckListModel *deckListModel;
QString zoneName;
QString cardGroupCategory;
QString activeGroupCriteria;
QStringList activeSortCriteria;
CardSizeWidget *cardSizeWidget;
public slots:
QList<CardInfoPtr> sortCardList(QList<CardInfoPtr> cardsToSort, QStringList properties, Qt::SortOrder order);
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
void onHover(CardInfoPtr card);
virtual void updateCardDisplays();
void onActiveSortCriteriaChanged(QStringList activeSortCriteria);
signals:
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
void cardHovered(CardInfoPtr card);
protected:
QVBoxLayout *layout;
BannerWidget *banner;
};
#endif // CARD_GROUP_DISPLAY_WIDGET_H

View file

@ -0,0 +1,107 @@
#include "flat_card_group_display_widget.h"
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database_manager.h"
#include "../../../../../utility/card_info_comparator.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include <QResizeEvent>
FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _cardGroupCategory,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
CardSizeWidget *_cardSizeWidget)
: CardGroupDisplayWidget(parent,
_deckListModel,
_zoneName,
_cardGroupCategory,
_activeGroupCriteria,
_activeSortCriteria,
bannerOpacity,
_cardSizeWidget)
{
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
banner->setBuddy(flowWidget);
layout->addWidget(flowWidget);
FlatCardGroupDisplayWidget::updateCardDisplays();
connect(deckListModel, &DeckListModel::dataChanged, this, &FlatCardGroupDisplayWidget::updateCardDisplays);
}
void FlatCardGroupDisplayWidget::updateCardDisplays()
{
// Retrieve and sort cards
QList<CardInfoPtr> cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName));
// Show or hide widget
bool shouldBeVisible = !cardsInZone.isEmpty();
if (shouldBeVisible != isVisible()) {
setVisible(shouldBeVisible);
}
// Retrieve existing widgets
QList<CardInfoPictureWithTextOverlayWidget *> existingWidgets =
flowWidget->findChildren<CardInfoPictureWithTextOverlayWidget *>();
QHash<QString, QList<CardInfoPictureWithTextOverlayWidget *>> widgetMap;
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
widgetMap[widget->getInfo()->getName()].append(widget);
}
QList<CardInfoPictureWithTextOverlayWidget *> sortedWidgets;
QSet<CardInfoPictureWithTextOverlayWidget *> usedWidgets;
// Ensure widgets are ordered to match the sorted cards
for (const CardInfoPtr &card : cardsInZone) {
QString name = card->getName();
CardInfoPictureWithTextOverlayWidget *widget = nullptr;
if (!widgetMap[name].isEmpty()) {
// Reuse an existing widget
widget = widgetMap[name].takeFirst();
} else {
// Create a new widget if needed
widget = new CardInfoPictureWithTextOverlayWidget(flowWidget, true);
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
widget->setCard(card);
connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this,
&FlatCardGroupDisplayWidget::onClick);
connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this,
&FlatCardGroupDisplayWidget::onHover);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget,
&CardInfoPictureWidget::setScaleFactor);
flowWidget->addWidget(widget);
}
// Store in sorted order
sortedWidgets.append(widget);
usedWidgets.insert(widget);
}
// Remove extra widgets
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
if (!usedWidgets.contains(widget)) {
flowWidget->layout()->removeWidget(widget);
widget->deleteLater();
}
}
// **Reorder widgets in place**
for (int i = 0; i < sortedWidgets.size(); ++i) {
sortedWidgets[i]->setParent(nullptr); // Temporarily detach
}
for (int i = 0; i < sortedWidgets.size(); ++i) {
flowWidget->addWidget(sortedWidgets[i]); // Reattach in correct order
}
}
void FlatCardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}

View file

@ -0,0 +1,30 @@
#ifndef FLAT_CARD_GROUP_DISPLAY_WIDGET_H
#define FLAT_CARD_GROUP_DISPLAY_WIDGET_H
#include "../../general/layout_containers/flow_widget.h"
#include "card_group_display_widget.h"
class FlatCardGroupDisplayWidget : public CardGroupDisplayWidget
{
Q_OBJECT
public:
FlatCardGroupDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString cardGroupCategory,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
CardSizeWidget *cardSizeWidget);
void resizeEvent(QResizeEvent *event) override;
public slots:
void updateCardDisplays() override;
private:
FlowWidget *flowWidget;
};
#endif // FLAT_CARD_GROUP_DISPLAY_WIDGET_H

View file

@ -0,0 +1,122 @@
#include "overlapped_card_group_display_widget.h"
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database_manager.h"
#include "../../../../../utility/card_info_comparator.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include <QResizeEvent>
OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _cardGroupCategory,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
CardSizeWidget *_cardSizeWidget)
: CardGroupDisplayWidget(parent,
_deckListModel,
_zoneName,
_cardGroupCategory,
_activeGroupCriteria,
_activeSortCriteria,
bannerOpacity,
_cardSizeWidget)
{
overlapWidget = new OverlapWidget(this, 80, 1, 1, Qt::Vertical, true);
banner->setBuddy(overlapWidget);
layout->addWidget(overlapWidget);
OverlappedCardGroupDisplayWidget::updateCardDisplays();
connect(deckListModel, &DeckListModel::dataChanged, this, &OverlappedCardGroupDisplayWidget::updateCardDisplays);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this,
[this]() { overlapWidget->adjustMaxColumnsAndRows(); });
}
void OverlappedCardGroupDisplayWidget::updateCardDisplays()
{
overlapWidget->setUpdatesEnabled(false);
// Retrieve and sort cards
QList<CardInfoPtr> cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName));
// Show or hide widget
bool shouldBeVisible = !cardsInZone.isEmpty();
if (shouldBeVisible != isVisible()) {
setVisible(shouldBeVisible);
}
// Retrieve existing widgets
QList<CardInfoPictureWithTextOverlayWidget *> existingWidgets =
overlapWidget->findChildren<CardInfoPictureWithTextOverlayWidget *>();
QHash<QString, QList<CardInfoPictureWithTextOverlayWidget *>> widgetMap;
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
widgetMap[widget->getInfo()->getName()].append(widget);
}
QList<CardInfoPictureWithTextOverlayWidget *> sortedWidgets;
QSet<CardInfoPictureWithTextOverlayWidget *> usedWidgets;
// Ensure widgets are ordered to match the sorted cards
for (const CardInfoPtr &card : cardsInZone) {
QString name = card->getName();
CardInfoPictureWithTextOverlayWidget *widget = nullptr;
if (!widgetMap[name].isEmpty()) {
// Reuse an existing widget
widget = widgetMap[name].takeFirst();
} else {
// Create a new widget if needed
widget = new CardInfoPictureWithTextOverlayWidget(overlapWidget, true);
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
widget->setCard(card);
connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this,
&OverlappedCardGroupDisplayWidget::onClick);
connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this,
&OverlappedCardGroupDisplayWidget::onHover);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget,
&CardInfoPictureWidget::setScaleFactor);
overlapWidget->addWidget(widget);
}
// Store in sorted order
sortedWidgets.append(widget);
usedWidgets.insert(widget);
}
// Remove extra widgets
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
if (!usedWidgets.contains(widget)) {
overlapWidget->layout()->removeWidget(widget);
delete widget;
}
}
// **Reorder widgets in place**
for (int i = 0; i < sortedWidgets.size(); ++i) {
sortedWidgets[i]->setParent(nullptr); // Temporarily detach
}
for (int i = 0; i < sortedWidgets.size(); ++i) {
overlapWidget->addWidget(sortedWidgets[i]); // Reattach in correct order
}
// Ensure proper layering
for (CardInfoPictureWithTextOverlayWidget *widget : sortedWidgets) {
widget->raise();
}
overlapWidget->adjustMaxColumnsAndRows();
overlapWidget->setUpdatesEnabled(true);
overlapWidget->update();
}
void OverlappedCardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
overlapWidget->resize(event->size());
overlapWidget->adjustMaxColumnsAndRows();
}

View file

@ -0,0 +1,30 @@
#ifndef OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
#define OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
#include "../../general/layout_containers/overlap_widget.h"
#include "card_group_display_widget.h"
class OverlappedCardGroupDisplayWidget : public CardGroupDisplayWidget
{
Q_OBJECT
public:
OverlappedCardGroupDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString cardGroupCategory,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
CardSizeWidget *cardSizeWidget);
void resizeEvent(QResizeEvent *event) override;
public slots:
void updateCardDisplays() override;
private:
OverlapWidget *overlapWidget;
};
#endif // OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H

View file

@ -61,6 +61,7 @@ CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *paren
setViewMode(SettingsCache::instance().getCardInfoViewMode());
// TODO: Change this to be by UUID
setCard(CardDatabaseManager::getInstance()->getCard(cardName));
}

View file

@ -0,0 +1,168 @@
#include "deck_card_zone_display_widget.h"
#include "../../../../deck/deck_list_model.h"
#include "../../../../utility/card_info_comparator.h"
#include "card_group_display_widgets/flat_card_group_display_widget.h"
#include "card_group_display_widgets/overlapped_card_group_display_widget.h"
#include <QResizeEvent>
DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
int subBannerOpacity,
CardSizeWidget *_cardSizeWidget)
: QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), activeGroupCriteria(_activeGroupCriteria),
activeSortCriteria(_activeSortCriteria), bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity),
cardSizeWidget(_cardSizeWidget)
{
layout = new QVBoxLayout(this);
setLayout(layout);
banner = new BannerWidget(this, zoneName, Qt::Orientation::Vertical, bannerOpacity);
layout->addWidget(banner);
cardGroupContainer = new QWidget(this);
cardGroupLayout = new QVBoxLayout(cardGroupContainer);
cardGroupContainer->setLayout(cardGroupLayout);
layout->addWidget(cardGroupContainer);
banner->setBuddy(cardGroupContainer);
displayCards();
connect(deckListModel, &DeckListModel::dataChanged, this, &DeckCardZoneDisplayWidget::displayCards);
}
void DeckCardZoneDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
for (QObject *child : layout->children()) {
QWidget *widget = qobject_cast<QWidget *>(child);
if (widget) {
widget->setMaximumWidth(width());
}
}
}
void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card, zoneName);
}
void DeckCardZoneDisplayWidget::onHover(CardInfoPtr card)
{
emit cardHovered(card);
}
void DeckCardZoneDisplayWidget::displayCards()
{
addCardGroupIfItDoesNotExist();
deleteCardGroupIfItDoesNotExist();
}
void DeckCardZoneDisplayWidget::refreshDisplayType(const QString &_displayType)
{
displayType = _displayType;
QLayoutItem *item;
while ((item = cardGroupLayout->takeAt(0)) != nullptr) {
if (item->widget()) {
item->widget()->deleteLater();
} else if (item->layout()) {
delete item->layout();
}
delete item;
}
// We gotta wait for all the deleteLater's to finish so we fire after the next event cycle
auto timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [this]() { displayCards(); });
timer->start();
}
void DeckCardZoneDisplayWidget::addCardGroupIfItDoesNotExist()
{
QList<CardGroupDisplayWidget *> cardGroupsDisplayWidgets =
cardGroupContainer->findChildren<CardGroupDisplayWidget *>();
QList<QString> cardGroups = getGroupCriteriaValueList();
for (QString cardGroup : cardGroups) {
bool found = false;
for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) {
if (cardGroupDisplayWidget->cardGroupCategory == cardGroup) {
found = true;
}
}
if (found) {
continue;
}
if (displayType == "overlap") {
auto *display_widget = new OverlappedCardGroupDisplayWidget(
cardGroupContainer, deckListModel, zoneName, cardGroup, activeGroupCriteria, activeSortCriteria,
subBannerOpacity, cardSizeWidget);
connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this,
SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)));
connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr)));
connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget,
&CardGroupDisplayWidget::onActiveSortCriteriaChanged);
cardGroupLayout->addWidget(display_widget);
} else if (displayType == "flat") {
auto *display_widget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, zoneName,
cardGroup, activeGroupCriteria, activeSortCriteria,
subBannerOpacity, cardSizeWidget);
connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this,
SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)));
connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr)));
connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget,
&CardGroupDisplayWidget::onActiveSortCriteriaChanged);
cardGroupLayout->addWidget(display_widget);
}
}
}
void DeckCardZoneDisplayWidget::deleteCardGroupIfItDoesNotExist()
{
QList<CardGroupDisplayWidget *> cardGroupsDisplayWidgets =
cardGroupContainer->findChildren<CardGroupDisplayWidget *>();
QList<QString> validGroups = getGroupCriteriaValueList();
for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) {
if (!validGroups.contains(cardGroupDisplayWidget->cardGroupCategory)) {
cardGroupLayout->removeWidget(cardGroupDisplayWidget);
cardGroupDisplayWidget->deleteLater(); // Properly delete the widget after the event loop cycles
}
}
}
void DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged(QString _activeGroupCriteria)
{
activeGroupCriteria = _activeGroupCriteria;
displayCards();
}
void DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria)
{
activeSortCriteria = _activeSortCriteria;
emit activeSortCriteriaChanged(activeSortCriteria);
}
QList<QString> DeckCardZoneDisplayWidget::getGroupCriteriaValueList()
{
QList<QString> groupCriteriaValues;
QList<CardInfoPtr> cardsInZone = deckListModel->getCardsAsCardInfoPtrsForZone(zoneName);
for (CardInfoPtr cardInZone : cardsInZone) {
groupCriteriaValues.append(cardInZone->getProperty(activeGroupCriteria));
}
groupCriteriaValues.removeDuplicates();
groupCriteriaValues.sort();
return groupCriteriaValues;
}

View file

@ -0,0 +1,62 @@
#ifndef DECK_CARD_ZONE_DISPLAY_WIDGET_H
#define DECK_CARD_ZONE_DISPLAY_WIDGET_H
#include "../../../../deck/deck_list_model.h"
#include "../../../../game/cards/card_database.h"
#include "../general/display/banner_widget.h"
#include "../general/layout_containers/overlap_widget.h"
#include "card_info_picture_with_text_overlay_widget.h"
#include "card_size_widget.h"
#include <QVBoxLayout>
#include <QWidget>
class DeckCardZoneDisplayWidget : public QWidget
{
Q_OBJECT
public:
DeckCardZoneDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
int subBannerOpacity,
CardSizeWidget *_cardSizeWidget);
DeckListModel *deckListModel;
QString zoneName;
void addCardsToOverlapWidget();
void resizeEvent(QResizeEvent *event) override;
public slots:
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
void onHover(CardInfoPtr card);
void displayCards();
void refreshDisplayType(const QString &displayType);
void addCardGroupIfItDoesNotExist();
void deleteCardGroupIfItDoesNotExist();
void onActiveGroupCriteriaChanged(QString activeGroupCriteria);
void onActiveSortCriteriaChanged(QStringList activeSortCriteria);
QList<QString> getGroupCriteriaValueList();
signals:
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card, QString zoneName);
void cardHovered(CardInfoPtr card);
void activeSortCriteriaChanged(QStringList activeSortCriteria);
private:
QString activeGroupCriteria;
QStringList activeSortCriteria;
int bannerOpacity = 20;
int subBannerOpacity = 10;
CardSizeWidget *cardSizeWidget;
QVBoxLayout *layout;
BannerWidget *banner;
QWidget *cardGroupContainer;
QVBoxLayout *cardGroupLayout;
QString displayType = "flat";
OverlapWidget *overlapWidget;
};
#endif // DECK_CARD_ZONE_DISPLAY_WIDGET_H

View file

@ -309,6 +309,8 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
deckView->expandAll();
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
emit deckChanged();
}
DeckLoader *DeckEditorDeckDockWidget::getDeckList()
@ -325,6 +327,7 @@ void DeckEditorDeckDockWidget::cleanDeck()
nameEdit->setText(QString());
commentsEdit->setText(QString());
hashLabel->setText(QString());
emit deckChanged();
updateBannerCardComboBox();
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
}

View file

@ -0,0 +1,315 @@
#include "visual_deck_editor_widget.h"
#include "../../../../deck/deck_list_model.h"
#include "../../../../deck/deck_loader.h"
#include "../../../../game/cards/card_completer_proxy_model.h"
#include "../../../../game/cards/card_database.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/cards/card_database_model.h"
#include "../../../../game/cards/card_search_model.h"
#include "../../../../main.h"
#include "../../../../utility/card_info_comparator.h"
#include "../../layouts/overlap_layout.h"
#include "../cards/card_info_picture_with_text_overlay_widget.h"
#include "../cards/deck_card_zone_display_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "../general/layout_containers/overlap_control_widget.h"
#include <QCheckBox>
#include <QCompleter>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QResizeEvent>
#include <qscrollarea.h>
VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_deckListModel)
: QWidget(parent), deckListModel(_deckListModel)
{
connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged);
// The Main Widget and Main Layout, which contain a single Widget: The Scroll Area
setMinimumSize(0, 0);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mainLayout = new QVBoxLayout(this);
setLayout(mainLayout);
mainLayout->setContentsMargins(9, 0, 9, 5);
mainLayout->setSpacing(0);
searchContainer = new QWidget(this);
searchLayout = new QHBoxLayout(searchContainer);
searchContainer->setLayout(searchLayout);
searchBar = new QLineEdit(this);
connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() {
if (!searchBar->hasFocus())
return;
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text());
if (card) {
emit cardAdditionRequested(card);
}
});
setFocusProxy(searchBar);
setFocusPolicy(Qt::ClickFocus);
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this);
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
CardSearchModel *searchModel = new CardSearchModel(cardDatabaseDisplayModel, this);
proxyModel = new CardCompleterProxyModel(this);
proxyModel->setSourceModel(searchModel);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterRole(Qt::DisplayRole);
completer = new QCompleter(proxyModel, this);
completer->setCompletionRole(Qt::DisplayRole);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setFilterMode(Qt::MatchContains);
completer->setMaxVisibleItems(15);
searchBar->setCompleter(completer);
// Update suggestions dynamically
connect(searchBar, &QLineEdit::textEdited, searchModel, &CardSearchModel::updateSearchResults);
connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) {
// Ensure substring matching
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (!text.isEmpty()) {
completer->complete(); // Force the dropdown to appear
}
});
connect(completer, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this,
[=, this](const QString &completion) {
// Prevent the text from changing automatically when navigating with arrow keys
if (searchBar->text() != completion) {
searchBar->setText(completion); // Set the completion explicitly
searchBar->setCursorPosition(searchBar->text().length()); // Move cursor to the end
}
});
// Ensure that the text stays consistent during selection
connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) {
if (searchBar->hasFocus() && !searchBar->completer()->popup()->isVisible()) {
// Allow text to change when typing, but not when navigating the completer
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
proxyModel->setFilterRegularExpression(
QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
}
});
// Search button functionality
searchPushButton = new QPushButton(this);
connect(searchPushButton, &QPushButton::clicked, this, [=, this]() {
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text());
if (card) {
emit cardAdditionRequested(card);
}
});
searchLayout->addWidget(searchBar);
searchLayout->addWidget(searchPushButton);
mainLayout->addWidget(searchContainer);
groupAndSortContainer = new QWidget(this);
groupAndSortLayout = new QHBoxLayout(groupAndSortContainer);
groupAndSortLayout->setAlignment(Qt::AlignLeft);
groupAndSortContainer->setLayout(groupAndSortLayout);
groupByComboBox = new QComboBox();
QStringList groupProperties = {"maintype", "colors", "cmc", "name"};
groupByComboBox->addItems(groupProperties);
groupByComboBox->setMinimumWidth(300);
connect(groupByComboBox, QOverload<const QString &>::of(&QComboBox::currentTextChanged), this,
&VisualDeckEditorWidget::actChangeActiveGroupCriteria);
actChangeActiveGroupCriteria();
sortCriteriaButton = new SettingsButtonWidget(this);
sortLabel = new QLabel(sortCriteriaButton);
sortLabel->setWordWrap(true);
QStringList sortProperties = {"colors", "cmc", "name", "maintype"};
sortByListWidget = new QListWidget();
sortByListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
sortByListWidget->setDragDropMode(QAbstractItemView::InternalMove);
sortByListWidget->setDefaultDropAction(Qt::MoveAction);
for (const QString &property : sortProperties) {
QListWidgetItem *item = new QListWidgetItem(property, sortByListWidget);
item->setFlags(item->flags() | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
connect(sortByListWidget->model(), &QAbstractItemModel::rowsMoved, this,
&VisualDeckEditorWidget::actChangeActiveSortCriteria);
actChangeActiveSortCriteria();
sortByListWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
sortCriteriaButton->addSettingsWidget(sortLabel);
sortCriteriaButton->addSettingsWidget(sortByListWidget);
displayTypeButton = new QPushButton(this);
connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckEditorWidget::updateDisplayType);
groupAndSortLayout->addWidget(groupByComboBox);
groupAndSortLayout->addWidget(sortCriteriaButton);
groupAndSortLayout->addWidget(displayTypeButton);
scrollArea = new QScrollArea();
scrollArea->setWidgetResizable(true);
scrollArea->setMinimumSize(0, 0);
// Set scrollbar policies
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
zoneContainer = new QWidget(scrollArea);
zoneContainerLayout = new QVBoxLayout(zoneContainer);
zoneContainer->setLayout(zoneContainerLayout);
scrollArea->addScrollBarWidget(zoneContainer, Qt::AlignHCenter);
scrollArea->setWidget(zoneContainer);
updateZoneWidgets();
cardSizeWidget = new CardSizeWidget(this);
mainLayout->addWidget(groupAndSortContainer);
mainLayout->addWidget(scrollArea);
mainLayout->addWidget(cardSizeWidget);
retranslateUi();
}
void VisualDeckEditorWidget::retranslateUi()
{
sortLabel->setText(tr("Click and drag to change the sort order within the groups"));
searchPushButton->setText(tr("Quick search and add card"));
displayTypeButton->setText(tr("Flat Layout"));
}
void VisualDeckEditorWidget::updateZoneWidgets()
{
addZoneIfDoesNotExist();
deleteZoneIfDoesNotExist();
}
void VisualDeckEditorWidget::updateDisplayType()
{
// Toggle the display type
currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap;
// Update UI and emit signal
switch (currentDisplayType) {
case DisplayType::Flat:
emit displayTypeChanged("flat");
displayTypeButton->setText(tr("Flat Layout"));
break;
case DisplayType::Overlap:
emit displayTypeChanged("overlap");
displayTypeButton->setText(tr("Overlap Layout"));
break;
}
}
void VisualDeckEditorWidget::addZoneIfDoesNotExist()
{
QList<DeckCardZoneDisplayWidget *> cardZoneDisplayWidgets =
zoneContainer->findChildren<DeckCardZoneDisplayWidget *>();
for (const QString &zone : *deckListModel->getZones()) {
bool found = false;
for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) {
if (displayWidget->zoneName == zone) {
found = true;
break;
}
}
if (found) {
continue;
}
DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget(
zoneContainer, deckListModel, zone, activeGroupCriteria, activeSortCriteria, 20, 10, cardSizeWidget);
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover);
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick);
connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged);
connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged);
connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::refreshDisplayType);
zoneContainerLayout->addWidget(zoneDisplayWidget);
}
}
void VisualDeckEditorWidget::deleteZoneIfDoesNotExist()
{
QList<DeckCardZoneDisplayWidget *> cardZoneDisplayWidgets =
zoneContainer->findChildren<DeckCardZoneDisplayWidget *>();
for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) {
bool found = false;
for (const QString &zone : *deckListModel->getZones()) {
if (displayWidget->zoneName == zone) {
found = true;
break;
}
}
if (!found) {
zoneContainerLayout->removeWidget(displayWidget);
}
}
}
void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
zoneContainer->setMaximumWidth(scrollArea->viewport()->width());
}
void VisualDeckEditorWidget::actChangeActiveGroupCriteria()
{
activeGroupCriteria = groupByComboBox->currentText();
emit activeGroupCriteriaChanged(activeGroupCriteria);
}
void VisualDeckEditorWidget::actChangeActiveSortCriteria()
{
QStringList selectedCriteria;
for (int i = 0; i < sortByListWidget->count(); ++i) {
QListWidgetItem *item = sortByListWidget->item(i);
selectedCriteria.append(item->text()); // Collect user-defined sort order
}
activeSortCriteria = selectedCriteria;
emit activeSortCriteriaChanged(selectedCriteria);
}
void VisualDeckEditorWidget::decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight)
{
// Might use these at some point.
Q_UNUSED(topLeft);
Q_UNUSED(bottomRight);
// Necessary to delay this in this manner else the updateDisplay will nuke widgets while their onClick event
// hasn't returned yet. Interval of 0 means QT will schedule this after the current event loop has finished.
updateZoneWidgets();
}
void VisualDeckEditorWidget::onHover(CardInfoPtr hoveredCard)
{
emit activeCardChanged(hoveredCard);
}
void VisualDeckEditorWidget::onCardClick(QMouseEvent *event,
CardInfoPictureWithTextOverlayWidget *instance,
QString zoneName)
{
emit cardClicked(event, instance, zoneName);
}

View file

@ -0,0 +1,88 @@
#ifndef VISUAL_DECK_EDITOR_H
#define VISUAL_DECK_EDITOR_H
#include "../../../../deck/deck_list_model.h"
#include "../../../../game/cards/card_completer_proxy_model.h"
#include "../../../../game/cards/card_database.h"
#include "../../../../game/cards/card_database_model.h"
#include "../cards/card_info_picture_with_text_overlay_widget.h"
#include "../cards/card_size_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "../general/layout_containers/overlap_control_widget.h"
#include "../quick_settings/settings_button_widget.h"
#include <QCheckBox>
#include <QListWidget>
#include <QPushButton>
#include <QWidget>
#include <qscrollarea.h>
enum class DisplayType
{
Flat,
Overlap
};
class VisualDeckEditorWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel);
void retranslateUi();
void resizeEvent(QResizeEvent *event) override;
void setDeckList(const DeckList &_deckListModel);
QLineEdit *searchBar;
CardSizeWidget *cardSizeWidget;
public slots:
void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight);
void updateZoneWidgets();
void updateDisplayType();
void addZoneIfDoesNotExist();
void deleteZoneIfDoesNotExist();
signals:
void activeCardChanged(CardInfoPtr activeCard);
void activeGroupCriteriaChanged(QString activeGroupCriteria);
void activeSortCriteriaChanged(QStringList activeSortCriteria);
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void cardAdditionRequested(CardInfoPtr card);
void displayTypeChanged(QString displayType);
protected slots:
void onHover(CardInfoPtr hoveredCard);
void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void actChangeActiveGroupCriteria();
void actChangeActiveSortCriteria();
private:
DeckListModel *deckListModel;
QVBoxLayout *mainLayout;
QWidget *searchContainer;
QHBoxLayout *searchLayout;
CardDatabaseModel *cardDatabaseModel;
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
CardCompleterProxyModel *proxyModel;
QCompleter *completer;
QPushButton *searchPushButton;
DisplayType currentDisplayType = DisplayType::Overlap;
QPushButton *displayTypeButton;
QWidget *groupAndSortContainer;
QHBoxLayout *groupAndSortLayout;
QComboBox *groupByComboBox;
QString activeGroupCriteria = "maintype";
SettingsButtonWidget *sortCriteriaButton;
QLabel *sortLabel;
QListWidget *sortByListWidget;
QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"};
QScrollArea *scrollArea;
QWidget *zoneContainer;
QVBoxLayout *zoneContainerLayout;
// OverlapControlWidget *overlapControlWidget;
QWidget *container;
};
#endif // VISUAL_DECK_EDITOR_H