mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-25 02:43:02 -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
|
|
@ -14,12 +14,15 @@
|
|||
#include <QMessageBox>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/playmat_resolver.h>
|
||||
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_ready_start.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_playmat.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
#include <libcockatrice/settings/visual_deck_storage_settings.h>
|
||||
#include <libcockatrice/utility/string_limits.h>
|
||||
|
||||
|
|
@ -100,6 +103,9 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
|
|||
connect(&SettingsCache::instance().visualDeckStorage(), &VisualDeckStorageSettings::visualDeckStorageInGameChanged,
|
||||
this, &DeckViewContainer::setVisualDeckStorageExists);
|
||||
|
||||
connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::playmatSettingsChanged, this,
|
||||
&DeckViewContainer::onPlaymatSettingsChanged);
|
||||
|
||||
switchToDeckSelectView();
|
||||
}
|
||||
|
||||
|
|
@ -277,6 +283,8 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath)
|
|||
|
||||
void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
|
||||
{
|
||||
currentDeck = deck;
|
||||
|
||||
QString deckString = deck.writeToString_Native();
|
||||
|
||||
if (deckString.length() > MAX_FILE_LENGTH) {
|
||||
|
|
@ -289,6 +297,52 @@ void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
|
|||
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished);
|
||||
parentGame->getGame()->getGameEventHandler()->sendGameCommand(pend, playerId);
|
||||
|
||||
resolveAndSendPlaymat();
|
||||
}
|
||||
|
||||
void DeckViewContainer::resolveAndSendPlaymat()
|
||||
{
|
||||
if (currentDeck.getCardRefList().isEmpty() && currentDeck.getPlaymat().card.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &settings = SettingsCache::instance().userInterface();
|
||||
const auto fallbackBehavior = static_cast<PlaymatFallbackMode>(settings.getPlaymatFallbackBehavior());
|
||||
|
||||
QList<PlaymatInfo> fallbackList = settings.getPlaymatFallbackList();
|
||||
|
||||
// In random mode with 2+ entries, remove the last-resolved mat to avoid repeats.
|
||||
if (fallbackBehavior == PlaymatFallbackModeRandom && fallbackList.size() > 1) {
|
||||
fallbackList.removeAll(lastResolvedPlaymat);
|
||||
}
|
||||
|
||||
const PlaymatInfo resolved =
|
||||
resolvePlaymatForDeck(currentDeck, fallbackList, static_cast<PlaymatMode>(settings.getPlaymatMode()),
|
||||
fallbackBehavior, playmatRotationIndex);
|
||||
|
||||
lastResolvedPlaymat = resolved;
|
||||
|
||||
Command_SetPlaymat playmatCmd;
|
||||
auto *pp = playmatCmd.mutable_playmat_params();
|
||||
pp->set_card_name(resolved.card.name.toStdString());
|
||||
pp->set_card_provider_id(resolved.card.providerId.toStdString());
|
||||
pp->set_margin_pct_l(resolved.params.marginPctL);
|
||||
pp->set_margin_pct_r(resolved.params.marginPctR);
|
||||
pp->set_vertical_offset(resolved.params.verticalOffset);
|
||||
pp->set_zoom(resolved.params.zoom);
|
||||
PendingCommand *playmatPend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(playmatCmd);
|
||||
parentGame->getGame()->getGameEventHandler()->sendGameCommand(playmatPend, playerId);
|
||||
}
|
||||
|
||||
void DeckViewContainer::onPlaymatSettingsChanged()
|
||||
{
|
||||
resolveAndSendPlaymat();
|
||||
}
|
||||
|
||||
void DeckViewContainer::advancePlaymatRotation()
|
||||
{
|
||||
playmatRotationIndex++;
|
||||
}
|
||||
|
||||
void DeckViewContainer::loadRemoteDeck()
|
||||
|
|
@ -379,6 +433,10 @@ void DeckViewContainer::sideboardPlanChanged()
|
|||
*/
|
||||
void DeckViewContainer::sendReadyStartCommand(bool ready)
|
||||
{
|
||||
if (ready) {
|
||||
resolveAndSendPlaymat();
|
||||
}
|
||||
|
||||
Command_ReadyStart cmd;
|
||||
cmd.set_ready(ready);
|
||||
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
|
||||
|
|
@ -416,6 +474,7 @@ void DeckViewContainer::setSideboardLocked(bool locked)
|
|||
|
||||
void DeckViewContainer::setDeck(const DeckList &deck)
|
||||
{
|
||||
currentDeck = deck;
|
||||
deckView->setDeck(deck);
|
||||
switchToDeckLoadedView();
|
||||
}
|
||||
|
|
@ -57,6 +57,9 @@ private:
|
|||
VisualDeckStorageWidget *visualDeckStorageWidget;
|
||||
TabGame *parentGame;
|
||||
int playerId;
|
||||
int playmatRotationIndex = 0; ///< Per-match cursor for round-robin playmat mode.
|
||||
DeckList currentDeck; ///< Cached deck for live settings re-resolution.
|
||||
PlaymatInfo lastResolvedPlaymat; ///< Tracks last sent playmat to avoid repeats in random mode.
|
||||
|
||||
void tryCreateVisualDeckStorageWidget();
|
||||
void sendReadyStartCommand(bool ready);
|
||||
|
|
@ -75,6 +78,7 @@ private slots:
|
|||
void sideboardLockButtonClicked();
|
||||
void updateSideboardLockButtonText();
|
||||
void refreshShortcuts();
|
||||
void onPlaymatSettingsChanged();
|
||||
signals:
|
||||
void newCardAdded(AbstractCardItem *card);
|
||||
void notIdle();
|
||||
|
|
@ -87,6 +91,8 @@ public:
|
|||
void setSideboardLocked(bool locked);
|
||||
void setDeck(const DeckList &deck);
|
||||
void setVisualDeckStorageExists(bool exists);
|
||||
void advancePlaymatRotation();
|
||||
void resolveAndSendPlaymat();
|
||||
|
||||
public slots:
|
||||
void loadDeckFromFile(const QString &filePath);
|
||||
|
|
|
|||
|
|
@ -252,17 +252,27 @@ void GameScene::adjustPlayerRotation(int rotationAdjustment)
|
|||
*/
|
||||
void GameScene::rearrange()
|
||||
{
|
||||
int firstPlayerIndex = 0;
|
||||
auto playersPlaying = collectActivePlayers(firstPlayerIndex);
|
||||
playersPlaying = rotatePlayers(playersPlaying, firstPlayerIndex);
|
||||
if (rearranging) {
|
||||
needsReArrange = true;
|
||||
return;
|
||||
}
|
||||
rearranging = true;
|
||||
do {
|
||||
needsReArrange = false;
|
||||
|
||||
int columns = determineColumnCount(playersPlaying.size());
|
||||
QSizeF sceneSize = computeSceneSizeAndPlayerLayout(playersPlaying, columns);
|
||||
int firstPlayerIndex = 0;
|
||||
auto playersPlaying = collectActivePlayers(firstPlayerIndex);
|
||||
playersPlaying = rotatePlayers(playersPlaying, firstPlayerIndex);
|
||||
|
||||
phasesToolbar->setHeight(sceneSize.height());
|
||||
setSceneRect(0, 0, sceneSize.width(), sceneSize.height());
|
||||
int columns = determineColumnCount(playersPlaying.size());
|
||||
QSizeF sceneSize = computeSceneSizeAndPlayerLayout(playersPlaying, columns);
|
||||
|
||||
processViewSizeChange(viewSize);
|
||||
phasesToolbar->setHeight(sceneSize.height());
|
||||
setSceneRect(0, 0, sceneSize.width(), sceneSize.height());
|
||||
|
||||
processViewSizeChange(viewSize);
|
||||
} while (needsReArrange);
|
||||
rearranging = false;
|
||||
}
|
||||
|
||||
// ---------- View Size ----------
|
||||
|
|
@ -459,8 +469,14 @@ void GameScene::resizeColumnsAndPlayers(const QList<qreal> &minWidthByColumn, qr
|
|||
qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size();
|
||||
qreal newx = phasesToolbar->getWidth();
|
||||
|
||||
for (int col = 0; col < playersByColumn.size(); ++col) {
|
||||
for (PlayerGraphicsItem *player : playersByColumn[col]) {
|
||||
// Snapshot the columns: resizing a player's table can synchronously trigger
|
||||
// GameScene::rearrange (table width -> sizeChanged -> updateBoundingRect ->
|
||||
// sizeChanged -> rearrange), and rearrange rebuilds playersByColumn. Iterating
|
||||
// the live container across that re-entrant call would use invalidated iterators.
|
||||
const QList<QList<PlayerGraphicsItem *>> columns = playersByColumn;
|
||||
|
||||
for (int col = 0; col < columns.size(); ++col) {
|
||||
for (PlayerGraphicsItem *player : columns[col]) {
|
||||
player->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn);
|
||||
player->setPos(newx, player->y());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ private:
|
|||
QBasicTimer *animationTimer; ///< Timer for scene animations
|
||||
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
|
||||
int playerRotation; ///< Rotation offset for player layout
|
||||
bool rearranging = false; ///< Guard against re-entrant rearrange
|
||||
bool needsReArrange = false; ///< Pending rearrange requested during a pass
|
||||
|
||||
/**
|
||||
* @brief Updates which card is currently hovered based on scene coordinates.
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ void GameView::startRubberBand(const QPointF &_selectionOrigin)
|
|||
}
|
||||
|
||||
selectionOrigin = _selectionOrigin;
|
||||
previousBandRect = QRect();
|
||||
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0)));
|
||||
rubberBand->show();
|
||||
}
|
||||
|
|
@ -128,7 +129,17 @@ void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount)
|
|||
|
||||
QPoint cursor = cursorPoint.toPoint();
|
||||
QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized();
|
||||
|
||||
rubberBand->setGeometry(rect);
|
||||
if (viewport()) {
|
||||
// Repaint the union of the previous and current band rects: the vacated
|
||||
// strip of a child widget is not reliably invalidated on all platforms
|
||||
// (notably macOS), leaving stale pixels under the selection.
|
||||
QRect dirty = previousBandRect.isNull() ? rect : previousBandRect.united(rect);
|
||||
dirty.adjust(-1, -1, 1, 1);
|
||||
viewport()->update(dirty);
|
||||
previousBandRect = rect;
|
||||
}
|
||||
|
||||
if (!SettingsCache::instance().userInterface().getShowDragSelectionCount()) {
|
||||
dragCountLabel->hide();
|
||||
|
|
@ -171,7 +182,13 @@ void GameView::stopRubberBand()
|
|||
return;
|
||||
}
|
||||
|
||||
// Same rationale as resizeRubberBand: repaint the last known band area
|
||||
// since hiding a child widget doesn't reliably invalidate its region.
|
||||
rubberBand->hide();
|
||||
if (viewport() && !previousBandRect.isNull()) {
|
||||
viewport()->update(previousBandRect.adjusted(-1, -1, 1, 1));
|
||||
previousBandRect = QRect();
|
||||
}
|
||||
dragCountLabel->hide();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ private:
|
|||
QWidget *tallyContainer;
|
||||
QGridLayout *tallyLayout;
|
||||
QPointF selectionOrigin;
|
||||
QRect previousBandRect; ///< Last rubber-band rect for targeted repaint
|
||||
QList<TallyRow> cachedTallyRows; ///< Cached entries to avoid redundant rebuilds
|
||||
|
||||
QSize rebuildTallyLabels(const QList<TallyRow> &entries);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#include "player_graphics_item.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 "../board/abstract_card_item.h"
|
||||
#include "../board/counter_general.h"
|
||||
|
|
@ -13,6 +16,9 @@
|
|||
#include "player_dialogs.h"
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/playmat_resolver.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
|
||||
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
||||
|
|
@ -28,6 +34,10 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
|||
|
||||
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
|
||||
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);
|
||||
|
||||
|
|
@ -67,6 +77,9 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
|||
|
||||
connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
|
||||
|
||||
connect(this, &PlayerGraphicsItem::playmatChanged, tableZoneGraphicsItem, &TableZone::onPlaymatChanged);
|
||||
connect(this, &PlayerGraphicsItem::playmatChanged, stackZoneGraphicsItem, &StackZone::onPlaymatChanged);
|
||||
|
||||
updateBoundingRect();
|
||||
|
||||
rearrangeZones();
|
||||
|
|
@ -112,7 +125,6 @@ void PlayerGraphicsItem::initializeZones()
|
|||
rfgZoneGraphicsItem->setPos(base + QPointF(0, 2 * h + h2 + 10));
|
||||
|
||||
tableZoneGraphicsItem = new TableZone(player->getTableZone(), mirrored, this);
|
||||
connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
|
||||
connect(this, &PlayerGraphicsItem::mirroredChanged, tableZoneGraphicsItem, &TableZone::setMirrored);
|
||||
|
||||
stackZoneGraphicsItem =
|
||||
|
|
@ -155,10 +167,61 @@ qreal PlayerGraphicsItem::getMinimumWidth() const
|
|||
return result;
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::paint(QPainter * /*painter*/,
|
||||
const QStyleOptionGraphicsItem * /*option*/,
|
||||
QWidget * /*widget*/)
|
||||
void PlayerGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
|
||||
{
|
||||
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->save();
|
||||
painter->setClipRect(combinedArea);
|
||||
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
// Render from a down-scaled copy of the art so the full-resolution source
|
||||
// pixmap is never re-sampled at a tiny device size (also much cheaper than
|
||||
// scaling it on every frame).
|
||||
const QPixmap scaledPixmap = scaledPlaymatFor(srcRect, painter->worldTransform().mapRect(dstRect).size());
|
||||
painter->drawPixmap(dstRect, scaledPixmap, QRectF(scaledPixmap.rect()));
|
||||
|
||||
painter->restore();
|
||||
|
||||
if (!playmatAttribution.isEmpty()) {
|
||||
paintArtAttribution(*painter, combinedArea, playmatAttribution, Qt::AlignRight | Qt::AlignBottom, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap PlayerGraphicsItem::scaledPlaymatFor(const QRectF &srcRect, const QSizeF &deviceDstSize)
|
||||
{
|
||||
// Bucket the render size so the source pixmap is re-scaled at most once per
|
||||
// zoom step instead of once per frame.
|
||||
constexpr int bucketSize = 32;
|
||||
const QSize target = QSize(qMax(1, qRound(deviceDstSize.width() / bucketSize) * bucketSize),
|
||||
qMax(1, qRound(deviceDstSize.height() / bucketSize) * bucketSize))
|
||||
.boundedTo(srcRect.toAlignedRect().size());
|
||||
|
||||
if (scaledPlaymatKey != target) {
|
||||
const QPixmap crop = playmatPixmap.copy(srcRect.toAlignedRect());
|
||||
scaledPlaymatPixmap = crop.scaled(target, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
scaledPlaymatKey = target;
|
||||
}
|
||||
return scaledPlaymatPixmap;
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
|
||||
|
|
@ -303,3 +366,100 @@ void PlayerGraphicsItem::updateBoundingRect()
|
|||
|
||||
emit sizeChanged();
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::updatePlaymat()
|
||||
{
|
||||
int visibility = SettingsCache::instance().userInterface().getPlaymatVisibility();
|
||||
|
||||
// "Don't use playmats" — never show
|
||||
if (visibility == PlaymatVisibilityNone) {
|
||||
clearPlaymat();
|
||||
return;
|
||||
}
|
||||
|
||||
// "Show own playmat only" — hide playmats for remote players
|
||||
if (visibility == PlaymatVisibilityOwnOnly && !player->getPlayerInfo()->getLocal()) {
|
||||
clearPlaymat();
|
||||
return;
|
||||
}
|
||||
|
||||
CardRef playmatCard;
|
||||
PlaymatParams params;
|
||||
|
||||
if (player->getHasRemotePlaymat()) {
|
||||
// Prefer the server-confirmed playmat (updated by Command_SetPlaymat).
|
||||
playmatCard = player->getRemotePlaymatCard();
|
||||
params = player->getRemotePlaymatParams();
|
||||
} else if (player->getPlayerInfo()->getLocal()) {
|
||||
// Local player without a server broadcast yet: apply the full
|
||||
// settings-based resolution chain (mode, fallback list, behavior).
|
||||
const auto &settings = SettingsCache::instance().userInterface();
|
||||
const PlaymatInfo resolved = resolvePlaymatForDeck(
|
||||
player->getDeck(), settings.getPlaymatFallbackList(), static_cast<PlaymatMode>(settings.getPlaymatMode()),
|
||||
static_cast<PlaymatFallbackMode>(settings.getPlaymatFallbackBehavior()), 0);
|
||||
playmatCard = resolved.card;
|
||||
params = resolved.params;
|
||||
} else {
|
||||
// Opponent without a server broadcast: use the deck-embedded playmat.
|
||||
const DeckList &deck = player->getDeck();
|
||||
const PlaymatInfo &deckPlaymat = deck.getPlaymat();
|
||||
if (!deckPlaymat.card.isEmpty()) {
|
||||
playmatCard = deckPlaymat.card;
|
||||
params = deckPlaymat.params;
|
||||
}
|
||||
}
|
||||
|
||||
if (playmatCard.isEmpty()) {
|
||||
clearPlaymat();
|
||||
return;
|
||||
}
|
||||
|
||||
playmatParams = params;
|
||||
scaledPlaymatKey = QSize(); // the art crop depends on the params, drop any cached scale
|
||||
|
||||
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();
|
||||
scaledPlaymatKey = QSize();
|
||||
emit playmatChanged(false);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::onPlaymatPixmapReady()
|
||||
{
|
||||
updatePlaymat();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "../game_scene.h"
|
||||
|
||||
#include <QGraphicsObject>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class HandZone;
|
||||
class PileZone;
|
||||
|
|
@ -126,6 +127,7 @@ signals:
|
|||
void playerCountChanged();
|
||||
void mirroredChanged(bool isMirrored);
|
||||
void cardInfoRequested(const CardRef &cardRef);
|
||||
void playmatChanged(bool hasPlaymat);
|
||||
|
||||
private:
|
||||
PlayerLogic *player;
|
||||
|
|
@ -146,9 +148,23 @@ private:
|
|||
bool mirrored;
|
||||
bool handVisible = false;
|
||||
|
||||
QPixmap playmatPixmap;
|
||||
QPixmap scaledPlaymatPixmap; // down-scaled copy of playmatPixmap for the current render size
|
||||
QSize scaledPlaymatKey; // size bucket scaledPlaymatPixmap was rendered for
|
||||
PlaymatParams playmatParams;
|
||||
QString playmatAttribution;
|
||||
bool hasPlaymat = false;
|
||||
QMetaObject::Connection playmatPixmapConnection;
|
||||
|
||||
private slots:
|
||||
void updateBoundingRect();
|
||||
void rearrangeZones();
|
||||
void clearPlaymat();
|
||||
void updatePlaymat();
|
||||
void onPlaymatPixmapReady();
|
||||
|
||||
private:
|
||||
QPixmap scaledPlaymatFor(const QRectF &srcRect, const QSizeF &deviceDstSize);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PLAYER_GRAPHICS_ITEM_H
|
||||
|
|
|
|||
|
|
@ -31,8 +31,22 @@ QRectF StackZone::boundingRect() const
|
|||
|
||||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
if (playmatActive) {
|
||||
// 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;
|
||||
// See TableZone::onPlaymatChanged for the rationale. Translucent overlay
|
||||
// over a dynamic playmat should not be held in the device cache.
|
||||
setCacheMode(active ? QGraphicsItem::NoCache : QGraphicsItem::DeviceCoordinateCache);
|
||||
update();
|
||||
}
|
||||
|
||||
void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
|
|
|
|||
|
|
@ -15,9 +15,13 @@ class StackZone : public SelectZone
|
|||
Q_OBJECT
|
||||
private:
|
||||
qreal zoneHeight;
|
||||
bool playmatActive = false;
|
||||
private slots:
|
||||
void updateBg();
|
||||
|
||||
public slots:
|
||||
void onPlaymatChanged(bool active);
|
||||
|
||||
public:
|
||||
StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent);
|
||||
/** @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*/)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, getLogic()->getPlayer()->getZoneId());
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
if (playmatActive) {
|
||||
// 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) {
|
||||
paintZoneOutline(painter);
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
@ -113,6 +118,17 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
|
|||
paintLandDivider(painter);
|
||||
}
|
||||
|
||||
void TableZone::onPlaymatChanged(bool active)
|
||||
{
|
||||
playmatActive = active;
|
||||
// While a playmat is shown the zone paints a translucent overlay over the
|
||||
// dynamic playmat behind it. Keep it out of the device cache so the cached
|
||||
// pixels are never stale relative to the playmat (and to avoid compositing
|
||||
// artifacts of cached translucent content on some platforms).
|
||||
setCacheMode(active ? QGraphicsItem::NoCache : QGraphicsItem::DeviceCoordinateCache);
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
Render a soft outline around the edge of the TableZone.
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ private:
|
|||
*/
|
||||
bool active = false;
|
||||
bool mirrored = false;
|
||||
bool playmatActive = false;
|
||||
|
||||
[[nodiscard]] bool isInverted() const;
|
||||
|
||||
|
|
@ -95,6 +96,9 @@ private slots:
|
|||
*/
|
||||
void updateBg();
|
||||
|
||||
public slots:
|
||||
void onPlaymatChanged(bool active);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
Reorganizes CardItems in the TableZone
|
||||
|
|
@ -184,8 +188,17 @@ public:
|
|||
}
|
||||
void setWidth(qreal _width)
|
||||
{
|
||||
// The width is stored as an int; truncate to match the previous implicit conversion.
|
||||
const int newWidth = static_cast<int>(_width);
|
||||
if (width == newWidth) {
|
||||
return;
|
||||
}
|
||||
prepareGeometryChange();
|
||||
width = _width;
|
||||
width = newWidth;
|
||||
// The parent player item's boundingRect (which clips the playmat painting) is
|
||||
// derived from this zone's size. Without this signal the playmat is cut off at
|
||||
// the stale boundingRect edge whenever the scene is resized wider.
|
||||
emit sizeChanged();
|
||||
}
|
||||
[[nodiscard]] qreal getWidth() const
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue