mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-16 20:17:45 -07:00
Merge branch 'master' into tooomm-dockerfile
This commit is contained in:
commit
568825fd76
48 changed files with 1183 additions and 759 deletions
10
.github/workflows/documentation-build.yml
vendored
10
.github/workflows/documentation-build.yml
vendored
|
|
@ -1,9 +1,9 @@
|
|||
name: Generate Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*' # Only re-generate docs when a new tagged version is pushed
|
||||
release:
|
||||
types:
|
||||
- published # publishing of stable releases and pre-releases
|
||||
pull_request:
|
||||
paths:
|
||||
- 'doc/doxygen/**'
|
||||
|
|
@ -53,11 +53,11 @@ jobs:
|
|||
run: doxygen Doxyfile
|
||||
|
||||
- name: Deploy to cockatrice.github.io
|
||||
if: github.event_name != 'pull_request'
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
|
||||
external_repository: Cockatrice/cockatrice.github.io
|
||||
publish_branch: master
|
||||
publish_dir: ./docs/html
|
||||
destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/
|
||||
destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/
|
||||
|
|
|
|||
|
|
@ -327,6 +327,8 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
|
||||
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
|
||||
src/interface/widgets/utility/compact_push_button.cpp
|
||||
src/interface/widgets/utility/compact_push_button.h
|
||||
)
|
||||
|
||||
add_subdirectory(sounds)
|
||||
|
|
|
|||
|
|
@ -537,6 +537,9 @@ private:
|
|||
{"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."),
|
||||
parseSequenceString("Alt+N"),
|
||||
ShortcutGroup::Playing_Area)},
|
||||
{"Player/aReduceLifeByPower", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reduce Life by Power"),
|
||||
parseSequenceString("Ctrl+Shift+L"),
|
||||
ShortcutGroup::Playing_Area)},
|
||||
{"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"),
|
||||
parseSequenceString("Ctrl+A"),
|
||||
ShortcutGroup::Playing_Area)},
|
||||
|
|
|
|||
|
|
@ -85,7 +85,11 @@ const CardInfo &AbstractCardItem::getCardInfo() const
|
|||
void AbstractCardItem::setRealZValue(qreal _zValue)
|
||||
{
|
||||
realZValue = _zValue;
|
||||
setZValue(_zValue);
|
||||
// During hover, zValue is overridden to HOVERED_CARD. Layout operations
|
||||
// like reorganizeCards() call setRealZValue() on all cards including the
|
||||
// hovered one — skip setZValue() here to avoid clobbering the override.
|
||||
if (!isHovered)
|
||||
setZValue(_zValue);
|
||||
}
|
||||
|
||||
QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
|
||||
|
|
@ -213,8 +217,16 @@ void AbstractCardItem::setHovered(bool _hovered)
|
|||
if (isHovered == _hovered)
|
||||
return;
|
||||
|
||||
if (_hovered)
|
||||
if (_hovered) {
|
||||
processHoverEvent();
|
||||
} else {
|
||||
// Mark the hovered card's current scene footprint dirty so overlapped
|
||||
// sibling zones (e.g. StackZone) repaint after the card moves away.
|
||||
if (scene()) {
|
||||
scene()->update(sceneBoundingRect());
|
||||
}
|
||||
}
|
||||
|
||||
isHovered = _hovered;
|
||||
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
|
||||
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file abstract_card_item.h
|
||||
* @ingroup GameGraphicsCards
|
||||
* @brief TODO: Document this.
|
||||
* @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic.
|
||||
*/
|
||||
|
||||
#ifndef ABSTRACTCARDITEM_H
|
||||
|
|
@ -96,6 +96,10 @@ public:
|
|||
}
|
||||
void setRealZValue(qreal _zValue);
|
||||
void setHovered(bool _hovered);
|
||||
bool getIsHovered() const
|
||||
{
|
||||
return isHovered;
|
||||
}
|
||||
QString getColor() const
|
||||
{
|
||||
return color;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "phases_toolbar.h"
|
||||
#include "player/player.h"
|
||||
#include "player/player_graphics_item.h"
|
||||
#include "zones/select_zone.h"
|
||||
#include "zones/view_zone.h"
|
||||
#include "zones/view_zone_widget.h"
|
||||
|
||||
|
|
@ -356,12 +357,26 @@ void GameScene::updateHover(const QPointF &scenePos)
|
|||
void GameScene::updateHoveredCard(CardItem *newCard)
|
||||
{
|
||||
if (hoveredCard && (newCard != hoveredCard))
|
||||
hoveredCard->setHovered(false);
|
||||
endCardHover(hoveredCard);
|
||||
if (newCard && (newCard != hoveredCard))
|
||||
newCard->setHovered(true);
|
||||
beginCardHover(newCard);
|
||||
hoveredCard = newCard;
|
||||
}
|
||||
|
||||
void GameScene::beginCardHover(CardItem *card)
|
||||
{
|
||||
card->setHovered(true);
|
||||
if (auto *zone = SelectZone::findOwningSelectZone(card))
|
||||
zone->escapeClipForHover(card);
|
||||
}
|
||||
|
||||
void GameScene::endCardHover(CardItem *card)
|
||||
{
|
||||
if (auto *zone = SelectZone::findOwningSelectZone(card))
|
||||
zone->restoreClipAfterHover(card);
|
||||
card->setHovered(false);
|
||||
}
|
||||
|
||||
CardZone *GameScene::findTopmostZone(const QList<QGraphicsItem *> &items)
|
||||
{
|
||||
for (QGraphicsItem *item : items)
|
||||
|
|
@ -496,6 +511,8 @@ bool GameScene::event(QEvent *event)
|
|||
{
|
||||
if (event->type() == QEvent::GraphicsSceneMouseMove)
|
||||
updateHover(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
|
||||
else if (event->type() == QEvent::Leave)
|
||||
updateHoveredCard(nullptr);
|
||||
|
||||
return QGraphicsScene::event(event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ private:
|
|||
*/
|
||||
void updateHover(const QPointF &scenePos);
|
||||
|
||||
/// Activates hover state and escapes the card from its clip container so hover scaling is visible beyond zone
|
||||
/// bounds.
|
||||
void beginCardHover(CardItem *card);
|
||||
/// Deactivates hover state and restores the card to its clip container.
|
||||
void endCardHover(CardItem *card);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs the GameScene.
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
|
|||
aSelectColumn = new QAction(this);
|
||||
connect(aSelectColumn, &QAction::triggered, playerActions, &PlayerActions::actSelectColumn);
|
||||
|
||||
aReduceLifeByPower = new QAction(this);
|
||||
connect(aReduceLifeByPower, &QAction::triggered, playerActions, &PlayerActions::actReduceLifeByPower);
|
||||
|
||||
aPlay = new QAction(this);
|
||||
connect(aPlay, &QAction::triggered, playerActions, &PlayerActions::actPlay);
|
||||
aHide = new QAction(this);
|
||||
|
|
@ -153,6 +156,8 @@ void CardMenu::createTableMenu(bool canModifyCard)
|
|||
addSeparator();
|
||||
addAction(aClone);
|
||||
addSeparator();
|
||||
addAction(aReduceLifeByPower);
|
||||
addSeparator();
|
||||
addAction(aSelectAll);
|
||||
addAction(aSelectRow);
|
||||
addRelatedCardView();
|
||||
|
|
@ -179,6 +184,8 @@ void CardMenu::createTableMenu(bool canModifyCard)
|
|||
addMenu(new PtMenu(player));
|
||||
addAction(aSetAnnotation);
|
||||
addSeparator();
|
||||
addAction(aReduceLifeByPower);
|
||||
addSeparator();
|
||||
addAction(aSelectAll);
|
||||
addAction(aSelectRow);
|
||||
|
||||
|
|
@ -463,6 +470,7 @@ void CardMenu::retranslateUi()
|
|||
aUnattach->setText(tr("Unattac&h"));
|
||||
aDrawArrow->setText(tr("&Draw arrow..."));
|
||||
aSetAnnotation->setText(tr("&Set annotation..."));
|
||||
aReduceLifeByPower->setText(tr("Reduce life by power"));
|
||||
|
||||
mCardCounters->setTitle(tr("Ca&rd counters"));
|
||||
|
||||
|
|
@ -497,6 +505,7 @@ void CardMenu::setShortcutsActive()
|
|||
aUnattach->setShortcuts(shortcuts.getShortcut("Player/aUnattach"));
|
||||
aDrawArrow->setShortcuts(shortcuts.getShortcut("Player/aDrawArrow"));
|
||||
aSetAnnotation->setShortcuts(shortcuts.getShortcut("Player/aSetAnnotation"));
|
||||
aReduceLifeByPower->setShortcuts(shortcuts.getShortcut("Player/aReduceLifeByPower"));
|
||||
|
||||
aSelectAll->setShortcuts(shortcuts.getShortcut("Player/aSelectAll"));
|
||||
aSelectRow->setShortcuts(shortcuts.getShortcut("Player/aSelectRow"));
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public:
|
|||
QAction *aFlip, *aPeek;
|
||||
QAction *aAttach, *aUnattach;
|
||||
QAction *aSetAnnotation;
|
||||
QAction *aReduceLifeByPower;
|
||||
|
||||
QList<QAction *> aAddCounter, aSetCounter, aRemoveCounter;
|
||||
|
||||
|
|
|
|||
|
|
@ -343,6 +343,16 @@ void Player::incrementAllCardCounters()
|
|||
}
|
||||
}
|
||||
|
||||
AbstractCounter *Player::getLifeCounter() const
|
||||
{
|
||||
for (auto counter : counters.values()) {
|
||||
if (counter->getName() == "life") {
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ArrowItem *Player::addArrow(const ServerInfo_Arrow &arrow)
|
||||
{
|
||||
const QMap<int, Player *> &playerList = game->getPlayerManager()->getPlayers();
|
||||
|
|
|
|||
|
|
@ -199,6 +199,11 @@ public:
|
|||
return counters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the counter that represents the life total.
|
||||
*/
|
||||
AbstractCounter *getLifeCounter() const;
|
||||
|
||||
ArrowItem *addArrow(const ServerInfo_Arrow &arrow);
|
||||
ArrowItem *addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color);
|
||||
void delArrow(int arrowId);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_flip_card.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_mulligan.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
|
||||
|
|
@ -1378,6 +1379,32 @@ void PlayerActions::actFlowT()
|
|||
actIncPT(-1, 1);
|
||||
}
|
||||
|
||||
void PlayerActions::actReduceLifeByPower()
|
||||
{
|
||||
// find life counter
|
||||
auto lifeCounter = player->getLifeCounter();
|
||||
if (!lifeCounter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate total power
|
||||
auto cards = player->getGameScene()->selectedCards();
|
||||
int total = 0;
|
||||
for (auto card : cards) {
|
||||
QVariantList parsed = CardItem::parsePT(card->getPT());
|
||||
if (!parsed.isEmpty()) {
|
||||
int power = parsed.first().toInt(); // toInt will default to 0 if it's not an int
|
||||
total += qMax(power, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// send cmd
|
||||
Command_IncCounter cmd;
|
||||
cmd.set_counter_id(lifeCounter->getId());
|
||||
cmd.set_delta(-total);
|
||||
sendGameCommand(prepareGameCommand(cmd));
|
||||
}
|
||||
|
||||
void AnnotationDialog::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Return && event->modifiers() & Qt::ControlModifier) {
|
||||
|
|
|
|||
|
|
@ -145,6 +145,9 @@ public slots:
|
|||
void actDecPT();
|
||||
void actFlowP();
|
||||
void actFlowT();
|
||||
|
||||
void actReduceLifeByPower();
|
||||
|
||||
void actSetAnnotation();
|
||||
void actReveal(QAction *action);
|
||||
void actRevealHand(int revealToPlayerId);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ CardZone::CardZone(CardZoneLogic *_logic, QGraphicsItem *parent)
|
|||
void CardZone::onCardAdded(CardItem *addedCard)
|
||||
{
|
||||
addedCard->setParentItem(this);
|
||||
addedCard->setVisible(true);
|
||||
addedCard->update();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file card_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Base graphics item for zones that contain cards.
|
||||
*/
|
||||
|
||||
#ifndef CARDZONE_H
|
||||
|
|
@ -40,7 +40,10 @@ protected:
|
|||
}
|
||||
public slots:
|
||||
bool showContextMenu(const QPoint &screenPos);
|
||||
void onCardAdded(CardItem *addedCard);
|
||||
/// @brief Called when a card is added to this zone. Default: reparents card to this item.
|
||||
/// Virtual so subclasses (e.g. SelectZone) can override parenting behavior — the Qt signal
|
||||
/// connection in CardZone's constructor dispatches through the vtable.
|
||||
virtual void onCardAdded(CardItem *addedCard);
|
||||
|
||||
public:
|
||||
enum
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr || dragItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPoint point = dropPoint + scenePos().toPoint();
|
||||
int x = -1;
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
|
|
@ -34,9 +38,7 @@ void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
if (point.x() < static_cast<CardItem *>(getLogic()->getCards().at(x))->scenePos().x())
|
||||
break;
|
||||
} else {
|
||||
for (x = 0; x < getLogic()->getCards().size(); x++)
|
||||
if (point.y() < static_cast<CardItem *>(getLogic()->getCards().at(x))->scenePos().y())
|
||||
break;
|
||||
x = calcDropIndexFromY(dropPoint.y());
|
||||
}
|
||||
|
||||
Command_MoveCard cmd;
|
||||
|
|
@ -58,7 +60,7 @@ QRectF HandZone::boundingRect() const
|
|||
if (SettingsCache::instance().getHorizontalHand())
|
||||
return QRectF(0, 0, width, CardDimensions::HEIGHT_F + 10);
|
||||
else
|
||||
return QRectF(0, 0, 100, zoneHeight);
|
||||
return QRectF(0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight);
|
||||
}
|
||||
|
||||
void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
|
|
@ -78,35 +80,31 @@ void HandZone::reorganizeCards()
|
|||
qreal totalWidth =
|
||||
leftJustified ? boundingRect().width() - (1 * xPadding) - 5 : boundingRect().width() - 2 * xPadding;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = getLogic()->getCards().at(i);
|
||||
// If the total width of the cards is smaller than the available width,
|
||||
// the cards do not need to overlap and are displayed in the center of the area.
|
||||
if (cardWidth * cardCount > totalWidth)
|
||||
c->setPos(xPadding + ((qreal)i) * (totalWidth - cardWidth) / (cardCount - 1), 5);
|
||||
else {
|
||||
qreal xPosition =
|
||||
leftJustified ? xPadding + ((qreal)i) * cardWidth
|
||||
: xPadding + ((qreal)i) * cardWidth + (totalWidth - cardCount * cardWidth) / 2;
|
||||
c->setPos(xPosition, 5);
|
||||
if (cardCount == 1) {
|
||||
CardItem *c = getLogic()->getCards().at(0);
|
||||
qreal xPosition = leftJustified ? xPadding : xPadding + (totalWidth - cardWidth) / 2;
|
||||
c->setPos(xPosition, 5);
|
||||
c->setRealZValue(0);
|
||||
} else {
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = getLogic()->getCards().at(i);
|
||||
// If the total width of the cards is smaller than the available width,
|
||||
// the cards do not need to overlap and are displayed in the center of the area.
|
||||
if (cardWidth * cardCount > totalWidth)
|
||||
c->setPos(xPadding + ((qreal)i) * (totalWidth - cardWidth) / (cardCount - 1), 5);
|
||||
else {
|
||||
qreal xPosition = leftJustified ? xPadding + ((qreal)i) * cardWidth
|
||||
: xPadding + ((qreal)i) * cardWidth +
|
||||
(totalWidth - cardCount * cardWidth) / 2;
|
||||
c->setPos(xPosition, 5);
|
||||
}
|
||||
c->setRealZValue(i);
|
||||
}
|
||||
c->setRealZValue(i);
|
||||
}
|
||||
} else {
|
||||
qreal totalWidth = boundingRect().width();
|
||||
qreal cardWidth = getLogic()->getCards().at(0)->boundingRect().width();
|
||||
qreal xspace = 5;
|
||||
qreal x1 = xspace;
|
||||
qreal x2 = totalWidth - xspace - cardWidth;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *card = getLogic()->getCards().at(i);
|
||||
qreal x = (i % 2) ? x2 : x1;
|
||||
qreal y = divideCardSpaceInZone(i, cardCount, boundingRect().height(),
|
||||
getLogic()->getCards().at(0)->boundingRect().height());
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
}
|
||||
// No clip container: hand cards should always be visible to the player.
|
||||
const auto params = buildStackParams();
|
||||
layoutCardsVertically(params);
|
||||
}
|
||||
}
|
||||
update();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file hand_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Graphical zone for the player's hand, supporting horizontal and vertical layouts.
|
||||
*/
|
||||
|
||||
#ifndef HANDZONE_H
|
||||
|
|
@ -14,7 +14,8 @@ class HandZone : public SelectZone
|
|||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
qreal width, zoneHeight;
|
||||
qreal width = 0.0;
|
||||
qreal zoneHeight;
|
||||
private slots:
|
||||
void updateBg();
|
||||
public slots:
|
||||
|
|
|
|||
|
|
@ -4,38 +4,207 @@
|
|||
#include "../board/card_item.h"
|
||||
#include "../game_scene.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QtMath>
|
||||
|
||||
qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse)
|
||||
static qreal stackingOffset(qreal cardHeight)
|
||||
{
|
||||
qreal cardMinOverlap = cardHeight * SettingsCache::instance().getStackCardOverlapPercent() / 100;
|
||||
qreal desiredHeight = cardHeight * cardCount - cardMinOverlap * (cardCount - 1);
|
||||
qreal y;
|
||||
if (desiredHeight > totalHeight) {
|
||||
if (reverse) {
|
||||
y = index / ((totalHeight - cardHeight) / (cardCount - 1));
|
||||
const qreal overlapPercent = SettingsCache::instance().getStackCardOverlapPercent();
|
||||
return cardHeight * (100.0 - overlapPercent) / 100.0;
|
||||
}
|
||||
|
||||
SelectZone::ZoneLayout SelectZone::computeZoneLayout(const StackLayoutParams ¶ms)
|
||||
{
|
||||
if (params.cardCount <= 0) {
|
||||
return {0.0, 0.0};
|
||||
}
|
||||
qreal effectiveOffset = params.desiredOffset;
|
||||
if (params.cardCount > 1) {
|
||||
qreal fitOffset;
|
||||
if (params.totalHeight < params.cardHeight && params.minOffset > 0.0) {
|
||||
// Zone is shorter than a card (e.g. minimized). Compress offsets so
|
||||
// every card has at least minOffset pixels of its top visible.
|
||||
fitOffset = (params.totalHeight - params.minOffset) / (params.cardCount - 1);
|
||||
effectiveOffset = qMax(0.0, qMin(params.desiredOffset, fitOffset));
|
||||
} else {
|
||||
y = index * (totalHeight - cardHeight) / (cardCount - 1);
|
||||
}
|
||||
} else {
|
||||
qreal start = (totalHeight - desiredHeight) / 2;
|
||||
if (reverse) {
|
||||
if (index <= start) {
|
||||
return 0;
|
||||
qreal reservedForBottomCard;
|
||||
if (params.allowBottomOverflow) {
|
||||
// Allow the bottom card to partially overflow in tight zones, scaling the
|
||||
// overflow allowance by sqrt(cardCount-1) so offsets decrease smoothly
|
||||
// as cards are added rather than dropping by 1/(n-1) each time.
|
||||
// The 0.75 ratio was tuned experimentally to balance card visibility vs. overflow.
|
||||
constexpr qreal bottomCardZoneRatio = 0.75;
|
||||
const qreal adjustedRatio = bottomCardZoneRatio / qSqrt(static_cast<qreal>(params.cardCount - 1));
|
||||
reservedForBottomCard = qMin(params.cardHeight, params.totalHeight * adjustedRatio);
|
||||
} else {
|
||||
// No overflow: reserve full card height for the bottom card
|
||||
reservedForBottomCard = params.cardHeight;
|
||||
}
|
||||
y = (index - start) / (cardHeight - cardMinOverlap);
|
||||
} else {
|
||||
y = index * (cardHeight - cardMinOverlap) + start;
|
||||
fitOffset = (params.totalHeight - reservedForBottomCard) / (params.cardCount - 1);
|
||||
effectiveOffset = qMax(params.minOffset, qMin(params.desiredOffset, fitOffset));
|
||||
}
|
||||
}
|
||||
return y;
|
||||
qreal stackHeight = (params.cardCount - 1) * effectiveOffset + params.cardHeight;
|
||||
qreal start = (stackHeight <= params.totalHeight) ? (params.totalHeight - stackHeight) / 2.0 : 0.0;
|
||||
return {effectiveOffset, start};
|
||||
}
|
||||
|
||||
SelectZone *SelectZone::findOwningSelectZone(const QGraphicsItem *card)
|
||||
{
|
||||
QGraphicsItem *parent = card ? card->parentItem() : nullptr;
|
||||
if (!parent) {
|
||||
return nullptr;
|
||||
}
|
||||
// Card may be direct child of zone (escaped for hover) or child of clip container.
|
||||
if (auto *zone = dynamic_cast<SelectZone *>(parent)) {
|
||||
return zone;
|
||||
}
|
||||
if (auto *zone = dynamic_cast<SelectZone *>(parent->parentItem())) {
|
||||
return zone;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SelectZone::StackLayoutParams SelectZone::buildStackParams(qreal minOffset) const
|
||||
{
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (cards.isEmpty())
|
||||
return {0, boundingRect().height(), 0.0, 0.0, minOffset};
|
||||
const auto cardCount = static_cast<int>(cards.size());
|
||||
const qreal cardHeight = cards.at(0)->boundingRect().height();
|
||||
const qreal offset = stackingOffset(cardHeight);
|
||||
return {cardCount, boundingRect().height(), cardHeight, offset, minOffset};
|
||||
}
|
||||
|
||||
int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
|
||||
{
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (cards.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
const auto params = buildStackParams(minOffset);
|
||||
auto [effectiveOffset, start] = computeZoneLayout(params);
|
||||
if (effectiveOffset <= 0.0) {
|
||||
return 0;
|
||||
}
|
||||
return qBound(0, qRound((dropY - start) / effectiveOffset), params.cardCount - 1);
|
||||
}
|
||||
|
||||
void SelectZone::restoreStaleEscapedCards()
|
||||
{
|
||||
if (!cardClipContainer)
|
||||
return;
|
||||
for (auto *card : getLogic()->getCards()) {
|
||||
// A card parented to the zone (instead of the clip container) should
|
||||
// only occur while it is actively hovered. If hover cleanup was
|
||||
// missed, reparent it back so clipping resumes.
|
||||
if (card && card->parentItem() == this && !card->getIsHovered()) {
|
||||
card->setParentItem(cardClipContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::layoutCardsVertically(const StackLayoutParams ¶ms)
|
||||
{
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (cards.isEmpty() || params.cardCount <= 0)
|
||||
return;
|
||||
if (params.cardCount > cards.size())
|
||||
return;
|
||||
|
||||
constexpr qreal xspace = 5;
|
||||
const qreal cardWidth = cards.at(0)->boundingRect().width();
|
||||
const qreal totalWidth = boundingRect().width();
|
||||
const qreal x1 = xspace;
|
||||
const qreal x2 = totalWidth - xspace - cardWidth;
|
||||
const qreal xCentered = (totalWidth - cardWidth) / 2.0;
|
||||
|
||||
auto [effectiveOffset, start] = computeZoneLayout(params);
|
||||
for (int i = 0; i < params.cardCount; i++) {
|
||||
CardItem *card = cards.at(i);
|
||||
qreal y = start + i * effectiveOffset;
|
||||
// Center single card; alternate left/right for multiple cards
|
||||
qreal x = (params.cardCount == 1) ? xCentered : ((i % 2) ? x2 : x1);
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
SelectZone::SelectZone(CardZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_logic, parent)
|
||||
{
|
||||
}
|
||||
|
||||
SelectZone::~SelectZone()
|
||||
{
|
||||
if (cardClipContainer) {
|
||||
// Reparent any hover-escaped cards back to the clip container so Qt's
|
||||
// parent-child tree is consistent for destruction. setParentItem() does
|
||||
// not invalidate getLogic()->getCards() (it modifies the graphics tree,
|
||||
// not the zone's logical card list).
|
||||
for (auto *card : getLogic()->getCards()) {
|
||||
if (card && card->parentItem() == this) {
|
||||
card->setParentItem(cardClipContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::onCardAdded(CardItem *addedCard)
|
||||
{
|
||||
if (cardClipContainer && addedCard) {
|
||||
addedCard->setParentItem(cardClipContainer);
|
||||
addedCard->setVisible(true);
|
||||
addedCard->update();
|
||||
} else {
|
||||
CardZone::onCardAdded(addedCard);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::setupClipContainer(std::optional<qreal> zValue)
|
||||
{
|
||||
if (cardClipContainer)
|
||||
return;
|
||||
|
||||
setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
|
||||
|
||||
cardClipContainer = new QGraphicsRectItem(this); // Owned by Qt parent-child tree; deleted with this zone.
|
||||
cardClipContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
|
||||
cardClipContainer->setPen(Qt::NoPen);
|
||||
cardClipContainer->setBrush(Qt::NoBrush);
|
||||
cardClipContainer->setRect(boundingRect());
|
||||
if (zValue.has_value()) {
|
||||
cardClipContainer->setZValue(*zValue);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::escapeClipForHover(QGraphicsItem *card)
|
||||
{
|
||||
// Reparent from clip container to zone so the hover-scaled card is visible
|
||||
// beyond clip bounds. Coordinates are identical because the clip container
|
||||
// is at (0,0) with no transform relative to this zone.
|
||||
if (cardClipContainer && card && card->parentItem() == cardClipContainer) {
|
||||
card->setParentItem(this);
|
||||
cardClipContainer->update();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::restoreClipAfterHover(QGraphicsItem *card)
|
||||
{
|
||||
// Restore card to clip container. If card's parent is not this zone,
|
||||
// a zone transition already reparented it via onCardAdded — skip.
|
||||
if (cardClipContainer && card && card->parentItem() == this) {
|
||||
card->setParentItem(cardClipContainer);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::updateClipRect()
|
||||
{
|
||||
if (cardClipContainer) {
|
||||
cardClipContainer->setRect(boundingRect());
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::LeftButton)) {
|
||||
|
|
@ -56,7 +225,7 @@ void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|||
continue;
|
||||
}
|
||||
|
||||
bool inRect = selectionRect.intersects(card->mapRectToParent(card->boundingRect()));
|
||||
bool inRect = selectionRect.intersects(card->mapRectToItem(this, card->boundingRect()));
|
||||
if (inRect && !cardsInSelectionRect.contains(card)) {
|
||||
// selection has just expanded to cover the card
|
||||
cardsInSelectionRect.insert(card);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file select_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Base class for zones where cards are laid out and individually interactable.
|
||||
*/
|
||||
|
||||
#ifndef SELECTZONE_H
|
||||
|
|
@ -10,6 +10,9 @@
|
|||
#include "card_zone.h"
|
||||
|
||||
#include <QSet>
|
||||
#include <optional>
|
||||
|
||||
class QGraphicsRectItem;
|
||||
|
||||
/**
|
||||
* A CardZone where the cards are laid out, with each card directly interactable by clicking.
|
||||
|
|
@ -17,19 +20,113 @@
|
|||
class SelectZone : public CardZone
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/// Finds the SelectZone that owns a card, regardless of whether the card is parented
|
||||
/// to the zone directly or to its clip container. Returns nullptr if not in a SelectZone.
|
||||
static SelectZone *findOwningSelectZone(const QGraphicsItem *card);
|
||||
|
||||
SelectZone(CardZoneLogic *logic, QGraphicsItem *parent = nullptr);
|
||||
~SelectZone() override;
|
||||
void onCardAdded(CardItem *addedCard) override;
|
||||
|
||||
/// @brief Temporarily reparents a card from the clip container to this zone so hover scaling is visible beyond clip
|
||||
/// bounds. Safe no-op if no clip container exists. Coordinates are preserved (clip container is at (0,0) with no
|
||||
/// transform).
|
||||
void escapeClipForHover(QGraphicsItem *card);
|
||||
/// @brief Restores a hover-escaped card back to the clip container. Guards against zone transitions that already
|
||||
/// reparented the card.
|
||||
void restoreClipAfterHover(QGraphicsItem *card);
|
||||
|
||||
private:
|
||||
QPointF selectionOrigin;
|
||||
QSet<CardItem *> cardsInSelectionRect;
|
||||
/// Invisible clipping parent for cards; owned by Qt parent-child tree (parented to this zone).
|
||||
/// Created by setupClipContainer(); null when no clip container is active.
|
||||
QGraphicsRectItem *cardClipContainer = nullptr;
|
||||
|
||||
protected:
|
||||
// -- Layout computation --
|
||||
|
||||
/// Parameters describing a vertical card stack's geometry.
|
||||
struct StackLayoutParams
|
||||
{
|
||||
int cardCount; ///< Number of cards in the stack
|
||||
qreal totalHeight; ///< Available height for the stack (zone height)
|
||||
qreal cardHeight; ///< Height of a single card
|
||||
qreal desiredOffset; ///< Preferred vertical offset between card tops
|
||||
qreal minOffset = 0.0; ///< Minimum offset to preserve (0 allows full compression)
|
||||
/// When false (default), reserves full cardHeight for the bottom card, ensuring
|
||||
/// all cards remain within zone bounds. When true, allows the bottom card to
|
||||
/// partially overflow using sqrt-scaled allowance. Use with setupClipContainer()
|
||||
/// for zones too short to fit a full card.
|
||||
bool allowBottomOverflow = false;
|
||||
};
|
||||
|
||||
/// Result of computing a vertical stack layout.
|
||||
struct ZoneLayout
|
||||
{
|
||||
qreal effectiveOffset; ///< Actual offset between card tops (may be compressed)
|
||||
qreal start; ///< Y coordinate of the first card's top edge
|
||||
};
|
||||
|
||||
/// Minimum visible pixels of each card's top edge when stacking compresses offsets in tight zones.
|
||||
static constexpr qreal MIN_CARD_VISIBLE = 10.0;
|
||||
|
||||
/**
|
||||
* @brief Computes layout for a vertical card stack (effective offset and start position).
|
||||
*
|
||||
* Three regimes:
|
||||
* 1. Minimized zone (totalHeight < card height with minOffset > 0): offsets compress
|
||||
* so each card retains at least minOffset visible pixels of its top edge.
|
||||
* 2. Normal zone with allowBottomOverflow=false (default): the bottom card is
|
||||
* guaranteed to fit within the zone boundary. Offsets compress as needed.
|
||||
* 3. Normal zone with allowBottomOverflow=true: the bottom card may partially
|
||||
* overflow. The overflow allowance is scaled by sqrt(cardCount-1) so that
|
||||
* adding one card shifts existing cards smoothly.
|
||||
*
|
||||
* When the stack fits with room to spare, it is centered vertically.
|
||||
*/
|
||||
static ZoneLayout computeZoneLayout(const StackLayoutParams ¶ms);
|
||||
|
||||
/// Builds StackLayoutParams from the current card list and zone geometry.
|
||||
StackLayoutParams buildStackParams(qreal minOffset = 0.0) const;
|
||||
|
||||
/// Computes the card index at a given y-coordinate within the zone's vertical layout.
|
||||
/// Returns 0 if the zone has no cards or the offset is zero.
|
||||
int calcDropIndexFromY(qreal dropY, qreal minOffset = 0.0) const;
|
||||
|
||||
/**
|
||||
* @brief Positions cards vertically with alternating left/right x-offsets.
|
||||
*
|
||||
* Cards alternate between left and right margins (5px padding from zone edges):
|
||||
* even-indexed cards at left, odd-indexed at right.
|
||||
* Cards are assigned ascending z-values.
|
||||
*
|
||||
* @param params Stack layout geometry parameters (use allowBottomOverflow to control overflow)
|
||||
*/
|
||||
void layoutCardsVertically(const StackLayoutParams ¶ms);
|
||||
|
||||
// -- Clip container --
|
||||
// The clip container mechanism is available for future zones that need visual clipping
|
||||
// (e.g., zones too short to fit a full card). To enable: call setupClipContainer() in the
|
||||
// zone's constructor, and set allowBottomOverflow=true in layout params.
|
||||
|
||||
/// Restores any cards that were hover-escaped but whose hover state was not properly cleaned up.
|
||||
/// Call at the start of reorganizeCards() in zones that use a clip container.
|
||||
void restoreStaleEscapedCards();
|
||||
|
||||
/// Creates a clip container child item that clips card overflow to zone bounds.
|
||||
/// Cards entering this zone are reparented to this container by the onCardAdded override.
|
||||
/// Disables zone-level child clipping; clipping is delegated to the container.
|
||||
/// @param zValue Optional z-value for the clip container (e.g. ZValues::CARD_BASE)
|
||||
void setupClipContainer(std::optional<qreal> zValue = std::nullopt);
|
||||
|
||||
/// Updates the clip container rect to match this zone's current boundingRect().
|
||||
void updateClipRect();
|
||||
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
|
||||
public:
|
||||
SelectZone(CardZoneLogic *logic, QGraphicsItem *parent = nullptr);
|
||||
};
|
||||
|
||||
qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse = false);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
#include "stack_zone.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
#include "../board/arrow_item.h"
|
||||
#include "../board/card_drag_item.h"
|
||||
#include "../board/card_item.h"
|
||||
#include "../card_dimensions.h"
|
||||
#include "../player/player.h"
|
||||
#include "../player/player_actions.h"
|
||||
#include "logic/stack_zone_logic.h"
|
||||
|
|
@ -27,7 +26,7 @@ void StackZone::updateBg()
|
|||
|
||||
QRectF StackZone::boundingRect() const
|
||||
{
|
||||
return {0, 0, 100, zoneHeight};
|
||||
return {0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight};
|
||||
}
|
||||
|
||||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
|
|
@ -40,7 +39,15 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr) {
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr || dragItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE);
|
||||
|
||||
// Same-zone no-op: don't move a card onto itself
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (!cards.isEmpty() && startZone == getLogic() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -49,36 +56,12 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
|
||||
int index = 0;
|
||||
|
||||
if (!getLogic()->getCards().isEmpty()) {
|
||||
const auto cardCount = static_cast<int>(getLogic()->getCards().size());
|
||||
const auto &card = getLogic()->getCards().at(0);
|
||||
|
||||
index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(),
|
||||
card->boundingRect().height(), true));
|
||||
|
||||
// divideCardSpaceInZone is not guaranteed to return a valid index
|
||||
// currently, so clamp it to avoid crashes.
|
||||
index = qBound(0, index, cardCount - 1);
|
||||
|
||||
if (startZone == getLogic()) {
|
||||
const auto &dragItem = dragItems.at(0);
|
||||
const auto &card = getLogic()->getCards().at(index);
|
||||
|
||||
if (card->getId() == dragItem->getId()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd.set_x(index);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (CardDragItem *item : dragItems) {
|
||||
for (const CardDragItem *item : dragItems) {
|
||||
if (item) {
|
||||
auto cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
cardToMove->set_card_id(item->getId());
|
||||
if (item->isForceFaceDown()) {
|
||||
cardToMove->set_face_down(true);
|
||||
|
|
@ -89,24 +72,22 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void StackZone::setHeight(qreal newHeight)
|
||||
{
|
||||
if (qFuzzyCompare(1.0 + zoneHeight, 1.0 + newHeight)) {
|
||||
return;
|
||||
}
|
||||
prepareGeometryChange();
|
||||
zoneHeight = newHeight;
|
||||
reorganizeCards();
|
||||
update();
|
||||
}
|
||||
|
||||
void StackZone::reorganizeCards()
|
||||
{
|
||||
if (!getLogic()->getCards().isEmpty()) {
|
||||
const auto cardCount = static_cast<int>(getLogic()->getCards().size());
|
||||
qreal totalWidth = boundingRect().width();
|
||||
qreal cardWidth = getLogic()->getCards().at(0)->boundingRect().width();
|
||||
qreal xspace = 5;
|
||||
qreal x1 = xspace;
|
||||
qreal x2 = totalWidth - xspace - cardWidth;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *card = getLogic()->getCards().at(i);
|
||||
qreal x = (i % 2) ? x2 : x1;
|
||||
qreal y = divideCardSpaceInZone(i, cardCount, boundingRect().height(),
|
||||
getLogic()->getCards().at(0)->boundingRect().height());
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
}
|
||||
const auto params = buildStackParams(MIN_CARD_VISIBLE);
|
||||
layoutCardsVertically(params);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file stack_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Graphical zone for the stack, displaying cards in a vertical pile.
|
||||
*/
|
||||
|
||||
#ifndef STACKZONE_H
|
||||
|
|
@ -20,6 +20,8 @@ private slots:
|
|||
|
||||
public:
|
||||
StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent);
|
||||
/// @brief Resizes the stack zone height, e.g. when sharing vertical space with the command zone.
|
||||
void setHeight(qreal newHeight);
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
QRectF boundingRect() const override;
|
||||
|
|
|
|||
|
|
@ -181,6 +181,11 @@ bool DeckLoader::saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFi
|
|||
*/
|
||||
bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck)
|
||||
{
|
||||
// text format doesn't support lastLoadedTimestamp, so there's no point in proceeding
|
||||
if (deck.lastLoadInfo.fileFormat != DeckFileFormat::Cockatrice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString fileName = deck.lastLoadInfo.fileName;
|
||||
|
||||
QFileInfo fileInfo(fileName);
|
||||
|
|
@ -201,15 +206,8 @@ bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck)
|
|||
bool result = false;
|
||||
|
||||
// Perform file modifications
|
||||
switch (deck.lastLoadInfo.fileFormat) {
|
||||
case DeckFileFormat::PlainText:
|
||||
result = deck.deckList.saveToFile_Plain(&file);
|
||||
break;
|
||||
case DeckFileFormat::Cockatrice:
|
||||
deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
|
||||
result = deck.deckList.saveToFile_Native(&file);
|
||||
break;
|
||||
}
|
||||
deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
|
||||
result = deck.deckList.saveToFile_Native(&file);
|
||||
|
||||
file.close(); // Close the file to ensure changes are flushed
|
||||
|
||||
|
|
@ -429,8 +427,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
|
|||
}
|
||||
if (addSetNameAndNumber) {
|
||||
if (!card->getCardSetShortName().isNull() && !card->getCardSetShortName().isEmpty()) {
|
||||
out << " "
|
||||
<< "(" << card->getCardSetShortName() << ")";
|
||||
out << " " << "(" << card->getCardSetShortName() << ")";
|
||||
}
|
||||
if (!card->getCardCollectorNumber().isNull()) {
|
||||
out << " " << card->getCardCollectorNumber();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
/**
|
||||
* @file flow_layout.cpp
|
||||
* @brief Implementation of the FlowLayout class, a custom layout for dynamically organizing widgets
|
||||
* in rows within the constraints of available width or parent scroll areas.
|
||||
* @brief Implementation of FlowLayout — a QLayout that wraps child widgets into rows
|
||||
* (Qt::Horizontal flow) or columns (Qt::Vertical flow).
|
||||
*
|
||||
* Design contract (following Qt layout conventions):
|
||||
* - setGeometry() places children inside the given rect. Nothing else.
|
||||
* - sizeHint() reports the unconstrained preferred size (all items in one line).
|
||||
* - minimumSize() reports the minimum size (largest single item).
|
||||
* - heightForWidth() reports the height needed for a given width (horizontal flow only).
|
||||
*
|
||||
* The layout never calls setFixedSize() or adjustSize() on its parent widget;
|
||||
* that is the responsibility of the parent widget / scroll area.
|
||||
*/
|
||||
|
||||
#include "flow_layout.h"
|
||||
|
|
@ -12,27 +21,18 @@
|
|||
#include <QLayoutItem>
|
||||
#include <QScrollArea>
|
||||
#include <QStyle>
|
||||
#include <QWidgetItem>
|
||||
|
||||
/**
|
||||
* @brief Constructs a FlowLayout instance with the specified parent widget, margin, and spacing values.
|
||||
* @param parent The parent widget for this layout.
|
||||
* @param margin The layout margin.
|
||||
* @param hSpacing The horizontal spacing between items.
|
||||
* @param vSpacing The vertical spacing between items.
|
||||
*/
|
||||
FlowLayout::FlowLayout(QWidget *parent,
|
||||
const Qt::Orientation _flowDirection,
|
||||
const Qt::Orientation flowDirection,
|
||||
const int margin,
|
||||
const int hSpacing,
|
||||
const int vSpacing)
|
||||
: QLayout(parent), flowDirection(_flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
|
||||
: QLayout(parent), flowDirection(flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing)
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor for FlowLayout, which cleans up all items in the layout.
|
||||
*/
|
||||
FlowLayout::~FlowLayout()
|
||||
{
|
||||
QLayoutItem *item;
|
||||
|
|
@ -42,499 +42,349 @@ FlowLayout::~FlowLayout()
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates the layout's support for expansion in both horizontal and vertical directions.
|
||||
* @return The orientations (Qt::Horizontal | Qt::Vertical) this layout can expand to fill.
|
||||
* @brief Reports which axis the layout can expand along.
|
||||
*
|
||||
* A horizontally-flowing layout expands horizontally (and wraps vertically,
|
||||
* but that is governed by heightForWidth, not by this flag).
|
||||
* A vertically-flowing layout expands vertically.
|
||||
*/
|
||||
Qt::Orientations FlowLayout::expandingDirections() const
|
||||
{
|
||||
return Qt::Horizontal | Qt::Vertical;
|
||||
return (flowDirection == Qt::Horizontal) ? Qt::Horizontal : Qt::Vertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates that this layout's height depends on its width.
|
||||
* @return True, as the layout adjusts its height to fit the specified width.
|
||||
* @brief Height-for-width is only meaningful for horizontal (wrapping) flow.
|
||||
*/
|
||||
bool FlowLayout::hasHeightForWidth() const
|
||||
{
|
||||
return true;
|
||||
return flowDirection == Qt::Horizontal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the required height to display all items within the specified width.
|
||||
* @param width The available width for arranging items.
|
||||
* @return The total height needed to fit all items in rows constrained by the specified width.
|
||||
* @brief Returns the height required to display all items within @p width.
|
||||
*
|
||||
* Only valid for horizontal flow; returns -1 otherwise so Qt ignores it.
|
||||
* Spacing is counted once between adjacent items, never before the first
|
||||
* or after the last.
|
||||
*/
|
||||
int FlowLayout::heightForWidth(const int width) const
|
||||
{
|
||||
if (flowDirection == Qt::Vertical) {
|
||||
int height = 0;
|
||||
int rowWidth = 0;
|
||||
int rowHeight = 0;
|
||||
if (flowDirection != Qt::Horizontal)
|
||||
return -1;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int totalHeight = 0;
|
||||
int rowUsedWidth = 0;
|
||||
int rowHeight = 0;
|
||||
|
||||
int itemWidth = item->sizeHint().width() + horizontalSpacing();
|
||||
if (rowWidth + itemWidth > width) {
|
||||
height += rowHeight + verticalSpacing();
|
||||
rowWidth = itemWidth;
|
||||
rowHeight = item->sizeHint().height();
|
||||
} else {
|
||||
rowWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, item->sizeHint().height());
|
||||
}
|
||||
}
|
||||
height += rowHeight; // Add height of the last row
|
||||
return height;
|
||||
} else {
|
||||
int width = 0;
|
||||
int colWidth = 0;
|
||||
int colHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int itemHeight = item->sizeHint().height();
|
||||
if (colHeight + itemHeight > width) {
|
||||
width += colWidth;
|
||||
colHeight = itemHeight;
|
||||
colWidth = item->sizeHint().width();
|
||||
} else {
|
||||
colHeight += itemHeight;
|
||||
colWidth = qMax(colWidth, item->sizeHint().width());
|
||||
}
|
||||
}
|
||||
width += colWidth; // Add width of the last column
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges layout items in rows within the specified rectangle bounds.
|
||||
* @param rect The area within which to position layout items.
|
||||
*/
|
||||
void FlowLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle.
|
||||
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
// If we have a parent scroll area, we're clamped to that, else we use our own rectangle.
|
||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth();
|
||||
|
||||
const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth);
|
||||
|
||||
if (QWidget *parentWidgetPtr = parentWidget()) {
|
||||
parentWidgetPtr->setFixedSize(availableWidth, totalHeight);
|
||||
}
|
||||
} else {
|
||||
const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight());
|
||||
|
||||
const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight);
|
||||
|
||||
if (QWidget *parentWidgetPtr = parentWidget()) {
|
||||
parentWidgetPtr->setFixedSize(totalWidth, availableHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lays out items into rows according to the available width, starting from a given origin.
|
||||
* Each row is arranged within `availableWidth`, wrapping to a new row as necessary.
|
||||
* @param originX The x-coordinate for the layout start position.
|
||||
* @param originY The y-coordinate for the layout start position.
|
||||
* @param availableWidth The width within which each row is constrained.
|
||||
* @return The total height after arranging all rows.
|
||||
*/
|
||||
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
|
||||
{
|
||||
QVector<QLayoutItem *> rowItems; // Holds items for the current row
|
||||
int currentXPosition = originX; // Tracks the x-coordinate while placing items
|
||||
int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row
|
||||
|
||||
int rowHeight = 0; // Tracks the maximum height of items in the current row
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
||||
int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing
|
||||
const QSize itemSize = item->sizeHint();
|
||||
// Spacing is only inserted between items, not before the first.
|
||||
const int spaceX = (rowUsedWidth > 0) ? horizontalSpacing() : 0;
|
||||
|
||||
// Check if the current item fits in the remaining width of the current row
|
||||
if (currentXPosition + itemWidth > availableWidth) {
|
||||
// If not, layout the current row and start a new row
|
||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
||||
rowItems.clear(); // Reset the list for the new row
|
||||
currentXPosition = originX; // Reset x-position to the row's start
|
||||
currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row
|
||||
rowHeight = 0; // Reset row height for the new row
|
||||
if (rowUsedWidth > 0 && rowUsedWidth + spaceX + itemSize.width() > width) {
|
||||
// This item overflows the current row — commit the row and start a new one.
|
||||
totalHeight += rowHeight + verticalSpacing();
|
||||
rowUsedWidth = itemSize.width();
|
||||
rowHeight = itemSize.height();
|
||||
} else {
|
||||
rowUsedWidth += spaceX + itemSize.width();
|
||||
rowHeight = qMax(rowHeight, itemSize.height());
|
||||
}
|
||||
}
|
||||
|
||||
return totalHeight + rowHeight; // Include the final (possibly only) row.
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Places all children within @p rect.
|
||||
*
|
||||
* This is the only method that may move/resize children. It does NOT resize
|
||||
* the parent widget; that would break Qt's layout protocol.
|
||||
*/
|
||||
void FlowLayout::setGeometry(const QRect &rect)
|
||||
{
|
||||
QLayout::setGeometry(rect);
|
||||
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
layoutAllRows(rect.x(), rect.y(), rect.width());
|
||||
} else {
|
||||
layoutAllColumns(rect.x(), rect.y(), rect.height());
|
||||
}
|
||||
}
|
||||
|
||||
QSize FlowLayout::sizeHint() const
|
||||
{
|
||||
return (flowDirection == Qt::Horizontal) ? calculateSizeHintHorizontal() : calculateSizeHintVertical();
|
||||
}
|
||||
|
||||
QSize FlowLayout::minimumSize() const
|
||||
{
|
||||
return (flowDirection == Qt::Horizontal) ? calculateMinimumSizeHorizontal() : calculateMinimumSizeVertical();
|
||||
}
|
||||
|
||||
// ─── Row layout (horizontal flow) ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Places all items into wrapping rows within @p availableWidth.
|
||||
* @return The y-coordinate of the bottom edge of the last row.
|
||||
*/
|
||||
int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth)
|
||||
{
|
||||
QVector<QLayoutItem *> rowItems;
|
||||
int rowUsedWidth = 0; // Width consumed by items already in the current row.
|
||||
int currentY = originY;
|
||||
int rowHeight = 0;
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QSize itemSize = item->sizeHint();
|
||||
// No leading space for the first item in a row.
|
||||
const int spaceX = rowItems.isEmpty() ? 0 : horizontalSpacing();
|
||||
|
||||
if (!rowItems.isEmpty() && rowUsedWidth + spaceX + itemSize.width() > availableWidth) {
|
||||
// Current item does not fit — flush the current row, begin a new one.
|
||||
layoutSingleRow(rowItems, originX, currentY, availableWidth);
|
||||
rowItems.clear();
|
||||
currentY += rowHeight + verticalSpacing();
|
||||
rowUsedWidth = 0;
|
||||
rowHeight = 0;
|
||||
}
|
||||
|
||||
// Add the item to the current row
|
||||
rowItems.append(item);
|
||||
rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item
|
||||
currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item
|
||||
// `rowItems.size() > 1` is equivalent to "this is not the first item in the row"
|
||||
// because we just appended above.
|
||||
rowUsedWidth += (rowItems.size() > 1 ? horizontalSpacing() : 0) + itemSize.width();
|
||||
rowHeight = qMax(rowHeight, itemSize.height());
|
||||
}
|
||||
|
||||
// Layout the final row if there are any remaining items
|
||||
layoutSingleRow(rowItems, originX, currentYPosition);
|
||||
|
||||
// Return the total height used, including the last row's height
|
||||
return currentYPosition + rowHeight;
|
||||
layoutSingleRow(rowItems, originX, currentY, availableWidth); // Flush the final row.
|
||||
return currentY + rowHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges a single row of items within specified x and y starting positions.
|
||||
* @param rowItems A list of items to be arranged in the row.
|
||||
* @param x The starting x-coordinate for the row.
|
||||
* @param y The starting y-coordinate for the row.
|
||||
* @brief Sets the geometry for every item in @p rowItems, starting at (@p x, @p y).
|
||||
*
|
||||
* Items whose horizontal size policy includes the Expand or MinimumExpanding flag
|
||||
* share the leftover row width proportionally (like QHBoxLayout stretch), so that
|
||||
* e.g. a QLineEdit can fill remaining space while fixed-size buttons stay compact.
|
||||
*
|
||||
* Items without an expanding policy are placed at their sizeHint, clamped to maximumSize.
|
||||
*/
|
||||
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y)
|
||||
void FlowLayout::layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, const int y, const int availableWidth)
|
||||
{
|
||||
if (rowItems.isEmpty())
|
||||
return;
|
||||
|
||||
// ── Pass 1: measure fixed width and count expanding items ────────────────
|
||||
int fixedWidth = 0;
|
||||
int expandingCount = 0;
|
||||
int spacingTotal = (rowItems.size() - 1) * horizontalSpacing();
|
||||
|
||||
for (QLayoutItem *item : rowItems) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the maximum allowed size for the item
|
||||
QSize itemMaxSize = item->widget()->maximumSize();
|
||||
// Constrain the item's width and height to its size hint or maximum size
|
||||
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
||||
// Set the item's geometry based on the computed size and position
|
||||
QWidget *widget = item->widget();
|
||||
const QSizePolicy::Policy hPolicy = widget ? widget->sizePolicy().horizontalPolicy() : QSizePolicy::Fixed;
|
||||
|
||||
if (hPolicy & QSizePolicy::ExpandFlag) {
|
||||
++expandingCount;
|
||||
} else {
|
||||
const int maxW = widget ? widget->maximumWidth() : QWIDGETSIZE_MAX;
|
||||
fixedWidth += qMin(item->sizeHint().width(), maxW);
|
||||
}
|
||||
}
|
||||
|
||||
// Extra pixels to share among expanding items (never negative).
|
||||
const int extra = qMax(0, availableWidth - spacingTotal - fixedWidth);
|
||||
const int expandingShare = (expandingCount > 0) ? extra / expandingCount : 0;
|
||||
|
||||
// ── Pass 2: place items ──────────────────────────────────────────────────
|
||||
for (QLayoutItem *item : rowItems) {
|
||||
if (!item || item->isEmpty())
|
||||
continue;
|
||||
|
||||
QWidget *widget = item->widget();
|
||||
if (!widget)
|
||||
continue;
|
||||
|
||||
const QSizePolicy::Policy hPolicy = widget->sizePolicy().horizontalPolicy();
|
||||
const QSize maxSize = widget->maximumSize();
|
||||
const bool expands = hPolicy & QSizePolicy::ExpandFlag;
|
||||
|
||||
const int itemWidth =
|
||||
expands ? qMin(expandingShare, maxSize.width()) : qMin(item->sizeHint().width(), maxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), maxSize.height());
|
||||
|
||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||
// Move the x-position to the right, leaving space for horizontal spacing
|
||||
x += itemWidth + horizontalSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Column layout (vertical flow) ───────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Lays out items into columns according to the available height, starting from a given origin.
|
||||
* Each column is arranged within `availableHeight`, wrapping to a new column as necessary.
|
||||
* @param originX The x-coordinate for the layout start position.
|
||||
* @param originY The y-coordinate for the layout start position.
|
||||
* @param availableHeight The height within which each column is constrained.
|
||||
* @return The total width after arranging all columns.
|
||||
* @brief Places all items into wrapping columns within @p availableHeight.
|
||||
* @return The x-coordinate of the right edge of the last column.
|
||||
*/
|
||||
int FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight)
|
||||
{
|
||||
QVector<QLayoutItem *> colItems; // Holds items for the current column
|
||||
int currentXPosition = originX; // Tracks the x-coordinate while placing items
|
||||
int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column
|
||||
|
||||
int colWidth = 0; // Tracks the maximum width of items in the current column
|
||||
QVector<QLayoutItem *> colItems;
|
||||
int colUsedHeight = 0; // Height consumed by items already in the current column.
|
||||
int currentX = originX;
|
||||
int colWidth = 0;
|
||||
|
||||
for (QLayoutItem *item : items) {
|
||||
if (item == nullptr || item->isEmpty()) {
|
||||
if (!item || item->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint(); // The suggested size for the current item
|
||||
const QSize itemSize = item->sizeHint();
|
||||
// No leading space for the first item in a column.
|
||||
const int spaceY = colItems.isEmpty() ? 0 : verticalSpacing();
|
||||
|
||||
// Check if the current item fits in the remaining height of the current column
|
||||
if (currentYPosition + itemSize.height() > availableHeight) {
|
||||
// If not, layout the current column and start a new column
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
colItems.clear(); // Reset the list for the new column
|
||||
currentYPosition = originY; // Reset y-position to the column's start
|
||||
currentXPosition += colWidth; // Move x-position to the next column
|
||||
colWidth = 0; // Reset column width for the new column
|
||||
if (!colItems.isEmpty() && colUsedHeight + spaceY + itemSize.height() > availableHeight) {
|
||||
// Current item does not fit — flush the current column, begin a new one.
|
||||
layoutSingleColumn(colItems, currentX, originY);
|
||||
colItems.clear();
|
||||
currentX += colWidth + horizontalSpacing();
|
||||
colUsedHeight = 0;
|
||||
colWidth = 0;
|
||||
}
|
||||
|
||||
// Add the item to the current column
|
||||
colItems.append(item);
|
||||
colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item
|
||||
currentYPosition += itemSize.height(); // Move y-position for the next item
|
||||
colUsedHeight += (colItems.size() > 1 ? verticalSpacing() : 0) + itemSize.height();
|
||||
colWidth = qMax(colWidth, itemSize.width());
|
||||
}
|
||||
|
||||
// Layout the final column if there are any remaining items
|
||||
layoutSingleColumn(colItems, currentXPosition, originY);
|
||||
|
||||
// Return the total width used, including the last column's width
|
||||
return currentXPosition + colWidth;
|
||||
layoutSingleColumn(colItems, currentX, originY); // Flush the final column.
|
||||
return currentX + colWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arranges a single column of items within specified x and y starting positions.
|
||||
* @param colItems A list of items to be arranged in the column.
|
||||
* @param x The starting x-coordinate for the column.
|
||||
* @param y The starting y-coordinate for the column.
|
||||
* @brief Sets the geometry for every item in @p colItems, starting at (@p x, @p y).
|
||||
*
|
||||
* Each item is placed at its sizeHint, clamped to its maximumSize.
|
||||
*/
|
||||
void FlowLayout::layoutSingleColumn(const QVector<QLayoutItem *> &colItems, const int x, int y)
|
||||
{
|
||||
for (QLayoutItem *item : colItems) {
|
||||
if (item == nullptr) {
|
||||
qCDebug(FlowLayoutLog) << "Item is null.";
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping null or empty item in column.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Debugging: Print the item's widget class name and size hint
|
||||
QWidget *widget = item->widget();
|
||||
if (widget) {
|
||||
qCDebug(FlowLayoutLog) << "Widget class:" << widget->metaObject()->className();
|
||||
qCDebug(FlowLayoutLog) << "Widget size hint:" << widget->sizeHint();
|
||||
qCDebug(FlowLayoutLog) << "Widget maximum size:" << widget->maximumSize();
|
||||
qCDebug(FlowLayoutLog) << "Widget minimum size:" << widget->minimumSize();
|
||||
|
||||
// Debugging: Print child widgets
|
||||
const QObjectList &children = widget->children();
|
||||
qCDebug(FlowLayoutLog) << "Child widgets:";
|
||||
for (QObject *child : children) {
|
||||
if (QWidget *childWidget = qobject_cast<QWidget *>(child)) {
|
||||
qCDebug(FlowLayoutLog) << " - Child widget class:" << childWidget->metaObject()->className();
|
||||
qCDebug(FlowLayoutLog) << " Size hint:" << childWidget->sizeHint();
|
||||
qCDebug(FlowLayoutLog) << " Maximum size:" << childWidget->maximumSize();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(FlowLayoutLog) << "Item does not have a widget.";
|
||||
if (!widget) {
|
||||
qCDebug(FlowLayoutLog) << "Item has no widget; skipping.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the maximum allowed size for the item
|
||||
QSize itemMaxSize = widget->maximumSize();
|
||||
// Constrain the item's width and height to its size hint or maximum size
|
||||
const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height());
|
||||
// Debugging: Print the computed geometry
|
||||
qCDebug(FlowLayoutLog) << "Computed geometry: x=" << x << ", y=" << y << ", width=" << itemWidth
|
||||
<< ", height=" << itemHeight;
|
||||
qCDebug(FlowLayoutLog) << "Widget:" << widget->metaObject()->className() << "sizeHint:" << widget->sizeHint()
|
||||
<< "maximumSize:" << widget->maximumSize() << "minimumSize:" << widget->minimumSize();
|
||||
|
||||
const QSize maxSize = widget->maximumSize();
|
||||
const int itemWidth = qMin(item->sizeHint().width(), maxSize.width());
|
||||
const int itemHeight = qMin(item->sizeHint().height(), maxSize.height());
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Placing at x=" << x << "y=" << y << "w=" << itemWidth << "h=" << itemHeight;
|
||||
|
||||
// Set the item's geometry based on the computed size and position
|
||||
item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight)));
|
||||
|
||||
// Move the y-position down by the item's height to place the next item below
|
||||
y += itemHeight;
|
||||
y += itemHeight + verticalSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the preferred size of the layout based on the flow direction.
|
||||
* @return A QSize representing the ideal dimensions of the layout.
|
||||
*/
|
||||
QSize FlowLayout::sizeHint() const
|
||||
{
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
return calculateSizeHintHorizontal();
|
||||
} else {
|
||||
return calculateSizeHintVertical();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the minimum size required by the layout based on the flow direction.
|
||||
* @return A QSize representing the minimum required dimensions.
|
||||
*/
|
||||
QSize FlowLayout::minimumSize() const
|
||||
{
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
return calculateMinimumSizeHorizontal();
|
||||
} else {
|
||||
return calculateMinimumSizeVertical();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size hint for horizontal flow direction.
|
||||
* @return A QSize representing the preferred dimensions.
|
||||
* @brief Preferred size for horizontal flow: all items in a single row (unconstrained).
|
||||
*
|
||||
* The actual displayed height is determined by heightForWidth() once Qt knows the
|
||||
* real available width.
|
||||
*/
|
||||
QSize FlowLayout::calculateSizeHintHorizontal() const
|
||||
{
|
||||
int maxWidth = 0; // Tracks the maximum width needed
|
||||
int totalHeight = 0; // Tracks the total height across all rows
|
||||
int rowHeight = 0; // Tracks the height of the current row
|
||||
int currentWidth = 0; // Tracks the current row's width
|
||||
|
||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating horizontal size hint. Available width:" << availableWidth;
|
||||
int totalWidth = 0;
|
||||
int maxHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint();
|
||||
int itemWidth = itemSize.width() + horizontalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize << "Width with spacing:" << itemWidth;
|
||||
|
||||
if (currentWidth + itemWidth > availableWidth) {
|
||||
qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight + verticalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
|
||||
|
||||
currentWidth = 0;
|
||||
rowHeight = 0;
|
||||
const QSize s = item->sizeHint();
|
||||
if (totalWidth > 0) {
|
||||
totalWidth += horizontalSpacing();
|
||||
}
|
||||
|
||||
currentWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, itemSize.height());
|
||||
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
|
||||
totalWidth += s.width();
|
||||
maxHeight = qMax(maxHeight, s.height());
|
||||
}
|
||||
|
||||
// Account for the final row
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight;
|
||||
qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
|
||||
|
||||
return QSize(maxWidth, totalHeight);
|
||||
return QSize(totalWidth, maxHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the minimum size for horizontal flow direction.
|
||||
* @return A QSize representing the minimum required dimensions.
|
||||
* @brief Minimum size for horizontal flow: the largest single item.
|
||||
*
|
||||
* This guarantees we can always display at least one item per row.
|
||||
*/
|
||||
QSize FlowLayout::calculateMinimumSizeHorizontal() const
|
||||
{
|
||||
int maxWidth = 0; // Tracks the maximum width of a row
|
||||
int totalHeight = 0; // Tracks the total height across all rows
|
||||
int rowHeight = 0; // Tracks the height of the current row
|
||||
int currentWidth = 0; // Tracks the current row's width
|
||||
QSize size(0, 0);
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth();
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth;
|
||||
/**
|
||||
* @brief Preferred size for vertical flow: all items in a single column (unconstrained).
|
||||
*/
|
||||
QSize FlowLayout::calculateSizeHintVertical() const
|
||||
{
|
||||
int maxWidth = 0;
|
||||
int totalHeight = 0;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemMinSize = item->minimumSize();
|
||||
int itemWidth = itemMinSize.width() + horizontalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize << "Width with spacing:" << itemWidth;
|
||||
|
||||
if (currentWidth + itemWidth > availableWidth) {
|
||||
qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight;
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight + verticalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth;
|
||||
|
||||
currentWidth = 0;
|
||||
rowHeight = 0;
|
||||
const QSize s = item->sizeHint();
|
||||
if (totalHeight > 0) {
|
||||
totalHeight += verticalSpacing();
|
||||
}
|
||||
|
||||
currentWidth += itemWidth;
|
||||
rowHeight = qMax(rowHeight, itemMinSize.height());
|
||||
qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight;
|
||||
totalHeight += s.height();
|
||||
maxWidth = qMax(maxWidth, s.width());
|
||||
}
|
||||
|
||||
// Account for the final row
|
||||
maxWidth = qMax(maxWidth, currentWidth);
|
||||
totalHeight += rowHeight;
|
||||
qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth;
|
||||
|
||||
return QSize(maxWidth, totalHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size hint for vertical flow direction.
|
||||
* @return A QSize representing the preferred dimensions.
|
||||
*/
|
||||
QSize FlowLayout::calculateSizeHintVertical() const
|
||||
{
|
||||
int totalWidth = 0;
|
||||
int maxHeight = 0;
|
||||
int colWidth = 0;
|
||||
int currentHeight = 0;
|
||||
|
||||
const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating vertical size hint. Available height:" << availableHeight;
|
||||
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemSize = item->sizeHint();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize;
|
||||
|
||||
if (currentHeight + itemSize.height() > availableHeight) {
|
||||
qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
|
||||
<< "Column width:" << colWidth;
|
||||
totalWidth += colWidth + horizontalSpacing();
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
|
||||
|
||||
currentHeight = 0;
|
||||
colWidth = 0;
|
||||
}
|
||||
|
||||
currentHeight += itemSize.height() + verticalSpacing();
|
||||
colWidth = qMax(colWidth, itemSize.width());
|
||||
qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
|
||||
}
|
||||
|
||||
// Account for the final column
|
||||
totalWidth += colWidth;
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
|
||||
|
||||
return QSize(totalWidth, maxHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the minimum size for vertical flow direction.
|
||||
* @return A QSize representing the minimum required dimensions.
|
||||
* @brief Minimum size for vertical flow: the largest single item.
|
||||
*/
|
||||
QSize FlowLayout::calculateMinimumSizeVertical() const
|
||||
{
|
||||
int totalWidth = 0; // Tracks the total width across all columns
|
||||
int maxHeight = 0; // Tracks the maximum height of a column
|
||||
int colWidth = 0; // Tracks the width of the current column
|
||||
int currentHeight = 0; // Tracks the current column's height
|
||||
|
||||
const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight());
|
||||
|
||||
qCDebug(FlowLayoutLog) << "Calculating vertical minimum size. Available height:" << availableHeight;
|
||||
|
||||
QSize size(0, 0);
|
||||
for (const QLayoutItem *item : items) {
|
||||
if (!item || item->isEmpty()) {
|
||||
qCDebug(FlowLayoutLog) << "Skipping empty item.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QSize itemMinSize = item->minimumSize();
|
||||
int itemHeight = itemMinSize.height() + verticalSpacing();
|
||||
qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize
|
||||
<< "Height with spacing:" << itemHeight;
|
||||
|
||||
if (currentHeight + itemHeight > availableHeight) {
|
||||
qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight
|
||||
<< "Column width:" << colWidth;
|
||||
totalWidth += colWidth + horizontalSpacing();
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight;
|
||||
|
||||
currentHeight = 0;
|
||||
colWidth = 0;
|
||||
}
|
||||
|
||||
currentHeight += itemHeight;
|
||||
colWidth = qMax(colWidth, itemMinSize.width());
|
||||
qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth;
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
}
|
||||
|
||||
// Account for the final column
|
||||
totalWidth += colWidth;
|
||||
maxHeight = qMax(maxHeight, currentHeight);
|
||||
qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight;
|
||||
|
||||
return QSize(totalWidth, maxHeight);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -543,7 +393,7 @@ QSize FlowLayout::calculateMinimumSizeVertical() const
|
|||
*/
|
||||
void FlowLayout::addItem(QLayoutItem *item)
|
||||
{
|
||||
if (item != nullptr) {
|
||||
if (item) {
|
||||
items.append(item);
|
||||
}
|
||||
}
|
||||
|
|
@ -551,11 +401,8 @@ void FlowLayout::addItem(QLayoutItem *item)
|
|||
void FlowLayout::insertWidgetAtIndex(QWidget *toInsert, int index)
|
||||
{
|
||||
addChildWidget(toInsert);
|
||||
|
||||
// We don't want to fail on an index that violates the bounds, so we just clamp it.
|
||||
int boundedIndex = qBound(0, index, qMax(0, static_cast<int>(items.size())));
|
||||
items.insert(boundedIndex, new QWidgetItem(toInsert));
|
||||
|
||||
const int bounded = qBound(0, index, static_cast<int>(items.size()));
|
||||
items.insert(bounded, new QWidgetItem(toInsert));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
|
@ -613,52 +460,13 @@ int FlowLayout::verticalSpacing() const
|
|||
*/
|
||||
int FlowLayout::smartSpacing(const QStyle::PixelMetric pm) const
|
||||
{
|
||||
QObject *parent = this->parent();
|
||||
|
||||
if (!parent) {
|
||||
QObject *p = parent();
|
||||
if (!p) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (parent->isWidgetType()) {
|
||||
const auto *pw = dynamic_cast<QWidget *>(parent);
|
||||
if (p->isWidgetType()) {
|
||||
const auto *pw = static_cast<QWidget *>(p);
|
||||
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||
}
|
||||
|
||||
return dynamic_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the width of the parent scroll area, if any.
|
||||
* @return The width of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaWidth() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->width();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the height of the parent scroll area, if any.
|
||||
* @return The height of the scroll area's viewport, or 0 if not found.
|
||||
*/
|
||||
int FlowLayout::getParentScrollAreaHeight() const
|
||||
{
|
||||
QWidget *parent = parentWidget();
|
||||
|
||||
while (parent) {
|
||||
if (const auto *scrollArea = qobject_cast<QScrollArea *>(parent)) {
|
||||
return scrollArea->viewport()->height();
|
||||
}
|
||||
parent = parent->parentWidget();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
return static_cast<QLayout *>(p)->spacing();
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* @file flow_layout.h
|
||||
* @ingroup UI
|
||||
* @brief TODO: Document this.
|
||||
* @brief A QLayout subclass that arranges child widgets in wrapping rows (horizontal flow)
|
||||
* or wrapping columns (vertical flow).
|
||||
*/
|
||||
|
||||
#ifndef FLOW_LAYOUT_H
|
||||
|
|
@ -10,8 +11,8 @@
|
|||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QStyle>
|
||||
#include <QWidget>
|
||||
#include <qstyle.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg);
|
||||
|
||||
|
|
@ -19,42 +20,55 @@ class FlowLayout : public QLayout
|
|||
{
|
||||
public:
|
||||
explicit FlowLayout(QWidget *parent = nullptr);
|
||||
FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
|
||||
FlowLayout(QWidget *parent, Qt::Orientation flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0);
|
||||
~FlowLayout() override;
|
||||
|
||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||
|
||||
[[nodiscard]] QSize calculateMinimumSizeHorizontal() const;
|
||||
[[nodiscard]] QSize calculateSizeHintVertical() const;
|
||||
[[nodiscard]] QSize calculateMinimumSizeVertical() const;
|
||||
// QLayout interface
|
||||
void addItem(QLayoutItem *item) override;
|
||||
[[nodiscard]] int count() const override;
|
||||
[[nodiscard]] QLayoutItem *itemAt(int index) const override;
|
||||
QLayoutItem *takeAt(int index) override;
|
||||
[[nodiscard]] int horizontalSpacing() const;
|
||||
void setGeometry(const QRect &rect) override;
|
||||
|
||||
// Size negotiation
|
||||
[[nodiscard]] Qt::Orientations expandingDirections() const override;
|
||||
[[nodiscard]] bool hasHeightForWidth() const override;
|
||||
[[nodiscard]] int heightForWidth(int width) const override;
|
||||
[[nodiscard]] int verticalSpacing() const;
|
||||
[[nodiscard]] int doLayout(const QRect &rect, bool testOnly) const;
|
||||
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
[[nodiscard]] int getParentScrollAreaWidth() const;
|
||||
[[nodiscard]] int getParentScrollAreaHeight() const;
|
||||
|
||||
void setGeometry(const QRect &rect) override;
|
||||
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
||||
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y);
|
||||
int layoutAllColumns(int originX, int originY, int availableHeight);
|
||||
void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
|
||||
[[nodiscard]] QSize sizeHint() const override;
|
||||
[[nodiscard]] QSize minimumSize() const override;
|
||||
[[nodiscard]] QSize calculateSizeHintHorizontal() const;
|
||||
|
||||
// Spacing helpers
|
||||
void setHorizontalMargin(int margin)
|
||||
{
|
||||
horizontalMargin = margin;
|
||||
}
|
||||
[[nodiscard]] int horizontalSpacing() const;
|
||||
void setVerticalMargin(int margin)
|
||||
{
|
||||
verticalMargin = margin;
|
||||
}
|
||||
[[nodiscard]] int verticalSpacing() const;
|
||||
[[nodiscard]] int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
|
||||
// Layout passes (virtual so subclasses can override placement logic)
|
||||
virtual int layoutAllRows(int originX, int originY, int availableWidth);
|
||||
virtual void layoutSingleRow(const QVector<QLayoutItem *> &rowItems, int x, int y, int availableWidth);
|
||||
int layoutAllColumns(int originX, int originY, int availableHeight);
|
||||
void layoutSingleColumn(const QVector<QLayoutItem *> &colItems, int x, int y);
|
||||
|
||||
protected:
|
||||
QList<QLayoutItem *> items; // List to store layout items
|
||||
// Size-hint helpers split by direction
|
||||
[[nodiscard]] QSize calculateSizeHintHorizontal() const;
|
||||
[[nodiscard]] QSize calculateMinimumSizeHorizontal() const;
|
||||
[[nodiscard]] QSize calculateSizeHintVertical() const;
|
||||
[[nodiscard]] QSize calculateMinimumSizeVertical() const;
|
||||
|
||||
QList<QLayoutItem *> items;
|
||||
Qt::Orientation flowDirection;
|
||||
int horizontalMargin;
|
||||
int verticalMargin;
|
||||
int horizontalMargin; ///< Horizontal spacing between items (-1 = use style default)
|
||||
int verticalMargin; ///< Vertical spacing between items (-1 = use style default)
|
||||
};
|
||||
|
||||
#endif // FLOW_LAYOUT_H
|
||||
|
|
@ -20,12 +20,11 @@
|
|||
DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer)
|
||||
: AbstractAnalyticsPanelWidget(parent, analyzer)
|
||||
{
|
||||
controls = new QWidget(this);
|
||||
controlLayout = new QHBoxLayout(controls);
|
||||
controlLayout->setContentsMargins(11, 0, 11, 0);
|
||||
controls = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
controls->setSpacing(4, 4);
|
||||
|
||||
labelPrefix = new QLabel(this);
|
||||
controlLayout->addWidget(labelPrefix);
|
||||
controls->addWidget(labelPrefix);
|
||||
|
||||
criteriaCombo = new QComboBox(this);
|
||||
// Give these things item-data so we can translate the actual user-facing strings
|
||||
|
|
@ -33,33 +32,32 @@ DrawProbabilityWidget::DrawProbabilityWidget(QWidget *parent, DeckListStatistics
|
|||
criteriaCombo->addItem(QString(), "type");
|
||||
criteriaCombo->addItem(QString(), "subtype");
|
||||
criteriaCombo->addItem(QString(), "cmc");
|
||||
controlLayout->addWidget(criteriaCombo);
|
||||
controls->addWidget(criteriaCombo);
|
||||
|
||||
exactnessCombo = new QComboBox(this);
|
||||
exactnessCombo->addItem(QString(), true); // At least
|
||||
exactnessCombo->addItem(QString(), false); // Exactly
|
||||
controlLayout->addWidget(exactnessCombo);
|
||||
controls->addWidget(exactnessCombo);
|
||||
|
||||
quantitySpin = new QSpinBox(this);
|
||||
quantitySpin->setRange(1, 60);
|
||||
controlLayout->addWidget(quantitySpin);
|
||||
controls->addWidget(quantitySpin);
|
||||
|
||||
labelMiddle = new QLabel(this);
|
||||
controlLayout->addWidget(labelMiddle);
|
||||
controls->addWidget(labelMiddle);
|
||||
|
||||
drawnSpin = new QSpinBox(this);
|
||||
drawnSpin->setRange(1, 60);
|
||||
drawnSpin->setValue(7);
|
||||
controlLayout->addWidget(drawnSpin);
|
||||
controls->addWidget(drawnSpin);
|
||||
|
||||
labelSuffix = new QLabel(this);
|
||||
controlLayout->addWidget(labelSuffix);
|
||||
controls->addWidget(labelSuffix);
|
||||
|
||||
labelPrefix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
labelMiddle->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
labelSuffix->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
|
||||
controlLayout->addStretch(1);
|
||||
layout->addWidget(controls);
|
||||
|
||||
// Table
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||
#define COCKATRICE_DRAW_PROBABILITY_WIDGET_H
|
||||
|
||||
#include "../../../../layouts/flow_layout.h"
|
||||
#include "../../../general/layout_containers/flow_widget.h"
|
||||
#include "../../abstract_analytics_panel_widget.h"
|
||||
#include "../../deck_list_statistics_analyzer.h"
|
||||
#include "draw_probability_config.h"
|
||||
|
|
@ -31,8 +33,7 @@ private slots:
|
|||
private:
|
||||
DrawProbabilityConfig config;
|
||||
|
||||
QWidget *controls;
|
||||
QHBoxLayout *controlLayout;
|
||||
FlowWidget *controls;
|
||||
QLabel *labelPrefix;
|
||||
QLabel *labelMiddle;
|
||||
QLabel *labelSuffix;
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
layout = new QVBoxLayout(this);
|
||||
|
||||
// Controls
|
||||
controlContainer = new QWidget(this);
|
||||
controlLayout = new QHBoxLayout(controlContainer);
|
||||
controlContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
controlContainer->setSpacing(4, 4);
|
||||
addButton = new QPushButton(this);
|
||||
removeButton = new QPushButton(this);
|
||||
saveButton = new QPushButton(this);
|
||||
|
|
@ -32,11 +32,11 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
includeSideboardCheckBox = new QCheckBox(this);
|
||||
includeSideboardCheckBox->setChecked(false);
|
||||
|
||||
controlLayout->addWidget(addButton);
|
||||
controlLayout->addWidget(removeButton);
|
||||
controlLayout->addWidget(saveButton);
|
||||
controlLayout->addWidget(loadButton);
|
||||
controlLayout->addWidget(includeSideboardCheckBox);
|
||||
controlContainer->addWidget(addButton);
|
||||
controlContainer->addWidget(removeButton);
|
||||
controlContainer->addWidget(saveButton);
|
||||
controlContainer->addWidget(loadButton);
|
||||
controlContainer->addWidget(includeSideboardCheckBox);
|
||||
|
||||
layout->addWidget(controlContainer);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef DECK_ANALYTICS_WIDGET_H
|
||||
#define DECK_ANALYTICS_WIDGET_H
|
||||
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "abstract_analytics_panel_widget.h"
|
||||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
|
@ -51,8 +52,7 @@ private:
|
|||
void addPanelInstance(const QString &typeId, AbstractAnalyticsPanelWidget *panel, const QJsonObject &cfg = {});
|
||||
|
||||
QVBoxLayout *layout;
|
||||
QWidget *controlContainer;
|
||||
QHBoxLayout *controlLayout;
|
||||
FlowWidget *controlContainer;
|
||||
|
||||
QPushButton *addButton;
|
||||
QPushButton *removeButton;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,3 @@ void DeckEditorCardDatabaseDockWidget::clearAllDatabaseFilters()
|
|||
{
|
||||
databaseDisplayWidget->clearAllDatabaseFilters();
|
||||
}
|
||||
void DeckEditorCardDatabaseDockWidget::highlightAllSearchEdit()
|
||||
{
|
||||
databaseDisplayWidget->searchEdit->setSelection(0, databaseDisplayWidget->searchEdit->text().length());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ public:
|
|||
public slots:
|
||||
void retranslateUi();
|
||||
void clearAllDatabaseFilters();
|
||||
void highlightAllSearchEdit();
|
||||
|
||||
private:
|
||||
void createDatabaseDisplayDock(AbstractTabDeckEditor *deckEditor);
|
||||
|
|
|
|||
|
|
@ -147,11 +147,13 @@ void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, con
|
|||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck()
|
||||
{
|
||||
highlightAllSearchEdit();
|
||||
emit addCardToMainDeck(currentCard());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToSideboard()
|
||||
{
|
||||
highlightAllSearchEdit();
|
||||
emit addCardToSideboard(currentCard());
|
||||
}
|
||||
|
||||
|
|
@ -240,4 +242,9 @@ void DeckEditorDatabaseDisplayWidget::retranslateUi()
|
|||
{
|
||||
aAddCard->setText(tr("Add card to &maindeck"));
|
||||
aAddCardToSideboard->setText(tr("Add card to &sideboard"));
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::highlightAllSearchEdit()
|
||||
{
|
||||
searchEdit->setSelection(0, searchEdit->text().length());
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ class DeckEditorDatabaseDisplayWidget : public QWidget
|
|||
public:
|
||||
explicit DeckEditorDatabaseDisplayWidget(QWidget *parent, AbstractTabDeckEditor *deckEditor);
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
SearchLineEdit *searchEdit;
|
||||
CardDatabaseModel *databaseModel;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
|
||||
|
|
@ -58,10 +57,13 @@ private:
|
|||
KeySignals searchKeySignals;
|
||||
QTreeView *databaseView;
|
||||
QHBoxLayout *searchLayout;
|
||||
SearchLineEdit *searchEdit;
|
||||
QAction *aAddCard, *aAddCardToSideboard;
|
||||
QVBoxLayout *centralFrame;
|
||||
QWidget *centralWidget;
|
||||
|
||||
void highlightAllSearchEdit();
|
||||
|
||||
private slots:
|
||||
void retranslateUi();
|
||||
void saveDbHeaderState();
|
||||
|
|
|
|||
|
|
@ -1,42 +1,52 @@
|
|||
/**
|
||||
* @file flow_widget.cpp
|
||||
* @brief Implementation of the FlowWidget class for organizing widgets in a flow layout within a scrollable area.
|
||||
* @brief Implementation of FlowWidget — a QWidget hosting a FlowLayout inside an
|
||||
* optional QScrollArea.
|
||||
*/
|
||||
|
||||
#include "flow_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollArea>
|
||||
#include <QSizePolicy>
|
||||
#include <QWidget>
|
||||
#include <qscrollarea.h>
|
||||
#include <qsizepolicy.h>
|
||||
|
||||
/**
|
||||
* @brief Constructs a FlowWidget with a scrollable layout.
|
||||
* @brief Constructs a FlowWidget.
|
||||
*
|
||||
* @param parent The parent widget of this FlowWidget.
|
||||
* @param horizontalPolicy The horizontal scroll bar policy for the scroll area.
|
||||
* @param verticalPolicy The vertical scroll bar policy for the scroll area.
|
||||
* When both scroll policies are Qt::ScrollBarAlwaysOff the scroll area is
|
||||
* omitted entirely and the container is placed directly in the main layout.
|
||||
*
|
||||
* @param parent Parent widget.
|
||||
* @param _flowDirection Qt::Horizontal for row-wrapping, Qt::Vertical for column-wrapping.
|
||||
* @param horizontalPolicy Horizontal scroll-bar policy.
|
||||
* @param verticalPolicy Vertical scroll-bar policy.
|
||||
*/
|
||||
FlowWidget::FlowWidget(QWidget *parent,
|
||||
const Qt::Orientation _flowDirection,
|
||||
const Qt::ScrollBarPolicy horizontalPolicy,
|
||||
const Qt::ScrollBarPolicy verticalPolicy)
|
||||
: QWidget(parent), flowDirection(_flowDirection)
|
||||
: QWidget(parent), scrollArea(nullptr), flowDirection(_flowDirection)
|
||||
|
||||
{
|
||||
// Main Widget and Layout
|
||||
if (_flowDirection == Qt::Horizontal) {
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
setMinimumWidth(0);
|
||||
// Top-level size policy
|
||||
// Horizontal flow: expand horizontally, let height be determined by wrapping.
|
||||
// Vertical flow: expand vertically, let width be determined by wrapping.
|
||||
if (flowDirection == Qt::Horizontal) {
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
} else {
|
||||
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
setMinimumHeight(0);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(mainLayout);
|
||||
|
||||
if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) {
|
||||
// Scroll Area, which should expand as much as possible, since it should be the only direct child widget.
|
||||
const bool useScrollArea = (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff);
|
||||
|
||||
// Scroll area (optional)
|
||||
if (useScrollArea) {
|
||||
scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setMinimumSize(0, 0);
|
||||
|
|
@ -48,39 +58,28 @@ FlowWidget::FlowWidget(QWidget *parent,
|
|||
scrollArea = nullptr;
|
||||
}
|
||||
|
||||
// Flow Layout inside the scroll area
|
||||
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
|
||||
container = new QWidget(this);
|
||||
} else {
|
||||
container = new QWidget(scrollArea);
|
||||
}
|
||||
// Container widget (holds the FlowLayout)
|
||||
container = new QWidget(useScrollArea ? static_cast<QWidget *>(scrollArea) : this);
|
||||
|
||||
// The container should be willing to grow in both axes; its actual size is
|
||||
// governed by the FlowLayout's sizeHint / heightForWidth, not by a fixed policy.
|
||||
container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
container->setMinimumSize(0, 0);
|
||||
|
||||
flowLayout = new FlowLayout(container, flowDirection);
|
||||
|
||||
container->setLayout(flowLayout);
|
||||
// The container should expand as much as possible, trusting the scrollArea to constrain it.
|
||||
if (_flowDirection == Qt::Horizontal) {
|
||||
container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
container->setMinimumWidth(0);
|
||||
} else {
|
||||
container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
container->setMinimumHeight(0);
|
||||
}
|
||||
|
||||
// Use the FlowLayout container directly if we disable the ScrollArea
|
||||
if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) {
|
||||
mainLayout->addWidget(container);
|
||||
} else {
|
||||
if (useScrollArea) {
|
||||
scrollArea->setWidget(container);
|
||||
mainLayout->addWidget(scrollArea);
|
||||
} else {
|
||||
mainLayout->addWidget(container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a widget to the flow layout within the FlowWidget.
|
||||
*
|
||||
* Adjusts the widget's size policy based on the scroll bar policies.
|
||||
*
|
||||
* @param widget_to_add The widget to add to the flow layout.
|
||||
*/
|
||||
void FlowWidget::addWidget(QWidget *widget_to_add) const
|
||||
|
|
@ -100,77 +99,74 @@ void FlowWidget::removeWidget(QWidget *widgetToRemove) const
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Clears all widgets from the flow layout.
|
||||
* @brief Removes all widgets from the flow layout and deletes them.
|
||||
*
|
||||
* Deletes each widget and layout item, and recreates the flow layout if it was removed.
|
||||
* If the layout pointer has somehow been lost it is recreated before returning.
|
||||
*/
|
||||
void FlowWidget::clearLayout()
|
||||
{
|
||||
if (flowLayout != nullptr) {
|
||||
if (flowLayout) {
|
||||
QLayoutItem *item;
|
||||
while ((item = flowLayout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater(); // Delete the widget
|
||||
delete item; // Delete the layout item
|
||||
while ((item = flowLayout->takeAt(0))) {
|
||||
if (item->widget())
|
||||
item->widget()->deleteLater();
|
||||
delete item;
|
||||
}
|
||||
} else {
|
||||
// Defensive fallback: recreate the layout if it was deleted externally.
|
||||
flowLayout = new FlowLayout(container, flowDirection);
|
||||
container->setLayout(flowLayout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles resize events for the FlowWidget.
|
||||
* @brief Marks the flow layout as dirty so Qt recomputes item positions.
|
||||
*
|
||||
* Triggers layout recalculation and adjusts the scroll area content size.
|
||||
*
|
||||
* @param event The resize event containing the new size information.
|
||||
* We do NOT call adjustSize() or activate() here:
|
||||
* - adjustSize() would freeze geometry by calling setFixedSize internally.
|
||||
* - activate() called inside a resize event can cause synchronous re-entrancy.
|
||||
* Qt automatically calls setGeometry on the layout after a resize, so simply
|
||||
* invalidating is sufficient.
|
||||
*/
|
||||
void FlowWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
qCDebug(FlowWidgetSizeLog) << "resizeEvent:" << event->size();
|
||||
|
||||
qCDebug(FlowWidgetSizeLog) << event->size();
|
||||
|
||||
// Trigger the layout to recalculate
|
||||
if (flowLayout != nullptr) {
|
||||
flowLayout->invalidate(); // Marks the layout as dirty and requires recalculation
|
||||
flowLayout->activate(); // Recalculate the layout based on the new size
|
||||
}
|
||||
|
||||
// Ensure the scroll area and its content adjust correctly
|
||||
if (scrollArea != nullptr && scrollArea->widget() != nullptr) {
|
||||
qCDebug(FlowWidgetSizeLog) << "Got a scrollarea: " << scrollArea->widget()->size();
|
||||
scrollArea->widget()->adjustSize();
|
||||
} else {
|
||||
container->adjustSize();
|
||||
if (flowLayout) {
|
||||
flowLayout->invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void FlowWidget::setSpacing(int hSpacing, int vSpacing)
|
||||
{
|
||||
flowLayout->setHorizontalMargin(hSpacing);
|
||||
flowLayout->setVerticalMargin(vSpacing);
|
||||
flowLayout->invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the minimum size for all widgets inside the FlowWidget to the maximum sizeHint of all of them.
|
||||
* @brief Sets every child widget's minimum size to the largest sizeHint in the layout.
|
||||
*
|
||||
* Useful for toolbars or button bars where all items should be the same size.
|
||||
*/
|
||||
void FlowWidget::setMinimumSizeToMaxSizeHint()
|
||||
{
|
||||
QSize maxSize(0, 0); // Initialize to a zero size
|
||||
QSize maxSize(0, 0);
|
||||
|
||||
// Iterate over all widgets in the flow layout to find the maximum sizeHint
|
||||
for (int i = 0; i < flowLayout->count(); ++i) {
|
||||
if (QLayoutItem *item = flowLayout->itemAt(i)) {
|
||||
if (QWidget *widget = item->widget()) {
|
||||
// Update the max size based on the sizeHint of each widget
|
||||
QSize widgetSizeHint = widget->sizeHint();
|
||||
maxSize.setWidth(qMax(maxSize.width(), widgetSizeHint.width()));
|
||||
maxSize.setHeight(qMax(maxSize.height(), widgetSizeHint.height()));
|
||||
}
|
||||
QLayoutItem *item = flowLayout->itemAt(i);
|
||||
if (item && item->widget()) {
|
||||
maxSize = maxSize.expandedTo(item->widget()->sizeHint());
|
||||
}
|
||||
}
|
||||
|
||||
// Set the minimum size for all widgets to the max sizeHint
|
||||
for (int i = 0; i < flowLayout->count(); ++i) {
|
||||
if (QLayoutItem *item = flowLayout->itemAt(i)) {
|
||||
if (QWidget *widget = item->widget()) {
|
||||
widget->setMinimumSize(maxSize);
|
||||
}
|
||||
QLayoutItem *item = flowLayout->itemAt(i);
|
||||
if (item && item->widget()) {
|
||||
item->widget()->setMinimumSize(maxSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
/**
|
||||
* @file flow_widget.h
|
||||
* @ingroup UI
|
||||
* @brief TODO: Document this.
|
||||
* @brief A QWidget that wraps a FlowLayout inside an optional QScrollArea.
|
||||
*/
|
||||
|
||||
#ifndef FLOW_WIDGET_H
|
||||
#define FLOW_WIDGET_H
|
||||
|
||||
#include "../../../layouts/flow_layout.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLoggingCategory>
|
||||
#include <QScrollArea>
|
||||
#include <QWidget>
|
||||
#include <qscrollarea.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget", QtInfoMsg);
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size", QtInfoMsg);
|
||||
|
||||
class FlowWidget final : public QWidget
|
||||
class FlowWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
|
@ -25,17 +26,20 @@ public:
|
|||
Qt::Orientation orientation,
|
||||
Qt::ScrollBarPolicy horizontalPolicy,
|
||||
Qt::ScrollBarPolicy verticalPolicy);
|
||||
|
||||
void addWidget(QWidget *widget_to_add) const;
|
||||
void insertWidgetAtIndex(QWidget *toInsert, int index);
|
||||
void removeWidget(QWidget *widgetToRemove) const;
|
||||
void clearLayout();
|
||||
|
||||
[[nodiscard]] int count() const;
|
||||
[[nodiscard]] QLayoutItem *itemAt(int index) const;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
QScrollArea *scrollArea; ///< Null when both scroll policies are AlwaysOff.
|
||||
|
||||
public slots:
|
||||
void setMinimumSizeToMaxSizeHint();
|
||||
void setSpacing(int hSpacing, int vSpacing);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
|
@ -47,4 +51,4 @@ private:
|
|||
QWidget *container;
|
||||
};
|
||||
|
||||
#endif // FLOW_WIDGET_H
|
||||
#endif // FLOW_WIDGET_H
|
||||
|
|
@ -35,19 +35,33 @@ void SettingsButtonWidget::setButtonIcon(QPixmap iconMap)
|
|||
button->setIcon(iconMap);
|
||||
}
|
||||
|
||||
void SettingsButtonWidget::setButtonText(const QString &buttonText)
|
||||
void SettingsButtonWidget::setButtonText(const QString &text)
|
||||
{
|
||||
// 🔓 unlock size constraints
|
||||
buttonText = text;
|
||||
|
||||
button->setMinimumSize(QSize(0, 0));
|
||||
button->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
|
||||
|
||||
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
button->setText(buttonText);
|
||||
|
||||
button->setText(text);
|
||||
button->setFixedHeight(32);
|
||||
button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
|
||||
button->setMinimumWidth(button->sizeHint().width());
|
||||
button->setMinimumWidth(32); // icon-only fallback minimum
|
||||
}
|
||||
|
||||
void SettingsButtonWidget::setCompact(bool _compact)
|
||||
{
|
||||
compact = _compact;
|
||||
if (compact) {
|
||||
button->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
button->setFixedWidth(32);
|
||||
} else {
|
||||
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
button->setText(buttonText);
|
||||
button->setFixedWidth(QWIDGETSIZE_MAX); // release fixed width
|
||||
button->setMinimumWidth(32);
|
||||
button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsButtonWidget::togglePopup()
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ public:
|
|||
void removeSettingsWidget(QWidget *toRemove) const;
|
||||
void setButtonIcon(QPixmap iconMap);
|
||||
void setButtonText(const QString &buttonText);
|
||||
void setCompact(bool compact);
|
||||
bool isCompact() const
|
||||
{
|
||||
return compact;
|
||||
};
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
|
@ -34,6 +39,8 @@ private slots:
|
|||
private:
|
||||
QHBoxLayout *layout;
|
||||
QToolButton *button;
|
||||
QString buttonText;
|
||||
bool compact;
|
||||
|
||||
public:
|
||||
SettingsPopupWidget *popup;
|
||||
|
|
|
|||
|
|
@ -137,8 +137,6 @@ void AbstractTabDeckEditor::onDeckModified()
|
|||
void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, const QString &zoneName)
|
||||
{
|
||||
deckStateManager->addCard(card, zoneName);
|
||||
|
||||
cardDatabaseDockWidget->highlightAllSearchEdit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ public:
|
|||
void retranslateUi() override;
|
||||
[[nodiscard]] QString getTabText() const override
|
||||
{
|
||||
return visualDatabaseDisplayWidget->displayModeButton->isChecked() ? tr("Database Display")
|
||||
: tr("Visual Database Display");
|
||||
return visualDatabaseDisplayWidget->isVisualDisplayMode() ? tr("Visual Database Display")
|
||||
: tr("Database Display");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
#include "compact_push_button.h"
|
||||
|
||||
CompactPushButton::CompactPushButton(QWidget *parent) : QPushButton(parent)
|
||||
{
|
||||
setCheckable(true);
|
||||
setFixedHeight(32);
|
||||
|
||||
// default sizing
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
|
||||
connect(this, &QPushButton::clicked, this, [] {
|
||||
// your popup logic here
|
||||
});
|
||||
}
|
||||
|
||||
void CompactPushButton::setButtonText(const QString &text)
|
||||
{
|
||||
fullText = text;
|
||||
|
||||
if (!compact) {
|
||||
setText(fullText);
|
||||
}
|
||||
|
||||
updateGeometryState();
|
||||
}
|
||||
|
||||
void CompactPushButton::setButtonIcon(const QIcon &icon)
|
||||
{
|
||||
setIcon(icon);
|
||||
}
|
||||
|
||||
void CompactPushButton::setCompact(bool enabled)
|
||||
{
|
||||
compact = enabled;
|
||||
|
||||
if (compact) {
|
||||
setText(QString()); // icon only
|
||||
} else {
|
||||
setText(fullText);
|
||||
}
|
||||
|
||||
updateGeometryState();
|
||||
}
|
||||
|
||||
void CompactPushButton::updateGeometryState()
|
||||
{
|
||||
const int buttonHeight = 32;
|
||||
|
||||
setMinimumHeight(buttonHeight);
|
||||
setMaximumHeight(buttonHeight);
|
||||
|
||||
if (compact) {
|
||||
setMinimumWidth(buttonHeight);
|
||||
setMaximumWidth(buttonHeight);
|
||||
} else {
|
||||
setMinimumWidth(0);
|
||||
setMaximumWidth(QWIDGETSIZE_MAX);
|
||||
}
|
||||
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
int CompactPushButton::expandedWidth() const
|
||||
{
|
||||
QFontMetrics fm(font());
|
||||
|
||||
return fm.horizontalAdvance(fullText) + 48; // icon + padding
|
||||
}
|
||||
|
||||
int CompactPushButton::compactWidth() const
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef COCKATRICE_COMPACT_PUSH_BUTTON_H
|
||||
#define COCKATRICE_COMPACT_PUSH_BUTTON_H
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
class CompactPushButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CompactPushButton(QWidget *parent = nullptr);
|
||||
|
||||
void setButtonText(const QString &text);
|
||||
|
||||
void setButtonIcon(const QIcon &icon);
|
||||
|
||||
void setCompact(bool enabled);
|
||||
|
||||
int expandedWidth() const;
|
||||
|
||||
int compactWidth() const;
|
||||
|
||||
private:
|
||||
void updateGeometryState();
|
||||
|
||||
private:
|
||||
QString fullText;
|
||||
bool compact = false;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMPACT_PUSH_BUTTON_H
|
||||
|
|
@ -5,16 +5,9 @@
|
|||
#include <QGroupBox>
|
||||
|
||||
VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *_parent)
|
||||
: QWidget(_parent), visualDatabaseDisplay(_parent)
|
||||
: FlowWidget(_parent, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff),
|
||||
visualDatabaseDisplay(_parent)
|
||||
{
|
||||
filterContainerLayout = new QHBoxLayout(this);
|
||||
filterContainerLayout->setContentsMargins(11, 0, 11, 0);
|
||||
filterContainerLayout->setSpacing(2);
|
||||
setLayout(filterContainerLayout);
|
||||
filterContainerLayout->setAlignment(Qt::AlignLeft);
|
||||
|
||||
setMaximumHeight(80);
|
||||
|
||||
connect(this, &VisualDatabaseDisplayFilterToolbarWidget::searchModelChanged, visualDatabaseDisplay,
|
||||
&VisualDatabaseDisplayWidget::onSearchModelChanged);
|
||||
|
||||
|
|
@ -101,7 +94,7 @@ void VisualDatabaseDisplayFilterToolbarWidget::initialize()
|
|||
filterLayout->setAlignment(Qt::AlignLeft);
|
||||
|
||||
// create settings widgets
|
||||
auto filterModel = visualDatabaseDisplay->filterModel;
|
||||
auto filterModel = visualDatabaseDisplay->getFilterModel();
|
||||
|
||||
saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel);
|
||||
nameFilterWidget =
|
||||
|
|
@ -131,10 +124,18 @@ void VisualDatabaseDisplayFilterToolbarWidget::initialize()
|
|||
filterLayout->addWidget(quickFilterFormatLegalityWidget);
|
||||
|
||||
// put everything into main layout
|
||||
filterContainerLayout->addWidget(sortGroupBox);
|
||||
filterContainerLayout->addWidget(filterGroupBox);
|
||||
filterContainerLayout->addStretch();
|
||||
filterContainerLayout->addWidget(quickFilterSaveLoadWidget);
|
||||
addWidget(sortGroupBox);
|
||||
addWidget(filterGroupBox);
|
||||
auto *spacer = new QWidget(this);
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
spacer->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
addWidget(spacer);
|
||||
addWidget(quickFilterSaveLoadWidget);
|
||||
addWidget(quickFilterSaveLoadWidget);
|
||||
|
||||
// Force a layout pass so sizeHint() is accurate
|
||||
layout()->activate();
|
||||
fullWidthHint = sizeHint().width();
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi()
|
||||
|
|
@ -155,4 +156,25 @@ void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi()
|
|||
quickFilterSubTypeWidget->setButtonText(tr("Sub Type"));
|
||||
quickFilterSetWidget->setButtonText(tr("Sets"));
|
||||
quickFilterFormatLegalityWidget->setButtonText(tr("Formats"));
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFilterToolbarWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
updateCompactMode(event->size().width());
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayFilterToolbarWidget::updateCompactMode(int availableWidth)
|
||||
{
|
||||
const bool compact = availableWidth < fullWidthHint;
|
||||
|
||||
const QList<SettingsButtonWidget *> filterButtons = {
|
||||
quickFilterSaveLoadWidget, quickFilterNameWidget, quickFilterMainTypeWidget,
|
||||
quickFilterSubTypeWidget, quickFilterSetWidget, quickFilterFormatLegalityWidget,
|
||||
};
|
||||
|
||||
for (auto *btn : filterButtons) {
|
||||
if (btn->isCompact() != compact) // only act on transitions
|
||||
btn->setCompact(compact);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
class VisualDatabaseDisplayWidget;
|
||||
|
||||
class VisualDatabaseDisplayFilterToolbarWidget : public QWidget
|
||||
class VisualDatabaseDisplayFilterToolbarWidget : public FlowWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
|
@ -32,7 +32,6 @@ private:
|
|||
QGroupBox *filterGroupBox;
|
||||
QLabel *filterByLabel;
|
||||
|
||||
QHBoxLayout *filterContainerLayout;
|
||||
SettingsButtonWidget *quickFilterSaveLoadWidget;
|
||||
VisualDatabaseDisplayFilterSaveLoadWidget *saveLoadWidget;
|
||||
SettingsButtonWidget *quickFilterNameWidget;
|
||||
|
|
@ -45,6 +44,12 @@ private:
|
|||
VisualDatabaseDisplaySetFilterWidget *setFilterWidget;
|
||||
SettingsButtonWidget *quickFilterFormatLegalityWidget;
|
||||
VisualDatabaseDisplayFormatLegalityFilterWidget *formatLegalityWidget;
|
||||
|
||||
int fullWidthHint = 0;
|
||||
void updateCompactMode(int availableWidth);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_TOOLBAR_WIDGET_H
|
||||
|
|
|
|||
|
|
@ -51,9 +51,7 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
|||
connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
|
||||
&SettingsCache::setVisualDatabaseDisplayCardSize);
|
||||
|
||||
searchContainer = new QWidget(this);
|
||||
searchLayout = new QHBoxLayout(searchContainer);
|
||||
searchContainer->setLayout(searchLayout);
|
||||
searchContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
|
||||
searchEdit = new SearchLineEdit();
|
||||
searchEdit->setObjectName("searchEdit");
|
||||
|
|
@ -99,6 +97,11 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
|
|||
&DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents);
|
||||
connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(searchEdit); });
|
||||
|
||||
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, this,
|
||||
&VisualDatabaseDisplayWidget::highlightAllSearchEdit);
|
||||
connect(databaseDisplayWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, this,
|
||||
&VisualDatabaseDisplayWidget::highlightAllSearchEdit);
|
||||
|
||||
databaseView = databaseDisplayWidget->getDatabaseView();
|
||||
databaseView->setFocusProxy(searchEdit);
|
||||
databaseView->setItemDelegate(nullptr);
|
||||
|
|
@ -147,10 +150,10 @@ void VisualDatabaseDisplayWidget::initialize()
|
|||
filterContainer->initialize();
|
||||
filterContainer->setVisible(true);
|
||||
|
||||
searchLayout->addWidget(colorFilterWidget);
|
||||
searchLayout->addWidget(clearFilterWidget);
|
||||
searchLayout->addWidget(searchEdit);
|
||||
searchLayout->addWidget(displayModeButton);
|
||||
searchContainer->addWidget(colorFilterWidget);
|
||||
searchContainer->addWidget(clearFilterWidget);
|
||||
searchContainer->addWidget(searchEdit);
|
||||
searchContainer->addWidget(displayModeButton);
|
||||
|
||||
mainLayout->addWidget(searchContainer);
|
||||
|
||||
|
|
@ -181,6 +184,11 @@ void VisualDatabaseDisplayWidget::retranslateUi()
|
|||
clearFilterWidget->setToolTip(tr("Clear all filters"));
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayWidget::highlightAllSearchEdit()
|
||||
{
|
||||
searchEdit->setSelection(0, searchEdit->text().length());
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
|
@ -237,6 +245,11 @@ void VisualDatabaseDisplayWidget::updateSearch(const QString &search) const
|
|||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
}
|
||||
|
||||
bool VisualDatabaseDisplayWidget::isVisualDisplayMode() const
|
||||
{
|
||||
return !displayModeButton->isChecked();
|
||||
}
|
||||
|
||||
void VisualDatabaseDisplayWidget::onSearchModelChanged()
|
||||
{
|
||||
if (flowWidget->isVisible()) {
|
||||
|
|
|
|||
|
|
@ -62,12 +62,15 @@ public:
|
|||
return databaseView;
|
||||
}
|
||||
|
||||
QWidget *searchContainer;
|
||||
QHBoxLayout *searchLayout;
|
||||
SearchLineEdit *searchEdit;
|
||||
QPushButton *displayModeButton;
|
||||
FilterTreeModel *filterModel;
|
||||
VisualDatabaseDisplayColorFilterWidget *colorFilterWidget;
|
||||
FilterTreeModel *getFilterModel()
|
||||
{
|
||||
return filterModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return False if the widget is in database display mode and true if it's in visual display mode
|
||||
*/
|
||||
bool isVisualDisplayMode() const;
|
||||
|
||||
public slots:
|
||||
void onSearchModelChanged();
|
||||
|
|
@ -88,6 +91,12 @@ protected slots:
|
|||
void onDisplayModeChanged(bool checked);
|
||||
|
||||
private:
|
||||
FlowWidget *searchContainer;
|
||||
SearchLineEdit *searchEdit;
|
||||
QPushButton *displayModeButton;
|
||||
FilterTreeModel *filterModel;
|
||||
VisualDatabaseDisplayColorFilterWidget *colorFilterWidget;
|
||||
|
||||
QLabel *databaseLoadIndicator;
|
||||
|
||||
QToolButton *clearFilterWidget;
|
||||
|
|
@ -112,6 +121,8 @@ private:
|
|||
int currentPage = 0; // Current page index
|
||||
int cardsPerPage = 100; // Number of cards per page
|
||||
|
||||
void highlightAllSearchEdit();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent)
|
|||
sortCriteriaButton->addSettingsWidget(sortLabel);
|
||||
sortCriteriaButton->addSettingsWidget(sortByListWidget);
|
||||
|
||||
displayTypeButton = new QPushButton(this);
|
||||
displayTypeButton = new CompactPushButton(this);
|
||||
connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckDisplayOptionsWidget::updateDisplayType);
|
||||
|
||||
groupAndSortLayout->addWidget(groupByLabel);
|
||||
|
|
@ -91,7 +91,8 @@ void VisualDeckDisplayOptionsWidget::retranslateUi()
|
|||
sortByLabel->setText(tr("Sort by:"));
|
||||
sortLabel->setText(tr("Click and drag to change the sort order within the groups"));
|
||||
sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups"));
|
||||
displayTypeButton->setText(tr("Toggle Layout: Overlap"));
|
||||
displayTypeButton->setButtonText(tr("Toggle Layout: Overlap"));
|
||||
displayTypeButton->setButtonIcon(QPixmap("theme:icons/scales"));
|
||||
displayTypeButton->setToolTip(
|
||||
tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)"));
|
||||
}
|
||||
|
|
@ -115,11 +116,32 @@ void VisualDeckDisplayOptionsWidget::updateDisplayType()
|
|||
// Update UI and emit signal
|
||||
switch (currentDisplayType) {
|
||||
case DisplayType::Flat:
|
||||
displayTypeButton->setText(tr("Toggle Layout: Flat"));
|
||||
displayTypeButton->setButtonText(tr("Toggle Layout: Flat"));
|
||||
displayTypeButton->setButtonIcon(QPixmap("theme:icons/scroll"));
|
||||
break;
|
||||
case DisplayType::Overlap:
|
||||
displayTypeButton->setText(tr("Toggle Layout: Overlap"));
|
||||
displayTypeButton->setButtonText(tr("Toggle Layout: Overlap"));
|
||||
displayTypeButton->setButtonIcon(QPixmap("theme:icons/scales"));
|
||||
break;
|
||||
}
|
||||
emit displayTypeChanged(currentDisplayType);
|
||||
}
|
||||
|
||||
void VisualDeckDisplayOptionsWidget::updateCompactMode(bool mode)
|
||||
{
|
||||
displayTypeButton->setCompact(mode);
|
||||
}
|
||||
|
||||
int VisualDeckDisplayOptionsWidget::expandedWidth() const
|
||||
{
|
||||
return groupByLabel->sizeHint().width() + groupByComboBox->sizeHint().width() + sortByLabel->sizeHint().width() +
|
||||
sortCriteriaButton->sizeHint().width() + displayTypeButton->expandedWidth() +
|
||||
(groupAndSortLayout->spacing() * 4);
|
||||
}
|
||||
|
||||
int VisualDeckDisplayOptionsWidget::compactWidth() const
|
||||
{
|
||||
return groupByLabel->sizeHint().width() + groupByComboBox->sizeHint().width() + sortByLabel->sizeHint().width() +
|
||||
sortCriteriaButton->sizeHint().width() + displayTypeButton->compactWidth() +
|
||||
(groupAndSortLayout->spacing() * 4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ public slots:
|
|||
* Called when the application language changes.
|
||||
*/
|
||||
void retranslateUi();
|
||||
void updateCompactMode(bool mode);
|
||||
int expandedWidth() const;
|
||||
int compactWidth() const;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
|
@ -108,7 +111,7 @@ private:
|
|||
DisplayType currentDisplayType = DisplayType::Overlap;
|
||||
|
||||
/// Button used to toggle the display layout.
|
||||
QPushButton *displayTypeButton;
|
||||
CompactPushButton *displayTypeButton;
|
||||
|
||||
/// Label for the group-by selector.
|
||||
QLabel *groupByLabel;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "../tabs/visual_deck_editor/tab_deck_editor_visual.h"
|
||||
#include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h"
|
||||
#include "../utility/compact_push_button.h"
|
||||
#include "visual_deck_display_options_widget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
|
@ -69,7 +70,13 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
|
|||
|
||||
void VisualDeckEditorWidget::initializeSearchBarAndCompleter()
|
||||
{
|
||||
searchBar = new QLineEdit(this);
|
||||
searchContainer = new QWidget(this);
|
||||
searchContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
searchLayout = new QHBoxLayout(searchContainer);
|
||||
searchContainer->setLayout(searchLayout);
|
||||
|
||||
searchBar = new QLineEdit(searchContainer);
|
||||
searchContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() {
|
||||
if (!searchBar->hasFocus())
|
||||
return;
|
||||
|
|
@ -80,6 +87,8 @@ void VisualDeckEditorWidget::initializeSearchBarAndCompleter()
|
|||
}
|
||||
});
|
||||
|
||||
searchLayout->addWidget(searchBar);
|
||||
|
||||
setFocusProxy(searchBar);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
|
|
@ -133,13 +142,16 @@ void VisualDeckEditorWidget::initializeSearchBarAndCompleter()
|
|||
});
|
||||
|
||||
// Search button functionality
|
||||
searchPushButton = new QPushButton(this);
|
||||
searchPushButton = new CompactPushButton(searchContainer);
|
||||
searchPushButton->setButtonIcon(QPixmap("theme:icons/search"));
|
||||
connect(searchPushButton, &QPushButton::clicked, this, [=, this]() {
|
||||
ExactCard card = CardDatabaseManager::query()->getCard({searchBar->text()});
|
||||
if (card) {
|
||||
emit cardAdditionRequested(card);
|
||||
}
|
||||
});
|
||||
|
||||
searchLayout->addWidget(searchPushButton);
|
||||
}
|
||||
|
||||
void VisualDeckEditorWidget::initializeDisplayOptionsWidget()
|
||||
|
|
@ -156,18 +168,14 @@ void VisualDeckEditorWidget::initializeDisplayOptionsWidget()
|
|||
void VisualDeckEditorWidget::initializeDisplayOptionsAndSearchWidget()
|
||||
{
|
||||
initializeSearchBarAndCompleter();
|
||||
|
||||
initializeDisplayOptionsWidget();
|
||||
|
||||
displayOptionsAndSearch = new QWidget(this);
|
||||
displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch);
|
||||
displayOptionsAndSearchLayout->setContentsMargins(0, 0, 0, 0);
|
||||
displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft);
|
||||
displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout);
|
||||
displayOptionsAndSearch = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
|
||||
|
||||
displayOptionsAndSearchLayout->addWidget(displayOptionsWidget);
|
||||
displayOptionsAndSearchLayout->addWidget(searchBar);
|
||||
displayOptionsAndSearchLayout->addWidget(searchPushButton);
|
||||
// We split into two sub-widgets here so that the searchBar and button wrap together. At this point, we've done
|
||||
// pretty much all we can and have reached our minimum size.
|
||||
displayOptionsAndSearch->addWidget(displayOptionsWidget);
|
||||
displayOptionsAndSearch->addWidget(searchContainer); // Expanding — fills remainder of its row
|
||||
}
|
||||
|
||||
void VisualDeckEditorWidget::initializeScrollAreaAndZoneContainer()
|
||||
|
|
@ -205,7 +213,7 @@ void VisualDeckEditorWidget::connectDeckListModel()
|
|||
void VisualDeckEditorWidget::retranslateUi()
|
||||
{
|
||||
searchBar->setPlaceholderText(tr("Type a card name here for suggestions from the database..."));
|
||||
searchPushButton->setText(tr("Quick search and add card"));
|
||||
searchPushButton->setButtonText(tr("Quick search and add card"));
|
||||
searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add "
|
||||
"preferred printing to the deck on pressing enter"));
|
||||
|
||||
|
|
@ -214,6 +222,44 @@ void VisualDeckEditorWidget::retranslateUi()
|
|||
}
|
||||
}
|
||||
|
||||
void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
updateCompactMode();
|
||||
}
|
||||
|
||||
void VisualDeckEditorWidget::updateCompactMode()
|
||||
{
|
||||
const int spacing = displayOptionsAndSearch->layout()->spacing();
|
||||
|
||||
const int available = displayOptionsAndSearch->width();
|
||||
|
||||
const int searchExpanded =
|
||||
searchBar->sizeHint().width() + searchPushButton->expandedWidth() + searchLayout->spacing();
|
||||
|
||||
const int fullWidth = displayOptionsWidget->expandedWidth() + spacing + searchExpanded;
|
||||
|
||||
const int displayCompactWidth = displayOptionsWidget->compactWidth() + spacing + searchExpanded;
|
||||
|
||||
// everything expanded
|
||||
if (available >= fullWidth) {
|
||||
displayOptionsWidget->updateCompactMode(false);
|
||||
searchPushButton->setCompact(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// only display compact
|
||||
if (available >= displayCompactWidth) {
|
||||
displayOptionsWidget->updateCompactMode(true);
|
||||
searchPushButton->setCompact(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// both compact
|
||||
displayOptionsWidget->updateCompactMode(true);
|
||||
searchPushButton->setCompact(true);
|
||||
}
|
||||
|
||||
void VisualDeckEditorWidget::updatePlaceholderVisibility()
|
||||
{
|
||||
if (placeholderWidget) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "../cards/card_size_widget.h"
|
||||
#include "../general/layout_containers/overlap_control_widget.h"
|
||||
#include "../quick_settings/settings_button_widget.h"
|
||||
#include "../utility/compact_push_button.h"
|
||||
#include "visual_deck_editor_placeholder_widget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
|
@ -39,6 +40,7 @@ class VisualDeckEditorWidget : public QWidget
|
|||
public:
|
||||
explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel, QItemSelectionModel *selectionModel);
|
||||
void retranslateUi();
|
||||
void updateCompactMode();
|
||||
void clearAllDisplayWidgets();
|
||||
|
||||
void setDeckList(const DeckList &_deckListModel);
|
||||
|
|
@ -82,19 +84,23 @@ protected slots:
|
|||
void onHover(const ExactCard &hoveredCard);
|
||||
void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
|
||||
void decklistModelReset();
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
int expandedWidthAll = -1;
|
||||
int expandedWidthDisplayCompact = -1;
|
||||
DeckListModel *deckListModel;
|
||||
QItemSelectionModel *selectionModel;
|
||||
QVBoxLayout *mainLayout;
|
||||
CardDatabaseModel *cardDatabaseModel;
|
||||
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
|
||||
CardCompleterProxyModel *proxyModel;
|
||||
QWidget *searchContainer;
|
||||
QHBoxLayout *searchLayout;
|
||||
QCompleter *completer;
|
||||
QWidget *displayOptionsAndSearch;
|
||||
QHBoxLayout *displayOptionsAndSearchLayout;
|
||||
FlowWidget *displayOptionsAndSearch;
|
||||
VisualDeckDisplayOptionsWidget *displayOptionsWidget;
|
||||
QPushButton *searchPushButton;
|
||||
CompactPushButton *searchPushButton;
|
||||
QScrollArea *scrollArea;
|
||||
QWidget *zoneContainer;
|
||||
QVBoxLayout *zoneContainerLayout;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue