diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 1d8121b19..a66897b4a 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -284,6 +284,9 @@ SettingsCache::SettingsCache() closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool(); focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); + showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); + showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); + showShortcuts = settings->value("menu/showshortcuts", true).toBool(); showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool(); @@ -1308,6 +1311,18 @@ void SettingsCache::setRoundCardCorners(bool _roundCardCorners) emit roundCardCornersChanged(roundCardCorners); } +void SettingsCache::setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount) +{ + showDragSelectionCount = static_cast(_showDragSelectionCount); + settings->setValue("interface/showlassoselectioncount", showDragSelectionCount); +} + +void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount) +{ + showTotalSelectionCount = static_cast(_showTotalSelectionCount); + settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount); +} + void SettingsCache::loadPaths() { QString dataPath = getDataPath(); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 2bbf85352..ece61487f 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -340,6 +340,8 @@ private: bool isPortableBuild; bool roundCardCorners; bool showStatusBar; + bool showDragSelectionCount; + bool showTotalSelectionCount; public: SettingsCache(); @@ -455,6 +457,14 @@ public: { return showStatusBar; } + [[nodiscard]] bool getShowDragSelectionCount() const + { + return showDragSelectionCount; + } + [[nodiscard]] bool getShowTotalSelectionCount() const + { + return showTotalSelectionCount; + } [[nodiscard]] bool getNotificationsEnabled() const { return notificationsEnabled; @@ -1120,5 +1130,7 @@ public slots: void setUpdateReleaseChannelIndex(int value); void setMaxFontSize(int _max); void setRoundCardCorners(bool _roundCardCorners); + void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount); + void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount); }; #endif diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index 5dc3b48f7..034ff6947 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -521,9 +521,9 @@ void GameScene::startRubberBand(const QPointF &selectionOrigin) emit sigStartRubberBand(selectionOrigin); } -void GameScene::resizeRubberBand(const QPointF &cursorPoint) +void GameScene::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) { - emit sigResizeRubberBand(cursorPoint); + emit sigResizeRubberBand(cursorPoint, selectedCount); } void GameScene::stopRubberBand() diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index 86fa0795a..f08e83aa4 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -163,7 +163,7 @@ public: /** Unregisters a card from animation updates. */ void unregisterAnimationItem(AbstractCardItem *card); void startRubberBand(const QPointF &selectionOrigin); - void resizeRubberBand(const QPointF &cursorPoint); + void resizeRubberBand(const QPointF &cursorPoint, int selectedCount); void stopRubberBand(); public slots: @@ -196,7 +196,7 @@ protected: signals: void sigStartRubberBand(const QPointF &selectionOrigin); - void sigResizeRubberBand(const QPointF &cursorPoint); + void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount); void sigStopRubberBand(); }; diff --git a/cockatrice/src/game/game_view.cpp b/cockatrice/src/game/game_view.cpp index dd5cc70c1..ce53828a7 100644 --- a/cockatrice/src/game/game_view.cpp +++ b/cockatrice/src/game/game_view.cpp @@ -4,9 +4,32 @@ #include "game_scene.h" #include +#include #include #include +// QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. +// This subclass disables that behavior so dragCountLabel can appear above it. +class SelectionRubberBand : public QRubberBand +{ +public: + using QRubberBand::QRubberBand; + +protected: + void showEvent(QShowEvent *event) override + { + QWidget::showEvent(event); // Skip QRubberBand's raise() + } + + void changeEvent(QEvent *event) override + { + if (event->type() == QEvent::ZOrderChange) { + return; // Skip QRubberBand's raise() on z-order changes + } + QRubberBand::changeEvent(event); + } +}; + GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0) { setBackgroundBrush(QBrush(QColor(0, 0, 0))); @@ -19,6 +42,7 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par connect(scene, &GameScene::sigStartRubberBand, this, &GameView::startRubberBand); connect(scene, &GameScene::sigResizeRubberBand, this, &GameView::resizeRubberBand); connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand); + connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); }); aCloseMostRecentZoneView = new QAction(this); @@ -27,7 +51,23 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &GameView::refreshShortcuts); refreshShortcuts(); - rubberBand = new QRubberBand(QRubberBand::Rectangle, this); + rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this); + + const QString countLabelStyle = "color: white; " + "font-size: 14px; " + "font-weight: bold; " + "background-color: rgba(0, 0, 0, 160); " + "border-radius: 3px; " + "padding: 1px 2px;"; + + dragCountLabel = new QLabel(this); + dragCountLabel->setStyleSheet(countLabelStyle); + dragCountLabel->hide(); + dragCountLabel->raise(); + + totalCountLabel = new QLabel(this); + totalCountLabel->setStyleSheet(countLabelStyle); + totalCountLabel->hide(); } void GameView::resizeEvent(QResizeEvent *event) @@ -39,6 +79,7 @@ void GameView::resizeEvent(QResizeEvent *event) s->processViewSizeChange(event->size()); updateSceneRect(scene()->sceneRect()); + updateTotalSelectionCount(event->size()); } void GameView::updateSceneRect(const QRectF &rect) @@ -48,20 +89,67 @@ void GameView::updateSceneRect(const QRectF &rect) void GameView::startRubberBand(const QPointF &_selectionOrigin) { + if (!rubberBand) + return; + selectionOrigin = _selectionOrigin; rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0))); rubberBand->show(); } -void GameView::resizeRubberBand(const QPointF &cursorPoint) +void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) { - if (rubberBand) - rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), cursorPoint.toPoint()).normalized()); + if (!rubberBand) + return; + + constexpr int kLabelPaddingInPixels = 4; + + QPoint cursor = cursorPoint.toPoint(); + QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized(); + rubberBand->setGeometry(rect); + + if (!SettingsCache::instance().getShowDragSelectionCount()) { + dragCountLabel->hide(); + return; + } + + if (selectedCount > 0) { + dragCountLabel->setText(QString::number(selectedCount)); + dragCountLabel->adjustSize(); + QSize labelSize = dragCountLabel->size(); + + if (rect.width() < labelSize.width() + 2 * kLabelPaddingInPixels || + rect.height() < labelSize.height() + 2 * kLabelPaddingInPixels) { + dragCountLabel->hide(); + return; + } + + const int minX = rect.left() + kLabelPaddingInPixels; + const int minY = rect.top() + kLabelPaddingInPixels; + + int x = qMax(minX, cursor.x() - labelSize.width() - kLabelPaddingInPixels); + int y = qMax(minY, cursor.y() - labelSize.height() - kLabelPaddingInPixels); + + bool isAtTopLeftCorner = (x == minX) && (y == minY); + if (isAtTopLeftCorner) { + constexpr int kCursorClearanceInPixels = 16; + x = qMin(cursor.x() + kCursorClearanceInPixels, rect.right() - labelSize.width() - kLabelPaddingInPixels); + } + + dragCountLabel->move(x, y); + dragCountLabel->show(); + } else { + dragCountLabel->hide(); + } } void GameView::stopRubberBand() { + if (!rubberBand) + return; + rubberBand->hide(); + dragCountLabel->hide(); } void GameView::refreshShortcuts() @@ -69,3 +157,28 @@ void GameView::refreshShortcuts() aCloseMostRecentZoneView->setShortcuts( SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } + +void GameView::updateTotalSelectionCount(const QSize &viewSize) +{ + if (!SettingsCache::instance().getShowTotalSelectionCount()) { + totalCountLabel->hide(); + return; + } + + int count = scene()->selectedItems().count(); + + if (count > 1) { + totalCountLabel->setText(QString::number(count)); + totalCountLabel->adjustSize(); + + constexpr int kMarginInPixels = 10; + int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width(); + int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height(); + int x = availableWidth - totalCountLabel->width() - kMarginInPixels; + int y = availableHeight - totalCountLabel->height() - kMarginInPixels; + totalCountLabel->move(x, y); + totalCountLabel->show(); + } else { + totalCountLabel->hide(); + } +} diff --git a/cockatrice/src/game/game_view.h b/cockatrice/src/game/game_view.h index 72df9cd08..a77ab9257 100644 --- a/cockatrice/src/game/game_view.h +++ b/cockatrice/src/game/game_view.h @@ -10,6 +10,7 @@ #include class GameScene; +class QLabel; class QRubberBand; class GameView : public QGraphicsView @@ -18,15 +19,18 @@ class GameView : public QGraphicsView private: QAction *aCloseMostRecentZoneView; QRubberBand *rubberBand; + QLabel *dragCountLabel; + QLabel *totalCountLabel; QPointF selectionOrigin; protected: void resizeEvent(QResizeEvent *event) override; private slots: void startRubberBand(const QPointF &selectionOrigin); - void resizeRubberBand(const QPointF &cursorPoint); + void resizeRubberBand(const QPointF &cursorPoint, int selectedCount); void stopRubberBand(); void refreshShortcuts(); + void updateTotalSelectionCount(const QSize &viewSize = QSize()); public slots: void updateSceneRect(const QRectF &rect); diff --git a/cockatrice/src/game/zones/select_zone.cpp b/cockatrice/src/game/zones/select_zone.cpp index 719eec148..9bf5f9faf 100644 --- a/cockatrice/src/game/zones/select_zone.cpp +++ b/cockatrice/src/game/zones/select_zone.cpp @@ -68,7 +68,8 @@ void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } } static_cast(scene())->resizeRubberBand( - deviceTransform(static_cast(scene())->getViewportTransform()).map(pos)); + deviceTransform(static_cast(scene())->getViewportTransform()).map(pos), + cardsInSelectionRect.size()); event->accept(); } } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index e7286f078..e3bf209dc 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -821,6 +821,14 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setAnnotateTokens); + showDragSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionCount()); + connect(&showDragSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowDragSelectionCount); + + showTotalSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionCount()); + connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowTotalSelectionCount); + useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); @@ -833,7 +841,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 7, 0); + generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); + generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 9, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -955,6 +965,8 @@ void UserInterfaceSettingsPage::retranslateUi() closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); + showDragSelectionCountCheckBox.setText(tr("Show selection counter during drag selection")); + showTotalSelectionCountCheckBox.setText(tr("Show total selection counter")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); notificationsGroupBox->setTitle(tr("Notifications settings")); notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index db107c6e2..b655a30bc 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -171,6 +171,8 @@ private: QCheckBox closeEmptyCardViewCheckBox; QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; + QCheckBox showDragSelectionCountCheckBox; + QCheckBox showTotalSelectionCountCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox tapAnimationCheckBox; QCheckBox openDeckInNewTabCheckBox;