Implement in-game navigation with keyboard

Implements a start to the keyboard navigation with the direction arrows
and the space key for card selection.
Some binds are still needed, but navigating the board is now possible.
The feature includes tests for the implemented feature.

Closes #5043

Co-authored-by: Manuel Ramos Monge <manuel.monge@tecnico.ulisboa.pt>
This commit is contained in:
Vasco Guerreiro Vintém Morais 2026-06-02 17:09:39 +01:00
parent 3fa377a11c
commit 5acce8998e
22 changed files with 1023 additions and 46 deletions

View file

@ -8,6 +8,7 @@
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QPainter>
#include <algorithm>
#include <libcockatrice/card/database/card_database.h>
@ -19,6 +20,7 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef
{
setCursor(Qt::OpenHandCursor);
setFlag(ItemIsSelectable);
setFlag(ItemIsFocusable);
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
@ -347,3 +349,20 @@ QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change,
return ArrowTarget::itemChange(change, value);
}
}
void AbstractCardItem::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
if (event->modifiers() & Qt::AltModifier) {
emit cardShiftClicked(cardRef.name);
} else if (event->modifiers() & Qt::ControlModifier) {
setSelected(!isSelected());
} else if (!isSelected() && isHovered) {
scene()->clearSelection();
setSelected(true);
}
event->accept();
} else {
ArrowTarget::keyPressEvent(event);
}
}

View file

@ -127,6 +127,7 @@ protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
void cacheBgColor();
void keyPressEvent(QKeyEvent *event) override;
};
#endif

View file

@ -228,52 +228,9 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
if (targetItem && targetItem != startItem) {
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
// For now, we can safely assume that the start item is always a card.
// The target item can be a player as well.
if (!startCard) {
delArrow();
return;
if (startCard) {
ArrowItem::sendCreateArrowCommand(player, startCard, targetItem, color, deleteInPhase);
}
CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId());
if (auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem)) {
CardZoneLogic *targetZone = targetCard->getZone();
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
} else if (auto *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem)) {
cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId());
} else {
delArrow();
return;
}
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow
if (startZone->getName() == ZoneNames::HAND) {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) {
cmd.set_start_zone(ZoneNames::STACK);
} else {
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
}
if (deleteInPhase != 0) {
cmd.set_delete_in_phase(deleteInPhase);
}
player->getPlayerActions()->sendGameCommand(cmd);
}
delArrow();
@ -282,6 +239,55 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
}
}
void ArrowItem::sendCreateArrowCommand(PlayerLogic *player,
CardItem *startCard,
ArrowTarget *targetItem,
const QColor &color,
int deleteInPhase)
{
if (!startCard || !targetItem || !player) {
return;
}
CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId());
if (auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem)) {
CardZoneLogic *targetZone = targetCard->getZone();
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
} else if (auto *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem)) {
cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId());
} else {
return;
}
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow
if (startZone->getName() == ZoneNames::HAND) {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) {
cmd.set_start_zone(ZoneNames::STACK);
} else {
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
}
if (deleteInPhase != 0) {
cmd.set_delete_in_phase(deleteInPhase);
}
player->getPlayerActions()->sendGameCommand(cmd);
}
// ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem(_startItem->getOwner(), -1, _startItem, nullptr, Qt::green)

View file

@ -77,6 +77,11 @@ public:
}
void delArrow();
static void sendCreateArrowCommand(PlayerLogic *player,
CardItem *startCard,
ArrowTarget *targetItem,
const QColor &color,
int deleteInPhase = 0);
};
class ArrowDragItem : public ArrowItem

View file

@ -5,6 +5,7 @@
#include "../../game_graphics/zones/view_zone.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h"
#include "../keyboard_card_navigator.h"
#include "../phase.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
@ -14,7 +15,10 @@
#include <../../client/settings/card_counter_settings.h>
#include <QApplication>
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <libcockatrice/card/card_info.h>
@ -500,6 +504,71 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
event->accept();
}
void CardItem::keyPressEvent(QKeyEvent *event)
{
auto *gameScene = static_cast<GameScene *>(scene());
KeyboardCardNavigator *navigator = gameScene ? gameScene->getCardNavigator() : nullptr;
if (event->key() == Qt::Key_Escape && navigator && navigator->isArrowModeActiveVar()) {
navigator->cancelArrowMode();
event->accept();
qWarning() << "Arrow mode cancelled from CardItem with id:" << id;
return;
}
if (event->key() == Qt::Key_A && (event->modifiers() & Qt::ShiftModifier) && getIsHovered()) {
qWarning() << "Starting arrow mode from CardItem with id:" << id;
if (navigator) {
qWarning() << "Navigator found, starting arrow mode.";
scene()->clearSelection();
setSelected(true);
navigator->startArrowMode(this);
event->accept();
return;
}
}
if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && navigator &&
navigator->isArrowModeActiveVar() && getIsHovered()) {
qWarning() << "Finalizing arrow mode from CardItem with id:" << id;
navigator->createArrow(this);
navigator->cancelArrowMode();
event->accept();
return;
}
if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && isSelected() &&
SettingsCache::instance().getDoubleClickToPlay()) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
event->accept();
} else if (event->key() == Qt::Key_Space && getIsHovered()) {
if (QWidget *popup = QApplication::activePopupWidget()) {
popup->close();
event->accept();
return;
}
if (owner != nullptr) {
scene()->clearSelection();
setSelected(true);
owner->getGame()->setActiveCard(this);
if (QMenu *cardMenu = owner->getPlayerMenu()->updateCardMenu(this)) {
QPointF scenePos = sceneBoundingRect().center();
if (!scene()->views().isEmpty()) {
QGraphicsView *view = scene()->views().first();
QPoint screenPos = view->mapToGlobal(view->mapFromScene(scenePos));
cardMenu->popup(screenPos);
}
}
}
event->accept();
} else {
AbstractCardItem::keyPressEvent(event);
}
}
bool CardItem::animationEvent()
{
int rotation = ROTATION_DEGREES_PER_FRAME;

View file

@ -169,6 +169,7 @@ protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
};

View file

@ -5,6 +5,7 @@
#include "../game_graphics/zones/view_zone.h"
#include "../game_graphics/zones/view_zone_widget.h"
#include "board/card_item.h"
#include "keyboard_card_navigator.h"
#include "phases_toolbar.h"
#include "player/player_graphics_item.h"
#include "player/player_logic.h"
@ -28,9 +29,11 @@
* Finally, calls rearrange() to layout players initially.
*/
GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
: QGraphicsScene(parent), phasesToolbar(_phasesToolbar), viewSize(QSize()), playerRotation(0)
: QGraphicsScene(parent), phasesToolbar(_phasesToolbar), viewSize(QSize()), playerRotation(0),
cardNavigator(nullptr)
{
animationTimer = new QBasicTimer;
cardNavigator = new KeyboardCardNavigator(nullptr);
addItem(phasesToolbar);
connect(&SettingsCache::instance(), &SettingsCache::minPlayersForMultiColumnLayoutChanged, this,
&GameScene::rearrange);
@ -41,6 +44,7 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
GameScene::~GameScene()
{
delete animationTimer;
delete cardNavigator;
// DO NOT call clearViews() here
// clearViews calls close() on the zoneViews, which sends signals; sending signals in destructors leads to segfaults
@ -460,6 +464,46 @@ void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
}
}
void GameScene::handleLeftArrow()
{
if (cardNavigator) {
QKeyEvent event(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier);
cardNavigator->switchCardInZone(&event);
}
}
void GameScene::handleRightArrow()
{
if (cardNavigator) {
QKeyEvent event(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier);
cardNavigator->switchCardInZone(&event);
}
}
void GameScene::handleUpArrow()
{
if (cardNavigator) {
QKeyEvent event(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
cardNavigator->switchZone(&event);
}
}
void GameScene::handleDownArrow()
{
if (cardNavigator) {
QKeyEvent event(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier);
cardNavigator->switchZone(&event);
}
}
void GameScene::setActivePlayer(PlayerLogic *player)
{
if (cardNavigator) {
cardNavigator->setPlayer(player);
cardNavigator->setCurrentZone(player->getHandZone());
}
}
// ---------- Hover Handling ----------
void GameScene::updateHover(const QPointF &scenePos)

View file

@ -23,6 +23,7 @@ class CardItem;
class ServerInfo_Card;
class PhasesToolbar;
class QBasicTimer;
class KeyboardCardNavigator;
/**
* @class GameScene
@ -52,6 +53,7 @@ private:
QBasicTimer *animationTimer; ///< Timer for card animations
QSet<CardItem *> cardsToAnimate; ///< Cards currently animating
int playerRotation; ///< Rotation offset for player layout
KeyboardCardNavigator *cardNavigator; ///< Handles keyboard-based card navigation
/**
* @brief Updates which card is currently hovered based on scene coordinates.
@ -171,6 +173,12 @@ public:
/** @brief Updates hovered card highlighting. */
void updateHoveredCard(CardItem *newCard);
/** @brief Gets the keyboard card navigator. */
KeyboardCardNavigator *getCardNavigator() const
{
return cardNavigator;
}
/** @brief Registers a card for animation updates. */
void registerAnimationItem(AbstractCardItem *card);
@ -206,6 +214,17 @@ public slots:
void deleteArrow(int arrowId);
void clearArrowsForPlayer(int playerId);
/** @brief Handles left arrow key for card navigation. */
void handleLeftArrow();
/** @brief Handles right arrow key for card navigation. */
void handleRightArrow();
/** @brief Handles up arrow key for zone navigation. */
void handleUpArrow();
/** @brief Handles down arrow key for zone navigation. */
void handleDownArrow();
/** @brief Sets the active player for keyboard navigation. */
void setActivePlayer(PlayerLogic *player);
/// Queues up arrow deletion but doesn't directly modify the scene
void requestArrowDeletion(int arrowId);
void requestClearArrowsForPlayer(int playerId);

View file

@ -0,0 +1,256 @@
#include "keyboard_card_navigator.h"
#include "../../client/settings/cache_settings.h"
#include "board/arrow_item.h"
#include "board/card_item.h"
#include "player/player_logic.h"
#include <QApplication>
#include <QKeyEvent>
KeyboardCardNavigator::KeyboardCardNavigator(PlayerLogic *player)
: currentZone(nullptr), hoveredCardIndex(-1), isArrowModeActive(false), arrowOriginCard(nullptr),
previewArrow(nullptr), playerLogic(player)
{
}
int KeyboardCardNavigator::getHoveredIndex()
{
return hoveredCardIndex;
}
CardZoneLogic *KeyboardCardNavigator::getCurrentZone()
{
return currentZone;
}
void KeyboardCardNavigator::setCurrentZone(CardZoneLogic *zone)
{
currentZone = zone;
}
void KeyboardCardNavigator::switchCardInZone(QKeyEvent *event)
{
if (!playerLogic) {
return;
}
if (!currentZone) {
return;
}
if (QApplication::activePopupWidget()) {
return;
}
const CardList &zoneCards = currentZone->getCards();
if (zoneCards.isEmpty()) {
// if the current zone is empty, try to force a zone change.
QKeyEvent event(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
KeyboardCardNavigator::switchZone(&event);
return;
}
event->accept();
// Check if this is an arrow key we care about
int keyCode = event->key();
if (keyCode != Qt::Key_Right && keyCode != Qt::Key_Left) {
return;
}
// Calculate new index
int newIndex = hoveredCardIndex;
bool isInitial = (hoveredCardIndex < 0);
if (isInitial) {
// If the first key pressed is the right, spawn the cursor at the first card
// Otherwise, spawn at the last card.
newIndex = keyCode == Qt::Key_Right ? 0 : zoneCards.size() - 1;
} else {
if (hoveredCardIndex >= zoneCards.size()) {
hoveredCardIndex = 0;
newIndex = 0;
}
if (keyCode == Qt::Key_Right) {
newIndex = (hoveredCardIndex + 1) % zoneCards.size();
} else {
newIndex = (hoveredCardIndex - 1 + zoneCards.size()) % zoneCards.size();
}
}
KeyboardCardNavigator::changeHoverCard(hoveredCardIndex, false);
hoveredCardIndex = newIndex;
KeyboardCardNavigator::changeHoverCard(newIndex, true);
}
void KeyboardCardNavigator::changeHoverCard(int cardIndex, bool hover)
{
const CardList &zoneCards = currentZone->getCards();
if (cardIndex >= 0 && cardIndex < zoneCards.size()) {
CardItem *card = zoneCards[cardIndex];
if (card) {
card->setHovered(hover);
card->setFocus();
// Force update of new card's area
if (card->scene()) {
card->scene()->update(card->sceneBoundingRect());
}
if (isArrowModeActive && hover) {
createTempArrow(card);
}
}
}
}
void KeyboardCardNavigator::setPlayer(PlayerLogic *player)
{
playerLogic = player;
}
void KeyboardCardNavigator::setHoveredCardIndex(int index)
{
hoveredCardIndex = index;
}
void KeyboardCardNavigator::unhoverCard()
{
if (!playerLogic || !currentZone) {
return;
}
changeHoverCard(hoveredCardIndex, false);
}
void KeyboardCardNavigator::createTempArrow(CardItem *targetCard)
{
if (!isArrowModeActive || !arrowOriginCard || !targetCard || !playerLogic) {
return;
}
if (previewArrow) {
delete previewArrow;
previewArrow = nullptr;
}
previewArrow = new ArrowItem(playerLogic, -1, arrowOriginCard, targetCard, Qt::red);
if (arrowOriginCard->scene()) {
arrowOriginCard->scene()->addItem(previewArrow);
}
}
void KeyboardCardNavigator::createArrow(CardItem *targetCard)
{
if (!isArrowModeActive || !arrowOriginCard || !targetCard || !playerLogic) {
return;
}
if (previewArrow) {
delete previewArrow;
previewArrow = nullptr;
}
isArrowModeActive = false;
if (arrowOriginCard == targetCard) {
arrowOriginCard = nullptr;
return;
}
ArrowItem::sendCreateArrowCommand(playerLogic, arrowOriginCard, targetCard, Qt::red);
arrowOriginCard = nullptr;
}
void KeyboardCardNavigator::startArrowMode(CardItem *originCard)
{
if (!originCard || !originCard->scene() || !playerLogic) {
return;
}
isArrowModeActive = true;
arrowOriginCard = originCard;
createTempArrow(originCard);
}
void KeyboardCardNavigator::cancelArrowMode()
{
if (previewArrow) {
delete previewArrow;
previewArrow = nullptr;
}
isArrowModeActive = false;
arrowOriginCard = nullptr;
}
CardZoneLogic *
KeyboardCardNavigator::findZoneWithCards(QList<CardZoneLogic *> &zonesList, int currentZoneIndex, bool upperZone)
{
CardZoneLogic *newZone;
int newZoneIndex = currentZoneIndex;
do {
// Calculate new zone index
if (upperZone) {
newZoneIndex = (newZoneIndex + 1) % zonesList.size();
} else {
newZoneIndex = (newZoneIndex - 1 + zonesList.size()) % zonesList.size();
}
newZone = zonesList[newZoneIndex];
// Prevent switching zone if the others are empty
} while (newZoneIndex != currentZoneIndex && newZone->getCards().size() == 0);
return newZone;
}
void KeyboardCardNavigator::switchZone(QKeyEvent *event)
{
if (!playerLogic) {
return;
}
if (QApplication::activePopupWidget()) {
return;
}
int keyCode = event->key();
if (keyCode != Qt::Key_Up && keyCode != Qt::Key_Down) {
return;
}
event->accept();
// Build list with only the zones of interest
QList<CardZoneLogic *> zonesList;
TableZoneLogic *tableZone = playerLogic->getTableZone();
StackZoneLogic *stackZone = playerLogic->getStackZone();
HandZoneLogic *handZone = playerLogic->getHandZone();
if (tableZone) {
zonesList.append(tableZone);
}
if (stackZone) {
zonesList.append(stackZone);
}
if (handZone) {
zonesList.append(handZone);
}
if (zonesList.isEmpty()) {
return;
}
int currentZoneIndex = zonesList.indexOf(currentZone);
if (currentZoneIndex < 0) {
currentZoneIndex = 0;
}
CardZoneLogic *newZone =
KeyboardCardNavigator::findZoneWithCards(zonesList, currentZoneIndex, keyCode == Qt::Key_Up);
if (currentZone != newZone) {
changeHoverCard(hoveredCardIndex, false);
setCurrentZone(newZone);
// Reset card index since we're in a new zone
hoveredCardIndex = 0;
// The new zone has to have cards, hover and select the first one
changeHoverCard(hoveredCardIndex, true);
}
}

View file

@ -0,0 +1,61 @@
/**
* @file keyboard_card_navigator.h
* @ingroup GameInput
* @brief Handles keyboard navigation for selecting cards using arrow keys.
*
* Allows players to navigate through hand cards using directional arrow keys.
* Spatially-aware: finds adjacent cards based on screen position.
*/
#ifndef KEYBOARD_CARD_NAVIGATOR_H
#define KEYBOARD_CARD_NAVIGATOR_H
#include "zones/card_zone_logic.h"
class PlayerLogic;
class CardItem;
class QKeyEvent;
class ArrowItem;
class KeyboardCardNavigator
{
private:
CardZoneLogic *currentZone;
int hoveredCardIndex = -1;
bool isArrowModeActive;
CardItem *arrowOriginCard;
ArrowItem *previewArrow;
PlayerLogic *playerLogic;
/**
* @brief Gets hand cards sorted by visual position.
* @return List of cards sorted by visual order (left-to-right for horizontal, top-to-bottom for vertical).
*/
CardList getVisuallyOrderedHandCards() const;
public:
KeyboardCardNavigator(PlayerLogic *player = nullptr);
int getHoveredIndex();
CardZoneLogic *getCurrentZone();
void setCurrentZone(CardZoneLogic *zone);
void switchCardInZone(QKeyEvent *event);
void switchZone(QKeyEvent *event);
void setPlayer(PlayerLogic *player);
/**
* @brief Validates and resets the hovered card if needed.
* Call this when hand composition changes.
*/
void setHoveredCardIndex(int index);
void unhoverCard();
void changeHoverCard(int cardIndex, bool hover);
void createArrow(CardItem *targetCard);
void createTempArrow(CardItem *targetCard);
void startArrowMode(CardItem *originCard);
void cancelArrowMode();
CardZoneLogic *findZoneWithCards(QList<CardZoneLogic *> &zonesList, int currentZoneIndex, bool upperZone);
bool isArrowModeActiveVar() const
{
return isArrowModeActive;
}
};
#endif

View file

@ -11,6 +11,7 @@
#include "../board/card_list.h"
#include "../board/counter_general.h"
#include "../game_scene.h"
#include "../keyboard_card_navigator.h"
#include "player_actions.h"
#include "player_target.h"
@ -323,6 +324,9 @@ bool PlayerLogic::clearCardsToDelete()
void PlayerLogic::setActive(bool _active)
{
active = _active;
if (_active == true) {
hoverFirstCardInHand();
}
emit activeChanged(active);
}
@ -348,3 +352,20 @@ void PlayerLogic::setGameStarted()
}
setConceded(false);
}
void PlayerLogic::hoverFirstCardInHand()
{
HandZoneLogic *handZone = getHandZone();
if (!handZone) {
return;
}
const CardList &handCards = handZone->getCards();
if (!handCards.isEmpty()) {
CardItem *firstCard = handCards.at(0);
if (firstCard) {
firstCard->setHovered(true);
getGameScene()->getCardNavigator()->unhoverCard();
getGameScene()->getCardNavigator()->setHoveredCardIndex(0);
}
}
}

View file

@ -229,6 +229,8 @@ public:
void setZoneId(int _zoneId);
void hoverFirstCardInHand();
private:
AbstractGame *game;
PlayerInfo *playerInfo;

View file

@ -1,5 +1,6 @@
#include "key_signals.h"
#include <QApplication>
#include <QKeyEvent>
bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
@ -26,15 +27,35 @@ bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
case Qt::Key_Right:
if (kevent->modifiers() & Qt::ShiftModifier) {
emit onShiftRight();
} else {
emit onRightArrow();
return true;
}
break;
case Qt::Key_Left:
if (kevent->modifiers() & Qt::ShiftModifier) {
emit onShiftLeft();
} else {
emit onLeftArrow();
return true;
}
break;
case Qt::Key_Up:
// This check exists so up and down arrows can still navigate all menu boxes while having
// normal browsing functionality in game
if (qApp->activePopupWidget() != nullptr || qApp->activeModalWidget() != nullptr) {
return false;
}
emit onUpArrow();
return true;
case Qt::Key_Down:
if (qApp->activePopupWidget() != nullptr || qApp->activeModalWidget() != nullptr) {
return false;
}
emit onDownArrow();
return true;
case Qt::Key_Delete:
case Qt::Key_Backspace:
emit onDelete();

View file

@ -28,6 +28,10 @@ signals:
void onCtrlAltRBracket();
void onShiftS();
void onCtrlC();
void onLeftArrow();
void onRightArrow();
void onUpArrow();
void onDownArrow();
protected:
bool eventFilter(QObject *, QEvent *event) override;

View file

@ -14,6 +14,7 @@
#include "../game/player/player_logic.h"
#include "../game/replay.h"
#include "../interface/card_picture_loader/card_picture_loader.h"
#include "../interface/key_signals.h"
#include "../interface/widgets/cards/card_info_frame_widget.h"
#include "../interface/widgets/dialogs/dlg_create_game.h"
#include "../interface/widgets/server/user/user_list_manager.h"
@ -863,6 +864,7 @@ PlayerLogic *TabGame::setActivePlayer(int id)
}
playerListWidget->setActivePlayer(id);
scene->setActivePlayer(player);
QMapIterator<int, PlayerLogic *> i(game->getPlayerManager()->getPlayers());
while (i.hasNext()) {
i.next();
@ -1153,6 +1155,14 @@ void TabGame::createPlayAreaWidget(bool bReplay)
connect(scene, &GameScene::arrowDeletionRequested, game->getGameEventHandler(),
&GameEventHandler::handleArrowDeletion);
connect(game->getGameEventHandler(), &GameEventHandler::arrowDeleted, scene, &GameScene::deleteArrow);
keySignals = new KeySignals();
qApp->installEventFilter(keySignals);
connect(keySignals, &KeySignals::onLeftArrow, scene, &GameScene::handleLeftArrow);
connect(keySignals, &KeySignals::onRightArrow, scene, &GameScene::handleRightArrow);
connect(keySignals, &KeySignals::onUpArrow, scene, &GameScene::handleUpArrow);
connect(keySignals, &KeySignals::onDownArrow, scene, &GameScene::handleDownArrow);
gameView = new GameView(scene);
auto gamePlayAreaVBox = new QVBoxLayout;

View file

@ -51,6 +51,7 @@ class GameReplay;
class LineEditCompleter;
class QDockWidget;
class QStackedWidget;
class KeySignals;
class TabGame : public Tab
{
@ -73,6 +74,7 @@ private:
PhasesToolbar *phasesToolbar;
GameScene *scene;
GameView *gameView;
KeySignals *keySignals;
QMap<int, TabbedDeckViewContainer *> deckViewContainers;
QVBoxLayout *deckViewContainerLayout;
QWidget *gamePlayAreaWidget, *deckViewContainerWidget;