mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-23 01:55:10 -07:00
[Game] Playmats (#7101)
* [Game] Playmats Took 19 seconds Took 1 minute * [Playmats] Add fixed override and configurable fallbacks to settings. Took 29 minutes Took 43 seconds * Add main to test. Took 1 minute Took 29 seconds * Move settings to own group Took 11 minutes * Some attempts to refresh macOS compositor Took 2 minutes * Try something else Took 17 minutes * Don't manipulate live list Took 11 minutes * Change things about resolution, address comments. Took 45 minutes Took 12 minutes * Comments. Took 14 minutes Took 8 seconds * Re-order settings menu location Took 2 minutes * Rename PlaymatResolution to Info and add enums Took 8 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
74a454552a
commit
9eafd90a91
52 changed files with 1931 additions and 28 deletions
|
|
@ -0,0 +1,188 @@
|
|||
#include "playmat_collection_dialog.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "playmat_settings_dialog.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
|
||||
PlaymatCollectionDialog::PlaymatCollectionDialog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setMinimumWidth(420);
|
||||
setupUi();
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::accept()
|
||||
{
|
||||
auto &interfaceSettings = SettingsCache::instance().userInterface();
|
||||
interfaceSettings.setPlaymatFallbackList(playmats);
|
||||
interfaceSettings.setPlaymatFallbackBehavior(modeCombo->currentData().toInt());
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
int PlaymatCollectionDialog::currentRow() const
|
||||
{
|
||||
return playmatList->currentRow();
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::setupUi()
|
||||
{
|
||||
auto &interfaceSettings = SettingsCache::instance().userInterface();
|
||||
playmats = interfaceSettings.getPlaymatFallbackList();
|
||||
|
||||
playmatList = new QListWidget;
|
||||
for (const PlaymatInfo &entry : playmats) {
|
||||
playmatList->addItem(entry.card.name);
|
||||
}
|
||||
connect(playmatList, &QListWidget::itemSelectionChanged, this, &PlaymatCollectionDialog::selectionChanged);
|
||||
connect(playmatList, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *) { editPlaymat(); });
|
||||
|
||||
addButton = new QPushButton;
|
||||
editButton = new QPushButton;
|
||||
removeButton = new QPushButton;
|
||||
moveUpButton = new QPushButton;
|
||||
moveDownButton = new QPushButton;
|
||||
|
||||
connect(addButton, &QPushButton::clicked, this, &PlaymatCollectionDialog::addPlaymat);
|
||||
connect(editButton, &QPushButton::clicked, this, &PlaymatCollectionDialog::editPlaymat);
|
||||
connect(removeButton, &QPushButton::clicked, this, &PlaymatCollectionDialog::removePlaymat);
|
||||
connect(moveUpButton, &QPushButton::clicked, this, &PlaymatCollectionDialog::movePlaymatUp);
|
||||
connect(moveDownButton, &QPushButton::clicked, this, &PlaymatCollectionDialog::movePlaymatDown);
|
||||
|
||||
auto *listButtons = new QVBoxLayout;
|
||||
listButtons->addWidget(addButton);
|
||||
listButtons->addWidget(editButton);
|
||||
listButtons->addWidget(removeButton);
|
||||
listButtons->addWidget(moveUpButton);
|
||||
listButtons->addWidget(moveDownButton);
|
||||
listButtons->addStretch();
|
||||
|
||||
auto *listRow = new QHBoxLayout;
|
||||
listRow->addWidget(playmatList, 1);
|
||||
listRow->addLayout(listButtons);
|
||||
|
||||
modeCombo = new QComboBox;
|
||||
modeCombo->addItem(QString(), PlaymatFallbackModeFixed);
|
||||
modeCombo->addItem(QString(), PlaymatFallbackModeRoundRobin);
|
||||
modeCombo->addItem(QString(), PlaymatFallbackModeRandom);
|
||||
const int modeIndex = modeCombo->findData(interfaceSettings.getPlaymatFallbackBehavior());
|
||||
if (modeIndex >= 0) {
|
||||
modeCombo->setCurrentIndex(modeIndex);
|
||||
}
|
||||
|
||||
auto *modeRow = new QHBoxLayout;
|
||||
modeLabel = new QLabel;
|
||||
modeLabel->setBuddy(modeCombo);
|
||||
modeRow->addWidget(modeLabel);
|
||||
modeRow->addWidget(modeCombo, 1);
|
||||
|
||||
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &PlaymatCollectionDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
auto *root = new QVBoxLayout;
|
||||
root->addLayout(listRow);
|
||||
root->addLayout(modeRow);
|
||||
root->addWidget(buttonBox);
|
||||
setLayout(root);
|
||||
|
||||
selectionChanged();
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::selectionChanged()
|
||||
{
|
||||
const bool hasSelection = playmatList->currentRow() >= 0;
|
||||
editButton->setEnabled(hasSelection);
|
||||
removeButton->setEnabled(hasSelection);
|
||||
moveUpButton->setEnabled(hasSelection && playmatList->currentRow() > 0);
|
||||
moveDownButton->setEnabled(hasSelection && playmatList->currentRow() < playmatList->count() - 1);
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::addPlaymat()
|
||||
{
|
||||
PlaymatSettingsDialog dialog(CardRef{}, PlaymatParams{}, this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
const CardRef card = dialog.card();
|
||||
if (!card.isEmpty()) {
|
||||
PlaymatInfo res = {card, dialog.params()};
|
||||
playmats.append(res);
|
||||
playmatList->addItem(res.card.name);
|
||||
playmatList->setCurrentRow(playmatList->count() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::editPlaymat()
|
||||
{
|
||||
const int row = currentRow();
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PlaymatInfo ¤t = playmats.at(row);
|
||||
PlaymatSettingsDialog dialog(current.card, current.params, this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
const CardRef card = dialog.card();
|
||||
if (card.isEmpty()) {
|
||||
return; // Removal is handled by the Remove button
|
||||
}
|
||||
playmats[row] = {card, dialog.params()};
|
||||
playmatList->item(row)->setText(card.name);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::removePlaymat()
|
||||
{
|
||||
const int row = currentRow();
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
playmats.removeAt(row);
|
||||
delete playmatList->takeItem(row);
|
||||
selectionChanged();
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::movePlaymatUp()
|
||||
{
|
||||
const int row = currentRow();
|
||||
if (row <= 0) {
|
||||
return;
|
||||
}
|
||||
playmats.swapItemsAt(row, row - 1);
|
||||
playmatList->insertItem(row - 1, playmatList->takeItem(row));
|
||||
playmatList->setCurrentRow(row - 1);
|
||||
selectionChanged();
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::movePlaymatDown()
|
||||
{
|
||||
const int row = currentRow();
|
||||
if (row < 0 || row >= playmats.size() - 1) {
|
||||
return;
|
||||
}
|
||||
playmats.swapItemsAt(row, row + 1);
|
||||
playmatList->insertItem(row + 1, playmatList->takeItem(row));
|
||||
playmatList->setCurrentRow(row + 1);
|
||||
selectionChanged();
|
||||
}
|
||||
|
||||
void PlaymatCollectionDialog::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Default Playmats"));
|
||||
addButton->setText(tr("Add..."));
|
||||
editButton->setText(tr("Edit..."));
|
||||
removeButton->setText(tr("Remove"));
|
||||
moveUpButton->setText(tr("Move Up"));
|
||||
moveDownButton->setText(tr("Move Down"));
|
||||
modeLabel->setText(tr("List mode:"));
|
||||
modeCombo->setItemText(0, tr("Fixed (always the first entry)"));
|
||||
modeCombo->setItemText(1, tr("Round-robin (cycle through entries)"));
|
||||
modeCombo->setItemText(2, tr("Random (pick one per game)"));
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef COCKATRICE_PLAYMAT_COLLECTION_DIALOG_H
|
||||
#define COCKATRICE_PLAYMAT_COLLECTION_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <libcockatrice/utility/playmat_params.h>
|
||||
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QPushButton;
|
||||
|
||||
/**
|
||||
* @brief Dialog for editing the user-level playmat collection.
|
||||
*
|
||||
* The collection is the fallback used when a deck has no playmat of its own.
|
||||
* It supports multiple entries and a pick mode (always first / round-robin /
|
||||
* random). The dialog edits a working copy and writes it to the settings only
|
||||
* when accepted.
|
||||
*/
|
||||
class PlaymatCollectionDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlaymatCollectionDialog(QWidget *parent = nullptr);
|
||||
|
||||
void accept() override;
|
||||
|
||||
private slots:
|
||||
void addPlaymat();
|
||||
void editPlaymat();
|
||||
void removePlaymat();
|
||||
void movePlaymatUp();
|
||||
void movePlaymatDown();
|
||||
void selectionChanged();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void retranslateUi();
|
||||
int currentRow() const;
|
||||
|
||||
QList<PlaymatInfo> playmats; ///< Working copy edited by the dialog.
|
||||
QListWidget *playmatList;
|
||||
QComboBox *modeCombo;
|
||||
QLabel *modeLabel;
|
||||
QPushButton *addButton;
|
||||
QPushButton *editButton;
|
||||
QPushButton *removeButton;
|
||||
QPushButton *moveUpButton;
|
||||
QPushButton *moveDownButton;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PLAYMAT_COLLECTION_DIALOG_H
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#include "playmat_preview_widget.h"
|
||||
|
||||
#include "../cards/art_crop_attribution.h"
|
||||
#include "playmat_utils.h"
|
||||
|
||||
#include <QLinearGradient>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
|
||||
PlaymatPreviewWidget::PlaymatPreviewWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
setMinimumSize(400, 120);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
void PlaymatPreviewWidget::setPixmap(const QPixmap &pixmap)
|
||||
{
|
||||
sourcePixmap = pixmap;
|
||||
update();
|
||||
}
|
||||
|
||||
void PlaymatPreviewWidget::setParams(const PlaymatParams &p)
|
||||
{
|
||||
params = p;
|
||||
update();
|
||||
}
|
||||
|
||||
void PlaymatPreviewWidget::setAttribution(const QString &attribution)
|
||||
{
|
||||
attributionText = attribution;
|
||||
update();
|
||||
}
|
||||
|
||||
void PlaymatPreviewWidget::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);
|
||||
|
||||
const QRect rect = this->rect();
|
||||
const QColor accentColor(100, 116, 139);
|
||||
|
||||
// Background
|
||||
const QRectF cardRect = QRectF(rect).adjusted(3, 2, -3, -2);
|
||||
QLinearGradient bg(cardRect.topLeft(), cardRect.topRight());
|
||||
bg.setColorAt(0, accentColor.darker(320));
|
||||
bg.setColorAt(1, QColor(18, 22, 30));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(bg);
|
||||
painter.drawRoundedRect(cardRect, 6, 6);
|
||||
painter.setBrush(accentColor);
|
||||
painter.drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2);
|
||||
|
||||
if (sourcePixmap.isNull()) {
|
||||
painter.setPen(QColor(150, 150, 150));
|
||||
painter.drawText(rect, Qt::AlignCenter, tr("No card selected"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw the playmat art using the same logic as PlayerGraphicsItem
|
||||
// The preview area represents the combined stack+table play area
|
||||
// Stack is ~20% width on the left, table is ~80% on the right
|
||||
const QRectF playArea = cardRect.adjusted(6, 4, -4, -4);
|
||||
|
||||
const QRectF srcRect = computeArtSourceRect(sourcePixmap.size(), params);
|
||||
const QRectF dstRect = coverFitRect(playArea, srcRect.size());
|
||||
|
||||
painter.setClipRect(playArea.toRect());
|
||||
painter.drawPixmap(dstRect, sourcePixmap, srcRect);
|
||||
painter.setClipping(false);
|
||||
|
||||
// Draw zone divider: stack is roughly the left portion
|
||||
const double stackWidthRatio = 0.18; // Stack is about 18% of total play area
|
||||
const double stackDividerX = playArea.left() + playArea.width() * stackWidthRatio;
|
||||
|
||||
// Subtle semi-transparent overlays to distinguish zones
|
||||
// Stack zone overlay (slightly darker)
|
||||
QRectF stackOverlay(playArea.left(), playArea.top(), playArea.width() * stackWidthRatio, playArea.height());
|
||||
painter.fillRect(stackOverlay, QColor(0, 0, 0, 40));
|
||||
|
||||
// Table zone overlay (very subtle)
|
||||
QRectF tableOverlay(stackDividerX, playArea.top(), playArea.width() * (1.0 - stackWidthRatio), playArea.height());
|
||||
painter.fillRect(tableOverlay, QColor(0, 0, 0, 20));
|
||||
|
||||
// Zone divider line
|
||||
painter.setPen(QPen(QColor(255, 255, 255, 50), 1));
|
||||
painter.drawLine(QPointF(stackDividerX, playArea.top()), QPointF(stackDividerX, playArea.bottom()));
|
||||
|
||||
// Land divider line (about 60% down the table area)
|
||||
const double landDividerY = playArea.top() + playArea.height() * 0.65;
|
||||
painter.setPen(QPen(QColor(255, 255, 255, 30), 1));
|
||||
painter.drawLine(QPointF(stackDividerX, landDividerY), QPointF(playArea.right(), landDividerY));
|
||||
|
||||
// Border around entire play area
|
||||
painter.setPen(QPen(QColor(70, 80, 95, 120), 1));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRoundedRect(playArea.adjusted(0, 0, -1, -1), 3, 3);
|
||||
|
||||
paintArtAttribution(painter, playArea, attributionText, Qt::AlignRight | Qt::AlignBottom, 0.8);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef COCKATRICE_PLAYMAT_PREVIEW_WIDGET_H
|
||||
#define COCKATRICE_PLAYMAT_PREVIEW_WIDGET_H
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
/**
|
||||
* @brief Preview widget that shows how a playmat card art will appear
|
||||
* across the combined table + stack play area.
|
||||
*
|
||||
* Renders a miniature mockup with the card art applied using the
|
||||
* given PlaymatParams, including faint zone divider lines.
|
||||
*/
|
||||
class PlaymatPreviewWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlaymatPreviewWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setPixmap(const QPixmap &pixmap);
|
||||
void setParams(const PlaymatParams ¶ms);
|
||||
void setAttribution(const QString &attribution);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
QPixmap sourcePixmap;
|
||||
PlaymatParams params;
|
||||
QString attributionText;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PLAYMAT_PREVIEW_WIDGET_H
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
#include "playmat_settings_dialog.h"
|
||||
|
||||
#include "../../card_picture_loader/card_picture_loader.h"
|
||||
#include "../cards/art_crop_attribution.h"
|
||||
#include "../utility/completer_utils.h"
|
||||
#include "card_database_display_model.h"
|
||||
#include "card_database_model.h"
|
||||
#include "playmat_preview_widget.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCompleter>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
||||
PlaymatSettingsDialog::PlaymatSettingsDialog(const CardRef &initialCard,
|
||||
const PlaymatParams &initialParams,
|
||||
QWidget *parent)
|
||||
: QDialog(parent), currentCard(initialCard), currentParams(initialParams)
|
||||
{
|
||||
setMinimumWidth(500);
|
||||
setupUi();
|
||||
|
||||
// Seed UI from initial values
|
||||
if (!initialCard.name.isEmpty()) {
|
||||
searchBar->setText(initialCard.name);
|
||||
onCardNameChanged(initialCard.name);
|
||||
|
||||
// onCardNameChanged leaves the printing combo on the first printing in
|
||||
// the database, which would silently change the deck's stored playmat
|
||||
// card on accept. Restore the stored printing when it resolves locally.
|
||||
const int storedPrintingIndex = providerComboBox->findData(initialCard.providerId);
|
||||
if (storedPrintingIndex != -1) {
|
||||
providerComboBox->setCurrentIndex(storedPrintingIndex);
|
||||
} else {
|
||||
// Stored printing not in the local database: keep it rather than
|
||||
// silently substituting the first printing.
|
||||
currentCard.providerId = initialCard.providerId;
|
||||
reloadPreview();
|
||||
}
|
||||
}
|
||||
marginLSpin->setValue(initialParams.marginPctL);
|
||||
marginRSpin->setValue(initialParams.marginPctR);
|
||||
verticalOffsetSpin->setValue(initialParams.verticalOffset);
|
||||
zoomSpin->setValue(initialParams.zoom);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
CardRef PlaymatSettingsDialog::card() const
|
||||
{
|
||||
return currentCard;
|
||||
}
|
||||
|
||||
PlaymatParams PlaymatSettingsDialog::params() const
|
||||
{
|
||||
return currentParams;
|
||||
}
|
||||
|
||||
QDoubleSpinBox *PlaymatSettingsDialog::makeSpinBox(double min, double max, double value, double step)
|
||||
{
|
||||
auto *spin = new QDoubleSpinBox;
|
||||
spin->setRange(min, max);
|
||||
spin->setSingleStep(step);
|
||||
spin->setDecimals(3);
|
||||
spin->setValue(value);
|
||||
return spin;
|
||||
}
|
||||
|
||||
void PlaymatSettingsDialog::initializeSearchBar()
|
||||
{
|
||||
searchBar = new QLineEdit;
|
||||
|
||||
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
|
||||
cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this);
|
||||
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
|
||||
|
||||
const CardCompleterSetup cardSetup = createCardCompleter(cardDatabaseDisplayModel, this, 15);
|
||||
searchModel = cardSetup.searchModel;
|
||||
proxyModel = cardSetup.proxyModel;
|
||||
completer = cardSetup.completer;
|
||||
searchBar->setCompleter(completer);
|
||||
|
||||
connectCardCompleterSearch(searchBar, cardSetup);
|
||||
|
||||
connect(completer, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this,
|
||||
[this](const QString &completion) {
|
||||
if (searchBar->text() != completion) {
|
||||
searchBar->setText(completion);
|
||||
searchBar->setCursorPosition(searchBar->text().length());
|
||||
}
|
||||
onCardNameChanged(completion);
|
||||
});
|
||||
|
||||
connect(searchBar, &QLineEdit::returnPressed, this, [this]() { onCardNameChanged(searchBar->text()); });
|
||||
}
|
||||
|
||||
void PlaymatSettingsDialog::setupUi()
|
||||
{
|
||||
initializeSearchBar();
|
||||
|
||||
providerComboBox = new QComboBox;
|
||||
connect(providerComboBox, &QComboBox::currentIndexChanged, this, [this]() {
|
||||
currentCard.providerId = providerComboBox->currentData().toString();
|
||||
reloadPreview();
|
||||
onParamChanged();
|
||||
});
|
||||
|
||||
marginLSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctL, 0.01);
|
||||
marginRSpin = makeSpinBox(0.0, 0.95, currentParams.marginPctR, 0.01);
|
||||
verticalOffsetSpin = makeSpinBox(0.0, 1.0, currentParams.verticalOffset, 0.01);
|
||||
zoomSpin = makeSpinBox(0.1, 4.0, currentParams.zoom, 0.05);
|
||||
|
||||
auto *form = new QFormLayout;
|
||||
cardNameLabel = new QLabel;
|
||||
printingLabel = new QLabel;
|
||||
leftMarginLabel = new QLabel;
|
||||
rightMarginLabel = new QLabel;
|
||||
verticalOffsetLabel = new QLabel;
|
||||
zoomLabel = new QLabel;
|
||||
form->addRow(cardNameLabel, searchBar);
|
||||
form->addRow(printingLabel, providerComboBox);
|
||||
form->addRow(leftMarginLabel, marginLSpin);
|
||||
form->addRow(rightMarginLabel, marginRSpin);
|
||||
form->addRow(verticalOffsetLabel, verticalOffsetSpin);
|
||||
form->addRow(zoomLabel, zoomSpin);
|
||||
|
||||
controlsGroup = new QGroupBox;
|
||||
controlsGroup->setLayout(form);
|
||||
|
||||
preview = new PlaymatPreviewWidget;
|
||||
|
||||
auto *previewLayout = new QVBoxLayout;
|
||||
previewLayout->addWidget(preview);
|
||||
previewGroup = new QGroupBox;
|
||||
previewGroup->setLayout(previewLayout);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
removeButton = new QPushButton;
|
||||
buttons->addButton(removeButton, QDialogButtonBox::ResetRole);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(removeButton, &QPushButton::clicked, this, [this]() {
|
||||
currentCard = CardRef{}; // empty signals removal
|
||||
accept();
|
||||
});
|
||||
|
||||
auto *root = new QVBoxLayout;
|
||||
root->addWidget(controlsGroup);
|
||||
root->addWidget(previewGroup);
|
||||
root->addWidget(buttons);
|
||||
setLayout(root);
|
||||
|
||||
connect(marginLSpin, &QDoubleSpinBox::valueChanged, this, &PlaymatSettingsDialog::onParamChanged);
|
||||
connect(marginRSpin, &QDoubleSpinBox::valueChanged, this, &PlaymatSettingsDialog::onParamChanged);
|
||||
connect(verticalOffsetSpin, &QDoubleSpinBox::valueChanged, this, &PlaymatSettingsDialog::onParamChanged);
|
||||
connect(zoomSpin, &QDoubleSpinBox::valueChanged, this, &PlaymatSettingsDialog::onParamChanged);
|
||||
}
|
||||
|
||||
void PlaymatSettingsDialog::populateProviderCombo(const QString &cardName)
|
||||
{
|
||||
providerComboBox->clear();
|
||||
|
||||
auto card = CardDatabaseManager::query()->getCard({cardName});
|
||||
|
||||
const auto &sets = card.getInfo().getSets();
|
||||
|
||||
for (const auto &printings : sets) {
|
||||
for (const auto &p : printings) {
|
||||
QString setName = p.getSet()->getLongName();
|
||||
QString collector = p.getProperty("num");
|
||||
QString uuid = p.getUuid();
|
||||
|
||||
QString label = setName;
|
||||
if (!collector.isEmpty()) {
|
||||
label += " #" + collector;
|
||||
}
|
||||
|
||||
providerComboBox->addItem(label, uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlaymatSettingsDialog::onCardNameChanged(const QString &name)
|
||||
{
|
||||
if (name.isEmpty()) {
|
||||
currentPixmap = QPixmap();
|
||||
preview->setPixmap(currentPixmap);
|
||||
return;
|
||||
}
|
||||
|
||||
const ExactCard card = CardDatabaseManager::query()->getCard({name});
|
||||
if (!card) {
|
||||
currentPixmap = QPixmap();
|
||||
preview->setPixmap(currentPixmap);
|
||||
providerComboBox->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
currentCard.name = name;
|
||||
|
||||
populateProviderCombo(name);
|
||||
|
||||
if (providerComboBox->count() == 0) {
|
||||
currentPixmap = QPixmap();
|
||||
preview->setPixmap(currentPixmap);
|
||||
currentCard.providerId.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
currentCard.providerId = providerComboBox->currentData().toString();
|
||||
reloadPreview();
|
||||
}
|
||||
|
||||
void PlaymatSettingsDialog::reloadPreview()
|
||||
{
|
||||
if (currentCard.name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExactCard card = CardDatabaseManager::query()->getCard({currentCard.name, currentCard.providerId});
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect(pixmapUpdatedConnection);
|
||||
|
||||
QPixmap fullRes;
|
||||
CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
|
||||
|
||||
if (fullRes.isNull()) {
|
||||
CardInfo *cardInfo = card.getCardPtr().data();
|
||||
if (cardInfo) {
|
||||
pixmapUpdatedConnection = connect(cardInfo, &CardInfo::pixmapUpdated, this, [this]() { reloadPreview(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
currentPixmap = fullRes;
|
||||
preview->setPixmap(currentPixmap);
|
||||
preview->setParams(currentParams);
|
||||
preview->setAttribution(buildArtAttribution(card));
|
||||
}
|
||||
|
||||
void PlaymatSettingsDialog::onParamChanged()
|
||||
{
|
||||
currentParams.marginPctL = marginLSpin->value();
|
||||
currentParams.marginPctR = marginRSpin->value();
|
||||
currentParams.verticalOffset = verticalOffsetSpin->value();
|
||||
currentParams.zoom = zoomSpin->value();
|
||||
preview->setParams(currentParams);
|
||||
}
|
||||
|
||||
void PlaymatSettingsDialog::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Playmat Settings"));
|
||||
searchBar->setPlaceholderText(tr("Type a card name..."));
|
||||
cardNameLabel->setText(tr("Card name:"));
|
||||
printingLabel->setText(tr("Printing:"));
|
||||
leftMarginLabel->setText(tr("Left margin (%):"));
|
||||
rightMarginLabel->setText(tr("Right margin (%):"));
|
||||
verticalOffsetLabel->setText(tr("Vertical offset:"));
|
||||
zoomLabel->setText(tr("Zoom:"));
|
||||
controlsGroup->setTitle(tr("Parameters"));
|
||||
previewGroup->setTitle(tr("Preview"));
|
||||
removeButton->setText(tr("Remove Playmat"));
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef COCKATRICE_PLAYMAT_SETTINGS_DIALOG_H
|
||||
#define COCKATRICE_PLAYMAT_SETTINGS_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPixmap>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class QComboBox;
|
||||
class QCompleter;
|
||||
class QDoubleSpinBox;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class CardDatabaseModel;
|
||||
class CardDatabaseDisplayModel;
|
||||
class CardSearchModel;
|
||||
class CardCompleterProxyModel;
|
||||
class PlaymatPreviewWidget;
|
||||
|
||||
/**
|
||||
* @brief Dialog for configuring the playmat card art for a deck.
|
||||
*
|
||||
* Allows the user to select a card from the database and adjust
|
||||
* positioning parameters (margins, zoom, vertical offset) for how
|
||||
* the card art appears as a playmat background across the
|
||||
* combined table + stack play area.
|
||||
*/
|
||||
class PlaymatSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlaymatSettingsDialog(const CardRef &initialCard = {},
|
||||
const PlaymatParams &initialParams = {},
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
CardRef card() const;
|
||||
PlaymatParams params() const;
|
||||
|
||||
private slots:
|
||||
void onCardNameChanged(const QString &name);
|
||||
void reloadPreview();
|
||||
void onParamChanged();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void populateProviderCombo(const QString &cardName);
|
||||
void initializeSearchBar();
|
||||
void retranslateUi();
|
||||
QDoubleSpinBox *makeSpinBox(double min, double max, double value, double step);
|
||||
|
||||
QLineEdit *searchBar;
|
||||
QCompleter *completer;
|
||||
CardDatabaseModel *cardDatabaseModel;
|
||||
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
|
||||
CardSearchModel *searchModel;
|
||||
CardCompleterProxyModel *proxyModel;
|
||||
|
||||
QComboBox *providerComboBox;
|
||||
|
||||
QMetaObject::Connection pixmapUpdatedConnection;
|
||||
|
||||
QLabel *cardNameLabel;
|
||||
QLabel *printingLabel;
|
||||
QLabel *leftMarginLabel;
|
||||
QLabel *rightMarginLabel;
|
||||
QLabel *verticalOffsetLabel;
|
||||
QLabel *zoomLabel;
|
||||
QGroupBox *controlsGroup;
|
||||
QGroupBox *previewGroup;
|
||||
QPushButton *removeButton;
|
||||
|
||||
QDoubleSpinBox *marginLSpin;
|
||||
QDoubleSpinBox *marginRSpin;
|
||||
QDoubleSpinBox *verticalOffsetSpin;
|
||||
QDoubleSpinBox *zoomSpin;
|
||||
PlaymatPreviewWidget *preview;
|
||||
|
||||
QPixmap currentPixmap;
|
||||
CardRef currentCard;
|
||||
PlaymatParams currentParams;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PLAYMAT_SETTINGS_DIALOG_H
|
||||
69
cockatrice/src/interface/widgets/playmat/playmat_utils.h
Normal file
69
cockatrice/src/interface/widgets/playmat/playmat_utils.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef COCKATRICE_PLAYMAT_UTILS_H
|
||||
#define COCKATRICE_PLAYMAT_UTILS_H
|
||||
|
||||
#include <QRectF>
|
||||
#include <QSize>
|
||||
#include <QSizeF>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
/**
|
||||
* @brief Computes the source region of the full-resolution card image to use as a playmat.
|
||||
*
|
||||
* Parameters are relative to the full card image: horizontal margins trim the card
|
||||
* borders, the vertical offset positions a square viewing window, and zoom scales
|
||||
* into that window. The result is clamped to the card image bounds.
|
||||
*
|
||||
* @param fullCardSize Size of the full card image.
|
||||
* @param params Positioning parameters.
|
||||
* @return Source rectangle in full-card image pixel coordinates.
|
||||
*/
|
||||
inline QRectF computeArtSourceRect(const QSize &fullCardSize, const PlaymatParams ¶ms)
|
||||
{
|
||||
const qreal srcW = fullCardSize.width();
|
||||
const qreal srcH = fullCardSize.height();
|
||||
|
||||
const qreal marginL = params.marginPctL * srcW;
|
||||
const qreal marginR = params.marginPctR * srcW;
|
||||
// Guard against margins summing to >= 1 (both are individually in range),
|
||||
// which would otherwise make the viewing window negative or zero.
|
||||
const qreal visibleW = qMax(0.0, srcW - marginL - marginR);
|
||||
const qreal visibleH = visibleW; // square viewing window, keeps art unskewed
|
||||
|
||||
const qreal vCenter = params.verticalOffset * srcH;
|
||||
qreal srcY = vCenter - visibleH / 2.0;
|
||||
srcY = qBound(0.0, srcY, srcH - visibleH);
|
||||
|
||||
// Guard the zoom divisor; everything that produces params clamps zoom to
|
||||
// [0.1, 4.0] already, this keeps the render path self-contained.
|
||||
const qreal zoom = qBound(0.1, params.zoom, 4.0);
|
||||
const qreal zoomedW = visibleW / zoom;
|
||||
const qreal zoomedH = visibleH / zoom;
|
||||
const qreal zoomedX = marginL + (visibleW - zoomedW) / 2.0;
|
||||
const qreal zoomedY = srcY + (visibleH - zoomedH) / 2.0;
|
||||
|
||||
return QRectF(zoomedX, zoomedY, zoomedW, zoomedH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the destination rectangle that fits a source of the given aspect
|
||||
* ratio into dstArea using "cover" semantics (no distortion, overflows cropped).
|
||||
*
|
||||
* @param dstArea Area to fill.
|
||||
* @param srcSize Size of the source; only its aspect ratio matters.
|
||||
* @return Destination rectangle centered in dstArea.
|
||||
*/
|
||||
inline QRectF coverFitRect(const QRectF &dstArea, const QSizeF &srcSize)
|
||||
{
|
||||
const qreal srcAspect = srcSize.width() / srcSize.height();
|
||||
const qreal dstAspect = dstArea.width() / dstArea.height();
|
||||
|
||||
if (srcAspect > dstAspect) {
|
||||
const qreal dstW = dstArea.height() * srcAspect;
|
||||
return QRectF(dstArea.left() + (dstArea.width() - dstW) / 2.0, dstArea.top(), dstW, dstArea.height());
|
||||
}
|
||||
|
||||
const qreal dstH = dstArea.width() / srcAspect;
|
||||
return QRectF(dstArea.left(), dstArea.top() + (dstArea.height() - dstH) / 2.0, dstArea.width(), dstH);
|
||||
}
|
||||
|
||||
#endif // COCKATRICE_PLAYMAT_UTILS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue