[Feature] TabArchidekt and Archidekt API integration (#6348)

* TabArchidekt and Archidekt API integration.


Took 37 seconds

Took 4 minutes

Took 40 seconds

Took 4 minutes

* Lint.

* Lont.

* Search bar, fancier display, resolve providerId

* Delegate click to base.

* Be explicit for pedantic compilers.

* Liiint.

* Leave them default I guess

* Leave them default I guess

* Small fixes.

* New utility display widgets.

* New style for deck listing.

* Lint.

* Lont.

* Scale things.

* Delegate paint to base.

* Use default Archidekt preview image for decks without featured.

* Consistent sizes.

* Increase font size, qt version guard.

* More version guards.

* Clean up filter layout, use mana symbols.

* Set content margins.

* Refresh on filter change.

* Lint.

* Better elision.

* Query actual new endpoints, new query parameters.

* Doxygen, reorder fields in constructor, readability.

* Update page size doc to min size.

* Update initial min deck size value.

* Add label to page selection.

* Okay, so, people upload a lot of 1 card decks frequently.

* Whoops.

* Add a selection combobox for sorting logic.

* Debounce and limit searches.

* Include.

* Lint.

* Don't imply that Archidekt supports multiple cards/commander names.

* Let's not lambda it and slot it instead.

* Overload.

* Add button to home tab.

Took 8 minutes

* Adjust to selection model change.

Took 5 minutes

* Cleanup auto-generated comments.

Took 8 minutes

* Remember card sizes.

Took 1 minute

* Initialize with correct size.

Took 3 minutes

* Use correct placeholders.

Took 2 minutes

* Style lint.

Took 16 minutes

* Parse double-faced cards correctly.

* Parse double-faced cards correctly.

* Allow TabArchidekt to use VDE group/sort/display buttons

* Lint.

* Indicate that things are clickable.

* Min treshold for nicer display.

* Lint.

* We have good labels at home.

* We do a little linting.

* Qt version guards.

* Qt5 is the devil.

* Update comments.

* Lint comments.

* More doxys.

* One more doxy.

* Lint.

* Update.

* Small fixes.

Took 7 minutes

Took 13 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-11-30 08:41:01 +01:00 committed by GitHub
parent de13c22552
commit eab4d435f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 3285 additions and 141 deletions

View file

@ -0,0 +1,34 @@
#include "background_plate_widget.h"
#include <QBrush>
#include <QColor>
#include <QPainter>
#include <QPen>
BackgroundPlateWidget::BackgroundPlateWidget(QWidget *parent) : QWidget(parent)
{
setAutoFillBackground(true); // For automatic background filling
}
void BackgroundPlateWidget::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// Set the background color to semi-transparent black with rounded corners
QRect rect = this->rect();
painter.setPen(Qt::NoPen); // No border
if (focused) {
painter.setBrush(QColor(85, 190, 75, 140));
} else {
painter.setBrush(QColor(0, 0, 0, 140)); // semi-transparent black
}
painter.drawRoundedRect(rect, 6, 6); // rounded corners
}
void BackgroundPlateWidget::setFocused(bool _focused)
{
focused = _focused;
update();
}

View file

@ -0,0 +1,22 @@
#ifndef COCKATRICE_BACKGROUND_PLATE_WIDGET_H
#define COCKATRICE_BACKGROUND_PLATE_WIDGET_H
#include <QWidget>
class BackgroundPlateWidget : public QWidget
{
Q_OBJECT
public:
explicit BackgroundPlateWidget(QWidget *parent = nullptr);
void setFocused(bool focused);
private:
bool focused = false;
protected:
void paintEvent(QPaintEvent *event) override;
};
#endif // COCKATRICE_BACKGROUND_PLATE_WIDGET_H

View file

@ -0,0 +1,163 @@
#include "color_bar.h"
#include <QLinearGradient>
#include <QMouseEvent>
#include <QPainter>
#include <QToolTip>
ColorBar::ColorBar(const QMap<QString, int> &_colors, QWidget *parent) : QWidget(parent), colors(_colors)
{
setMouseTracking(true);
}
void ColorBar::setColors(const QMap<QString, int> &_colors)
{
colors = _colors;
update();
}
QSize ColorBar::minimumSizeHint() const
{
return QSize(200, 22);
}
void ColorBar::paintEvent(QPaintEvent *)
{
if (colors.isEmpty())
return;
int total = 0;
for (int v : colors.values())
total += v;
// Prevent divide-by-zero
if (total == 0)
return;
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
const int w = width();
const int h = height();
int x = 0;
// Draw rounded border background
QRectF bounds(0.5, 0.5, w - 1, h - 1);
p.setPen(QPen(Qt::black, 1));
p.setBrush(Qt::NoBrush);
p.drawRoundedRect(bounds, 6, 6);
// Clip to inside the border
p.setClipRect(bounds.adjusted(2, 2, -2, -2));
// Ensure predictable order
QList<QString> sortedKeys = colors.keys();
std::sort(sortedKeys.begin(), sortedKeys.end()); // Sort alphabetically
// Draw each color segment in the sorted order
for (const QString &key : sortedKeys) {
int value = colors[key];
double ratio = double(value) / total;
if (ratio <= minRatioThreshold) {
continue;
}
int segmentWidth = int(ratio * w);
// Ensure the segment width is at least 1 to avoid degenerate rectangles
if (segmentWidth < 1)
segmentWidth = 1;
QColor base = colorFromName(key);
// Slight gradient for nicer look
QLinearGradient grad(x, 0, x, h);
grad.setColorAt(0, base.lighter(120));
grad.setColorAt(1, base.darker(120));
p.fillRect(QRect(x, 0, segmentWidth, h), grad);
x += segmentWidth;
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void ColorBar::enterEvent(QEnterEvent *event)
{
Q_UNUSED(event);
isHovered = true;
}
#else
void ColorBar::enterEvent(QEvent *event)
{
Q_UNUSED(event);
isHovered = true;
}
#endif
void ColorBar::leaveEvent(QEvent *)
{
isHovered = false;
}
void ColorBar::mouseMoveEvent(QMouseEvent *event)
{
if (!isHovered || colors.isEmpty())
return;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
int x = int(event->position().x());
QPoint gp = event->globalPosition().toPoint();
#else
int x = event->pos().x();
QPoint gp = event->globalPos();
#endif
QString text = tooltipForPosition(x);
if (!text.isEmpty())
QToolTip::showText(gp, text, this);
}
QString ColorBar::tooltipForPosition(int x) const
{
int total = 0;
for (int v : colors.values())
total += v;
if (total == 0)
return {};
int pos = 0;
for (auto it = colors.cbegin(); it != colors.cend(); ++it) {
const double ratio = double(it.value()) / total;
const int segmentWidth = int(ratio * width());
if (x >= pos && x < pos + segmentWidth) {
const double percent = (100.0 * it.value()) / total;
return QString("%1: %2 cards (%3%)").arg(it.key()).arg(it.value()).arg(QString::number(percent, 'f', 1));
}
pos += segmentWidth;
}
return {};
}
QColor ColorBar::colorFromName(const QString &name) const
{
static QMap<QString, QColor> map = {
{"R", QColor(220, 30, 30)}, {"G", QColor(40, 170, 40)}, {"U", QColor(40, 90, 200)},
{"W", QColor(235, 235, 230)}, {"B", QColor(30, 30, 30)},
};
if (map.contains(name))
return map[name];
QColor c(name);
if (!c.isValid())
c = Qt::gray;
return c;
}

View file

@ -0,0 +1,128 @@
#ifndef COCKATRICE_COLOR_BAR_H
#define COCKATRICE_COLOR_BAR_H
#include <QColor>
#include <QMap>
#include <QString>
#include <QWidget>
/**
* @class ColorBar
* @brief A widget for visualizing proportional color distributions as a horizontal bar.
*
* This widget renders a horizontal bar divided into colored segments whose widths reflect
* the relative values associated with each color key in a `QMap<QString, int>`. The class
* is designed as a small, lightweight, and self-contained visualization component suitable
* for representing distributions such as color counts, mana statistics, categorical frequencies, and similar data sets.
*
* Key features:
* - Filled segments for better visual clarity.
* - Deterministic alphabetical ordering of color keys.
* - Optional minimum percentage threshold for filtering out insignificant segments.
* - Mouse-hover tooltips showing each segments key, count, and percentage of total.
*
* Default color mappings exist for `"R"`, `"G"`, `"U"`, `"W"`, and `"B"`, using named
* colors, but any string recognized by `QColor` may be used. If an unknown name is provided,
* the segment will fall back to gray.
*
* This component is display-only and does not interpret or mutate domain-level data.
*/
class ColorBar : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructs a ColorBar widget.
*
* @param colors Map of color identifiers to integer counts.
* @param parent Optional parent widget.
*/
explicit ColorBar(const QMap<QString, int> &colors, QWidget *parent = nullptr);
/**
* @brief Updates the color distribution map.
* @param colors New color count mapping.
*
* Triggers an immediate repaint.
*/
void setColors(const QMap<QString, int> &colors);
/**
* @brief Sets a minimum percentage threshold below which segments are not drawn.
*
* @param treshold Percentage from 0 to 100.
*
* Internally converted into a ratio (0.05 = 5%).
*/
void setMinPercentThreshold(double treshold)
{
minRatioThreshold = treshold / 100.0;
}
/**
* @brief Returns the recommended minimum size.
*/
QSize minimumSizeHint() const override;
protected:
/**
* @brief Paints the color distribution bar.
*
* Draws:
* - A rounded border
* - Filled segments for each color
* - Only segments above the minimum ratio threshold
*/
void paintEvent(QPaintEvent *event) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
/**
* @brief Handles mouse hover entering (Qt6 version).
*/
void enterEvent(QEnterEvent *event) override;
#else
/**
* @brief Handles mouse hover entering (Qt5 version).
*/
void enterEvent(QEvent *event) override;
#endif
/**
* @brief Handles mouse hover leaving.
*/
void leaveEvent(QEvent *event) override;
/**
* @brief Handles mouse movement to update contextual tooltips.
*/
void mouseMoveEvent(QMouseEvent *event) override;
private:
/// Map of color keys to counts used for rendering.
QMap<QString, int> colors;
/// True if the mouse is currently inside the widget.
bool isHovered = false;
/// Minimum ratio a segment must exceed to be drawn.
double minRatioThreshold = 0.0;
/**
* @brief Converts a color name into a display QColor.
*
* Recognized special keys: `"R", "G", "U", "W", "B"`.
* Other strings are treated as QColor names or fall back to gray.
*/
QColor colorFromName(const QString &name) const;
/**
* @brief Returns tooltip text for a given x-coordinate in the bar.
*
* @param x Horizontal coordinate relative to widget.
* @return Tooltip text or empty string if no segment applies.
*/
QString tooltipForPosition(int x) const;
};
#endif // COCKATRICE_COLOR_BAR_H

View file

@ -12,12 +12,18 @@
*/
ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent)
{
setAttribute(Qt::WA_TranslucentBackground); // Allows transparency.
setText("<font color='white'>" + text + "</font>"); ///< Ensures the text is rendered in white.
setAttribute(Qt::WA_TranslucentBackground); // Allows transparency.
setLabelText(text);
setAlignment(Qt::AlignCenter); ///< Centers the text within the label.
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ///< Ensures minimum size constraints.
}
void ShadowBackgroundLabel::setLabelText(const QString &text)
{
setText("<font color='white'>" + text + "</font>"); ///< Ensures the text is rendered in white.
update();
}
/**
* @brief Handles resizing of the label.
*

View file

@ -15,6 +15,7 @@ class ShadowBackgroundLabel : public QLabel
public:
explicit ShadowBackgroundLabel(QWidget *parent, const QString &text);
void setLabelText(const QString &text);
protected:
void resizeEvent(QResizeEvent *event) override;

View file

@ -196,6 +196,9 @@ QGroupBox *HomeWidget::createButtons()
auto edhrecButton = new HomeStyledButton(tr("Browse EDHRec"), gradientColors);
connect(edhrecButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addEdhrecMainTab);
boxLayout->addWidget(edhrecButton);
auto archidektButton = new HomeStyledButton(tr("Browse Archidekt"), gradientColors);
connect(archidektButton, &QPushButton::clicked, tabSupervisor, &TabSupervisor::addArchidektTab);
boxLayout->addWidget(archidektButton);
auto replaybutton = new HomeStyledButton(tr("View Replays"), gradientColors);
connect(replaybutton, &QPushButton::clicked, tabSupervisor, [this] { tabSupervisor->actTabReplays(true); });
boxLayout->addWidget(replaybutton);