mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-29 09:22:18 -07:00
[Game] Playmats
Took 19 seconds Took 1 minute
This commit is contained in:
parent
74a454552a
commit
92c8f1a1a1
29 changed files with 967 additions and 12 deletions
|
|
@ -240,6 +240,8 @@ set(cockatrice_SOURCES
|
||||||
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
|
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
|
||||||
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
|
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
|
||||||
src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
|
src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
|
||||||
|
src/interface/widgets/playmat/playmat_preview_widget.cpp
|
||||||
|
src/interface/widgets/playmat/playmat_settings_dialog.cpp
|
||||||
src/interface/widgets/quick_settings/settings_button_widget.cpp
|
src/interface/widgets/quick_settings/settings_button_widget.cpp
|
||||||
src/interface/widgets/quick_settings/settings_popup_widget.cpp
|
src/interface/widgets/quick_settings/settings_popup_widget.cpp
|
||||||
src/interface/widgets/replay/replay_manager.cpp
|
src/interface/widgets/replay/replay_manager.cpp
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,10 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
|
||||||
emit playerJoined(prop);
|
emit playerJoined(prop);
|
||||||
}
|
}
|
||||||
player->processPlayerInfo(playerInfo);
|
player->processPlayerInfo(playerInfo);
|
||||||
|
// Extract playmat from player properties for opponent display
|
||||||
|
if (prop.has_playmat_card_name()) {
|
||||||
|
player->setPlaymatFromProperties(prop);
|
||||||
|
}
|
||||||
if (player->getPlayerInfo()->getLocal()) {
|
if (player->getPlayerInfo()->getLocal()) {
|
||||||
emit localPlayerDeckSelected(player, playerId, playerInfo);
|
emit localPlayerDeckSelected(player, playerId, playerInfo);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -351,6 +355,11 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
|
||||||
const ServerInfo_PlayerProperties &prop = event.player_properties();
|
const ServerInfo_PlayerProperties &prop = event.player_properties();
|
||||||
emit playerPropertiesChanged(prop, eventPlayerId);
|
emit playerPropertiesChanged(prop, eventPlayerId);
|
||||||
|
|
||||||
|
// Update playmat from player properties
|
||||||
|
if (prop.has_playmat_card_name()) {
|
||||||
|
player->setPlaymatFromProperties(prop);
|
||||||
|
}
|
||||||
|
|
||||||
const auto contextType = static_cast<GameEventContext::ContextType>(getPbExtension(context));
|
const auto contextType = static_cast<GameEventContext::ContextType>(getPbExtension(context));
|
||||||
switch (contextType) {
|
switch (contextType) {
|
||||||
case GameEventContext::READY_START: {
|
case GameEventContext::READY_START: {
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,23 @@ void PlayerLogic::setDeck(const DeckList &_deck)
|
||||||
emit deckChanged();
|
emit deckChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerLogic::setPlaymatFromProperties(const ServerInfo_PlayerProperties &props)
|
||||||
|
{
|
||||||
|
if (props.has_playmat_card_name() && !props.playmat_card_name().empty()) {
|
||||||
|
remotePlaymatCard = {QString::fromStdString(props.playmat_card_name()),
|
||||||
|
QString::fromStdString(props.playmat_card_provider_id())};
|
||||||
|
remotePlaymatParams = {
|
||||||
|
qBound(0.0, props.playmat_margin_pct_l(), 0.95), qBound(0.0, props.playmat_margin_pct_r(), 0.95),
|
||||||
|
qBound(0.0, props.playmat_vertical_offset(), 1.0), qBound(0.1, props.playmat_zoom(), 4.0)};
|
||||||
|
hasRemotePlaymat = true;
|
||||||
|
} else {
|
||||||
|
remotePlaymatCard = CardRef{};
|
||||||
|
remotePlaymatParams = PlaymatParams{};
|
||||||
|
hasRemotePlaymat = false;
|
||||||
|
}
|
||||||
|
emit playmatChanged();
|
||||||
|
}
|
||||||
|
|
||||||
CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter)
|
CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter)
|
||||||
{
|
{
|
||||||
return addCounter(counter.id(), QString::fromStdString(counter.name()),
|
return addCounter(counter.id(), QString::fromStdString(counter.name()),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include "../zones/table_zone_logic.h"
|
#include "../zones/table_zone_logic.h"
|
||||||
#include "player_event_handler.h"
|
#include "player_event_handler.h"
|
||||||
#include "player_info.h"
|
#include "player_info.h"
|
||||||
|
#include "player_manager.h"
|
||||||
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
|
@ -72,6 +73,8 @@ signals:
|
||||||
const QList<const ServerInfo_Card *> &cardList,
|
const QList<const ServerInfo_Card *> &cardList,
|
||||||
bool withWritePermission);
|
bool withWritePermission);
|
||||||
void deckChanged();
|
void deckChanged();
|
||||||
|
/** @brief Emitted when the remote playmat (card/params) is updated from player properties. */
|
||||||
|
void playmatChanged();
|
||||||
void newCardAdded(AbstractCardItem *card);
|
void newCardAdded(AbstractCardItem *card);
|
||||||
void requestCardMenuUpdate(const CardItem *card);
|
void requestCardMenuUpdate(const CardItem *card);
|
||||||
void counterAdded(CounterState *state);
|
void counterAdded(CounterState *state);
|
||||||
|
|
@ -226,6 +229,20 @@ public:
|
||||||
|
|
||||||
void setZoneId(int _zoneId);
|
void setZoneId(int _zoneId);
|
||||||
|
|
||||||
|
void setPlaymatFromProperties(const ServerInfo_PlayerProperties &props);
|
||||||
|
const CardRef &getRemotePlaymatCard() const
|
||||||
|
{
|
||||||
|
return remotePlaymatCard;
|
||||||
|
}
|
||||||
|
const PlaymatParams &getRemotePlaymatParams() const
|
||||||
|
{
|
||||||
|
return remotePlaymatParams;
|
||||||
|
}
|
||||||
|
bool getHasRemotePlaymat() const
|
||||||
|
{
|
||||||
|
return hasRemotePlaymat;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AbstractGame *game;
|
AbstractGame *game;
|
||||||
PlayerInfo *playerInfo;
|
PlayerInfo *playerInfo;
|
||||||
|
|
@ -243,6 +260,11 @@ private:
|
||||||
|
|
||||||
bool dialogSemaphore;
|
bool dialogSemaphore;
|
||||||
QList<CardItem *> cardsToDelete;
|
QList<CardItem *> cardsToDelete;
|
||||||
|
|
||||||
|
// Playmat from player properties (for opponent display)
|
||||||
|
CardRef remotePlaymatCard;
|
||||||
|
PlaymatParams remotePlaymatParams;
|
||||||
|
bool hasRemotePlaymat = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AnnotationDialog : public QInputDialog
|
class AnnotationDialog : public QInputDialog
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
#include "player_graphics_item.h"
|
#include "player_graphics_item.h"
|
||||||
|
|
||||||
#include "../../game/player/player_actions.h"
|
#include "../../game/player/player_actions.h"
|
||||||
|
#include "../../interface/card_picture_loader/card_picture_loader.h"
|
||||||
|
#include "../../interface/widgets/cards/art_crop_attribution.h"
|
||||||
|
#include "../../interface/widgets/playmat/playmat_utils.h"
|
||||||
#include "../../interface/widgets/tabs/tab_game.h"
|
#include "../../interface/widgets/tabs/tab_game.h"
|
||||||
#include "../board/abstract_card_item.h"
|
#include "../board/abstract_card_item.h"
|
||||||
#include "../board/counter_general.h"
|
#include "../board/counter_general.h"
|
||||||
|
|
@ -13,6 +16,8 @@
|
||||||
#include "player_dialogs.h"
|
#include "player_dialogs.h"
|
||||||
|
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
#include <libcockatrice/settings/interface_settings.h>
|
#include <libcockatrice/settings/interface_settings.h>
|
||||||
|
|
||||||
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
||||||
|
|
@ -28,6 +33,10 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
||||||
|
|
||||||
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
|
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
|
||||||
connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved);
|
connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved);
|
||||||
|
connect(player, &PlayerLogic::deckChanged, this, &PlayerGraphicsItem::updatePlaymat);
|
||||||
|
connect(player, &PlayerLogic::playmatChanged, this, &PlayerGraphicsItem::updatePlaymat);
|
||||||
|
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::playmatVisibilityChanged, this,
|
||||||
|
[this](int) { updatePlaymat(); });
|
||||||
|
|
||||||
playerMenu = new PlayerMenu(this);
|
playerMenu = new PlayerMenu(this);
|
||||||
|
|
||||||
|
|
@ -67,6 +76,9 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
||||||
|
|
||||||
connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
|
connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
|
||||||
|
|
||||||
|
connect(this, &PlayerGraphicsItem::playmatChanged, tableZoneGraphicsItem, &TableZone::onPlaymatChanged);
|
||||||
|
connect(this, &PlayerGraphicsItem::playmatChanged, stackZoneGraphicsItem, &StackZone::onPlaymatChanged);
|
||||||
|
|
||||||
updateBoundingRect();
|
updateBoundingRect();
|
||||||
|
|
||||||
rearrangeZones();
|
rearrangeZones();
|
||||||
|
|
@ -155,10 +167,36 @@ qreal PlayerGraphicsItem::getMinimumWidth() const
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerGraphicsItem::paint(QPainter * /*painter*/,
|
void PlayerGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
|
||||||
const QStyleOptionGraphicsItem * /*option*/,
|
|
||||||
QWidget * /*widget*/)
|
|
||||||
{
|
{
|
||||||
|
if (!hasPlaymat || playmatPixmap.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the combined bounding rect of stack + table zones
|
||||||
|
QPointF stackPos = stackZoneGraphicsItem->pos();
|
||||||
|
QPointF tablePos = tableZoneGraphicsItem->pos();
|
||||||
|
QSizeF stackSize = stackZoneGraphicsItem->boundingRect().size();
|
||||||
|
QSizeF tableSize = tableZoneGraphicsItem->boundingRect().size();
|
||||||
|
|
||||||
|
// Combined area: from stack left edge to table right edge
|
||||||
|
double combinedLeft = qMin(stackPos.x(), tablePos.x());
|
||||||
|
double combinedTop = qMin(stackPos.y(), tablePos.y());
|
||||||
|
double combinedRight = qMax(stackPos.x() + stackSize.width(), tablePos.x() + tableSize.width());
|
||||||
|
double combinedBottom = qMax(stackPos.y() + stackSize.height(), tablePos.y() + tableSize.height());
|
||||||
|
|
||||||
|
QRectF combinedArea(combinedLeft, combinedTop, combinedRight - combinedLeft, combinedBottom - combinedTop);
|
||||||
|
|
||||||
|
const QRectF srcRect = computeArtSourceRect(playmatPixmap.size(), playmatParams);
|
||||||
|
const QRectF dstRect = coverFitRect(combinedArea, srcRect.size());
|
||||||
|
|
||||||
|
painter->setClipRect(combinedArea);
|
||||||
|
painter->drawPixmap(dstRect, playmatPixmap, srcRect);
|
||||||
|
painter->setClipping(false);
|
||||||
|
|
||||||
|
if (!playmatAttribution.isEmpty()) {
|
||||||
|
paintArtAttribution(*painter, combinedArea, playmatAttribution, Qt::AlignRight | Qt::AlignBottom, 0.8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
|
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
|
||||||
|
|
@ -303,3 +341,80 @@ void PlayerGraphicsItem::updateBoundingRect()
|
||||||
|
|
||||||
emit sizeChanged();
|
emit sizeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerGraphicsItem::updatePlaymat()
|
||||||
|
{
|
||||||
|
int visibility = SettingsCache::instance().userInterface().getPlaymatVisibility();
|
||||||
|
|
||||||
|
// "Don't use playmats" — never show
|
||||||
|
if (visibility == 0) {
|
||||||
|
clearPlaymat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardRef playmatCard;
|
||||||
|
PlaymatParams params;
|
||||||
|
|
||||||
|
// Check local deck first (for local player)
|
||||||
|
const DeckList &deck = player->getDeck();
|
||||||
|
if (!deck.getPlaymatCard().isEmpty()) {
|
||||||
|
playmatCard = deck.getPlaymatCard();
|
||||||
|
params = deck.getPlaymatParams();
|
||||||
|
} else if (visibility == 2 && player->getHasRemotePlaymat()) {
|
||||||
|
// "Show all" — also show remote playmats from opponents
|
||||||
|
playmatCard = player->getRemotePlaymatCard();
|
||||||
|
params = player->getRemotePlaymatParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playmatCard.isEmpty()) {
|
||||||
|
clearPlaymat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playmatParams = params;
|
||||||
|
|
||||||
|
ExactCard card = CardDatabaseManager::query()->getCard(playmatCard);
|
||||||
|
if (!card) {
|
||||||
|
clearPlaymat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playmatAttribution = buildArtAttribution(card);
|
||||||
|
|
||||||
|
QPixmap fullRes;
|
||||||
|
CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
|
||||||
|
|
||||||
|
if (fullRes.isNull()) {
|
||||||
|
disconnect(playmatPixmapConnection);
|
||||||
|
CardInfo *cardInfo = card.getCardPtr().data();
|
||||||
|
if (cardInfo) {
|
||||||
|
playmatPixmapConnection =
|
||||||
|
connect(cardInfo, &CardInfo::pixmapUpdated, this, &PlayerGraphicsItem::onPlaymatPixmapReady);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasPlaymat) {
|
||||||
|
hasPlaymat = true;
|
||||||
|
emit playmatChanged(true);
|
||||||
|
}
|
||||||
|
playmatPixmap = fullRes;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerGraphicsItem::clearPlaymat()
|
||||||
|
{
|
||||||
|
disconnect(playmatPixmapConnection);
|
||||||
|
playmatAttribution.clear();
|
||||||
|
if (hasPlaymat) {
|
||||||
|
hasPlaymat = false;
|
||||||
|
playmatPixmap = QPixmap();
|
||||||
|
emit playmatChanged(false);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerGraphicsItem::onPlaymatPixmapReady()
|
||||||
|
{
|
||||||
|
updatePlaymat();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "../game_scene.h"
|
#include "../game_scene.h"
|
||||||
|
|
||||||
#include <QGraphicsObject>
|
#include <QGraphicsObject>
|
||||||
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
|
|
||||||
class HandZone;
|
class HandZone;
|
||||||
class PileZone;
|
class PileZone;
|
||||||
|
|
@ -126,6 +127,7 @@ signals:
|
||||||
void playerCountChanged();
|
void playerCountChanged();
|
||||||
void mirroredChanged(bool isMirrored);
|
void mirroredChanged(bool isMirrored);
|
||||||
void cardInfoRequested(const CardRef &cardRef);
|
void cardInfoRequested(const CardRef &cardRef);
|
||||||
|
void playmatChanged(bool hasPlaymat);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PlayerLogic *player;
|
PlayerLogic *player;
|
||||||
|
|
@ -146,9 +148,18 @@ private:
|
||||||
bool mirrored;
|
bool mirrored;
|
||||||
bool handVisible = false;
|
bool handVisible = false;
|
||||||
|
|
||||||
|
QPixmap playmatPixmap;
|
||||||
|
PlaymatParams playmatParams;
|
||||||
|
QString playmatAttribution;
|
||||||
|
bool hasPlaymat = false;
|
||||||
|
QMetaObject::Connection playmatPixmapConnection;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateBoundingRect();
|
void updateBoundingRect();
|
||||||
void rearrangeZones();
|
void rearrangeZones();
|
||||||
|
void clearPlaymat();
|
||||||
|
void updatePlaymat();
|
||||||
|
void onPlaymatPixmapReady();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_PLAYER_GRAPHICS_ITEM_H
|
#endif // COCKATRICE_PLAYER_GRAPHICS_ITEM_H
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,19 @@ QRectF StackZone::boundingRect() const
|
||||||
|
|
||||||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||||
{
|
{
|
||||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
if (playmatActive) {
|
||||||
painter->fillRect(boundingRect(), brush);
|
// Subtle overlay to distinguish stack zone from table zone (slightly darker)
|
||||||
|
painter->fillRect(boundingRect(), QColor(0, 0, 0, 80));
|
||||||
|
} else {
|
||||||
|
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
||||||
|
painter->fillRect(boundingRect(), brush);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StackZone::onPlaymatChanged(bool active)
|
||||||
|
{
|
||||||
|
playmatActive = active;
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,13 @@ class StackZone : public SelectZone
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
qreal zoneHeight;
|
qreal zoneHeight;
|
||||||
|
bool playmatActive = false;
|
||||||
private slots:
|
private slots:
|
||||||
void updateBg();
|
void updateBg();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onPlaymatChanged(bool active);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent);
|
StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent);
|
||||||
/** @brief Resizes the stack zone height, e.g. when sharing vertical space with the command zone. */
|
/** @brief Resizes the stack zone height, e.g. when sharing vertical space with the command zone. */
|
||||||
|
|
|
||||||
|
|
@ -92,14 +92,19 @@ bool TableZone::isInverted() const
|
||||||
|
|
||||||
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||||
{
|
{
|
||||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, getLogic()->getPlayer()->getZoneId());
|
if (playmatActive) {
|
||||||
painter->fillRect(boundingRect(), brush);
|
// Subtle overlay to distinguish table zone from stack zone
|
||||||
|
painter->fillRect(boundingRect(), QColor(0, 0, 0, 60));
|
||||||
|
} else {
|
||||||
|
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, getLogic()->getPlayer()->getZoneId());
|
||||||
|
painter->fillRect(boundingRect(), brush);
|
||||||
|
}
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
paintZoneOutline(painter);
|
paintZoneOutline(painter);
|
||||||
} else {
|
} else {
|
||||||
// inactive player gets a darker table zone with a semi transparent black mask
|
// inactive player gets a darker table zone with a semi transparent black mask
|
||||||
// this means if the user provides a custom background it will fade
|
// this means if the user provides a custom background or playmat it will fade
|
||||||
painter->fillRect(boundingRect(), FADE_MASK);
|
painter->fillRect(boundingRect(), FADE_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +118,12 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
|
||||||
paintLandDivider(painter);
|
paintLandDivider(painter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TableZone::onPlaymatChanged(bool active)
|
||||||
|
{
|
||||||
|
playmatActive = active;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Render a soft outline around the edge of the TableZone.
|
Render a soft outline around the edge of the TableZone.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ private:
|
||||||
*/
|
*/
|
||||||
bool active = false;
|
bool active = false;
|
||||||
bool mirrored = false;
|
bool mirrored = false;
|
||||||
|
bool playmatActive = false;
|
||||||
|
|
||||||
[[nodiscard]] bool isInverted() const;
|
[[nodiscard]] bool isInverted() const;
|
||||||
|
|
||||||
|
|
@ -95,6 +96,9 @@ private slots:
|
||||||
*/
|
*/
|
||||||
void updateBg();
|
void updateBg();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onPlaymatChanged(bool active);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/**
|
/**
|
||||||
Reorganizes CardItems in the TableZone
|
Reorganizes CardItems in the TableZone
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "../../../client/settings/cache_settings.h"
|
#include "../../../client/settings/cache_settings.h"
|
||||||
#include "../../../client/settings/shortcuts_settings.h"
|
#include "../../../client/settings/shortcuts_settings.h"
|
||||||
|
#include "../playmat/playmat_settings_dialog.h"
|
||||||
#include "../settings_page/user_interface_settings_page.h"
|
#include "../settings_page/user_interface_settings_page.h"
|
||||||
#include "../tabs/api/commander_spellbook/commander_bracket_widget.h"
|
#include "../tabs/api/commander_spellbook/commander_bracket_widget.h"
|
||||||
#include "deck_list_style_proxy.h"
|
#include "deck_list_style_proxy.h"
|
||||||
|
|
@ -11,6 +12,7 @@
|
||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
|
|
@ -228,10 +230,18 @@ void DeckEditorDeckDockWidget::createDeckDock()
|
||||||
upperLayout->addWidget(bannerCardLabel, 4, 0);
|
upperLayout->addWidget(bannerCardLabel, 4, 0);
|
||||||
upperLayout->addWidget(bannerCardComboBox, 4, 1);
|
upperLayout->addWidget(bannerCardComboBox, 4, 1);
|
||||||
|
|
||||||
upperLayout->addWidget(deckTagsDisplayWidget, 5, 1);
|
playmatLabel = new QLabel();
|
||||||
|
playmatLabel->setObjectName("playmatLabel");
|
||||||
|
playmatLabel->setText(tr("Playmat"));
|
||||||
|
playmatSettingsButton = new QPushButton(tr("Edit Playmat..."));
|
||||||
|
connect(playmatSettingsButton, &QPushButton::clicked, this, &DeckEditorDeckDockWidget::openPlaymatSettings);
|
||||||
|
upperLayout->addWidget(playmatLabel, 5, 0);
|
||||||
|
upperLayout->addWidget(playmatSettingsButton, 5, 1);
|
||||||
|
|
||||||
upperLayout->addWidget(activeGroupCriteriaLabel, 6, 0);
|
upperLayout->addWidget(deckTagsDisplayWidget, 6, 1);
|
||||||
upperLayout->addWidget(activeGroupCriteriaComboBox, 6, 1);
|
|
||||||
|
upperLayout->addWidget(activeGroupCriteriaLabel, 7, 0);
|
||||||
|
upperLayout->addWidget(activeGroupCriteriaComboBox, 7, 1);
|
||||||
|
|
||||||
hashLabel1 = new QLabel();
|
hashLabel1 = new QLabel();
|
||||||
hashLabel1->setObjectName("hashLabel1");
|
hashLabel1->setObjectName("hashLabel1");
|
||||||
|
|
@ -440,6 +450,38 @@ void DeckEditorDeckDockWidget::writeBannerCard(int index)
|
||||||
deckStateManager->setBannerCard(bannerCard);
|
deckStateManager->setBannerCard(bannerCard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::openPlaymatSettings()
|
||||||
|
{
|
||||||
|
CardRef currentCard = deckStateManager->getMetadata().playmatCard;
|
||||||
|
PlaymatParams currentParams = deckStateManager->getMetadata().playmatParams;
|
||||||
|
|
||||||
|
PlaymatSettingsDialog dialog(currentCard, currentParams, this);
|
||||||
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
|
CardRef newCard = dialog.card();
|
||||||
|
PlaymatParams newParams = dialog.params();
|
||||||
|
|
||||||
|
if (newCard.isEmpty()) {
|
||||||
|
// User clicked "Remove Playmat"
|
||||||
|
deckStateManager->setPlaymatCard(CardRef{});
|
||||||
|
deckStateManager->setPlaymatParams(PlaymatParams{});
|
||||||
|
} else {
|
||||||
|
deckStateManager->setPlaymatCard(newCard);
|
||||||
|
deckStateManager->setPlaymatParams(newParams);
|
||||||
|
}
|
||||||
|
updatePlaymatLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckEditorDeckDockWidget::updatePlaymatLabel()
|
||||||
|
{
|
||||||
|
CardRef playmat = deckStateManager->getMetadata().playmatCard;
|
||||||
|
if (playmat.isEmpty()) {
|
||||||
|
playmatSettingsButton->setText(tr("Edit Playmat..."));
|
||||||
|
} else {
|
||||||
|
playmatSettingsButton->setText(tr("Edit Playmat (%1)").arg(playmat.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DeckEditorDeckDockWidget::applyActiveGroupCriteria()
|
void DeckEditorDeckDockWidget::applyActiveGroupCriteria()
|
||||||
{
|
{
|
||||||
getModel()->setActiveGroupCriteria(
|
getModel()->setActiveGroupCriteria(
|
||||||
|
|
@ -497,6 +539,7 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||||
syncBannerCardComboBoxSelectionWithDeck();
|
syncBannerCardComboBoxSelectionWithDeck();
|
||||||
updateBannerCardComboBox();
|
updateBannerCardComboBox();
|
||||||
bannerCardComboBox->blockSignals(false);
|
bannerCardComboBox->blockSignals(false);
|
||||||
|
updatePlaymatLabel();
|
||||||
updateHash();
|
updateHash();
|
||||||
|
|
||||||
formatComboBox->blockSignals(true);
|
formatComboBox->blockSignals(true);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <libcockatrice/card/card_info.h>
|
#include <libcockatrice/card/card_info.h>
|
||||||
|
#include <libcockatrice/deck_list/deck_list.h>
|
||||||
|
|
||||||
class CommanderBracketWidget;
|
class CommanderBracketWidget;
|
||||||
class DeckListModel;
|
class DeckListModel;
|
||||||
|
|
@ -33,6 +34,8 @@ public:
|
||||||
DeckListStyleProxy *proxy;
|
DeckListStyleProxy *proxy;
|
||||||
QTreeView *deckView;
|
QTreeView *deckView;
|
||||||
QComboBox *bannerCardComboBox;
|
QComboBox *bannerCardComboBox;
|
||||||
|
QLabel *playmatLabel;
|
||||||
|
QPushButton *playmatSettingsButton;
|
||||||
void createDeckDock();
|
void createDeckDock();
|
||||||
ExactCard getCurrentCard();
|
ExactCard getCurrentCard();
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
|
|
@ -102,6 +105,8 @@ private slots:
|
||||||
void writeName();
|
void writeName();
|
||||||
void writeComments();
|
void writeComments();
|
||||||
void writeBannerCard(int);
|
void writeBannerCard(int);
|
||||||
|
void openPlaymatSettings();
|
||||||
|
void updatePlaymatLabel();
|
||||||
void applyActiveGroupCriteria();
|
void applyActiveGroupCriteria();
|
||||||
void setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus);
|
void setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus);
|
||||||
void updateHash();
|
void updateHash();
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,32 @@ void DeckStateManager::setBannerCard(const CardRef &bannerCard)
|
||||||
doMetadataModified();
|
doMetadataModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeckStateManager::setPlaymatCard(const CardRef &playmatCard)
|
||||||
|
{
|
||||||
|
CardRef previous = deckList->getPlaymatCard();
|
||||||
|
if (previous == playmatCard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestHistorySave(tr("Set playmat card to %1 (%2)").arg(playmatCard.name).arg(playmatCard.providerId));
|
||||||
|
deckList->setPlaymatCard(playmatCard);
|
||||||
|
|
||||||
|
doMetadataModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckStateManager::setPlaymatParams(const PlaymatParams &playmatParams)
|
||||||
|
{
|
||||||
|
PlaymatParams previous = deckList->getPlaymatParams();
|
||||||
|
if (previous == playmatParams) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestHistorySave(tr("Updated playmat positioning"));
|
||||||
|
deckList->setPlaymatParams(playmatParams);
|
||||||
|
|
||||||
|
doMetadataModified();
|
||||||
|
}
|
||||||
|
|
||||||
void DeckStateManager::setTags(const QStringList &tags)
|
void DeckStateManager::setTags(const QStringList &tags)
|
||||||
{
|
{
|
||||||
QStringList previous = deckList->getTags();
|
QStringList previous = deckList->getTags();
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,8 @@ public:
|
||||||
void setName(const QString &name);
|
void setName(const QString &name);
|
||||||
void setComments(const QString &comments);
|
void setComments(const QString &comments);
|
||||||
void setBannerCard(const CardRef &bannerCard);
|
void setBannerCard(const CardRef &bannerCard);
|
||||||
|
void setPlaymatCard(const CardRef &playmatCard);
|
||||||
|
void setPlaymatParams(const PlaymatParams &playmatParams);
|
||||||
void setTags(const QStringList &tags);
|
void setTags(const QStringList &tags);
|
||||||
void setFormat(const QString &format);
|
void setFormat(const QString &format);
|
||||||
///@}
|
///@}
|
||||||
|
|
|
||||||
|
|
@ -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,256 @@
|
||||||
|
#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)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Playmat Settings"));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
searchBar->setPlaceholderText(tr("Type a card name..."));
|
||||||
|
|
||||||
|
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;
|
||||||
|
form->addRow(tr("Card name:"), searchBar);
|
||||||
|
form->addRow(tr("Printing:"), providerComboBox);
|
||||||
|
form->addRow(tr("Left margin (%):"), marginLSpin);
|
||||||
|
form->addRow(tr("Right margin (%):"), marginRSpin);
|
||||||
|
form->addRow(tr("Vertical offset:"), verticalOffsetSpin);
|
||||||
|
form->addRow(tr("Zoom:"), zoomSpin);
|
||||||
|
|
||||||
|
auto *controlsGroup = new QGroupBox(tr("Parameters"));
|
||||||
|
controlsGroup->setLayout(form);
|
||||||
|
|
||||||
|
preview = new PlaymatPreviewWidget;
|
||||||
|
|
||||||
|
auto *previewLayout = new QVBoxLayout;
|
||||||
|
previewLayout->addWidget(preview);
|
||||||
|
auto *previewGroup = new QGroupBox(tr("Preview"));
|
||||||
|
previewGroup->setLayout(previewLayout);
|
||||||
|
|
||||||
|
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||||
|
auto *removeBtn = new QPushButton(tr("Remove Playmat"));
|
||||||
|
buttons->addButton(removeBtn, QDialogButtonBox::ResetRole);
|
||||||
|
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
connect(removeBtn, &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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
#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 QLineEdit;
|
||||||
|
class QDoubleSpinBox;
|
||||||
|
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();
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -317,10 +317,24 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
||||||
&InterfaceSettings::setMinPlayersForMultiColumnLayout);
|
&InterfaceSettings::setMinPlayersForMultiColumnLayout);
|
||||||
minPlayersForMultiColumnLayoutLabel.setBuddy(&minPlayersForMultiColumnLayoutEdit);
|
minPlayersForMultiColumnLayoutLabel.setBuddy(&minPlayersForMultiColumnLayoutEdit);
|
||||||
|
|
||||||
|
playmatVisibilityCombo.addItem(tr("Show all playmats"), 2);
|
||||||
|
playmatVisibilityCombo.addItem(tr("Show own playmat only"), 1);
|
||||||
|
playmatVisibilityCombo.addItem(tr("Don't use playmats"), 0);
|
||||||
|
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
|
||||||
|
if (visIdx >= 0) {
|
||||||
|
playmatVisibilityCombo.setCurrentIndex(visIdx);
|
||||||
|
}
|
||||||
|
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||||
|
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
|
||||||
|
});
|
||||||
|
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
|
||||||
|
|
||||||
auto *tableGrid = new QGridLayout;
|
auto *tableGrid = new QGridLayout;
|
||||||
tableGrid->addWidget(&invertVerticalCoordinateCheckBox, 0, 0, 1, 2);
|
tableGrid->addWidget(&invertVerticalCoordinateCheckBox, 0, 0, 1, 2);
|
||||||
tableGrid->addWidget(&minPlayersForMultiColumnLayoutLabel, 1, 0, 1, 1);
|
tableGrid->addWidget(&minPlayersForMultiColumnLayoutLabel, 1, 0, 1, 1);
|
||||||
tableGrid->addWidget(&minPlayersForMultiColumnLayoutEdit, 1, 1, 1, 1);
|
tableGrid->addWidget(&minPlayersForMultiColumnLayoutEdit, 1, 1, 1, 1);
|
||||||
|
tableGrid->addWidget(&playmatVisibilityLabel, 2, 0, 1, 1);
|
||||||
|
tableGrid->addWidget(&playmatVisibilityCombo, 2, 1, 1, 1);
|
||||||
|
|
||||||
tableGroupBox = new QGroupBox;
|
tableGroupBox = new QGroupBox;
|
||||||
tableGroupBox->setLayout(tableGrid);
|
tableGroupBox->setLayout(tableGrid);
|
||||||
|
|
@ -489,4 +503,5 @@ void AppearanceSettingsPage::retranslateUi()
|
||||||
tableGroupBox->setTitle(tr("Table grid layout"));
|
tableGroupBox->setTitle(tr("Table grid layout"));
|
||||||
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
|
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
|
||||||
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
|
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
|
||||||
|
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +59,8 @@ private:
|
||||||
QCheckBox horizontalHandCheckBox;
|
QCheckBox horizontalHandCheckBox;
|
||||||
QCheckBox leftJustifiedHandCheckBox;
|
QCheckBox leftJustifiedHandCheckBox;
|
||||||
QCheckBox invertVerticalCoordinateCheckBox;
|
QCheckBox invertVerticalCoordinateCheckBox;
|
||||||
|
QLabel playmatVisibilityLabel;
|
||||||
|
QComboBox playmatVisibilityCombo;
|
||||||
QGroupBox *themeGroupBox;
|
QGroupBox *themeGroupBox;
|
||||||
QGroupBox *homeTabGroupBox;
|
QGroupBox *homeTabGroupBox;
|
||||||
QGroupBox *stylingGroupBox;
|
QGroupBox *stylingGroupBox;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ static const QString CURRENT_SIDEBOARD_PLAN_KEY = "";
|
||||||
|
|
||||||
bool DeckList::Metadata::isEmpty() const
|
bool DeckList::Metadata::isEmpty() const
|
||||||
{
|
{
|
||||||
return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty();
|
return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty() && playmatCard.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
DeckList::DeckList()
|
DeckList::DeckList()
|
||||||
|
|
@ -74,6 +74,36 @@ bool DeckList::readElement(QXmlStreamReader *xml)
|
||||||
QString providerId = xml->attributes().value("providerId").toString();
|
QString providerId = xml->attributes().value("providerId").toString();
|
||||||
QString cardName = xml->readElementText();
|
QString cardName = xml->readElementText();
|
||||||
metadata.bannerCard = {cardName, providerId};
|
metadata.bannerCard = {cardName, providerId};
|
||||||
|
} else if (childName == "playmatCard") {
|
||||||
|
QString providerId = xml->attributes().value("providerId").toString();
|
||||||
|
bool ok;
|
||||||
|
QString marginLStr = xml->attributes().value("marginPctL").toString();
|
||||||
|
QString marginRStr = xml->attributes().value("marginPctR").toString();
|
||||||
|
QString vOffStr = xml->attributes().value("verticalOffset").toString();
|
||||||
|
QString zoomStr = xml->attributes().value("zoom").toString();
|
||||||
|
QString cardName = xml->readElementText();
|
||||||
|
metadata.playmatCard = {cardName, providerId};
|
||||||
|
PlaymatParams params;
|
||||||
|
// Clamp to the same ranges as the settings dialog and the remote
|
||||||
|
// player-properties path so malformed deck files cannot produce
|
||||||
|
// degenerate art rectangles (e.g. a zoom of 0 dividing by zero).
|
||||||
|
params.marginPctL = qBound(0.0, marginLStr.toDouble(&ok), 0.95);
|
||||||
|
if (!ok) {
|
||||||
|
params.marginPctL = 0.07;
|
||||||
|
}
|
||||||
|
params.marginPctR = qBound(0.0, marginRStr.toDouble(&ok), 0.95);
|
||||||
|
if (!ok) {
|
||||||
|
params.marginPctR = 0.07;
|
||||||
|
}
|
||||||
|
params.verticalOffset = qBound(0.0, vOffStr.toDouble(&ok), 1.0);
|
||||||
|
if (!ok) {
|
||||||
|
params.verticalOffset = 0.33;
|
||||||
|
}
|
||||||
|
params.zoom = qBound(0.1, zoomStr.toDouble(&ok), 4.0);
|
||||||
|
if (!ok) {
|
||||||
|
params.zoom = 1.0;
|
||||||
|
}
|
||||||
|
metadata.playmatParams = params;
|
||||||
} else if (childName == "tags") {
|
} else if (childName == "tags") {
|
||||||
metadata.tags.clear(); // Clear existing tags
|
metadata.tags.clear(); // Clear existing tags
|
||||||
while (xml->readNextStartElement()) {
|
while (xml->readNextStartElement()) {
|
||||||
|
|
@ -104,6 +134,16 @@ static void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metad
|
||||||
xml->writeAttribute("providerId", metadata.bannerCard.providerId);
|
xml->writeAttribute("providerId", metadata.bannerCard.providerId);
|
||||||
xml->writeCharacters(metadata.bannerCard.name);
|
xml->writeCharacters(metadata.bannerCard.name);
|
||||||
xml->writeEndElement();
|
xml->writeEndElement();
|
||||||
|
if (!metadata.playmatCard.isEmpty()) {
|
||||||
|
xml->writeStartElement("playmatCard");
|
||||||
|
xml->writeAttribute("providerId", metadata.playmatCard.providerId);
|
||||||
|
xml->writeAttribute("marginPctL", QString::number(metadata.playmatParams.marginPctL, 'f', 4));
|
||||||
|
xml->writeAttribute("marginPctR", QString::number(metadata.playmatParams.marginPctR, 'f', 4));
|
||||||
|
xml->writeAttribute("verticalOffset", QString::number(metadata.playmatParams.verticalOffset, 'f', 4));
|
||||||
|
xml->writeAttribute("zoom", QString::number(metadata.playmatParams.zoom, 'f', 4));
|
||||||
|
xml->writeCharacters(metadata.playmatCard.name);
|
||||||
|
xml->writeEndElement();
|
||||||
|
}
|
||||||
xml->writeTextElement("comments", metadata.comments);
|
xml->writeTextElement("comments", metadata.comments);
|
||||||
|
|
||||||
// Write tags
|
// Write tags
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,34 @@ class QIODevice;
|
||||||
class QTextStream;
|
class QTextStream;
|
||||||
class InnerDecklistNode;
|
class InnerDecklistNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct PlaymatParams
|
||||||
|
* @ingroup Decks
|
||||||
|
* @brief Positioning parameters for a playmat card image.
|
||||||
|
*
|
||||||
|
* Controls how the cropped card art is positioned within the
|
||||||
|
* combined table+stack play area. The coordinate system is
|
||||||
|
* relative to the cropped art source image.
|
||||||
|
*/
|
||||||
|
struct PlaymatParams
|
||||||
|
{
|
||||||
|
double marginPctL = 0.07; ///< Left margin as fraction of card width (0.0–0.95).
|
||||||
|
double marginPctR = 0.07; ///< Right margin as fraction of card width (0.0–0.95).
|
||||||
|
double verticalOffset = 0.33; ///< Vertical position within card (0.0=top, 1.0=bottom).
|
||||||
|
double zoom = 1.0; ///< Scale factor (0.1–4.0).
|
||||||
|
|
||||||
|
bool operator==(const PlaymatParams &other) const
|
||||||
|
{
|
||||||
|
return qFuzzyCompare(marginPctL, other.marginPctL) && qFuzzyCompare(marginPctR, other.marginPctR) &&
|
||||||
|
qFuzzyCompare(verticalOffset, other.verticalOffset) && qFuzzyCompare(zoom, other.zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const PlaymatParams &other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class DeckList
|
* @class DeckList
|
||||||
* @ingroup Decks
|
* @ingroup Decks
|
||||||
|
|
@ -70,6 +98,8 @@ public:
|
||||||
CardRef bannerCard; ///< Optional representative card for the deck.
|
CardRef bannerCard; ///< Optional representative card for the deck.
|
||||||
QStringList tags; ///< User-defined tags for deck classification.
|
QStringList tags; ///< User-defined tags for deck classification.
|
||||||
QString lastLoadedTimestamp; ///< Timestamp string of last load.
|
QString lastLoadedTimestamp; ///< Timestamp string of last load.
|
||||||
|
CardRef playmatCard; ///< Optional card used as playmat background for table+stack zones.
|
||||||
|
PlaymatParams playmatParams; ///< Positioning parameters for the playmat card image.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Checks if all values (except for lastLoadedTimestamp) in the metadata is empty.
|
* @brief Checks if all values (except for lastLoadedTimestamp) in the metadata is empty.
|
||||||
|
|
@ -115,6 +145,14 @@ public:
|
||||||
{
|
{
|
||||||
metadata.bannerCard = _bannerCard;
|
metadata.bannerCard = _bannerCard;
|
||||||
}
|
}
|
||||||
|
void setPlaymatCard(const CardRef &_playmatCard = {})
|
||||||
|
{
|
||||||
|
metadata.playmatCard = _playmatCard;
|
||||||
|
}
|
||||||
|
void setPlaymatParams(const PlaymatParams &_playmatParams = {})
|
||||||
|
{
|
||||||
|
metadata.playmatParams = _playmatParams;
|
||||||
|
}
|
||||||
void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString())
|
void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString())
|
||||||
{
|
{
|
||||||
metadata.lastLoadedTimestamp = _lastLoadedTimestamp;
|
metadata.lastLoadedTimestamp = _lastLoadedTimestamp;
|
||||||
|
|
@ -170,6 +208,14 @@ public:
|
||||||
{
|
{
|
||||||
return metadata.bannerCard;
|
return metadata.bannerCard;
|
||||||
}
|
}
|
||||||
|
CardRef getPlaymatCard() const
|
||||||
|
{
|
||||||
|
return metadata.playmatCard;
|
||||||
|
}
|
||||||
|
PlaymatParams getPlaymatParams() const
|
||||||
|
{
|
||||||
|
return metadata.playmatParams;
|
||||||
|
}
|
||||||
QString getLastLoadedTimestamp() const
|
QString getLastLoadedTimestamp() const
|
||||||
{
|
{
|
||||||
return metadata.lastLoadedTimestamp;
|
return metadata.lastLoadedTimestamp;
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ public:
|
||||||
[[nodiscard]] virtual bool getLifeCounterAnimationsEnabled() const = 0;
|
[[nodiscard]] virtual bool getLifeCounterAnimationsEnabled() const = 0;
|
||||||
[[nodiscard]] virtual bool getBattlefieldFlashEnabled() const = 0;
|
[[nodiscard]] virtual bool getBattlefieldFlashEnabled() const = 0;
|
||||||
[[nodiscard]] virtual QStringList getUserListExpandedSections() const = 0;
|
[[nodiscard]] virtual QStringList getUserListExpandedSections() const = 0;
|
||||||
|
[[nodiscard]] virtual int getPlaymatVisibility() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_INTERFACE_INTERFACE_SETTINGS_PROVIDER_H
|
#endif // COCKATRICE_INTERFACE_INTERFACE_SETTINGS_PROVIDER_H
|
||||||
|
|
|
||||||
|
|
@ -1653,5 +1653,13 @@ void Server_AbstractPlayer::getPlayerProperties(ServerInfo_PlayerProperties &res
|
||||||
result.set_ready_start(readyStart);
|
result.set_ready_start(readyStart);
|
||||||
if (deck) {
|
if (deck) {
|
||||||
result.set_deck_hash(deck->getDeckHash().toStdString());
|
result.set_deck_hash(deck->getDeckHash().toStdString());
|
||||||
|
const auto &playmatCard = deck->getPlaymatCard();
|
||||||
|
const auto ¶ms = deck->getPlaymatParams();
|
||||||
|
result.set_playmat_card_name(playmatCard.name.toStdString());
|
||||||
|
result.set_playmat_card_provider_id(playmatCard.providerId.toStdString());
|
||||||
|
result.set_playmat_margin_pct_l(params.marginPctL);
|
||||||
|
result.set_playmat_margin_pct_r(params.marginPctR);
|
||||||
|
result.set_playmat_vertical_offset(params.verticalOffset);
|
||||||
|
result.set_playmat_zoom(params.zoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,14 @@ Server_Player::cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &r
|
||||||
Event_PlayerPropertiesChanged event;
|
Event_PlayerPropertiesChanged event;
|
||||||
event.mutable_player_properties()->set_sideboard_locked(true);
|
event.mutable_player_properties()->set_sideboard_locked(true);
|
||||||
event.mutable_player_properties()->set_deck_hash(deck->getDeckHash().toStdString());
|
event.mutable_player_properties()->set_deck_hash(deck->getDeckHash().toStdString());
|
||||||
|
const auto &playmatCard = deck->getPlaymatCard();
|
||||||
|
const auto ¶ms = deck->getPlaymatParams();
|
||||||
|
event.mutable_player_properties()->set_playmat_card_name(playmatCard.name.toStdString());
|
||||||
|
event.mutable_player_properties()->set_playmat_card_provider_id(playmatCard.providerId.toStdString());
|
||||||
|
event.mutable_player_properties()->set_playmat_margin_pct_l(params.marginPctL);
|
||||||
|
event.mutable_player_properties()->set_playmat_margin_pct_r(params.marginPctR);
|
||||||
|
event.mutable_player_properties()->set_playmat_vertical_offset(params.verticalOffset);
|
||||||
|
event.mutable_player_properties()->set_playmat_zoom(params.zoom);
|
||||||
ges.enqueueGameEvent(event, playerId);
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
|
||||||
Context_DeckSelect context;
|
Context_DeckSelect context;
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,10 @@ message ServerInfo_PlayerProperties {
|
||||||
optional sint32 ping_seconds = 7;
|
optional sint32 ping_seconds = 7;
|
||||||
optional bool sideboard_locked = 8;
|
optional bool sideboard_locked = 8;
|
||||||
optional bool judge = 9;
|
optional bool judge = 9;
|
||||||
|
optional string playmat_card_name = 10;
|
||||||
|
optional string playmat_card_provider_id = 11;
|
||||||
|
optional double playmat_margin_pct_l = 12 [default = 0.07];
|
||||||
|
optional double playmat_margin_pct_r = 13 [default = 0.07];
|
||||||
|
optional double playmat_vertical_offset = 14 [default = 0.33];
|
||||||
|
optional double playmat_zoom = 15 [default = 1.0];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,11 @@ bool InterfaceSettings::getShowGameSelectorFilterToolbar() const
|
||||||
return getValue("showGameSelectorFilterToolbar", QString(), QString(), true).toBool();
|
return getValue("showGameSelectorFilterToolbar", QString(), QString(), true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int InterfaceSettings::getPlaymatVisibility() const
|
||||||
|
{
|
||||||
|
return getValue("playmatvisibility", QString(), QString(), 2).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
bool InterfaceSettings::getLifeCounterAnimationsEnabled() const
|
bool InterfaceSettings::getLifeCounterAnimationsEnabled() const
|
||||||
{
|
{
|
||||||
return getValue("lifeCounterAnimationsEnabled", QString(), QString(), true).toBool();
|
return getValue("lifeCounterAnimationsEnabled", QString(), QString(), true).toBool();
|
||||||
|
|
@ -343,6 +348,15 @@ void InterfaceSettings::setShowGameSelectorFilterToolbar(bool _showGameSelectorF
|
||||||
emit showGameSelectorFilterToolbarChanged(_showGameSelectorFilterToolbar);
|
emit showGameSelectorFilterToolbarChanged(_showGameSelectorFilterToolbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InterfaceSettings::setPlaymatVisibility(int _visibility)
|
||||||
|
{
|
||||||
|
if (getPlaymatVisibility() == _visibility) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setValue(_visibility, "playmatvisibility");
|
||||||
|
emit playmatVisibilityChanged(_visibility);
|
||||||
|
}
|
||||||
|
|
||||||
void InterfaceSettings::setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled)
|
void InterfaceSettings::setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled)
|
||||||
{
|
{
|
||||||
setValue(_lifeCounterAnimationsEnabled, "lifeCounterAnimationsEnabled");
|
setValue(_lifeCounterAnimationsEnabled, "lifeCounterAnimationsEnabled");
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ public:
|
||||||
[[nodiscard]] bool getShowStatusBar() const override;
|
[[nodiscard]] bool getShowStatusBar() const override;
|
||||||
[[nodiscard]] bool getShowShortcuts() const override;
|
[[nodiscard]] bool getShowShortcuts() const override;
|
||||||
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const override;
|
[[nodiscard]] bool getShowGameSelectorFilterToolbar() const override;
|
||||||
|
[[nodiscard]] int getPlaymatVisibility() const override;
|
||||||
[[nodiscard]] bool getLifeCounterAnimationsEnabled() const override;
|
[[nodiscard]] bool getLifeCounterAnimationsEnabled() const override;
|
||||||
[[nodiscard]] bool getBattlefieldFlashEnabled() const override;
|
[[nodiscard]] bool getBattlefieldFlashEnabled() const override;
|
||||||
[[nodiscard]] QStringList getUserListExpandedSections() const override;
|
[[nodiscard]] QStringList getUserListExpandedSections() const override;
|
||||||
|
|
@ -77,6 +78,7 @@ public:
|
||||||
void setShowStatusBar(bool _showStatusBar);
|
void setShowStatusBar(bool _showStatusBar);
|
||||||
void setShowShortcuts(bool _showShortcuts);
|
void setShowShortcuts(bool _showShortcuts);
|
||||||
void setShowGameSelectorFilterToolbar(bool _showGameSelectorFilterToolbar);
|
void setShowGameSelectorFilterToolbar(bool _showGameSelectorFilterToolbar);
|
||||||
|
void setPlaymatVisibility(int _visibility);
|
||||||
void setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled);
|
void setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled);
|
||||||
void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled);
|
void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled);
|
||||||
void setUserListExpandedSections(const QStringList §ions);
|
void setUserListExpandedSections(const QStringList §ions);
|
||||||
|
|
@ -91,6 +93,7 @@ signals:
|
||||||
void tallyTypeChanged(int type);
|
void tallyTypeChanged(int type);
|
||||||
void showStatusBarChanged(bool state);
|
void showStatusBarChanged(bool state);
|
||||||
void showGameSelectorFilterToolbarChanged(bool state);
|
void showGameSelectorFilterToolbarChanged(bool state);
|
||||||
|
void playmatVisibilityChanged(int visibility);
|
||||||
void lifeCounterAnimationsEnabledChanged(bool state);
|
void lifeCounterAnimationsEnabledChanged(bool state);
|
||||||
void battlefieldFlashEnabledChanged(bool state);
|
void battlefieldFlashEnabledChanged(bool state);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue