From d626aaea1d599b1fc2c1aeac24c16a48b4592beb Mon Sep 17 00:00:00 2001 From: PoonamMehan <116332299+PoonamMehan@users.noreply.github.com> Date: Wed, 25 Feb 2026 23:21:53 +0530 Subject: [PATCH] Add "Go to Tile" action (#4348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds the ability to jump to a particular tile location on the map by entering tile coordinates. Accessible via the Map menu: Map -> Go to Tile... menu item or the Ctrl+Shift+G keyboard shortcut. Implemented as a LocatorSource, with a TileHighlightItem to highlight the location. Closes #1367 Co-authored-by: Thorbjørn Lindeijer --- NEWS.md | 1 + docs/manual/keyboard-shortcuts.rst | 1 + src/tiled/libtilededitor.qbs | 2 + src/tiled/locatorwidget.cpp | 98 ++++++++++++++++++++++++++ src/tiled/locatorwidget.h | 24 +++++++ src/tiled/mainwindow.cpp | 8 +++ src/tiled/mainwindow.h | 1 + src/tiled/mainwindow.ui | 10 +++ src/tiled/tilehighlightitem.cpp | 109 +++++++++++++++++++++++++++++ src/tiled/tilehighlightitem.h | 55 +++++++++++++++ 10 files changed, 309 insertions(+) create mode 100644 src/tiled/tilehighlightitem.cpp create mode 100644 src/tiled/tilehighlightitem.h diff --git a/NEWS.md b/NEWS.md index b6365b6733..204d2dbd12 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,7 @@ * Added status info for various Stamp Brush, Terrain Brush and Eraser modes (#3092, #4201) * Added Escape to clear tile selection when any tile related tool is selected (#4243) * Added Escape to cancel tile selection and shape drawing operations +* Added a "Go to Tile" action to jump to specific coordinates (by PoonamMehan, #4348) * Made the shortcut for current tool switch to previous tool (#4280) * Allow canceling Select Same Tile, Magic Wand and Bucket Fill operations with right-click and Escape * Allow dragging over multiple tiles with Select Same Tile, Magic Wand and Bucket Fill tools (#4276) diff --git a/docs/manual/keyboard-shortcuts.rst b/docs/manual/keyboard-shortcuts.rst index 6dc2d19a89..206bc61015 100644 --- a/docs/manual/keyboard-shortcuts.rst +++ b/docs/manual/keyboard-shortcuts.rst @@ -37,6 +37,7 @@ General - ``Ctrl + V`` - Paste (tiles, objects or properties) - ``Del`` - Delete (tiles, objects, properties or layers) - ``Ctrl + G`` - Toggle displaying of the tile grid +- ``Ctrl + Shift + G`` - Go to a tile by its coordinates - ``H`` - Toggle highlighting of the current layer - ``Ctrl + M`` - Invokes :doc:`automapping` - ``Alt + C`` - Copy current position of mouse cursor to clipboard (in diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index f9af5409b4..7b869b9e4f 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -519,6 +519,8 @@ DynamicLibrary { "tilepainter.h", "tileselectionitem.cpp", "tileselectionitem.h", + "tilehighlightitem.cpp", + "tilehighlightitem.h", "tileselectiontool.cpp", "tileselectiontool.h", "tilesetchanges.cpp", diff --git a/src/tiled/locatorwidget.cpp b/src/tiled/locatorwidget.cpp index c7dac2c712..682dabbd16 100644 --- a/src/tiled/locatorwidget.cpp +++ b/src/tiled/locatorwidget.cpp @@ -22,8 +22,15 @@ #include "documentmanager.h" #include "filteredit.h" +#include "map.h" +#include "mapdocument.h" +#include "mapitem.h" +#include "maprenderer.h" +#include "mapscene.h" +#include "mapview.h" #include "preferences.h" #include "projectmanager.h" +#include "tilehighlightitem.h" #include "utils.h" #include @@ -398,6 +405,97 @@ void FileLocatorSource::setFilterWords(const QStringList &words) endResetModel(); } +/////////////////////////////////////////////////////////////////////////////// + +TileLocatorSource::TileLocatorSource(QObject *parent) + : LocatorSource(parent) + , mDelegate(new QStyledItemDelegate(this)) +{} + +int TileLocatorSource::rowCount(const QModelIndex &parent) const +{ + return (!parent.isValid() && mHasValidCoord) ? 1 : 0; +} + +QVariant TileLocatorSource::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole && index.row() == 0 && mHasValidCoord) + return tr("Go to tile (%1, %2)").arg(mTileX).arg(mTileY); + return QVariant(); +} + +QAbstractItemDelegate *TileLocatorSource::delegate() const +{ + return mDelegate; +} + +QString TileLocatorSource::placeholderText() const +{ + return tr("Go to tile: x, y"); +} + +void TileLocatorSource::setFilterWords(const QStringList &words) +{ + bool validX = false; + bool validY = false; + int x = 0; + int y = 0; + + const QString input = words.join(QStringView()); + const int commaIndex = input.indexOf(QLatin1Char(',')); + if (commaIndex >= 0 && commaIndex < input.size() - 1) { + x = QStringView(input).left(commaIndex).toInt(&validX); + y = QStringView(input).mid(commaIndex + 1).toInt(&validY); + } + + if (!(validX && validY) && words.size() == 2) { + x = words.at(0).toInt(&validX); + y = words.at(1).toInt(&validY); + } + + beginResetModel(); + mHasValidCoord = validX && validY; + mTileX = x; + mTileY = y; + endResetModel(); +} + +void TileLocatorSource::activate(const QModelIndex &) +{ + auto documentManager = DocumentManager::instance(); + auto mapDocument = qobject_cast(documentManager->currentDocument()); + if (!mapDocument) + return; + + const auto *map = mapDocument->map(); + if (map->width() < 1 || map->height() < 1) + return; + + auto mapView = documentManager->currentMapView(); + if (!mapView) + return; + + const int x = qBound(0, mTileX, map->width() - 1); + const int y = qBound(0, mTileY, map->height() - 1); + + auto renderer = mapDocument->renderer(); + QPointF pixelPos = renderer->tileToPixelCoords(x + 0.5, y + 0.5); + + if (Layer *layer = mapDocument->currentLayer()) { + mapView->forceCenterOn(pixelPos, *layer); + + if (auto mapScene = mapView->mapScene()) { + if (MapItem *mapItem = mapScene->mapItem(mapDocument)) { + auto *highlight = new TileHighlightItem(mapDocument, x, y, mapItem); + highlight->setZValue(10003); + highlight->startBlink(); + } + } + } else { + mapView->forceCenterOn(pixelPos); + } +} + } // namespace Tiled #include "moc_locatorwidget.cpp" diff --git a/src/tiled/locatorwidget.h b/src/tiled/locatorwidget.h index 9436b5e969..a706fa6e63 100644 --- a/src/tiled/locatorwidget.h +++ b/src/tiled/locatorwidget.h @@ -26,6 +26,7 @@ #include "projectmodel.h" class QAbstractItemDelegate; +class QStyledItemDelegate; namespace Tiled { @@ -85,4 +86,27 @@ class FileLocatorSource : public LocatorSource QVector mMatches; }; +class TileLocatorSource : public LocatorSource +{ + Q_OBJECT + +public: + explicit TileLocatorSource(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // LocatorSource + QAbstractItemDelegate *delegate() const override; + QString placeholderText() const override; + void setFilterWords(const QStringList &words) override; + void activate(const QModelIndex &index) override; + +private: + QStyledItemDelegate *mDelegate; + bool mHasValidCoord = false; + int mTileX = 0; + int mTileY = 0; +}; + } // namespace Tiled diff --git a/src/tiled/mainwindow.cpp b/src/tiled/mainwindow.cpp index b7a272fbd7..7f9ea50644 100644 --- a/src/tiled/mainwindow.cpp +++ b/src/tiled/mainwindow.cpp @@ -274,6 +274,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) ActionManager::registerAction(mUi->actionFullScreen, "FullScreen"); ActionManager::registerAction(mUi->actionHighlightCurrentLayer, "HighlightCurrentLayer"); ActionManager::registerAction(mUi->actionHighlightHoveredObject, "HighlightHoveredObject"); + ActionManager::registerAction(mUi->actionGoToTile, "GoToTile"); ActionManager::registerAction(mUi->actionLabelForHoveredObject, "LabelForHoveredObject"); ActionManager::registerAction(mUi->actionLabelsForAllObjects, "LabelsForAllObjects"); ActionManager::registerAction(mUi->actionLabelsForSelectedObjects, "LabelsForSelectedObjects"); @@ -530,6 +531,7 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) connect(mUi->actionOpen, &QAction::triggered, this, &MainWindow::openFileDialog); connect(mUi->actionOpenFileInProject, &QAction::triggered, this, &MainWindow::openFileInProject); connect(mUi->actionSearchActions, &QAction::triggered, this, &MainWindow::searchActions); + connect(mUi->actionGoToTile, &QAction::triggered, this, &MainWindow::goToTile); connect(mUi->actionReopenClosedFile, &QAction::triggered, this, &MainWindow::reopenClosedFile); connect(mUi->actionClearRecentFiles, &QAction::triggered, preferences, &Preferences::clearRecentFiles); connect(mUi->actionSave, &QAction::triggered, this, &MainWindow::saveFile); @@ -1154,6 +1156,11 @@ void MainWindow::searchActions() showLocatorWidget(new ActionLocatorSource); } +void MainWindow::goToTile() +{ + showLocatorWidget(new TileLocatorSource); +} + void MainWindow::showLocatorWidget(LocatorSource *source) { if (mLocatorWidget) @@ -2205,6 +2212,7 @@ void MainWindow::updateActions() mUi->actionOffsetMap->setEnabled(mapDocument); mUi->actionMapProperties->setEnabled(mapDocument); mUi->actionAutoMap->setEnabled(mapDocument); + mUi->actionGoToTile->setEnabled(mapDocument); mUi->menuTileset->menuAction()->setVisible(tilesetDocument); mUi->actionTilesetProperties->setEnabled(tilesetDocument); diff --git a/src/tiled/mainwindow.h b/src/tiled/mainwindow.h index a0f0fcbd3e..2e65540c1b 100644 --- a/src/tiled/mainwindow.h +++ b/src/tiled/mainwindow.h @@ -117,6 +117,7 @@ class TILED_EDITOR_EXPORT MainWindow : public QMainWindow void openFileDialog(); void openFileInProject(); void searchActions(); + void goToTile(); void showLocatorWidget(LocatorSource *source); bool saveFile(); bool saveFileAs(); diff --git a/src/tiled/mainwindow.ui b/src/tiled/mainwindow.ui index 5ce8315a48..cecbbac73b 100644 --- a/src/tiled/mainwindow.ui +++ b/src/tiled/mainwindow.ui @@ -139,6 +139,8 @@ + + @@ -823,6 +825,14 @@ QAction::MenuRole::NoRole + + + Go to &Tile... + + + Ctrl+Shift+G + + diff --git a/src/tiled/tilehighlightitem.cpp b/src/tiled/tilehighlightitem.cpp new file mode 100644 index 0000000000..349f241555 --- /dev/null +++ b/src/tiled/tilehighlightitem.cpp @@ -0,0 +1,109 @@ +/* + * tilehighlightitem.cpp + * Copyright 2026, PoonamMehan + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "tilehighlightitem.h" + +#include "mapdocument.h" +#include "maprenderer.h" +#include "mapscene.h" + +#include +#include +#include +#include +#include +#include + +using namespace Tiled; + +TileHighlightItem::TileHighlightItem(MapDocument *mapDocument, + int tileX, int tileY, + QGraphicsItem *parent) + : QGraphicsObject(parent) + , mMapDocument(mapDocument) + , mTileX(tileX) + , mTileY(tileY) +{ + setFlag(QGraphicsItem::ItemUsesExtendedStyleOption); + + const QRect b(tileX, tileY, 1, 1); + mBoundingRect = mapDocument->renderer()->boundingRect(b); + mBoundingRect.adjust(-1, -1, 1, 1); +} + +void TileHighlightItem::startBlink() +{ + updatePosition(); + + constexpr int blinkDuration = 120; + constexpr int blinkCount = 3; + + auto *group = new QSequentialAnimationGroup(this); + for (int i = 0; i < blinkCount; ++i) { + auto *toVisible = new QPropertyAnimation(this, "opacity"); + toVisible->setDuration(blinkDuration); + toVisible->setStartValue(0.0); + toVisible->setEndValue(1.0); + group->addAnimation(toVisible); + + auto *toHidden = new QPropertyAnimation(this, "opacity"); + toHidden->setDuration(blinkDuration); + toHidden->setStartValue(1.0); + toHidden->setEndValue(0.0); + group->addAnimation(toHidden); + } + + setOpacity(0.0); + connect(group, &QAbstractAnimation::finished, this, &QObject::deleteLater); + group->start(QAbstractAnimation::DeleteWhenStopped); +} + +void TileHighlightItem::updatePosition() +{ + if (!mMapDocument) + return; + if (auto currentLayer = mMapDocument->currentLayer()) + if (auto mapScene = qobject_cast(scene())) + setPos(mapScene->absolutePositionForLayer(*currentLayer)); +} + +QRectF TileHighlightItem::boundingRect() const +{ + return mBoundingRect; +} + +void TileHighlightItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *) +{ + if (!mMapDocument) { + deleteLater(); + return; + } + const QRegion region(QRect(mTileX, mTileY, 1, 1)); + QColor highlight = QApplication::palette().highlight().color(); + highlight.setAlpha(128); + + MapRenderer *renderer = mMapDocument->renderer(); + renderer->drawTileSelection(painter, region, highlight, + option->exposedRect); +} + +#include "moc_tilehighlightitem.cpp" diff --git a/src/tiled/tilehighlightitem.h b/src/tiled/tilehighlightitem.h new file mode 100644 index 0000000000..02cbd6ca2a --- /dev/null +++ b/src/tiled/tilehighlightitem.h @@ -0,0 +1,55 @@ +/* + * tilehighlightitem.h + * Copyright 2026, PoonamMehan + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include +#include + +namespace Tiled { + +class MapDocument; + +class TileHighlightItem : public QGraphicsObject +{ + Q_OBJECT + +public: + TileHighlightItem(MapDocument *mapDocument, + int tileX, int tileY, + QGraphicsItem *parent = nullptr); + + void startBlink(); + + QRectF boundingRect() const override; + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + +private: + void updatePosition(); + + QPointer mMapDocument; + int mTileX; + int mTileY; + QRectF mBoundingRect; +}; + +} // namespace Tiled