mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[VisualDatabase] Draw the card grid with a model/view (#7701)
* [VisualDatabase] Draw the card grid with a model/view instead of a widget per card The visual card database built one CardInfoPictureWithTextOverlayWidget per card, 100 per page, and tore them down on every search, resize and scroll page. It also looked each card up again by name from the model row, kept a shadow QList<ExactCard>, and carried paging state plus a 5 s timer that only existed to defer the first page. Replace that with a real view on a flattened model: - CardDatabaseDisplayModel gains CardInfoRole and cardInfoAt(), so a view can get the CardInfoPtr behind a proxy row without a name round trip. - New CardDatabaseVisualDisplayModel (libcockatrice_models) flattens the filtered and sorted proxy into one entry per displayed card picture: the preferred printing normally, every printing of the selected sets while set filters are active. Proxy changes rebuild it on a 30 ms coalescing timer. - New CardDatabaseVisualView (QListView IconMode) paints only visible rows through the new CardPictureDelegate, keeps the old interaction contract (left/right/middle press -> cardClicked, hover -> cardHovered, Ctrl+wheel resize, no selection, no focus stealing, scroll to top on a new result) and repaints when CardPictureLoader reports an async image. - CardSizeWidget::enableCtrlScrollResize() also watches scroll-area viewports, which is what makes Ctrl+wheel work over a QListView. - VisualDatabaseDisplayWidget drops the paging, the shadow list, the dead members and the 5 s timer. Behaviour-preserving: no gameplay change, no new user-facing strings. * [VisualDatabase] Fix the card grid wheel step QListView hands the delegate's item height to the scroll bar as its single step under ScrollPerPixel, so QAbstractSlider moved wheelScrollLines() whole card rows per notch. Handle the wheel in the view instead: one line is an eighth of the grid cell, so a notch moves about a third of a card row at any card size and still follows the OS line count. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
94e6281d87
commit
a1a8a59f4a
24 changed files with 1019 additions and 277 deletions
188
tests/carddatabase/visual_display_model_test.cpp
Normal file
188
tests/carddatabase/visual_display_model_test.cpp
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* @file visual_display_model_test.cpp
|
||||
* @brief Tests for the flattened model behind the visual card database grid.
|
||||
*/
|
||||
|
||||
#include "card_database_display_model.h"
|
||||
#include "card_database_model.h"
|
||||
#include "card_database_visual_display_model.h"
|
||||
#include "libcockatrice/card/card_info.h"
|
||||
#include "libcockatrice/card/database/card_database.h"
|
||||
#include "libcockatrice/card/database/card_database_querier.h"
|
||||
#include "libcockatrice/card/printing/exact_card.h"
|
||||
#include "libcockatrice/card/printing/printing_info.h"
|
||||
#include "libcockatrice/card/set/card_set.h"
|
||||
#include "libcockatrice/interfaces/noop_card_preference_provider.h"
|
||||
#include "libcockatrice/interfaces/noop_card_set_priority_controller.h"
|
||||
#include "test_card_database_path_provider.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDate>
|
||||
#include <QEventLoop>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
namespace
|
||||
{
|
||||
// GTest's main() creates no QCoreApplication, which QTimer needs for its event loop.
|
||||
const QCoreApplication &application()
|
||||
{
|
||||
static int argc = 1;
|
||||
static char executable[] = "visual_display_model_test";
|
||||
static char *argv[] = {executable, nullptr};
|
||||
static const QCoreApplication app(argc, argv);
|
||||
return app;
|
||||
}
|
||||
|
||||
/// Lets the rebuild timer fire, as it would in the running client.
|
||||
void waitForRebuild()
|
||||
{
|
||||
QEventLoop loop;
|
||||
QTimer::singleShot(100, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
class CardDatabaseVisualDisplayModelTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
(void)application();
|
||||
|
||||
db = std::make_unique<CardDatabase>(nullptr, new NoopCardPreferenceProvider(),
|
||||
new TestCardDatabasePathProvider(), new NoopCardSetPriorityController());
|
||||
db->loadCardDatabases();
|
||||
|
||||
cardModel = std::make_unique<CardDatabaseModel>(db.get(), false);
|
||||
displayModel = std::make_unique<CardDatabaseDisplayModel>();
|
||||
displayModel->setSourceModel(cardModel.get());
|
||||
visualModel = std::make_unique<CardDatabaseVisualDisplayModel>(displayModel.get(), db->query());
|
||||
visualModel->refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Builds a card with @p printingCount printings in each named set.
|
||||
* @param printingCountPerSet Set short name to the number of printings in that set.
|
||||
*/
|
||||
[[nodiscard]] CardInfoPtr makeCard(const QString &name, const QMap<QString, int> &printingCountPerSet)
|
||||
{
|
||||
SetToPrintingsMap setsInfo;
|
||||
for (auto it = printingCountPerSet.constBegin(); it != printingCountPerSet.constEnd(); ++it) {
|
||||
CardSetPtr set = CardSet::newInstance(&setController, it.key(), it.key() + " test set", "expansion",
|
||||
QDate(), CardSet::PriorityPrimary);
|
||||
for (int printing = 0; printing < it.value(); ++printing) {
|
||||
QHash<QString, QString> properties;
|
||||
properties["uuid"] = QString("%1-%2-%3").arg(it.key(), name).arg(printing);
|
||||
setsInfo[it.key()].append(PrintingInfo(set, LazyPropertiesHash(properties)));
|
||||
}
|
||||
}
|
||||
|
||||
CardInfo::UiAttributes attributes = {.tableRow = 1};
|
||||
return CardInfo::newInstance(name, "", false, {}, {}, {}, setsInfo, attributes);
|
||||
}
|
||||
|
||||
NoopCardSetPriorityController setController;
|
||||
std::unique_ptr<CardDatabase> db;
|
||||
std::unique_ptr<CardDatabaseModel> cardModel;
|
||||
std::unique_ptr<CardDatabaseDisplayModel> displayModel;
|
||||
std::unique_ptr<CardDatabaseVisualDisplayModel> visualModel;
|
||||
};
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, ExposesTheCardInfoBehindAProxyRow)
|
||||
{
|
||||
ASSERT_GT(displayModel->rowCount(), 0);
|
||||
|
||||
const QVariant info = displayModel->data(displayModel->index(0, CardDatabaseModel::NameColumn),
|
||||
CardDatabaseDisplayModel::CardInfoRole);
|
||||
ASSERT_TRUE(info.isValid());
|
||||
EXPECT_FALSE(info.value<CardInfoPtr>().isNull());
|
||||
}
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, FlattensOneRowPerProxyCardWithoutSetFilters)
|
||||
{
|
||||
ASSERT_GT(displayModel->rowCount(), 0);
|
||||
EXPECT_EQ(displayModel->rowCount(), visualModel->rowCount());
|
||||
|
||||
for (int row = 0; row < visualModel->rowCount(); ++row) {
|
||||
const ExactCard card = visualModel->cardAt(row);
|
||||
ASSERT_TRUE(card) << "row " << row;
|
||||
EXPECT_EQ(
|
||||
displayModel->data(displayModel->index(row, CardDatabaseModel::NameColumn), Qt::DisplayRole).toString(),
|
||||
card.getName())
|
||||
<< "row " << row;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, ShowsOneRowPerCardWhenNoSetIsFiltered)
|
||||
{
|
||||
const int rowsBefore = visualModel->rowCount();
|
||||
|
||||
db->addCard(makeCard("Multi", {{"TST", 2}, {"OTH", 1}}));
|
||||
visualModel->refresh();
|
||||
|
||||
EXPECT_EQ(rowsBefore + 1, visualModel->rowCount());
|
||||
EXPECT_EQ("Multi", visualModel->cardAt(visualModel->rowCount() - 1).getName());
|
||||
}
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, ShowsEveryPrintingOfAFilteredSet)
|
||||
{
|
||||
db->addCard(makeCard("Multi", {{"TST", 2}, {"OTH", 1}}));
|
||||
visualModel->refresh();
|
||||
|
||||
visualModel->setSetFilterTerms({"TST"});
|
||||
|
||||
ASSERT_EQ(2, visualModel->rowCount());
|
||||
for (int row = 0; row < visualModel->rowCount(); ++row) {
|
||||
const ExactCard card = visualModel->cardAt(row);
|
||||
ASSERT_TRUE(card) << "row " << row;
|
||||
EXPECT_EQ("Multi", card.getName());
|
||||
ASSERT_FALSE(card.getPrinting().isEmpty()) << "row " << row;
|
||||
EXPECT_EQ("TST", card.getPrinting().getSet()->getShortName()) << "row " << row;
|
||||
}
|
||||
EXPECT_NE(visualModel->cardAt(0).getPrinting(), visualModel->cardAt(1).getPrinting());
|
||||
}
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, HidesCardsWithoutAPrintingInTheFilteredSets)
|
||||
{
|
||||
visualModel->setSetFilterTerms({"NO SUCH SET"});
|
||||
|
||||
EXPECT_EQ(0, visualModel->rowCount());
|
||||
}
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, RestoresOneRowPerCardWhenTheSetTermsClear)
|
||||
{
|
||||
db->addCard(makeCard("Multi", {{"TST", 2}, {"OTH", 1}}));
|
||||
visualModel->refresh();
|
||||
const int rowsBefore = visualModel->rowCount();
|
||||
|
||||
visualModel->setSetFilterTerms({"TST"});
|
||||
ASSERT_EQ(2, visualModel->rowCount());
|
||||
|
||||
visualModel->setSetFilterTerms({});
|
||||
EXPECT_EQ(rowsBefore, visualModel->rowCount());
|
||||
}
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, RebuildsByItselfWhenTheProxyGainsARow)
|
||||
{
|
||||
const int rowsBefore = visualModel->rowCount();
|
||||
|
||||
db->addCard(makeCard("Late", {{"TST", 1}}));
|
||||
waitForRebuild();
|
||||
|
||||
EXPECT_EQ(rowsBefore + 1, visualModel->rowCount());
|
||||
}
|
||||
|
||||
TEST_F(CardDatabaseVisualDisplayModelTest, ReportsTheCardNameForDisplayAndAccessibleText)
|
||||
{
|
||||
ASSERT_GT(visualModel->rowCount(), 0);
|
||||
const QString expected = visualModel->cardAt(0).getName();
|
||||
|
||||
EXPECT_EQ(expected, visualModel->data(visualModel->index(0), Qt::DisplayRole).toString());
|
||||
EXPECT_EQ(expected, visualModel->data(visualModel->index(0), Qt::AccessibleTextRole).toString());
|
||||
EXPECT_FALSE(visualModel->data(QModelIndex(), Qt::DisplayRole).isValid());
|
||||
}
|
||||
} // namespace
|
||||
Loading…
Add table
Add a link
Reference in a new issue