mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-29 09:22:18 -07:00
Compare commits
7 commits
813a3ea034
...
daa896866f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daa896866f | ||
|
|
404b0cdf28 | ||
|
|
1eca89d75a | ||
|
|
d93f63050c | ||
|
|
7971ebfe94 | ||
|
|
a40969003c | ||
|
|
2eca362e2b |
21 changed files with 423 additions and 105 deletions
|
|
@ -4,12 +4,14 @@
|
||||||
#include "../../client/settings/cache_settings.h"
|
#include "../../client/settings/cache_settings.h"
|
||||||
#include "../../game/player/player_actions.h"
|
#include "../../game/player/player_actions.h"
|
||||||
#include "../../game/player/player_logic.h"
|
#include "../../game/player/player_logic.h"
|
||||||
|
#include "../game_scene.h"
|
||||||
#include "../player/player_target.h"
|
#include "../player/player_target.h"
|
||||||
#include "../z_values.h"
|
#include "../z_values.h"
|
||||||
#include "../zones/card_zone.h"
|
#include "../zones/card_zone.h"
|
||||||
#include "card_item.h"
|
#include "card_item.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QGraphicsScene>
|
#include <QGraphicsScene>
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
@ -18,10 +20,27 @@
|
||||||
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
|
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
|
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <libcockatrice/settings/interface_settings.h>
|
#include <libcockatrice/settings/interface_settings.h>
|
||||||
#include <libcockatrice/utility/color.h>
|
#include <libcockatrice/utility/color.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr qreal kMinStrokeDurationMs = 200.0;
|
||||||
|
constexpr qreal kMaxStrokeDurationMs = 450.0;
|
||||||
|
constexpr qreal kMsPerPixel = 0.8;
|
||||||
|
constexpr qreal kGlowFadeDurationMs = 120.0;
|
||||||
|
constexpr qreal kSheenHalfWidth = 14.0;
|
||||||
|
|
||||||
|
/// @brief Ease-out cubic, for a natural "slow in / slow out" reveal.
|
||||||
|
qreal easeOutCubic(qreal t)
|
||||||
|
{
|
||||||
|
const qreal inverse = 1.0 - t;
|
||||||
|
return 1.0 - inverse * inverse * inverse;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
|
ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
|
||||||
: data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
|
: data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
|
||||||
{
|
{
|
||||||
|
|
@ -47,6 +66,13 @@ ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrowItem::~ArrowItem()
|
||||||
|
{
|
||||||
|
if (auto *scene = qobject_cast<GameScene *>(this->scene())) {
|
||||||
|
scene->unregisterAnimationItem(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ArrowItem::onTargetDestroyed()
|
void ArrowItem::onTargetDestroyed()
|
||||||
{
|
{
|
||||||
emit requestDeletion(data->creatorId, data->id);
|
emit requestDeletion(data->creatorId, data->id);
|
||||||
|
|
@ -91,16 +117,21 @@ void ArrowItem::updatePath(const QPointF &endPoint)
|
||||||
prepareGeometryChange();
|
prepareGeometryChange();
|
||||||
if (lineLength < 30) {
|
if (lineLength < 30) {
|
||||||
path = QPainterPath();
|
path = QPainterPath();
|
||||||
|
bodyPath = QPainterPath();
|
||||||
|
headPath = QPainterPath();
|
||||||
|
shaftOutlinePath = QPainterPath();
|
||||||
|
centerLine = QPainterPath();
|
||||||
|
headBaseFraction = 1.0;
|
||||||
} else {
|
} else {
|
||||||
QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength);
|
QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength);
|
||||||
|
|
||||||
QPainterPath centerLine;
|
centerLine = QPainterPath();
|
||||||
centerLine.moveTo(0, 0);
|
centerLine.moveTo(0, 0);
|
||||||
centerLine.quadTo(c, QPointF(lineLength, 0));
|
centerLine.quadTo(c, QPointF(lineLength, 0));
|
||||||
|
|
||||||
double percentage = 1 - headLength / lineLength;
|
headBaseFraction = 1 - headLength / lineLength;
|
||||||
QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage);
|
QPointF arrowBodyEndPoint = centerLine.pointAtPercent(headBaseFraction);
|
||||||
QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001));
|
QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(headBaseFraction + 0.001));
|
||||||
qreal alpha = testLine.angle() - 90;
|
qreal alpha = testLine.angle() - 90;
|
||||||
QPointF endPoint1 =
|
QPointF endPoint1 =
|
||||||
arrowBodyEndPoint + arrowWidth / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180));
|
arrowBodyEndPoint + arrowWidth / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180));
|
||||||
|
|
@ -111,20 +142,89 @@ void ArrowItem::updatePath(const QPointF &endPoint)
|
||||||
QPointF point2 =
|
QPointF point2 =
|
||||||
endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180));
|
endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180));
|
||||||
|
|
||||||
path = QPainterPath(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
|
QPointF start1 = -arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180));
|
||||||
|
QPointF start2 = arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180));
|
||||||
|
|
||||||
|
path = QPainterPath(start1);
|
||||||
path.quadTo(c, endPoint1);
|
path.quadTo(c, endPoint1);
|
||||||
path.lineTo(point1);
|
path.lineTo(point1);
|
||||||
path.lineTo(QPointF(lineLength, 0));
|
path.lineTo(QPointF(lineLength, 0));
|
||||||
path.lineTo(point2);
|
path.lineTo(point2);
|
||||||
path.lineTo(endPoint2);
|
path.lineTo(endPoint2);
|
||||||
path.quadTo(c, arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
|
path.quadTo(c, start2);
|
||||||
path.lineTo(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
|
path.lineTo(start1);
|
||||||
|
|
||||||
|
bodyPath = QPainterPath(start1);
|
||||||
|
bodyPath.quadTo(c, endPoint1);
|
||||||
|
bodyPath.lineTo(endPoint2);
|
||||||
|
bodyPath.quadTo(c, start2);
|
||||||
|
bodyPath.lineTo(start1);
|
||||||
|
|
||||||
|
headPath = QPainterPath(endPoint1);
|
||||||
|
headPath.lineTo(point1);
|
||||||
|
headPath.lineTo(QPointF(lineLength, 0));
|
||||||
|
headPath.lineTo(point2);
|
||||||
|
headPath.lineTo(endPoint2);
|
||||||
|
|
||||||
|
shaftOutlinePath = QPainterPath(start1);
|
||||||
|
shaftOutlinePath.quadTo(c, endPoint1);
|
||||||
|
shaftOutlinePath.moveTo(endPoint2);
|
||||||
|
shaftOutlinePath.quadTo(c, start2);
|
||||||
|
shaftOutlinePath.lineTo(start1);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPos(startPoint);
|
setPos(startPoint);
|
||||||
setTransform(QTransform().rotate(-line.angle()));
|
setTransform(QTransform().rotate(-line.angle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ArrowItem::startDrawAnimation()
|
||||||
|
{
|
||||||
|
if (!SettingsCache::instance().cardsDisplay().getArrowDrawAnimation() || centerLine.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
strokeDurationMs = qBound(kMinStrokeDurationMs, centerLine.length() * kMsPerPixel, kMaxStrokeDurationMs);
|
||||||
|
glowFadeDurationMs = kGlowFadeDurationMs;
|
||||||
|
// The clock is started on the first animationEvent() tick so that t=0
|
||||||
|
// corresponds to the first rendered frame. Starting it here would count
|
||||||
|
// the time spent before the item's first paint (event-loop delays, bursts
|
||||||
|
// of arrows created together), making the arrow appear already partway
|
||||||
|
// drawn when it first shows up.
|
||||||
|
animationStarted = false;
|
||||||
|
drawProgress = 0.0;
|
||||||
|
glowAlpha = 1.0;
|
||||||
|
update();
|
||||||
|
if (auto *scene = qobject_cast<GameScene *>(this->scene())) {
|
||||||
|
scene->registerAnimationItem(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArrowItem::animationEvent()
|
||||||
|
{
|
||||||
|
if (!animationStarted) {
|
||||||
|
animationClock.start();
|
||||||
|
animationStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qint64 elapsed = animationClock.elapsed();
|
||||||
|
if (elapsed >= strokeDurationMs + glowFadeDurationMs) {
|
||||||
|
drawProgress = 1.0;
|
||||||
|
glowAlpha = 0.0;
|
||||||
|
update();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsed < strokeDurationMs) {
|
||||||
|
drawProgress = easeOutCubic(qBound<qreal>(0.0, elapsed / strokeDurationMs, 1.0));
|
||||||
|
glowAlpha = 1.0;
|
||||||
|
} else {
|
||||||
|
drawProgress = 1.0;
|
||||||
|
glowAlpha = 1.0 - (elapsed - strokeDurationMs) / glowFadeDurationMs;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||||
{
|
{
|
||||||
QColor paintColor(data->color);
|
QColor paintColor(data->color);
|
||||||
|
|
@ -133,8 +233,66 @@ void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
|
||||||
} else {
|
} else {
|
||||||
paintColor.setAlpha(150);
|
paintColor.setAlpha(150);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
painter->save();
|
||||||
|
const QPen outlinePen = painter->pen();
|
||||||
painter->setBrush(paintColor);
|
painter->setBrush(paintColor);
|
||||||
|
|
||||||
|
const auto drawShaft = [this, painter, &outlinePen, paintColor]() {
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
painter->drawPath(bodyPath);
|
||||||
|
painter->setPen(outlinePen);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawPath(shaftOutlinePath);
|
||||||
|
painter->setBrush(paintColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (drawProgress >= 1.0 || path.isEmpty()) {
|
||||||
painter->drawPath(path);
|
painter->drawPath(path);
|
||||||
|
} else if (drawProgress < headBaseFraction) {
|
||||||
|
// The reveal edge and the sheen share the same arc-length parameterization,
|
||||||
|
// so the stroke stays exactly in sync with the trailing sheen.
|
||||||
|
const qreal revealX = centerLine.pointAtPercent(drawProgress).x();
|
||||||
|
QPainterPath clip;
|
||||||
|
clip.addRect(QRectF(-glowExtent, path.boundingRect().top() - glowExtent, revealX + glowExtent,
|
||||||
|
path.boundingRect().height() + 2 * glowExtent));
|
||||||
|
painter->setClipPath(clip);
|
||||||
|
drawShaft();
|
||||||
|
} else {
|
||||||
|
// Once the reveal reaches the head base, pop the whole head in with a fade
|
||||||
|
// instead of slicing the triangle into a growing stub.
|
||||||
|
drawShaft();
|
||||||
|
const qreal headFadeIn = (drawProgress - headBaseFraction) / (1.0 - headBaseFraction);
|
||||||
|
painter->setOpacity(headFadeIn);
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
painter->drawPath(headPath);
|
||||||
|
painter->setPen(outlinePen);
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawPath(headPath);
|
||||||
|
painter->setOpacity(1.0);
|
||||||
|
painter->setBrush(paintColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glowAlpha > 0.0 && !centerLine.isEmpty()) {
|
||||||
|
// Sweep a bright band across the arrow. Clipping to the
|
||||||
|
// silhouette keeps it flat against the shaft so it reads as a light reflection.
|
||||||
|
const qreal anticipation = qMin<qreal>(1.0, drawProgress / 0.08);
|
||||||
|
const QPointF sweep = centerLine.pointAtPercent(qMin<qreal>(drawProgress, 1.0));
|
||||||
|
QLinearGradient sheen(sweep.x() - kSheenHalfWidth, 0.0, sweep.x() + kSheenHalfWidth, 0.0);
|
||||||
|
sheen.setColorAt(0.0, QColor(paintColor.red(), paintColor.green(), paintColor.blue(), 0));
|
||||||
|
sheen.setColorAt(0.5, QColor(255, 255, 255, 200));
|
||||||
|
sheen.setColorAt(1.0, QColor(paintColor.red(), paintColor.green(), paintColor.blue(), 0));
|
||||||
|
painter->save();
|
||||||
|
painter->setPen(Qt::NoPen);
|
||||||
|
painter->setClipPath(path);
|
||||||
|
painter->setBrush(sheen);
|
||||||
|
painter->setOpacity(glowAlpha * anticipation);
|
||||||
|
painter->drawRect(QRectF(sweep.x() - kSheenHalfWidth - glowExtent, path.boundingRect().top() - glowExtent,
|
||||||
|
(kSheenHalfWidth + glowExtent) * 2.0,
|
||||||
|
path.boundingRect().height() + glowExtent * 2.0));
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
painter->restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@
|
||||||
#define ARROWITEM_H
|
#define ARROWITEM_H
|
||||||
|
|
||||||
#include "../../game/board/arrow_data.h"
|
#include "../../game/board/arrow_data.h"
|
||||||
|
#include "../animated_item.h"
|
||||||
#include "arrow_target.h"
|
#include "arrow_target.h"
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QGraphicsItem>
|
#include <QGraphicsItem>
|
||||||
|
#include <QPainterPath>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
|
@ -12,7 +15,7 @@ class CardItem;
|
||||||
class QGraphicsSceneMouseEvent;
|
class QGraphicsSceneMouseEvent;
|
||||||
class PlayerLogic;
|
class PlayerLogic;
|
||||||
|
|
||||||
class ArrowItem : public QObject, public QGraphicsItem
|
class ArrowItem : public QObject, public QGraphicsItem, public IAnimatedItem
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_INTERFACES(QGraphicsItem)
|
Q_INTERFACES(QGraphicsItem)
|
||||||
|
|
@ -21,6 +24,19 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPainterPath path;
|
QPainterPath path;
|
||||||
|
QPainterPath bodyPath;
|
||||||
|
QPainterPath headPath;
|
||||||
|
QPainterPath shaftOutlinePath;
|
||||||
|
QPainterPath centerLine;
|
||||||
|
qreal headBaseFraction = 1.0;
|
||||||
|
QElapsedTimer animationClock;
|
||||||
|
qreal strokeDurationMs = 0;
|
||||||
|
qreal glowFadeDurationMs = 0;
|
||||||
|
qreal drawProgress = 1.0;
|
||||||
|
qreal glowAlpha = 0.0;
|
||||||
|
bool animationStarted = false;
|
||||||
|
|
||||||
|
static constexpr qreal glowExtent = 12.0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QSharedPointer<const ArrowData> data;
|
QSharedPointer<const ArrowData> data;
|
||||||
|
|
@ -33,16 +49,19 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
|
ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
|
||||||
|
~ArrowItem() override;
|
||||||
|
|
||||||
void onTargetDestroyed();
|
void onTargetDestroyed();
|
||||||
void delArrow();
|
void delArrow();
|
||||||
void updatePath();
|
void updatePath();
|
||||||
void updatePath(const QPointF &endPoint);
|
void updatePath(const QPointF &endPoint);
|
||||||
|
void startDrawAnimation();
|
||||||
|
bool animationEvent() override;
|
||||||
|
|
||||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||||
[[nodiscard]] QRectF boundingRect() const override
|
[[nodiscard]] QRectF boundingRect() const override
|
||||||
{
|
{
|
||||||
return path.boundingRect();
|
return path.boundingRect().adjusted(-glowExtent, -glowExtent, glowExtent, glowExtent);
|
||||||
}
|
}
|
||||||
[[nodiscard]] QPainterPath shape() const override
|
[[nodiscard]] QPainterPath shape() const override
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -502,6 +502,7 @@ void GameScene::addArrow(QSharedPointer<ArrowData> data)
|
||||||
|
|
||||||
auto *arrow = new ArrowItem(data, startCard, targetItem);
|
auto *arrow = new ArrowItem(data, startCard, targetItem);
|
||||||
addItem(arrow);
|
addItem(arrow);
|
||||||
|
arrow->startDrawAnimation();
|
||||||
arrowRegistry.insert(data, arrow);
|
arrowRegistry.insert(data, arrow);
|
||||||
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion);
|
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
||||||
connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().cardsDisplay(),
|
connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().cardsDisplay(),
|
||||||
&CardsDisplaySettings::setTapAnimation);
|
&CardsDisplaySettings::setTapAnimation);
|
||||||
|
|
||||||
|
arrowDrawAnimationCheckBox.setChecked(SettingsCache::instance().cardsDisplay().getArrowDrawAnimation());
|
||||||
|
connect(&arrowDrawAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().cardsDisplay(),
|
||||||
|
&CardsDisplaySettings::setArrowDrawAnimation);
|
||||||
|
|
||||||
lifeCounterAnimationsCheckBox.setChecked(
|
lifeCounterAnimationsCheckBox.setChecked(
|
||||||
SettingsCache::instance().userInterface().getLifeCounterAnimationsEnabled());
|
SettingsCache::instance().userInterface().getLifeCounterAnimationsEnabled());
|
||||||
connect(&lifeCounterAnimationsCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
connect(&lifeCounterAnimationsCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
|
||||||
|
|
@ -132,8 +136,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
||||||
animationGrid->addWidget(&enableAllAnimationsButton, 0, 0);
|
animationGrid->addWidget(&enableAllAnimationsButton, 0, 0);
|
||||||
animationGrid->addWidget(&disableAllAnimationsButton, 0, 1);
|
animationGrid->addWidget(&disableAllAnimationsButton, 0, 1);
|
||||||
animationGrid->addWidget(&tapAnimationCheckBox, 1, 0);
|
animationGrid->addWidget(&tapAnimationCheckBox, 1, 0);
|
||||||
animationGrid->addWidget(&lifeCounterAnimationsCheckBox, 2, 0);
|
animationGrid->addWidget(&arrowDrawAnimationCheckBox, 2, 0);
|
||||||
animationGrid->addWidget(&battlefieldFlashCheckBox, 3, 0);
|
animationGrid->addWidget(&lifeCounterAnimationsCheckBox, 3, 0);
|
||||||
|
animationGrid->addWidget(&battlefieldFlashCheckBox, 4, 0);
|
||||||
|
|
||||||
animationGroupBox = new QGroupBox;
|
animationGroupBox = new QGroupBox;
|
||||||
animationGroupBox->setLayout(animationGrid);
|
animationGroupBox->setLayout(animationGrid);
|
||||||
|
|
@ -287,6 +292,7 @@ void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i)
|
||||||
void UserInterfaceSettingsPage::enableAllAnimations()
|
void UserInterfaceSettingsPage::enableAllAnimations()
|
||||||
{
|
{
|
||||||
tapAnimationCheckBox.setChecked(true);
|
tapAnimationCheckBox.setChecked(true);
|
||||||
|
arrowDrawAnimationCheckBox.setChecked(true);
|
||||||
lifeCounterAnimationsCheckBox.setChecked(true);
|
lifeCounterAnimationsCheckBox.setChecked(true);
|
||||||
battlefieldFlashCheckBox.setChecked(true);
|
battlefieldFlashCheckBox.setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
@ -294,6 +300,7 @@ void UserInterfaceSettingsPage::enableAllAnimations()
|
||||||
void UserInterfaceSettingsPage::disableAllAnimations()
|
void UserInterfaceSettingsPage::disableAllAnimations()
|
||||||
{
|
{
|
||||||
tapAnimationCheckBox.setChecked(false);
|
tapAnimationCheckBox.setChecked(false);
|
||||||
|
arrowDrawAnimationCheckBox.setChecked(false);
|
||||||
lifeCounterAnimationsCheckBox.setChecked(false);
|
lifeCounterAnimationsCheckBox.setChecked(false);
|
||||||
battlefieldFlashCheckBox.setChecked(false);
|
battlefieldFlashCheckBox.setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
@ -343,6 +350,7 @@ void UserInterfaceSettingsPage::retranslateUi()
|
||||||
enableAllAnimationsButton.setText(tr("&Enable all animations"));
|
enableAllAnimationsButton.setText(tr("&Enable all animations"));
|
||||||
disableAllAnimationsButton.setText(tr("&Disable all animations"));
|
disableAllAnimationsButton.setText(tr("&Disable all animations"));
|
||||||
tapAnimationCheckBox.setText(tr("&Tap/untap animation"));
|
tapAnimationCheckBox.setText(tr("&Tap/untap animation"));
|
||||||
|
arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation"));
|
||||||
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
|
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
|
||||||
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
|
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
|
||||||
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
|
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ private:
|
||||||
QPushButton enableAllAnimationsButton;
|
QPushButton enableAllAnimationsButton;
|
||||||
QPushButton disableAllAnimationsButton;
|
QPushButton disableAllAnimationsButton;
|
||||||
QCheckBox tapAnimationCheckBox;
|
QCheckBox tapAnimationCheckBox;
|
||||||
|
QCheckBox arrowDrawAnimationCheckBox;
|
||||||
QCheckBox lifeCounterAnimationsCheckBox;
|
QCheckBox lifeCounterAnimationsCheckBox;
|
||||||
QCheckBox battlefieldFlashCheckBox;
|
QCheckBox battlefieldFlashCheckBox;
|
||||||
QCheckBox openDeckInNewTabCheckBox;
|
QCheckBox openDeckInNewTabCheckBox;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
|
||||||
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
#include <libcockatrice/models/deck_list/deck_list_model.h>
|
||||||
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
|
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
|
||||||
#include <libcockatrice/protocol/pending_command.h>
|
#include <libcockatrice/protocol/pending_command.h>
|
||||||
|
|
@ -67,9 +66,6 @@ void TabDeckEditorVisual::createCentralFrame()
|
||||||
centralFrame = new QVBoxLayout;
|
centralFrame = new QVBoxLayout;
|
||||||
centralWidget->setLayout(centralFrame);
|
centralWidget->setLayout(centralFrame);
|
||||||
|
|
||||||
auto databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this);
|
|
||||||
databaseModel->setObjectName("databaseModel");
|
|
||||||
|
|
||||||
tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(), databaseModel);
|
tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckStateManager->getModel(), databaseModel);
|
||||||
|
|
||||||
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChanged, this,
|
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChanged, this,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent,
|
||||||
layout = new QVBoxLayout(this);
|
layout = new QVBoxLayout(this);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
visualDeckView = new VisualDeckEditorWidget(this, deckModel, _deckEditor->deckDockWidget->getSelectionModel());
|
visualDeckView = new VisualDeckEditorWidget(this, deckModel, _deckEditor->deckDockWidget->getSelectionModel(),
|
||||||
|
_cardDatabaseModel);
|
||||||
visualDeckView->setObjectName("visualDeckView");
|
visualDeckView->setObjectName("visualDeckView");
|
||||||
connect(visualDeckView, &VisualDeckEditorWidget::activeCardChanged, this,
|
connect(visualDeckView, &VisualDeckEditorWidget::activeCardChanged, this,
|
||||||
&TabDeckEditorVisualTabWidget::onCardChanged);
|
&TabDeckEditorVisualTabWidget::onCardChanged);
|
||||||
|
|
|
||||||
|
|
@ -82,17 +82,14 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg
|
||||||
|
|
||||||
void VisualDatabaseDisplayFilterToolbarWidget::initialize()
|
void VisualDatabaseDisplayFilterToolbarWidget::initialize()
|
||||||
{
|
{
|
||||||
// create groupbox layouts
|
auto sortLayout = new QHBoxLayout(sortGroupBox);
|
||||||
auto sortLayout = new QHBoxLayout(this);
|
|
||||||
sortLayout->setContentsMargins(0, 0, 0, 0);
|
sortLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
sortLayout->setSpacing(0);
|
sortLayout->setSpacing(0);
|
||||||
sortGroupBox->setLayout(sortLayout);
|
|
||||||
sortLayout->setAlignment(Qt::AlignLeft);
|
sortLayout->setAlignment(Qt::AlignLeft);
|
||||||
|
|
||||||
auto filterLayout = new QHBoxLayout(this);
|
auto filterLayout = new QHBoxLayout(filterGroupBox);
|
||||||
filterLayout->setContentsMargins(0, 0, 0, 0);
|
filterLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
filterLayout->setSpacing(2);
|
filterLayout->setSpacing(2);
|
||||||
filterGroupBox->setLayout(filterLayout);
|
|
||||||
filterLayout->setAlignment(Qt::AlignLeft);
|
filterLayout->setAlignment(Qt::AlignLeft);
|
||||||
|
|
||||||
// create settings widgets
|
// create settings widgets
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
|
#include <QShowEvent>
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
#include <libcockatrice/card/card_info_comparator.h>
|
#include <libcockatrice/card/card_info_comparator.h>
|
||||||
#include <libcockatrice/card/database/card_database.h>
|
#include <libcockatrice/card/database/card_database.h>
|
||||||
|
|
@ -140,9 +141,6 @@ void VisualDatabaseDisplayWidget::initialize()
|
||||||
{
|
{
|
||||||
databaseLoadIndicator->setVisible(false);
|
databaseLoadIndicator->setVisible(false);
|
||||||
|
|
||||||
filterContainer->initialize();
|
|
||||||
filterContainer->setVisible(true);
|
|
||||||
|
|
||||||
searchContainer->addWidget(colorFilterWidget);
|
searchContainer->addWidget(colorFilterWidget);
|
||||||
searchContainer->addWidget(clearFilterWidget);
|
searchContainer->addWidget(clearFilterWidget);
|
||||||
searchContainer->addWidget(searchEdit);
|
searchContainer->addWidget(searchEdit);
|
||||||
|
|
@ -158,17 +156,43 @@ void VisualDatabaseDisplayWidget::initialize()
|
||||||
|
|
||||||
mainLayout->addWidget(cardSizeWidget);
|
mainLayout->addWidget(cardSizeWidget);
|
||||||
|
|
||||||
databaseDisplayModel->setFilterTree(filterModel->filterTree());
|
|
||||||
|
|
||||||
connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::onSearchModelChanged);
|
connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::onSearchModelChanged);
|
||||||
|
|
||||||
loadCardsTimer = new QTimer(this);
|
initializeFilters();
|
||||||
loadCardsTimer->setSingleShot(true); // Ensure it only fires once after the timeout
|
}
|
||||||
|
|
||||||
connect(loadCardsTimer, &QTimer::timeout, this, [this]() { loadCurrentPage(); });
|
void VisualDatabaseDisplayWidget::initializeFilters()
|
||||||
loadCardsTimer->start(5000);
|
{
|
||||||
|
if (filtersInitialized || !isVisible() || CardDatabaseManager::getInstance()->getLoadStatus() != LoadStatus::Ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filtersInitialized = true;
|
||||||
|
|
||||||
|
// The filter toolbar builds its widgets by iterating the entire card database
|
||||||
|
// (per-set, per-main-type, per-sub-type and per-format buttons). Building it
|
||||||
|
// inside showEvent would block the tab switch, so keep it hidden and defer the
|
||||||
|
// build to the next event loop turn, letting the tab paint first. The toolbar
|
||||||
|
// then appears one event loop turn later, shifting the grid down by the toolbar
|
||||||
|
// height -- the intended tradeoff of an responsive tab switch.
|
||||||
|
filterContainer->setVisible(false);
|
||||||
|
|
||||||
|
QTimer::singleShot(0, this, [this] {
|
||||||
|
filterContainer->initialize();
|
||||||
|
filterContainer->setVisible(true);
|
||||||
|
|
||||||
|
databaseDisplayModel->setFilterTree(filterModel->filterTree());
|
||||||
|
|
||||||
|
QTimer::singleShot(5000, this, [this] { loadCurrentPage(); });
|
||||||
|
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualDatabaseDisplayWidget::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
initializeFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VisualDatabaseDisplayWidget::retranslateUi()
|
void VisualDatabaseDisplayWidget::retranslateUi()
|
||||||
|
|
@ -292,9 +316,17 @@ void VisualDatabaseDisplayWidget::loadCurrentPage()
|
||||||
{
|
{
|
||||||
// Ensure only the initial page is loaded
|
// Ensure only the initial page is loaded
|
||||||
if (currentPage == 0) {
|
if (currentPage == 0) {
|
||||||
// Only load the first page initially
|
if (!initialLoadScheduled) {
|
||||||
|
initialLoadScheduled = true;
|
||||||
qCDebug(VisualDatabaseDisplayLog) << "Loading the first page";
|
qCDebug(VisualDatabaseDisplayLog) << "Loading the first page";
|
||||||
|
// Defer the first page so the tab switch stays responsive. The card
|
||||||
|
// grid builds one event loop turn later. This also applies to
|
||||||
|
// search-driven reloads, which reset currentPage back to 0.
|
||||||
|
QTimer::singleShot(0, this, [this] {
|
||||||
|
initialLoadScheduled = false;
|
||||||
populateCards();
|
populateCards();
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (nearEndOfPage()) {
|
} else if (nearEndOfPage()) {
|
||||||
// If not the first page, just load the next page and append to the flow widget
|
// If not the first page, just load the next page and append to the flow widget
|
||||||
loadNextPage();
|
loadNextPage();
|
||||||
|
|
|
||||||
|
|
@ -115,17 +115,20 @@ private:
|
||||||
OverlapControlWidget *overlapControlWidget;
|
OverlapControlWidget *overlapControlWidget;
|
||||||
CardSizeWidget *cardSizeWidget;
|
CardSizeWidget *cardSizeWidget;
|
||||||
QTimer *debounceTimer;
|
QTimer *debounceTimer;
|
||||||
QTimer *loadCardsTimer;
|
|
||||||
|
|
||||||
int debounceTime = 300; // in Ms
|
int debounceTime = 300; // in Ms
|
||||||
int currentPage = 0; // Current page index
|
int currentPage = 0; // Current page index
|
||||||
int cardsPerPage = 100; // Number of cards per page
|
int cardsPerPage = 100; // Number of cards per page
|
||||||
|
bool filtersInitialized = false;
|
||||||
|
bool initialLoadScheduled = false;
|
||||||
|
|
||||||
|
void initializeFilters();
|
||||||
void highlightAllSearchEdit();
|
void highlightAllSearchEdit();
|
||||||
bool nearEndOfPage() const;
|
bool nearEndOfPage() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VISUAL_DATABASE_DISPLAY_WIDGET_H
|
#endif // VISUAL_DATABASE_DISPLAY_WIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,10 @@
|
||||||
|
|
||||||
VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
|
VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
|
||||||
DeckListModel *_deckListModel,
|
DeckListModel *_deckListModel,
|
||||||
QItemSelectionModel *_selectionModel)
|
QItemSelectionModel *_selectionModel,
|
||||||
: QWidget(parent), deckListModel(_deckListModel), selectionModel(_selectionModel)
|
CardDatabaseModel *_cardDatabaseModel)
|
||||||
|
: QWidget(parent), deckListModel(_deckListModel), selectionModel(_selectionModel),
|
||||||
|
cardDatabaseModel(_cardDatabaseModel)
|
||||||
{
|
{
|
||||||
// The Main Widget and Main Layout, which contain a single Widget: The Scroll Area
|
// The Main Widget and Main Layout, which contain a single Widget: The Scroll Area
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
@ -94,7 +96,9 @@ void VisualDeckEditorWidget::initializeSearchBarAndCompleter()
|
||||||
setFocusProxy(searchBar);
|
setFocusProxy(searchBar);
|
||||||
setFocusPolicy(Qt::ClickFocus);
|
setFocusPolicy(Qt::ClickFocus);
|
||||||
|
|
||||||
|
if (!cardDatabaseModel) {
|
||||||
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
|
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
|
||||||
|
}
|
||||||
cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this);
|
cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this);
|
||||||
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
|
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,10 @@ class VisualDeckEditorWidget : public QWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel, QItemSelectionModel *selectionModel);
|
explicit VisualDeckEditorWidget(QWidget *parent,
|
||||||
|
DeckListModel *deckListModel,
|
||||||
|
QItemSelectionModel *selectionModel,
|
||||||
|
CardDatabaseModel *_cardDatabaseModel = nullptr);
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
void updateCompactMode();
|
void updateCompactMode();
|
||||||
void clearAllDisplayWidgets();
|
void clearAllDisplayWidgets();
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,17 @@ CardDatabaseQuerier::CardDatabaseQuerier(QObject *_parent,
|
||||||
const ICardPreferenceProvider *prefs)
|
const ICardPreferenceProvider *prefs)
|
||||||
: QObject(_parent), db(_db), prefs(prefs)
|
: QObject(_parent), db(_db), prefs(prefs)
|
||||||
{
|
{
|
||||||
|
// Invalidate the cached count maps whenever the database contents change.
|
||||||
|
connect(db, &CardDatabase::cardAdded, this, &CardDatabaseQuerier::invalidateCaches);
|
||||||
|
connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseQuerier::invalidateCaches);
|
||||||
|
connect(db, &CardDatabase::cardDatabaseReset, this, &CardDatabaseQuerier::invalidateCaches);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardDatabaseQuerier::invalidateCaches()
|
||||||
|
{
|
||||||
|
mainCardTypeCountsCache.clear();
|
||||||
|
subCardTypeCountsCache.clear();
|
||||||
|
formatsCountCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -304,29 +315,27 @@ QString CardDatabaseQuerier::getPreferredPrintingProviderId(const QString &cardN
|
||||||
|
|
||||||
QStringList CardDatabaseQuerier::getAllMainCardTypes() const
|
QStringList CardDatabaseQuerier::getAllMainCardTypes() const
|
||||||
{
|
{
|
||||||
QSet<QString> types;
|
return getAllMainCardTypesWithCount().keys();
|
||||||
for (const auto &card : db->cards.values()) {
|
|
||||||
types.insert(card->getMainCardType());
|
|
||||||
}
|
|
||||||
return types.values();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QString, int> CardDatabaseQuerier::getAllMainCardTypesWithCount() const
|
QMap<QString, int> CardDatabaseQuerier::getAllMainCardTypesWithCount() const
|
||||||
{
|
{
|
||||||
QMap<QString, int> typeCounts;
|
// An empty cache is always recomputed correctly: a database with no cards
|
||||||
|
// produces an empty map, so the cache is only ever empty when it needs a
|
||||||
|
// (trivially cheap) rebuild.
|
||||||
|
if (mainCardTypeCountsCache.isEmpty()) {
|
||||||
for (const auto &card : db->cards.values()) {
|
for (const auto &card : db->cards.values()) {
|
||||||
QString type = card->getMainCardType();
|
QString type = card->getMainCardType();
|
||||||
typeCounts[type]++;
|
mainCardTypeCountsCache[type]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeCounts;
|
return mainCardTypeCountsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
|
QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
|
||||||
{
|
{
|
||||||
QMap<QString, int> typeCounts;
|
if (subCardTypeCountsCache.isEmpty()) {
|
||||||
|
|
||||||
for (const auto &card : db->cards.values()) {
|
for (const auto &card : db->cards.values()) {
|
||||||
QString type = card->getCardType();
|
QString type = card->getCardType();
|
||||||
|
|
||||||
|
|
@ -336,12 +345,13 @@ QMap<QString, int> CardDatabaseQuerier::getAllSubCardTypesWithCount() const
|
||||||
QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
|
QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts);
|
||||||
|
|
||||||
for (const QString &subtype : subtypes) {
|
for (const QString &subtype : subtypes) {
|
||||||
typeCounts[subtype]++;
|
subCardTypeCountsCache[subtype]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeCounts;
|
return subCardTypeCountsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatRulesPtr CardDatabaseQuerier::getFormat(const QString &formatName) const
|
FormatRulesPtr CardDatabaseQuerier::getFormat(const QString &formatName) const
|
||||||
|
|
@ -351,18 +361,18 @@ FormatRulesPtr CardDatabaseQuerier::getFormat(const QString &formatName) const
|
||||||
|
|
||||||
QMap<QString, int> CardDatabaseQuerier::getAllFormatsWithCount() const
|
QMap<QString, int> CardDatabaseQuerier::getAllFormatsWithCount() const
|
||||||
{
|
{
|
||||||
QMap<QString, int> formatCounts;
|
if (formatsCountCache.isEmpty()) {
|
||||||
|
|
||||||
for (const auto &card : db->cards.values()) {
|
for (const auto &card : db->cards.values()) {
|
||||||
QStringList allProps = card->getProperties();
|
QStringList allProps = card->getProperties();
|
||||||
|
|
||||||
for (const QString &prop : allProps) {
|
for (const QString &prop : allProps) {
|
||||||
if (prop.startsWith("format-")) {
|
if (prop.startsWith("format-")) {
|
||||||
QString formatName = prop.mid(QStringLiteral("format-").size());
|
QString formatName = prop.mid(QStringLiteral("format-").size());
|
||||||
formatCounts[formatName]++;
|
formatsCountCache[formatName]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatCounts;
|
return formatsCountCache;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,16 @@ public:
|
||||||
private:
|
private:
|
||||||
const CardDatabase *db; //!< Card database used for all lookups.
|
const CardDatabase *db; //!< Card database used for all lookups.
|
||||||
const ICardPreferenceProvider *prefs; //!< Preference provider for preferred printings.
|
const ICardPreferenceProvider *prefs; //!< Preference provider for preferred printings.
|
||||||
|
|
||||||
|
// Count maps are expensive to compute (they iterate the whole database) and are
|
||||||
|
// queried every time a filter widget is built, so cache them and invalidate on
|
||||||
|
// any database mutation. Only the main thread reads or writes these.
|
||||||
|
mutable QMap<QString, int> mainCardTypeCountsCache;
|
||||||
|
mutable QMap<QString, int> subCardTypeCountsCache;
|
||||||
|
mutable QMap<QString, int> formatsCountCache;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void invalidateCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_CARD_DATABASE_QUERIER_H
|
#endif // COCKATRICE_CARD_DATABASE_QUERIER_H
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ public:
|
||||||
[[nodiscard]] virtual bool getIncludeRebalancedCards() const = 0;
|
[[nodiscard]] virtual bool getIncludeRebalancedCards() const = 0;
|
||||||
[[nodiscard]] virtual bool getPrintingSelectorNavigationButtonsVisible() const = 0;
|
[[nodiscard]] virtual bool getPrintingSelectorNavigationButtonsVisible() const = 0;
|
||||||
[[nodiscard]] virtual bool getTapAnimation() const = 0;
|
[[nodiscard]] virtual bool getTapAnimation() const = 0;
|
||||||
|
[[nodiscard]] virtual bool getArrowDrawAnimation() const = 0;
|
||||||
[[nodiscard]] virtual bool getAutoRotateSidewaysLayoutCards() const = 0;
|
[[nodiscard]] virtual bool getAutoRotateSidewaysLayoutCards() const = 0;
|
||||||
[[nodiscard]] virtual bool getScaleCards() const = 0;
|
[[nodiscard]] virtual bool getScaleCards() const = 0;
|
||||||
[[nodiscard]] virtual int getStackCardOverlapPercent() const = 0;
|
[[nodiscard]] virtual int getStackCardOverlapPercent() const = 0;
|
||||||
|
|
|
||||||
|
|
@ -111,19 +111,50 @@ bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(const CardInfoPtr &card
|
||||||
|
|
||||||
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
|
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
|
||||||
{
|
{
|
||||||
// remove all the cards no more present in at least one enabled set
|
// Build the new card list in a single pass.
|
||||||
|
QList<CardInfoPtr> newCardList;
|
||||||
|
newCardList.reserve(cardList.size());
|
||||||
for (const CardInfoPtr &card : cardList) {
|
for (const CardInfoPtr &card : cardList) {
|
||||||
if (!checkCardHasAtLeastOneEnabledSet(card)) {
|
if (checkCardHasAtLeastOneEnabledSet(card)) {
|
||||||
cardRemoved(card);
|
newCardList.append(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const CardInfoPtr &card : db->getCardList()) {
|
||||||
|
if (!cardListSet.contains(card) && checkCardHasAtLeastOneEnabledSet(card)) {
|
||||||
|
newCardList.append(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// re-check all the card currently not shown, maybe their part of a newly-enabled set
|
if (newCardList == cardList) {
|
||||||
for (const CardInfoPtr &card : db->getCardList()) {
|
return;
|
||||||
if (!cardListSet.contains(card)) {
|
}
|
||||||
cardAdded(card);
|
|
||||||
|
// Rebuild the whole list inside a single model reset instead of emitting
|
||||||
|
// per-card insert/remove notifications. With tens of thousands of cards the
|
||||||
|
// per-card path is the dominant cost of constructing a CardDatabaseModel.
|
||||||
|
QSet<CardInfoPtr> oldCardListSet = cardListSet;
|
||||||
|
QSet<CardInfoPtr> newCardListSet(newCardList.begin(), newCardList.end());
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
// Disconnect cards that are no longer shown.
|
||||||
|
for (const CardInfoPtr &card : cardList) {
|
||||||
|
if (!newCardListSet.contains(card)) {
|
||||||
|
disconnect(card.data(), nullptr, this, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cardList = newCardList;
|
||||||
|
cardListSet = newCardListSet;
|
||||||
|
|
||||||
|
// Connect cards that are now shown for the first time.
|
||||||
|
for (const CardInfoPtr &card : cardList) {
|
||||||
|
if (!oldCardListSet.contains(card)) {
|
||||||
|
connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CardDatabaseModel::cardAdded(const CardInfoPtr &card)
|
void CardDatabaseModel::cardAdded(const CardInfoPtr &card)
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,22 @@ void Server_ProtocolHandler::sendProtocolItem(const RoomEvent &item)
|
||||||
Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(const CommandContainer &cont,
|
Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(const CommandContainer &cont,
|
||||||
ResponseContainer &rc)
|
ResponseContainer &rc)
|
||||||
{
|
{
|
||||||
|
const auto isPreAuthSessionCommand = [](SessionCommand::SessionCommandType type) {
|
||||||
|
switch (type) {
|
||||||
|
case SessionCommand::PING:
|
||||||
|
case SessionCommand::LOGIN:
|
||||||
|
case SessionCommand::REGISTER:
|
||||||
|
case SessionCommand::ACTIVATE:
|
||||||
|
case SessionCommand::FORGOT_PASSWORD_REQUEST:
|
||||||
|
case SessionCommand::FORGOT_PASSWORD_RESET:
|
||||||
|
case SessionCommand::FORGOT_PASSWORD_CHALLENGE:
|
||||||
|
case SessionCommand::REQUEST_PASSWORD_SALT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Response::ResponseCode finalResponseCode = Response::RespOk;
|
Response::ResponseCode finalResponseCode = Response::RespOk;
|
||||||
for (int i = cont.session_command_size() - 1; i >= 0; --i) {
|
for (int i = cont.session_command_size() - 1; i >= 0; --i) {
|
||||||
Response::ResponseCode resp = Response::RespInvalidCommand;
|
Response::ResponseCode resp = Response::RespInvalidCommand;
|
||||||
|
|
@ -141,7 +157,11 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co
|
||||||
if (num != SessionCommand::PING) { // don't log ping commands
|
if (num != SessionCommand::PING) { // don't log ping commands
|
||||||
logDebugMessage(getSafeDebugString(sc));
|
logDebugMessage(getSafeDebugString(sc));
|
||||||
}
|
}
|
||||||
switch ((SessionCommand::SessionCommandType)num) {
|
const auto commandType = static_cast<SessionCommand::SessionCommandType>(num);
|
||||||
|
if (authState == NotLoggedIn && !isPreAuthSessionCommand(commandType)) {
|
||||||
|
resp = Response::RespLoginNeeded;
|
||||||
|
} else {
|
||||||
|
switch (commandType) {
|
||||||
case SessionCommand::PING:
|
case SessionCommand::PING:
|
||||||
resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc);
|
resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc);
|
||||||
break;
|
break;
|
||||||
|
|
@ -169,6 +189,7 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co
|
||||||
default:
|
default:
|
||||||
resp = processExtendedSessionCommand(num, sc, rc);
|
resp = processExtendedSessionCommand(num, sc, rc);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (resp != Response::RespOk) {
|
if (resp != Response::RespOk) {
|
||||||
finalResponseCode = resp;
|
finalResponseCode = resp;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,11 @@ bool CardsDisplaySettings::getTapAnimation() const
|
||||||
return getValue("tapAnimation", QString(), QString(), true).toBool();
|
return getValue("tapAnimation", QString(), QString(), true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CardsDisplaySettings::getArrowDrawAnimation() const
|
||||||
|
{
|
||||||
|
return getValue("arrowDrawAnimation", QString(), QString(), true).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
bool CardsDisplaySettings::getAutoRotateSidewaysLayoutCards() const
|
bool CardsDisplaySettings::getAutoRotateSidewaysLayoutCards() const
|
||||||
{
|
{
|
||||||
return getValue("autoRotateSidewaysLayoutCards", QString(), QString(), true).toBool();
|
return getValue("autoRotateSidewaysLayoutCards", QString(), QString(), true).toBool();
|
||||||
|
|
@ -159,6 +164,11 @@ void CardsDisplaySettings::setTapAnimation(bool _tapAnimation)
|
||||||
setValue(_tapAnimation, "tapAnimation");
|
setValue(_tapAnimation, "tapAnimation");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CardsDisplaySettings::setArrowDrawAnimation(bool _arrowDrawAnimation)
|
||||||
|
{
|
||||||
|
setValue(_arrowDrawAnimation, "arrowDrawAnimation");
|
||||||
|
}
|
||||||
|
|
||||||
void CardsDisplaySettings::setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards)
|
void CardsDisplaySettings::setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards)
|
||||||
{
|
{
|
||||||
setValue(_autoRotateSidewaysLayoutCards, "autoRotateSidewaysLayoutCards");
|
setValue(_autoRotateSidewaysLayoutCards, "autoRotateSidewaysLayoutCards");
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public:
|
||||||
[[nodiscard]] bool getIncludeRebalancedCards() const override;
|
[[nodiscard]] bool getIncludeRebalancedCards() const override;
|
||||||
[[nodiscard]] bool getPrintingSelectorNavigationButtonsVisible() const override;
|
[[nodiscard]] bool getPrintingSelectorNavigationButtonsVisible() const override;
|
||||||
[[nodiscard]] bool getTapAnimation() const override;
|
[[nodiscard]] bool getTapAnimation() const override;
|
||||||
|
[[nodiscard]] bool getArrowDrawAnimation() const override;
|
||||||
[[nodiscard]] bool getAutoRotateSidewaysLayoutCards() const override;
|
[[nodiscard]] bool getAutoRotateSidewaysLayoutCards() const override;
|
||||||
[[nodiscard]] bool getScaleCards() const override;
|
[[nodiscard]] bool getScaleCards() const override;
|
||||||
[[nodiscard]] int getStackCardOverlapPercent() const override;
|
[[nodiscard]] int getStackCardOverlapPercent() const override;
|
||||||
|
|
@ -40,6 +41,7 @@ public:
|
||||||
void setIncludeRebalancedCards(bool _includeRebalancedCards);
|
void setIncludeRebalancedCards(bool _includeRebalancedCards);
|
||||||
void setPrintingSelectorNavigationButtonsVisible(bool _navigationButtonsVisible);
|
void setPrintingSelectorNavigationButtonsVisible(bool _navigationButtonsVisible);
|
||||||
void setTapAnimation(bool _tapAnimation);
|
void setTapAnimation(bool _tapAnimation);
|
||||||
|
void setArrowDrawAnimation(bool _arrowDrawAnimation);
|
||||||
void setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards);
|
void setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards);
|
||||||
void setCardScaling(bool _scaleCards);
|
void setCardScaling(bool _scaleCards);
|
||||||
void setStackCardOverlapPercent(int _verticalCardOverlapPercent);
|
void setStackCardOverlapPercent(int _verticalCardOverlapPercent);
|
||||||
|
|
|
||||||
|
|
@ -896,6 +896,10 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplayGetCode(const Com
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const Command_ReplaySubmitCode &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const Command_ReplaySubmitCode &cmd,
|
||||||
ResponseContainer & /*rc*/)
|
ResponseContainer & /*rc*/)
|
||||||
{
|
{
|
||||||
|
if (authState != PasswordRight) {
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
// code is of the form <game-id>-<hash>
|
// code is of the form <game-id>-<hash>
|
||||||
QString code = QString::fromStdString(cmd.replay_code());
|
QString code = QString::fromStdString(cmd.replay_code());
|
||||||
QStringList split = code.split("-");
|
QStringList split = code.split("-");
|
||||||
|
|
|
||||||
|
|
@ -476,6 +476,12 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_SampleHandSize_Default)
|
||||||
ASSERT_EQ(s.getSampleHandSize(), 7);
|
ASSERT_EQ(s.getSampleHandSize(), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, CardsDisplay_ArrowDrawAnimation_Default)
|
||||||
|
{
|
||||||
|
CardsDisplaySettings s(settingsPath, nullptr);
|
||||||
|
ASSERT_EQ(s.getArrowDrawAnimation(), true);
|
||||||
|
}
|
||||||
|
|
||||||
// --- VisualDeckStorageSettings ---
|
// --- VisualDeckStorageSettings ---
|
||||||
|
|
||||||
TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)
|
TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue