diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst
index 0651f7d9..4e016f6d 100644
--- a/docsrc/manual/scripting-capabilities.rst
+++ b/docsrc/manual/scripting-capabilities.rst
@@ -1222,6 +1222,58 @@ All tileset functions are callable via the global ``map`` object.
:returns: the pixel data
:rtype: array
+.. |describe-metatile-layer-order|
+ replace:: where ``0`` is the bottom layer, ``1`` is the middle layer, and ``2`` is the top layer. The default order is ``[0, 1, 2]``
+
+.. |describe-metatile-layer-order-handling|
+ replace:: If no elements are provided the layer order will be reset to the default. Any layer not listed in the provided ``order`` will not be rendered. Any additional elements after the first 3 are ignored
+
+.. js:function:: map.getMetatileLayerOrder()
+
+ Gets the order that metatile layers are rendered for the current layout, |describe-metatile-layer-order|.
+
+ If you'd like to get the default metatile layer order for all layouts, see :js:func:`utility.getMetatileLayerOrder` instead.
+
+ :returns: array of layers
+ :rtype: array
+
+.. js:function:: map.setMetatileLayerOrder(order)
+
+ Sets the order that metatile layers are rendered for the current layout, |describe-metatile-layer-order|.
+
+ |describe-metatile-layer-order-handling|.
+
+ If you'd like to set the default metatile layer order for all layouts, see :js:func:`utility.setMetatileLayerOrder` instead.
+
+ :param order: array of layers
+ :type order: array
+
+.. |describe-metatile-layer-opacity|
+ replace:: where the first element is the bottom layer, the second element is the middle layer, and the third element is the top layer. The default opacities are ``[1.0, 1.0, 1.0]``
+
+.. |describe-metatile-layer-opacity-handling|
+ replace:: Any additional elements after the first 3 are ignored. Any elements not provided will be rendered with opacity ``1.0``
+
+.. js:function:: map.getMetatileLayerOpacity()
+
+ Gets the opacities that metatile layers are rendered with for the current layout, |describe-metatile-layer-opacity|.
+
+ If you'd like to get the default metatile layer opacities for all layouts, see :js:func:`utility.getMetatileLayerOpacity` instead.
+
+ :returns: array of opacities for each layer
+ :rtype: array
+
+.. js:function:: map.setMetatileLayerOpacity(opacities)
+
+ Sets the opacities that metatile layers are rendered with for the current layout, |describe-metatile-layer-opacity|.
+
+ |describe-metatile-layer-opacity-handling|.
+
+ If you'd like to set the default metatile layer opacities for all layouts, see :js:func:`utility.setMetatileLayerOpacity` instead.
+
+ :param opacities: array of opacities for each layer
+ :type opacities: array
+
Overlay Functions
^^^^^^^^^^^^^^^^^
@@ -1811,30 +1863,42 @@ All settings functions are callable via the global ``utility`` object.
.. js:function:: utility.getMetatileLayerOrder()
- Gets the order that metatile layers are rendered.
+ Gets the order that metatile layers are rendered by default, |describe-metatile-layer-order|.
- :returns: array of layers. The bottom layer is represented as 0.
+ If you'd like to get the metatile layer order for only the current layout, see :js:func:`map.getMetatileLayerOrder` instead.
+
+ :returns: array of layers
:rtype: array
.. js:function:: utility.setMetatileLayerOrder(order)
- Sets the order that metatile layers are rendered.
+ Sets the order that metatile layers are rendered by default, |describe-metatile-layer-order|.
- :param order: array of layers. The bottom layer is represented as 0.
+ |describe-metatile-layer-order-handling|.
+
+ If you'd like to set the metatile layer order for only the current layout, see :js:func:`map.setMetatileLayerOrder` instead.
+
+ :param order: array of layers
:type order: array
.. js:function:: utility.getMetatileLayerOpacity()
- Gets the opacities that metatile layers are rendered with.
+ Gets the opacities that metatile layers are rendered with by default, |describe-metatile-layer-opacity|.
- :returns: array of opacities for each layer. The bottom layer is the first element.
+ If you'd like to get the metatile layer opacities for only the current layout, see :js:func:`map.getMetatileLayerOpacity` instead.
+
+ :returns: array of opacities for each layer
:rtype: array
.. js:function:: utility.setMetatileLayerOpacity(opacities)
- Sets the opacities that metatile layers are rendered with.
+ Sets the opacities that metatile layers are rendered with by default, |describe-metatile-layer-opacity|.
- :param opacities: array of opacities for each layer. The bottom layer is the first element.
+ |describe-metatile-layer-opacity-handling|.
+
+ If you'd like to set the metatile layer opacities for only the current layout, see :js:func:`map.setMetatileLayerOpacity` instead.
+
+ :param opacities: array of opacities for each layer
:type opacities: array
diff --git a/forms/metatileimageexporter.ui b/forms/metatileimageexporter.ui
new file mode 100644
index 00000000..7c6bfe49
--- /dev/null
+++ b/forms/metatileimageexporter.ui
@@ -0,0 +1,526 @@
+
+
+ MetatileImageExporter
+
+
+
+ 0
+ 0
+ 649
+ 601
+
+
+
+ Export Metatiles Image
+
+
+ true
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::FocusPolicy::ClickFocus
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ QFrame::Shape::NoFrame
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 304
+ 532
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Tilesets
+
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+ <html><head/><body><p>If checked, automatically update the metatile range to include the full secondary tileset.</p></body></html>
+
+
+ Secondary Tileset
+
+
+
+ -
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ <html><head/><body><p>If checked, automatically update the metatile range to include the full primary tileset.</p></body></html>
+
+
+ Primary Tileset
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 1
+ 20
+
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ Metatile Range
+
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+ Start
+
+
+
+ -
+
+
+ <html><head/><body><p>The metatile ID to start the rendered image at.</p></body></html>
+
+
+
+ -
+
+
+ End
+
+
+
+ -
+
+
+ <html><head/><body><p>The metatile ID to end the rendered image at.</p></body></html>
+
+
+
+
+
+
+ -
+
+
+ <html><head/><body><p>Each metatile consists of 3 layers of tiles. These layers can be toggled here by clicking the checkbox, or rearranged by clicking and dragging them up or down in the list.</p></body></html>
+
+
+ Layers
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+
+
+ Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+
+
+ QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow
+
+
+ true
+
+
+ QAbstractItemView::DragDropMode::InternalMove
+
+
+ Qt::DropAction::MoveAction
+
+
+ QListView::ResizeMode::Adjust
+
+
+ Qt::AlignmentFlag::AlignVCenter
+
+
+
+
+
+
+ -
+
+
+ Transparency
+
+
+
-
+
+
+ <html><head/><body><p>If checked, transparent pixels in the image will be rendered with alpha of 0.</p></body></html>
+
+
+ Normal
+
+
+
+ -
+
+
+ <html><head/><body><p>If checked, transparent pixels in the image will be rendered as black. This is the default in-game behavior.</p></body></html>
+
+
+ Black
+
+
+
+ -
+
+
+ <html><head/><body><p>If checked, transparent pixels in the image will be rendered using the first color in tileset palette 0. This is the default behavior of the GBA.</p></body></html>
+
+
+ First palette color
+
+
+
+
+
+
+ -
+
+
+ Miscellaneous
+
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+ <html><head/><body><p>If checked, display the placeholder tiles that are rendered for the unused layer in-game. For a given metatile only 2 of the 3 tile layers are used, and the 3rd layer is filled with these placeholder tiles. The unused layer and placeholder tile change depending on the metatile's layer type.</p></body></html>
+
+
+ Render placeholder metatiles
+
+
+
+ -
+
+
+ Width (metatiles)
+
+
+
+ -
+
+
+ <html><head/><body><p>Width of the output image in metatiles.</p></body></html>
+
+
+
+ -
+
+
+ Width (pixels)
+
+
+
+ -
+
+
+ <html><head/><body><p>Width of the output image in pixels. Automatically rounded up to a multiple of a metatile's pixel width.</p></body></html>
+
+
+ 16
+
+
+ 128
+
+
+ 16
+
+
+ 128
+
+
+
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Reset
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+ false
+
+
+
+ -
+
+
+ Save
+
+
+ false
+
+
+
+
+
+
+
+
+ -
+
+
+ Preview
+
+
+
+ 6
+
+
+ 6
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+ QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents
+
+
+ true
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+
+ 0
+ 0
+ 285
+ 551
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ false
+
+
+ QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored
+
+
+ QGraphicsView::DragMode::NoDrag
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NoScrollComboBox
+ QComboBox
+
+
+
+ UIntSpinBox
+ QAbstractSpinBox
+
+
+
+ UIntHexSpinBox
+ UIntSpinBox
+
+
+
+ ReorderableListWidget
+ QListWidget
+
+
+
+
+
+
diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui
index f732f335..f2284c07 100644
--- a/forms/projectsettingseditor.ui
+++ b/forms/projectsettingseditor.ui
@@ -852,7 +852,7 @@
0
0
570
- 798
+ 837
@@ -973,6 +973,27 @@
+ -
+
+
-
+
+
+ Metatile Selector Width
+
+
+
+ -
+
+
+ <html><head/><body><p>The width (in metatiles) of the metatile selectors on the Map tab and in the Tileset Editor.</p></body></html>
+
+
+ 1
+
+
+
+
+
-
@@ -1873,16 +1894,6 @@
QComboBox
-
- NoScrollSpinBox
- QSpinBox
-
-
-
- NoScrollTextEdit
- QTextEdit
-
-
UIntSpinBox
QAbstractSpinBox
@@ -1893,6 +1904,16 @@
UIntSpinBox
+
+ NoScrollSpinBox
+ QSpinBox
+
+
+
+ NoScrollTextEdit
+ QTextEdit
+
+
diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui
index 6c696bf3..895fae0f 100644
--- a/forms/tileseteditor.ui
+++ b/forms/tileseteditor.ui
@@ -65,7 +65,7 @@
0
0
- 239
+ 253
659
@@ -555,7 +555,7 @@
0
0
- 499
+ 446
241
@@ -625,18 +625,42 @@
File
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
diff --git a/include/config.h b/include/config.h
index 6e9bbfca..e929d64c 100644
--- a/include/config.h
+++ b/include/config.h
@@ -60,7 +60,8 @@ protected:
static bool getConfigBool(const QString &key, const QString &value);
static int getConfigInteger(const QString &key, const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0);
static uint32_t getConfigUint32(const QString &key, const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0);
- static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = Qt::black);
+ static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = QColor(Qt::black));
+ static QString toConfigColor(const QColor &color);
QString m_root;
QString m_filename;
@@ -71,57 +72,7 @@ class PorymapConfig: public KeyValueConfigBase
{
public:
PorymapConfig();
- virtual void reset() override {
- setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
- this->recentProjects.clear();
- this->projectManuallyClosed = false;
- this->reopenOnLaunch = true;
- this->mapListTab = 0;
- this->mapListEditGroupsEnabled = false;
- this->mapListHideEmptyEnabled.clear();
- this->prettyCursors = true;
- this->mirrorConnectingMaps = true;
- this->showDiveEmergeMaps = false;
- this->diveEmergeMapOpacity = 30;
- this->diveMapOpacity = 15;
- this->emergeMapOpacity = 15;
- this->collisionOpacity = 50;
- this->collisionZoom = 30;
- this->metatilesZoom = 30;
- this->tilesetEditorMetatilesZoom = 45;
- this->tilesetEditorTilesZoom = 30;
- this->showPlayerView = false;
- this->showCursorTile = true;
- this->showBorder = true;
- this->showGrid = false;
- this->showTilesetEditorMetatileGrid = false;
- this->showTilesetEditorLayerGrid = true;
- this->showTilesetEditorDivider = false;
- this->showTilesetEditorRawAttributes = false;
- this->monitorFiles = true;
- this->tilesetCheckerboardFill = true;
- this->newMapHeaderSectionExpanded = false;
- this->theme = "default";
- this->wildMonChartTheme = "";
- this->textEditorOpenFolder = "";
- this->textEditorGotoLine = "";
- this->paletteEditorBitDepth = 24;
- this->projectSettingsTab = 0;
- this->scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly;
- this->warpBehaviorWarningDisabled = false;
- this->eventDeleteWarningDisabled = false;
- this->eventOverlayEnabled = false;
- this->checkForUpdates = true;
- this->lastUpdateCheckTime = QDateTime();
- this->lastUpdateCheckVersion = porymapVersion;
- this->rateLimitTimes.clear();
- this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
- this->shownInGameReloadMessage = false;
- this->gridSettings = GridSettings();
- this->statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN };
- this->applicationFont = QFont();
- this->mapListFont = PorymapConfig::defaultMapListFont();
- }
+ virtual void reset() override;
void addRecentProject(QString project);
void setRecentProjects(QStringList projects);
QString getRecentProject();
@@ -357,7 +308,7 @@ public:
this->prefabImportPrompted = false;
this->tilesetsHaveCallback = true;
this->tilesetsHaveIsCompressed = true;
- this->setTransparentPixelsBlack = true;
+ this->transparencyColor = QColor(Qt::black);
this->preserveMatchingOnlyData = false;
this->filePaths.clear();
this->eventIconPaths.clear();
@@ -374,6 +325,7 @@ public:
this->unusedTileSplit = 0x0000;
this->maxEventsPerGroup = 255;
this->forcedMajorVersion = 0;
+ this->metatileSelectorWidth = 8;
this->globalConstantsFilepaths.clear();
this->globalConstants.clear();
this->identifiers.clear();
@@ -432,7 +384,7 @@ public:
bool prefabImportPrompted;
bool tilesetsHaveCallback;
bool tilesetsHaveIsCompressed;
- bool setTransparentPixelsBlack;
+ QColor transparencyColor;
bool preserveMatchingOnlyData;
int metatileAttributesSize;
uint32_t metatileBehaviorMask;
@@ -453,6 +405,7 @@ public:
QList warpBehaviors;
int maxEventsPerGroup;
int forcedMajorVersion;
+ int metatileSelectorWidth;
QStringList globalConstantsFilepaths;
QMap globalConstants;
diff --git a/include/core/map.h b/include/core/map.h
index 1d460cf1..734d5945 100644
--- a/include/core/map.h
+++ b/include/core/map.h
@@ -51,10 +51,12 @@ public:
void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; }
QString layoutId() const { return layout() ? layout()->id : m_layoutId; }
- int getWidth() const;
- int getHeight() const;
- int getBorderWidth() const;
- int getBorderHeight() const;
+ int getWidth() const { return m_layout ? m_layout->getWidth() : 0; }
+ int getHeight() const { return m_layout ? m_layout->getHeight() : 0; }
+ int getBorderWidth() const { return m_layout ? m_layout->getBorderWidth() : 0; }
+ int getBorderHeight() const { return m_layout ? m_layout->getBorderHeight() : 0; }
+ int pixelWidth() const { return m_layout ? m_layout->pixelWidth() : 0; }
+ int pixelHeight() const { return m_layout ? m_layout->pixelHeight() : 0; }
void setHeader(const MapHeader &header) { *m_header = header; }
MapHeader* header() const { return m_header; }
@@ -133,9 +135,6 @@ private:
QMap> m_events;
QSet m_ownedEvents; // for memory management
- QList m_metatileLayerOrder;
- QList m_metatileLayerOpacity;
-
void trackConnection(MapConnection*);
// MapConnections in 'ownedConnections' but not 'connections' persist in the edit history.
diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h
index 5df071bd..ba7c95a8 100644
--- a/include/core/mapconnection.h
+++ b/include/core/mapconnection.h
@@ -42,7 +42,7 @@ public:
MapConnection* createMirror();
QPixmap render() const;
- QPoint relativePos(bool clipped = false) const;
+ QPoint relativePixelPos(bool clipped = false) const;
static QPointer project;
static const QMap oppositeDirections;
diff --git a/include/core/maplayout.h b/include/core/maplayout.h
index c7790ad3..9f073d20 100644
--- a/include/core/maplayout.h
+++ b/include/core/maplayout.h
@@ -17,7 +17,7 @@ class BorderMetatilesPixmapItem;
class Layout : public QObject {
Q_OBJECT
public:
- Layout() {}
+ Layout() {};
Layout(const Layout &other);
static QString layoutConstantFromName(const QString &name);
@@ -32,6 +32,9 @@ public:
int height;
int border_width;
int border_height;
+ int pixelWidth() const { return this->width * Metatile::pixelWidth(); }
+ int pixelHeight() const { return this->height * Metatile::pixelHeight(); }
+ QSize pixelSize() const { return QSize(pixelWidth(), pixelHeight()); }
QString border_path;
QString blockdata_path;
@@ -64,8 +67,25 @@ public:
QSize borderDimensions;
} lastCommitBlocks; // to track map changes
- QList metatileLayerOrder;
- QList metatileLayerOpacity;
+ void setMetatileLayerOrder(const QList &layerOrder) { m_metatileLayerOrder = layerOrder; }
+ const QList &metatileLayerOrder() const {
+ return !m_metatileLayerOrder.isEmpty() ? m_metatileLayerOrder : Layout::globalMetatileLayerOrder();
+ }
+ static void setGlobalMetatileLayerOrder(const QList &layerOrder) { s_globalMetatileLayerOrder = layerOrder; }
+ static const QList &globalMetatileLayerOrder() {
+ static const QList defaultLayerOrder = {0, 1, 2};
+ return !s_globalMetatileLayerOrder.isEmpty() ? s_globalMetatileLayerOrder : defaultLayerOrder;
+ }
+
+ void setMetatileLayerOpacity(const QList &layerOpacity) { m_metatileLayerOpacity = layerOpacity; }
+ const QList &metatileLayerOpacity() const {
+ return !m_metatileLayerOpacity.isEmpty() ? m_metatileLayerOpacity : Layout::globalMetatileLayerOpacity();
+ }
+ static void setGlobalMetatileLayerOpacity(const QList &layerOpacity) { s_globalMetatileLayerOpacity = layerOpacity; }
+ static const QList &globalMetatileLayerOpacity() {
+ static const QList defaultLayerOpacity = {1.0, 1.0, 1.0};
+ return !s_globalMetatileLayerOpacity.isEmpty() ? s_globalMetatileLayerOpacity : defaultLayerOpacity;
+ }
LayoutPixmapItem *layoutItem = nullptr;
CollisionPixmapItem *collisionItem = nullptr;
@@ -136,9 +156,8 @@ public:
void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);
void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);
- QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, QRect bounds = QRect(0, 0, -1, -1));
+ QPixmap render(bool ignoreCache = false, Layout *fromLayout = nullptr, const QRect &bounds = QRect(0, 0, -1, -1));
QPixmap renderCollision(bool ignoreCache);
- // QPixmap renderConnection(MapConnection, Layout *);
QPixmap renderBorder(bool ignoreCache = false);
QPixmap getLayoutItemPixmap();
@@ -147,6 +166,8 @@ public:
void setCollisionItem(CollisionPixmapItem *item) { collisionItem = item; }
void setBorderItem(BorderMetatilesPixmapItem *item) { borderItem = item; }
+ bool metatileIsValid(uint16_t metatileId) { return Tileset::metatileIsValid(metatileId, this->tileset_primary, this->tileset_secondary); }
+
private:
void setNewDimensionsBlockdata(int newWidth, int newHeight);
void setNewBorderDimensionsBlockdata(int newWidth, int newHeight);
@@ -155,6 +176,11 @@ private:
static int getBorderDrawDistance(int dimension, qreal minimum);
+ QList m_metatileLayerOrder;
+ QList m_metatileLayerOpacity;
+ static QList s_globalMetatileLayerOrder;
+ static QList s_globalMetatileLayerOpacity;
+
signals:
void dimensionsChanged(const QSize &size);
void needsRedrawing();
diff --git a/include/core/metatile.h b/include/core/metatile.h
index a1c805d1..e35e1fa3 100644
--- a/include/core/metatile.h
+++ b/include/core/metatile.h
@@ -62,6 +62,14 @@ public:
static void setLayout(Project*);
static QString getMetatileIdString(uint16_t metatileId);
static QString getMetatileIdStrings(const QList metatileIds);
+ static QString getLayerName(int layerNum);
+
+ static constexpr int tileWidth() { return 2; }
+ static constexpr int tileHeight() { return 2; }
+ static constexpr int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); }
+ static constexpr int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); }
+ static constexpr int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); }
+ static constexpr QSize pixelSize() { return QSize(pixelWidth(), pixelHeight()); }
inline bool operator==(const Metatile &other) {
return this->tiles == other.tiles && this->attributes == other.attributes;
diff --git a/include/core/tile.h b/include/core/tile.h
index 1ae7e23d..a13e9fc7 100644
--- a/include/core/tile.h
+++ b/include/core/tile.h
@@ -3,6 +3,7 @@
#define TILE_H
#include
+#include
class Tile
{
@@ -24,6 +25,11 @@ public:
static int getIndexInTileset(int);
static const uint16_t maxValue;
+
+ static constexpr int pixelWidth() { return 8; }
+ static constexpr int pixelHeight() { return 8; }
+ static constexpr QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); }
+ static constexpr int sizeInBytes() { return sizeof(uint16_t); }
};
inline bool operator==(const Tile &a, const Tile &b) {
diff --git a/include/core/tileset.h b/include/core/tileset.h
index abb31ab1..be5bdace 100644
--- a/include/core/tileset.h
+++ b/include/core/tileset.h
@@ -30,14 +30,13 @@ public:
QString metatile_attrs_label;
QString metatile_attrs_path;
QString tilesImagePath;
- QImage tilesImage;
QStringList palettePaths;
- QList tiles;
QHash metatileLabels;
QList> palettes;
QList> palettePreviews;
+ static QString stripPrefix(const QString &fullName);
static Tileset* getMetatileTileset(int, Tileset*, Tileset*);
static Tileset* getTileTileset(int, Tileset*, Tileset*);
static Metatile* getMetatile(int, Tileset*, Tileset*);
@@ -76,15 +75,28 @@ public:
void setMetatiles(const QList &metatiles);
void addMetatile(Metatile* metatile);
- QList metatiles() const { return m_metatiles; }
- Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); }
+ const QList &metatiles() const { return m_metatiles; }
+ const Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); }
void clearMetatiles();
void resizeMetatiles(int newNumMetatiles);
int numMetatiles() const { return m_metatiles.length(); }
+ int maxMetatiles() const;
+
+ uint16_t firstMetatileId() const;
+ uint16_t lastMetatileId() const;
+ bool contains(uint16_t metatileId) const { return metatileId >= firstMetatileId() && metatileId <= lastMetatileId(); }
+
+ int numTiles() const { return m_tiles.length(); }
+ int maxTiles() const;
+
+ QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); }
private:
QList m_metatiles;
+
+ QList m_tiles;
+ QImage m_tilesImage;
bool m_hasUnsavedTilesImage = false;
};
diff --git a/include/core/utility.h b/include/core/utility.h
index f7f5da61..41c1dcde 100644
--- a/include/core/utility.h
+++ b/include/core/utility.h
@@ -7,7 +7,7 @@
namespace Util {
void numericalModeSort(QStringList &list);
- int roundUp(int numToRound, int multiple);
+ int roundUpToMultiple(int numToRound, int multiple);
QString toDefineCase(QString input);
QString toHexString(uint32_t value, int minLength = 0);
QString toHtmlParagraph(const QString &text);
diff --git a/include/editor.h b/include/editor.h
index 9de1bc94..fd9a809e 100644
--- a/include/editor.h
+++ b/include/editor.h
@@ -285,7 +285,6 @@ signals:
void wildMonTableEdited();
void currentMetatilesSelectionChanged();
void mapRulerStatusChanged(const QString &);
- void tilesetUpdated(QString);
void gridToggled(bool);
void editActionSet(EditAction newEditAction);
};
diff --git a/include/mainwindow.h b/include/mainwindow.h
index c28e78f3..813370b4 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -145,6 +145,10 @@ public:
Q_INVOKABLE void setMetatileTiles(int metatileId, QJSValue tilesObj, int tileStart = 0, int tileEnd = -1, bool forceRedraw = true);
Q_INVOKABLE void setMetatileTiles(int metatileId, int tileId, bool xflip, bool yflip, int palette, int tileStart = 0, int tileEnd = -1, bool forceRedraw = true);
Q_INVOKABLE QJSValue getTilePixels(int tileId);
+ Q_INVOKABLE QList getMetatileLayerOrder() const;
+ Q_INVOKABLE void setMetatileLayerOrder(const QList &order);
+ Q_INVOKABLE QList getMetatileLayerOpacity() const;
+ Q_INVOKABLE void setMetatileLayerOpacity(const QList &opacities);
Q_INVOKABLE QString getSong();
Q_INVOKABLE void setSong(QString song);
Q_INVOKABLE QString getLocation();
diff --git a/include/project.h b/include/project.h
index 45df8143..dd66c2e5 100644
--- a/include/project.h
+++ b/include/project.h
@@ -255,13 +255,17 @@ public:
static QString getDynamicMapDefineName();
static QString getDynamicMapName();
static QString getEmptySpeciesName();
+ static QMargins getPixelViewDistance();
static QMargins getMetatileViewDistance();
static int getNumTilesPrimary() { return num_tiles_primary; }
static int getNumTilesTotal() { return num_tiles_total; }
+ static int getNumTilesSecondary() { return getNumTilesTotal() - getNumTilesPrimary(); }
static int getNumMetatilesPrimary() { return num_metatiles_primary; }
static int getNumMetatilesTotal() { return Block::getMaxMetatileId() + 1; }
+ static int getNumMetatilesSecondary() { return getNumMetatilesTotal() - getNumMetatilesPrimary(); }
static int getNumPalettesPrimary(){ return num_pals_primary; }
static int getNumPalettesTotal() { return num_pals_total; }
+ static int getNumPalettesSecondary() { return getNumPalettesTotal() - getNumPalettesPrimary(); }
static QString getEmptyMapsecName();
static QString getMapGroupPrefix();
diff --git a/include/scripting.h b/include/scripting.h
index b85e9e26..b2c43ed2 100644
--- a/include/scripting.h
+++ b/include/scripting.h
@@ -51,7 +51,7 @@ public:
static void cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta);
static void cb_BorderResized(int oldWidth, int oldHeight, int newWidth, int newHeight);
static void cb_MapShifted(int xDelta, int yDelta);
- static void cb_TilesetUpdated(QString tilesetName);
+ static void cb_TilesetUpdated(const QString &tilesetName);
static void cb_MainTabChanged(int oldTab, int newTab);
static void cb_MapViewTabChanged(int oldTab, int newTab);
static void cb_BorderVisibilityToggled(bool visible);
diff --git a/include/scriptutility.h b/include/scriptutility.h
index 78272eec..09bfaae6 100644
--- a/include/scriptutility.h
+++ b/include/scriptutility.h
@@ -38,9 +38,9 @@ public:
Q_INVOKABLE bool getSmartPathsEnabled();
Q_INVOKABLE QList getCustomScripts();
Q_INVOKABLE QList getMetatileLayerOrder();
- Q_INVOKABLE void setMetatileLayerOrder(QList order);
+ Q_INVOKABLE void setMetatileLayerOrder(const QList &order);
Q_INVOKABLE QList getMetatileLayerOpacity();
- Q_INVOKABLE void setMetatileLayerOpacity(QList order);
+ Q_INVOKABLE void setMetatileLayerOpacity(const QList &order);
Q_INVOKABLE QList getMapNames();
Q_INVOKABLE QList getMapConstants();
Q_INVOKABLE QList getLayoutNames();
@@ -57,6 +57,8 @@ public:
Q_INVOKABLE bool isPrimaryTileset(QString tilesetName);
Q_INVOKABLE bool isSecondaryTileset(QString tilesetName);
+ static bool validateMetatileLayerOrder(const QList &order);
+
private:
void callTimeoutFunction(QJSValue callback);
void runMessageBox(QString text, QString informativeText, QString detailedText, QMessageBox::Icon icon);
diff --git a/include/ui/checkeredbgscene.h b/include/ui/checkeredbgscene.h
new file mode 100644
index 00000000..bf0452a7
--- /dev/null
+++ b/include/ui/checkeredbgscene.h
@@ -0,0 +1,39 @@
+#ifndef CHECKEREDBGSCENE_H
+#define CHECKEREDBGSCENE_H
+
+#include
+
+// Custom scene that paints its background a gray checkered pattern.
+// Additionally there is a definable "valid" area which will paint the checkerboard green inside.
+class CheckeredBgScene : public QGraphicsScene {
+ Q_OBJECT
+
+public:
+ CheckeredBgScene(const QSize &gridSize, QObject *parent = nullptr)
+ : QGraphicsScene(parent),
+ gridSize(gridSize)
+ {};
+ CheckeredBgScene(int width, int height, QObject *parent = nullptr)
+ : CheckeredBgScene(QSize(width, height), parent)
+ {};
+
+ void setValidRect(int x, int y, int width, int height) {
+ this->validRect = QRect(x * this->gridSize.width(),
+ y * this->gridSize.height(),
+ width * this->gridSize.width(),
+ height * this->gridSize.height());
+ }
+ void setValidRect(const QRect &rect) {
+ this->validRect = rect;
+ }
+ QRect getValidRect() { return this->validRect; }
+
+protected:
+ void drawBackground(QPainter *painter, const QRectF &rect) override;
+
+private:
+ QSize gridSize;
+ QRect validRect;
+};
+
+#endif // CHECKEREDBGSCENE_H
diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h
index 32e309f9..5106ba8f 100644
--- a/include/ui/connectionpixmapitem.h
+++ b/include/ui/connectionpixmapitem.h
@@ -2,6 +2,7 @@
#define CONNECTIONPIXMAPITEM_H
#include "mapconnection.h"
+#include "metatile.h"
#include
#include
#include
@@ -31,8 +32,8 @@ private:
bool selected = false;
unsigned actionId = 0;
- static const int mWidth = 16;
- static const int mHeight = 16;
+ static const int mWidth = Metatile::pixelWidth();
+ static const int mHeight = Metatile::pixelHeight();
void updatePos();
void updateOrigin();
diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h
index 806ffd5b..17b823ce 100644
--- a/include/ui/imageproviders.h
+++ b/include/ui/imageproviders.h
@@ -6,13 +6,40 @@
#include
#include
+class Layout;
+
QImage getCollisionMetatileImage(Block);
QImage getCollisionMetatileImage(int, int);
-QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false);
-QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList&, bool useTruePalettes = false);
+
+QImage getMetatileImage(uint16_t, Layout*, bool useTruePalettes = false);
+QImage getMetatileImage(Metatile*, Layout*, bool useTruePalettes = false);
+QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false);
+QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList&, const QList& = {}, bool useTruePalettes = false);
+
+QImage getMetatileSheetImage(Layout *layout, int numMetatilesWIde, bool useTruePalettes = false);
+QImage getMetatileSheetImage(Tileset *primaryTileset,
+ Tileset *secondaryTileset,
+ uint16_t metatileIdStart,
+ uint16_t metatileIdEnd,
+ int numMetatilesWIde,
+ const QList &layerOrder,
+ const QList &layerOpacity = {},
+ const QSize &metatileSize = Metatile::pixelSize(),
+ bool useTruePalettes = false);
+QImage getMetatileSheetImage(Tileset *primaryTileset,
+ Tileset *secondaryTileset,
+ int numMetatilesWide,
+ const QList &layerOrder,
+ const QList &layerOpacity = {},
+ const QSize &metatileSize = Metatile::pixelSize(),
+ bool useTruePalettes = false);
+
+
QImage getTileImage(uint16_t, Tileset*, Tileset*);
QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false);
-QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset);
+QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette);
+QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset);
+
void flattenTo4bppImage(QImage * image);
static QList greyscalePalette({
diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h
index 51c1afe3..789cf9b9 100644
--- a/include/ui/mapimageexporter.h
+++ b/include/ui/mapimageexporter.h
@@ -2,6 +2,7 @@
#define MAPIMAGEEXPORTER_H
#include "project.h"
+#include "checkeredbgscene.h"
class QGifImage;
@@ -52,7 +53,7 @@ private:
Project *m_project = nullptr;
Map *m_map = nullptr;
Layout *m_layout = nullptr;
- QGraphicsScene *m_scene = nullptr;
+ CheckeredBgScene *m_scene = nullptr;
QGifImage *m_timelapseGifImage = nullptr;
QBuffer *m_timelapseBuffer = nullptr;
QMovie *m_timelapseMovie = nullptr;
diff --git a/include/ui/mapruler.h b/include/ui/mapruler.h
index 151492e3..160978cc 100644
--- a/include/ui/mapruler.h
+++ b/include/ui/mapruler.h
@@ -3,6 +3,7 @@
#include
#include
+#include "metatile.h"
class MapRuler : public QGraphicsObject, private QLine
@@ -63,8 +64,8 @@ private:
QPoint snapToWithinBounds(QPoint pos) const;
void updateGeometry();
void updateStatus(Qt::Corner corner);
- int pixWidth() const { return width() * 16; }
- int pixHeight() const { return height() * 16; }
+ int pixWidth() const { return width() * Metatile::pixelWidth(); }
+ int pixHeight() const { return height() * Metatile::pixelHeight(); }
};
#endif // MAPRULER_H
diff --git a/include/ui/metatileimageexporter.h b/include/ui/metatileimageexporter.h
new file mode 100644
index 00000000..1d486bd4
--- /dev/null
+++ b/include/ui/metatileimageexporter.h
@@ -0,0 +1,103 @@
+#ifndef METATILEIMAGEEXPORTER_H
+#define METATILEIMAGEEXPORTER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+#include "checkeredbgscene.h"
+
+class Tileset;
+
+namespace Ui {
+class MetatileImageExporter;
+}
+
+class ReorderableListWidget : public QListWidget
+{
+ Q_OBJECT
+public:
+ explicit ReorderableListWidget(QWidget *parent = nullptr) : QListWidget(parent) {
+ setDragEnabled(true);
+ setDragDropMode(QAbstractItemView::InternalMove);
+ setDefaultDropAction(Qt::MoveAction);
+ };
+
+signals:
+ void reordered();
+
+protected:
+ virtual void dropEvent(QDropEvent *event) override {
+ QListWidget::dropEvent(event);
+ if (event->isAccepted()) {
+ emit reordered();
+ }
+ }
+};
+
+class MetatileImageExporter : public QDialog
+{
+ Q_OBJECT
+
+public:
+ struct Settings {
+ OrderedMap layerOrder = {
+ {2, true},
+ {1, true},
+ {0, true},
+ };
+ uint16_t metatileStart = 0;
+ uint16_t metatileEnd = 0xFFFF;
+ uint16_t numMetatilesWide = projectConfig.metatileSelectorWidth;
+ bool usePrimaryTileset = true;
+ bool useSecondaryTileset = false;
+ bool renderPlaceholders = false;
+ int transparencyMode = 0;
+ };
+
+ explicit MetatileImageExporter(QWidget *parent, Tileset *primaryTileset, Tileset *secondaryTileset, Settings *savedSettings = nullptr);
+ ~MetatileImageExporter();
+
+ bool saveImage(QString filepath = QString());
+ QImage getImage();
+ QString getDefaultFileName() const;
+ void applySettings(const Settings &settings);
+ void reset();
+
+protected:
+ virtual void showEvent(QShowEvent *) override;
+ virtual void closeEvent(QCloseEvent *) override;
+
+private:
+ Ui::MetatileImageExporter *ui;
+
+ Tileset *m_primaryTileset;
+ Tileset *m_secondaryTileset;
+ Settings *m_savedSettings;
+
+ CheckeredBgScene *m_scene = nullptr;
+ QGraphicsPixmapItem *m_preview = nullptr;
+ bool m_previewUpdateQueued = false;
+ QList m_layerOrder;
+ ProjectConfig m_savedConfig;
+ QList m_transparencyButtons;
+
+ void populate(const Settings &settings);
+ void updatePreview();
+ void tryUpdatePreview();
+ void queuePreviewUpdate();
+ void tryEnforceMetatileRange();
+ void syncPixelWidth();
+ void syncMetatileWidth();
+ void validateMetatileStart();
+ void validateMetatileEnd();
+ void updateMetatileRange();
+ void copyRenderSettings();
+ void restoreRenderSettings();
+};
+
+#endif // METATILEIMAGEEXPORTER_H
diff --git a/include/ui/metatilelayersitem.h b/include/ui/metatilelayersitem.h
index 1ca27c1c..ceea3ff8 100644
--- a/include/ui/metatilelayersitem.h
+++ b/include/ui/metatilelayersitem.h
@@ -26,7 +26,7 @@ private:
signals:
void tileChanged(int, int);
void selectedTilesChanged(QPoint, int, int);
- void hoveredTileChanged(uint16_t);
+ void hoveredTileChanged(const Tile &tile);
void hoveredTileCleared();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent*);
diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h
index 7bb3f131..edc3bf77 100644
--- a/include/ui/metatileselector.h
+++ b/include/ui/metatileselector.h
@@ -31,33 +31,34 @@ struct MetatileSelection
class MetatileSelector: public SelectablePixmapItem {
Q_OBJECT
public:
- MetatileSelector(int numMetatilesWide, Layout *layout): SelectablePixmapItem(16, 16) {
+ MetatileSelector(int numMetatilesWide, Layout *layout)
+ : SelectablePixmapItem(Metatile::pixelSize()),
+ numMetatilesWide(qMax(numMetatilesWide, 1))
+ {
this->externalSelection = false;
this->prefabSelection = false;
- this->numMetatilesWide = numMetatilesWide;
this->layout = layout;
- this->primaryTileset = layout->tileset_primary;
- this->secondaryTileset = layout->tileset_secondary;
this->selection = MetatileSelection{};
this->cellPos = QPoint(-1, -1);
setAcceptHoverEvents(true);
}
- QPoint getSelectionDimensions() override;
+ QPoint getSelectionDimensions() const override;
void draw() override;
+ void refresh();
bool select(uint16_t metatile);
void selectFromMap(uint16_t metatileId, uint16_t collision, uint16_t elevation);
- void setTilesets(Tileset*, Tileset*);
- MetatileSelection getMetatileSelection();
+ MetatileSelection getMetatileSelection() const { return this->selection; }
void setPrefabSelection(MetatileSelection selection);
- void setExternalSelection(int, int, QList, QList>);
- QPoint getMetatileIdCoordsOnWidget(uint16_t);
+ void setExternalSelection(int, int, const QList&, const QList>&);
+ QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId) const;
void setLayout(Layout *layout);
bool isInternalSelection() const { return (!this->externalSelection && !this->prefabSelection); }
- Tileset *primaryTileset;
- Tileset *secondaryTileset;
+ Tileset *primaryTileset() const { return this->layout->tileset_primary; }
+ Tileset *secondaryTileset() const { return this->layout->tileset_secondary; }
+
protected:
void mousePressEvent(QGraphicsSceneMouseEvent*) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
@@ -66,10 +67,10 @@ protected:
void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
void drawSelection() override;
private:
+ const int numMetatilesWide;
QPixmap basePixmap;
bool externalSelection;
bool prefabSelection;
- int numMetatilesWide;
Layout *layout;
int externalSelectionWidth;
int externalSelectionHeight;
@@ -80,8 +81,9 @@ private:
void updateBasePixmap();
void updateSelectedMetatiles();
void updateExternalSelectedMetatiles();
- uint16_t getMetatileId(int x, int y) const;
- QPoint getMetatileIdCoords(uint16_t);
+ uint16_t posToMetatileId(int x, int y, bool *ok = nullptr) const;
+ uint16_t posToMetatileId(const QPoint &pos, bool *ok = nullptr) const;
+ QPoint metatileIdToPos(uint16_t metatileId, bool *ok = nullptr) const;
bool positionIsValid(const QPoint &pos) const;
bool selectionIsValid();
void hoverChanged();
diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h
index 552ad6f5..7940e592 100644
--- a/include/ui/movablerect.h
+++ b/include/ui/movablerect.h
@@ -10,7 +10,7 @@
class MovableRect : public QGraphicsRectItem
{
public:
- MovableRect(const QRectF &rect, const QRgb &color);
+ MovableRect(const QRectF &rect, const QSize &cellSize, const QRgb &color);
QRectF boundingRect() const override {
qreal penWidth = 4;
return QRectF(-penWidth,
@@ -31,6 +31,7 @@ public:
protected:
QRectF baseRect;
+ QSize cellSize;
QRgb color;
void updateVisibility();
@@ -43,7 +44,7 @@ class ResizableRect : public QObject, public MovableRect
{
Q_OBJECT
public:
- ResizableRect(QObject *parent, int width, int height, QRgb color);
+ ResizableRect(QObject *parent, const QSize &cellSize, const QSize &size, const QRgb &color);
QRectF boundingRect() const override {
return QRectF(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth));
diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h
index 89ce2e99..b187044f 100644
--- a/include/ui/resizelayoutpopup.h
+++ b/include/ui/resizelayoutpopup.h
@@ -3,10 +3,10 @@
#include "maplayout.h"
#include "project.h"
+#include "checkeredbgscene.h"
#include
#include
-#include
#include
#include
#include
@@ -16,39 +16,12 @@ namespace Ui {
class ResizeLayoutPopup;
}
-
-
-/// Custom scene that paints its background a gray checkered pattern.
-/// Additionally there is a definable "valid" area which will paint the checkerboard green inside.
-class CheckeredBgScene : public QGraphicsScene {
- Q_OBJECT
-
-public:
- CheckeredBgScene(QObject *parent = nullptr);
- void setValidRect(int x, int y, int width, int height) {
- this->validRect = QRect(x * this->gridSize, y * this->gridSize, width * this->gridSize, height * this->gridSize);
- }
- void setValidRect(QRect rect) {
- this->validRect = rect;
- }
- QRect getValidRect() { return this->validRect; }
-
-protected:
- void drawBackground(QPainter *painter, const QRectF &rect) override;
-
-private:
- int gridSize = 16; // virtual pixels
- QRect validRect = QRect();
-};
-
-
-
/// PixmapItem subclass which allows for creating a boundary which determine whether
/// the pixmap paints normally or with a black tint.
-/// This item is movable and snaps on a 16x16 grid.
+/// This item is movable and snaps on a 'cellSize' grid.
class BoundedPixmapItem : public QGraphicsPixmapItem {
public:
- BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = nullptr);
+ BoundedPixmapItem(const QPixmap &pixmap, const QSize &cellSize, QGraphicsItem *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override;
void setBoundary(ResizableRect *rect) { this->boundary = rect; }
@@ -59,6 +32,7 @@ protected:
private:
ResizableRect *boundary = nullptr;
QPointF clickedPos = QPointF();
+ QSize cellSize;
};
diff --git a/include/ui/selectablepixmapitem.h b/include/ui/selectablepixmapitem.h
index 2681e485..8323f7e1 100644
--- a/include/ui/selectablepixmapitem.h
+++ b/include/ui/selectablepixmapitem.h
@@ -7,14 +7,19 @@
class SelectablePixmapItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
- SelectablePixmapItem(int cellWidth, int cellHeight): SelectablePixmapItem(cellWidth, cellHeight, INT_MAX, INT_MAX) {}
- SelectablePixmapItem(int cellWidth, int cellHeight, int maxSelectionWidth, int maxSelectionHeight) {
- this->cellWidth = cellWidth;
- this->cellHeight = cellHeight;
- this->maxSelectionWidth = maxSelectionWidth;
- this->maxSelectionHeight = maxSelectionHeight;
- }
- virtual QPoint getSelectionDimensions();
+ SelectablePixmapItem(const QSize &size, const QSize &maxSelectionSize = QSize(INT_MAX, INT_MAX))
+ : SelectablePixmapItem(size.width(), size.height(), maxSelectionSize.width(), maxSelectionSize.height()) {}
+ SelectablePixmapItem(int cellWidth, int cellHeight, int maxSelectionWidth = INT_MAX, int maxSelectionHeight = INT_MAX)
+ : cellWidth(cellWidth),
+ cellHeight(cellHeight),
+ maxSelectionWidth(maxSelectionWidth),
+ maxSelectionHeight(maxSelectionHeight),
+ selectionInitialX(0),
+ selectionInitialY(0),
+ selectionOffsetX(0),
+ selectionOffsetY(0)
+ {}
+ virtual QPoint getSelectionDimensions() const;
virtual void draw() = 0;
protected:
@@ -28,7 +33,8 @@ protected:
int selectionOffsetY;
QPoint getSelectionStart();
- void select(int, int, int, int);
+ void select(int x, int y, int width = 0, int height = 0);
+ void select(const QPoint &pos, const QSize &size = QSize(0,0)) { select(pos.x(), pos.y(), size.width(), size.height()); }
void updateSelection(int, int);
QPoint getCellPos(QPointF);
virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override;
diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h
index b6a60a61..dc5b935a 100644
--- a/include/ui/tileseteditor.h
+++ b/include/ui/tileseteditor.h
@@ -8,6 +8,7 @@
#include "tileseteditormetatileselector.h"
#include "tileseteditortileselector.h"
#include "metatilelayersitem.h"
+#include "metatileimageexporter.h"
class NoScrollComboBox;
class Layout;
@@ -61,6 +62,7 @@ private slots:
void onWindowActivated();
void onHoveredMetatileChanged(uint16_t);
void onHoveredMetatileCleared();
+ void onHoveredTileChanged(const Tile&);
void onHoveredTileChanged(uint16_t);
void onHoveredTileCleared();
void onSelectedTilesChanged();
@@ -71,10 +73,6 @@ private slots:
void on_spinBox_paletteSelector_valueChanged(int arg1);
- void on_actionImport_Primary_Tiles_triggered();
-
- void on_actionImport_Secondary_Tiles_triggered();
-
void on_actionChange_Metatiles_Count_triggered();
void on_actionChange_Palettes_triggered();
@@ -91,14 +89,6 @@ private slots:
void on_lineEdit_metatileLabel_editingFinished();
- void on_actionExport_Primary_Tiles_Image_triggered();
- void on_actionExport_Secondary_Tiles_Image_triggered();
- void on_actionExport_Primary_Metatiles_Image_triggered();
- void on_actionExport_Secondary_Metatiles_Image_triggered();
-
- void on_actionImport_Primary_Metatiles_triggered();
- void on_actionImport_Secondary_Metatiles_triggered();
-
void on_copyButton_metatileLabel_clicked();
void on_actionCut_triggered();
@@ -122,8 +112,11 @@ private:
void drawSelectedTiles();
void redrawTileSelector();
void redrawMetatileSelector();
- void importTilesetTiles(Tileset*, bool);
- void importTilesetMetatiles(Tileset*, bool);
+ void importTilesetTiles(Tileset*);
+ void importAdvanceMapMetatiles(Tileset*);
+ void exportTilesImage(Tileset*);
+ void exportPorytilesLayerImages(Tileset*);
+ void exportMetatilesImage();
void refresh();
void commitMetatileLabel();
void closeEvent(QCloseEvent*);
@@ -170,6 +163,7 @@ private:
QGraphicsScene *metatileLayersScene = nullptr;
bool lockSelection = false;
QSet metatileReloadQueue;
+ MetatileImageExporter::Settings *metatileImageExportSettings = nullptr;
bool save();
diff --git a/include/ui/tileseteditormetatileselector.h b/include/ui/tileseteditormetatileselector.h
index 8a3fdfa7..6ca9088f 100644
--- a/include/ui/tileseteditormetatileselector.h
+++ b/include/ui/tileseteditormetatileselector.h
@@ -9,7 +9,7 @@ class Layout;
class TilesetEditorMetatileSelector: public SelectablePixmapItem {
Q_OBJECT
public:
- TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout);
+ TilesetEditorMetatileSelector(int numMetatilesWide, Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout);
Layout *layout = nullptr;
void draw() override;
@@ -18,11 +18,9 @@ public:
bool select(uint16_t metatileId);
void setTilesets(Tileset*, Tileset*);
- uint16_t getSelectedMetatileId();
+ uint16_t getSelectedMetatileId() const { return this->selectedMetatileId; }
void updateSelectedMetatile();
- QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId);
- QImage buildPrimaryMetatilesImage();
- QImage buildSecondaryMetatilesImage();
+ QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId) const;
QVector usedMetatiles;
bool selectorShowUnused = false;
@@ -38,26 +36,24 @@ protected:
void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
private:
+ const int numMetatilesWide;
QImage baseImage;
QPixmap basePixmap;
Tileset *primaryTileset = nullptr;
Tileset *secondaryTileset = nullptr;
uint16_t selectedMetatileId;
- int numMetatilesWide;
- int numMetatilesHigh;
void updateBasePixmap();
- uint16_t getMetatileId(int x, int y);
- QPoint getMetatileIdCoords(uint16_t);
+ uint16_t posToMetatileId(int x, int y, bool *ok = nullptr) const;
+ uint16_t posToMetatileId(const QPoint &pos, bool *ok = nullptr) const;
+ QPoint metatileIdToPos(uint16_t metatileId, bool *ok = nullptr) const;
bool shouldAcceptEvent(QGraphicsSceneMouseEvent*);
- int numRows(int numMetatiles);
- int numRows();
+ int numRows(int numMetatiles) const;
+ int numRows() const;
void drawGrid();
void drawDivider();
void drawFilters();
void drawUnused();
void drawCounts();
- QImage buildAllMetatilesImage();
- QImage buildImage(int metatileIdStart, int numMetatiles);
int numPrimaryMetatilesRounded() const;
signals:
diff --git a/include/ui/tileseteditortileselector.h b/include/ui/tileseteditortileselector.h
index aa2a1923..0ad22b97 100644
--- a/include/ui/tileseteditortileselector.h
+++ b/include/ui/tileseteditortileselector.h
@@ -8,7 +8,7 @@ class TilesetEditorTileSelector: public SelectablePixmapItem {
Q_OBJECT
public:
TilesetEditorTileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, int numLayers)
- : SelectablePixmapItem(16, 16, numLayers * 2, 2) {
+ : SelectablePixmapItem(16, 16, numLayers * Metatile::tileWidth(), Metatile::tileHeight()) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
this->numTilesWide = 16;
@@ -18,8 +18,8 @@ public:
this->paletteChanged = false;
setAcceptHoverEvents(true);
}
- QPoint getSelectionDimensions();
- void draw();
+ QPoint getSelectionDimensions() const override;
+ void draw() override;
void select(uint16_t metatileId);
void highlight(uint16_t metatileId);
void setTilesets(Tileset*, Tileset*);
@@ -36,13 +36,14 @@ public:
bool showDivider = false;
protected:
- void mousePressEvent(QGraphicsSceneMouseEvent*);
- void mouseMoveEvent(QGraphicsSceneMouseEvent*);
- void mouseReleaseEvent(QGraphicsSceneMouseEvent*);
- void hoverMoveEvent(QGraphicsSceneHoverEvent*);
- void hoverLeaveEvent(QGraphicsSceneHoverEvent*);
+ void mousePressEvent(QGraphicsSceneMouseEvent*) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
+ void hoverMoveEvent(QGraphicsSceneHoverEvent*) override;
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
private:
+ QPixmap basePixmap;
bool externalSelection;
int externalSelectionWidth;
int externalSelectionHeight;
@@ -63,7 +64,7 @@ private:
QList getCurPaletteTable();
QList buildSelectedTiles(int, int, QList);
QImage buildImage(int tileIdStart, int numTiles);
-
+ void updateBasePixmap();
void drawUnused();
signals:
diff --git a/include/ui/uintspinbox.h b/include/ui/uintspinbox.h
index bc217ec2..aa8bcb77 100644
--- a/include/ui/uintspinbox.h
+++ b/include/ui/uintspinbox.h
@@ -21,6 +21,7 @@ public:
uint32_t value() const { return m_value; }
uint32_t minimum() const { return m_minimum; }
uint32_t maximum() const { return m_maximum; }
+ uint32_t singleStep() const { return m_singleStep; }
QString prefix() const { return m_prefix; }
int displayIntegerBase() const { return m_displayIntegerBase; }
bool hasPadding() const { return m_hasPadding; }
@@ -28,6 +29,7 @@ public:
void setMinimum(uint32_t min);
void setMaximum(uint32_t max);
void setRange(uint32_t min, uint32_t max);
+ void setSingleStep(uint32_t val);
void setPrefix(const QString &prefix);
void setDisplayIntegerBase(int base);
void setHasPadding(bool enabled);
@@ -36,6 +38,7 @@ private:
uint32_t m_minimum;
uint32_t m_maximum;
uint32_t m_value;
+ uint32_t m_singleStep;
QString m_prefix;
int m_displayIntegerBase;
bool m_hasPadding;
diff --git a/porymap.pro b/porymap.pro
index 4045e230..9c6327b7 100644
--- a/porymap.pro
+++ b/porymap.pro
@@ -66,6 +66,7 @@ SOURCES += src/core/advancemapparser.cpp \
src/scriptapi/apiutility.cpp \
src/scriptapi/scripting.cpp \
src/ui/aboutporymap.cpp \
+ src/ui/checkeredbgscene.cpp \
src/ui/colorinputwidget.cpp \
src/ui/connectionslistitem.cpp \
src/ui/customattributesdialog.cpp \
@@ -124,6 +125,7 @@ SOURCES += src/core/advancemapparser.cpp \
src/ui/regionmapeditor.cpp \
src/ui/newmapdialog.cpp \
src/ui/mapimageexporter.cpp \
+ src/ui/metatileimageexporter.cpp \
src/ui/newtilesetdialog.cpp \
src/ui/flowlayout.cpp \
src/ui/mapruler.cpp \
@@ -180,6 +182,7 @@ HEADERS += include/core/advancemapparser.h \
include/lib/orderedmap.h \
include/lib/orderedjson.h \
include/ui/aboutporymap.h \
+ include/ui/checkeredbgscene.h \
include/ui/connectionslistitem.h \
include/ui/customattributesdialog.h \
include/ui/customattributestable.h \
@@ -240,6 +243,7 @@ HEADERS += include/core/advancemapparser.h \
include/ui/regionmapeditor.h \
include/ui/newmapdialog.h \
include/ui/mapimageexporter.h \
+ include/ui/metatileimageexporter.h \
include/ui/newtilesetdialog.h \
include/ui/overlay.h \
include/ui/flowlayout.h \
@@ -289,6 +293,7 @@ FORMS += forms/mainwindow.ui \
forms/aboutporymap.ui \
forms/newtilesetdialog.ui \
forms/mapimageexporter.ui \
+ forms/metatileimageexporter.ui \
forms/shortcutseditor.ui \
forms/preferenceeditor.ui \
forms/regionmappropertiesdialog.ui \
diff --git a/src/config.cpp b/src/config.cpp
index 666ec12c..d5c5b5c9 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -4,6 +4,7 @@
#include "map.h"
#include "validator.h"
#include "utility.h"
+#include "metatile.h"
#include
#include
#include
@@ -283,7 +284,7 @@ int KeyValueConfigBase::getConfigInteger(const QString &key, const QString &valu
logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue));
result = defaultValue;
}
- return qMin(max, qMax(min, result));
+ return qBound(min, result, max);
}
uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString &value, uint32_t min, uint32_t max, uint32_t defaultValue) {
@@ -293,10 +294,13 @@ uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString &
logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue));
result = defaultValue;
}
- return qMin(max, qMax(min, result));
+ return qBound(min, result, max);
}
QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &value, const QColor &defaultValue) {
+ if (value.isEmpty())
+ return QColor();
+
QColor color = QColor("#" + value);
if (!color.isValid()) {
logWarn(QString("Invalid config value for %1: '%2'. Must be a color in the format 'RRGGBB'. Using default value '%3'.").arg(key).arg(value).arg(defaultValue.name()));
@@ -305,12 +309,70 @@ QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &val
return color;
}
+QString KeyValueConfigBase::toConfigColor(const QColor &color) {
+ return color.isValid() ? color.name().remove("#") : QString(); // Our text config treats '#' as the start of a comment.
+}
+
PorymapConfig porymapConfig;
PorymapConfig::PorymapConfig() : KeyValueConfigBase(QStringLiteral("porymap.cfg")) {
reset();
}
+void PorymapConfig::reset() {
+ setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
+ this->recentProjects.clear();
+ this->projectManuallyClosed = false;
+ this->reopenOnLaunch = true;
+ this->mapListTab = 0;
+ this->mapListEditGroupsEnabled = false;
+ this->mapListHideEmptyEnabled.clear();
+ this->prettyCursors = true;
+ this->mirrorConnectingMaps = true;
+ this->showDiveEmergeMaps = false;
+ this->diveEmergeMapOpacity = 30;
+ this->diveMapOpacity = 15;
+ this->emergeMapOpacity = 15;
+ this->collisionOpacity = 50;
+ this->collisionZoom = 30;
+ this->metatilesZoom = 30;
+ this->tilesetEditorMetatilesZoom = 30;
+ this->tilesetEditorTilesZoom = 30;
+ this->showPlayerView = false;
+ this->showCursorTile = true;
+ this->showBorder = true;
+ this->showGrid = false;
+ this->showTilesetEditorMetatileGrid = false;
+ this->showTilesetEditorLayerGrid = true;
+ this->showTilesetEditorDivider = false;
+ this->showTilesetEditorRawAttributes = false;
+ this->monitorFiles = true;
+ this->tilesetCheckerboardFill = true;
+ this->newMapHeaderSectionExpanded = false;
+ this->theme = "default";
+ this->wildMonChartTheme = "";
+ this->textEditorOpenFolder = "";
+ this->textEditorGotoLine = "";
+ this->paletteEditorBitDepth = 24;
+ this->projectSettingsTab = 0;
+ this->scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly;
+ this->warpBehaviorWarningDisabled = false;
+ this->eventDeleteWarningDisabled = false;
+ this->eventOverlayEnabled = false;
+ this->checkForUpdates = true;
+ this->lastUpdateCheckTime = QDateTime();
+ this->lastUpdateCheckVersion = porymapVersion;
+ this->rateLimitTimes.clear();
+ this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
+ this->shownInGameReloadMessage = false;
+ this->gridSettings = GridSettings();
+ this->gridSettings.width = Metatile::pixelWidth();
+ this->gridSettings.height = Metatile::pixelHeight();
+ this->statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN };
+ this->applicationFont = QFont();
+ this->mapListFont = PorymapConfig::defaultMapListFont();
+}
+
void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
if (key == "recent_project") {
this->recentProjects = value.split(",", Qt::SkipEmptyParts);
@@ -576,7 +638,7 @@ QMap PorymapConfig::getKeyValueMap() {
map.insert("grid_x", QString::number(this->gridSettings.offsetX));
map.insert("grid_y", QString::number(this->gridSettings.offsetY));
map.insert("grid_style", GridSettings::getStyleName(this->gridSettings.style));
- map.insert("grid_color", this->gridSettings.color.name().remove("#")); // Our text config treats '#' as the start of a comment.
+ map.insert("grid_color", toConfigColor(this->gridSettings.color));
QStringList logTypesStrings;
for (const auto &type : this->statusBarLogTypes) {
@@ -903,8 +965,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
this->tilesetsHaveCallback = getConfigBool(key, value);
} else if (key == "tilesets_have_is_compressed") {
this->tilesetsHaveIsCompressed = getConfigBool(key, value);
- } else if (key == "set_transparent_pixels_black") {
- this->setTransparentPixelsBlack = getConfigBool(key, value);
+ } else if (key == "transparency_color") {
+ this->transparencyColor = getConfigColor(key, value);
} else if (key == "preserve_matching_only_data") {
this->preserveMatchingOnlyData = getConfigBool(key, value);
} else if (key == "event_icon_path_object") {
@@ -945,6 +1007,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255);
} else if (key == "forced_major_version") {
this->forcedMajorVersion = getConfigInteger(key, value);
+ } else if (key == "metatile_selector_width") {
+ this->metatileSelectorWidth = getConfigInteger(key, value, 1, INT_MAX, 8);
} else {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->filepath()).arg(key));
}
@@ -1010,7 +1074,7 @@ QMap ProjectConfig::getKeyValueMap() {
}
map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback));
map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed));
- map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack));
+ map.insert("transparency_color", toConfigColor(this->transparencyColor));
map.insert("preserve_matching_only_data", QString::number(this->preserveMatchingOnlyData));
map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize));
map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask));
@@ -1054,6 +1118,7 @@ QMap ProjectConfig::getKeyValueMap() {
map.insert("warp_behaviors", warpBehaviorStrs.join(","));
map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup));
map.insert("forced_major_version", QString::number(this->forcedMajorVersion));
+ map.insert("metatile_selector_width", QString::number(this->metatileSelectorWidth));
return map;
}
@@ -1187,7 +1252,7 @@ int ProjectConfig::getNumLayersInMetatile() {
}
int ProjectConfig::getNumTilesInMetatile() {
- return this->tripleLayerMetatilesEnabled ? 12 : 8;
+ return getNumLayersInMetatile() * Metatile::tilesPerLayer();
}
void ProjectConfig::setEventIconPath(Event::Group group, const QString &path) {
diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp
index 6a8428ec..2e4ef7cd 100644
--- a/src/core/advancemapparser.cpp
+++ b/src/core/advancemapparser.cpp
@@ -131,7 +131,7 @@ QList AdvanceMapParser::parseMetatiles(const QString &filepath, bool
}
int attrSize = Metatile::getDefaultAttributesSize(version);
- int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary();
+ int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesSecondary();
int numMetatiles = static_cast(in.at(0)) |
(static_cast(in.at(1)) << 8) |
(static_cast(in.at(2)) << 16) |
diff --git a/src/core/map.cpp b/src/core/map.cpp
index 6110a26f..079f336a 100644
--- a/src/core/map.cpp
+++ b/src/core/map.cpp
@@ -30,8 +30,6 @@ Map::Map(const Map &other, QObject *parent) : Map(parent) {
*m_header = *other.m_header;
m_layout = other.m_layout;
m_isPersistedToFile = false;
- m_metatileLayerOrder = other.m_metatileLayerOrder;
- m_metatileLayerOpacity = other.m_metatileLayerOpacity;
// Copy events
for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) {
@@ -64,44 +62,28 @@ QString Map::mapConstantFromName(const QString &name) {
return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name);
}
-int Map::getWidth() const {
- return m_layout ? m_layout->getWidth() : 0;
-}
-
-int Map::getHeight() const {
- return m_layout ? m_layout->getHeight() : 0;
-}
-
-int Map::getBorderWidth() const {
- return m_layout ? m_layout->getBorderWidth() : 0;
-}
-
-int Map::getBorderHeight() const {
- return m_layout ? m_layout->getBorderHeight() : 0;
-}
-
// Get the portion of the map that can be rendered when rendered as a map connection.
// Cardinal connections render the nearest segment of their map and within the bounds of the border draw distance,
// Dive/Emerge connections are rendered normally within the bounds of their parent map.
QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) const {
int x = 0, y = 0;
- int w = getWidth(), h = getHeight();
+ int w = pixelWidth(), h = pixelHeight();
- QMargins viewDistance = Project::getMetatileViewDistance();
+ QMargins viewDistance = Project::getPixelViewDistance();
if (direction == "up") {
h = qMin(h, viewDistance.top());
- y = getHeight() - h;
+ y = pixelHeight() - h;
} else if (direction == "down") {
h = qMin(h, viewDistance.bottom());
} else if (direction == "left") {
w = qMin(w, viewDistance.left());
- x = getWidth() - w;
+ x = pixelWidth() - w;
} else if (direction == "right") {
w = qMin(w, viewDistance.right());
} else if (MapConnection::isDiving(direction)) {
if (fromLayout) {
- w = qMin(w, fromLayout->getWidth());
- h = qMin(h, fromLayout->getHeight());
+ w = qMin(w, fromLayout->pixelWidth());
+ h = qMin(h, fromLayout->pixelHeight());
}
} else {
// Unknown direction
@@ -124,7 +106,7 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) {
fromLayout = nullptr;
QPixmap connectionPixmap = m_layout->render(true, fromLayout, bounds);
- return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16);
+ return connectionPixmap.copy(bounds);
}
void Map::openScript(const QString &label) {
diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp
index d37a8c74..e1478fad 100644
--- a/src/core/mapconnection.cpp
+++ b/src/core/mapconnection.cpp
@@ -72,20 +72,20 @@ QPixmap MapConnection::render() const {
// For right/down connections this is offset by the dimensions of the parent map.
// For left/up connections this is offset by the dimensions of the target map.
// If 'clipped' is true, only the rendered dimensions of the target map will be used, rather than its full dimensions.
-QPoint MapConnection::relativePos(bool clipped) const {
+QPoint MapConnection::relativePixelPos(bool clipped) const {
int x = 0, y = 0;
if (m_direction == "right") {
- if (m_parentMap) x = m_parentMap->getWidth();
- y = m_offset;
+ if (m_parentMap) x = m_parentMap->pixelWidth();
+ y = m_offset * Metatile::pixelHeight();
} else if (m_direction == "down") {
- x = m_offset;
- if (m_parentMap) y = m_parentMap->getHeight();
+ x = m_offset * Metatile::pixelWidth();
+ if (m_parentMap) y = m_parentMap->pixelHeight();
} else if (m_direction == "left") {
- if (targetMap()) x = !clipped ? -targetMap()->getWidth() : -targetMap()->getConnectionRect(m_direction).width();
- y = m_offset;
+ if (targetMap()) x = !clipped ? -targetMap()->pixelWidth() : -targetMap()->getConnectionRect(m_direction).width();
+ y = m_offset * Metatile::pixelHeight();
} else if (m_direction == "up") {
- x = m_offset;
- if (targetMap()) y = !clipped ? -targetMap()->getHeight() : -targetMap()->getConnectionRect(m_direction).height();
+ x = m_offset * Metatile::pixelWidth();
+ if (targetMap()) y = !clipped ? -targetMap()->pixelHeight() : -targetMap()->getConnectionRect(m_direction).height();
}
return QPoint(x, y);
}
diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp
index 4ddb3b75..a965ddb8 100644
--- a/src/core/maplayout.cpp
+++ b/src/core/maplayout.cpp
@@ -6,6 +6,9 @@
#include "imageproviders.h"
#include "utility.h"
+QList Layout::s_globalMetatileLayerOrder;
+QList Layout::s_globalMetatileLayerOpacity;
+
Layout::Layout(const Layout &other) : Layout() {
copyFrom(&other);
}
@@ -89,12 +92,11 @@ QMargins Layout::getBorderMargins() const {
return distance;
}
-// Get a rectangle that represents (in pixels) the layout's map area and the visible area of its border.
-// At maximum, this is equal to the map size plus the border margins.
-// If the border is large (and so beyond player the view) it may be smaller than that.
+// Get a rectangle that represents (in pixels) the layout's map area + the distance the player can see.
+// Note that this may be smaller than the map area + the size of the border for layouts with large border dimensions.
QRect Layout::getVisibleRect() const {
- QRect area = QRect(0, 0, this->width * 16, this->height * 16);
- return area += (Project::getMetatileViewDistance() * 16);
+ QRect area = QRect(0, 0, this->pixelWidth(), this->pixelHeight());
+ return area += Project::getPixelViewDistance();
}
bool Layout::getBlock(int x, int y, Block *out) {
@@ -341,15 +343,13 @@ void Layout::magicFillCollisionElevation(int initialX, int initialY, uint16_t co
}
}
-QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) {
+QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, const QRect &bounds) {
bool changed_any = false;
- int width_ = getWidth();
- int height_ = getHeight();
- if (this->image.isNull() || this->image.width() != width_ * 16 || this->image.height() != height_ * 16) {
- this->image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
+ if (this->image.isNull() || this->image.width() != pixelWidth() || this->image.height() != pixelHeight()) {
+ this->image = QImage(pixelWidth(), pixelHeight(), QImage::Format_RGBA8888);
changed_any = true;
}
- if (this->blockdata.isEmpty() || !width_ || !height_) {
+ if (this->blockdata.isEmpty() || this->width == 0 || this->height == 0) {
this->pixmap = this->pixmap.fromImage(this->image);
return this->pixmap;
}
@@ -365,9 +365,9 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) {
if (!ignoreCache && !layoutBlockChanged(i, this->blockdata, this->cached_blockdata)) {
continue;
}
- int map_y = width_ ? i / width_ : 0;
- int map_x = width_ ? i % width_ : 0;
- if (bounds.isValid() && !bounds.contains(map_x, map_y)) {
+ int x = this->width ? ((i % this->width) * Metatile::pixelWidth()) : 0;
+ int y = this->width ? ((i / this->width) * Metatile::pixelHeight()) : 0;
+ if (bounds.isValid() && !bounds.contains(x, y)) {
continue;
}
@@ -380,14 +380,12 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) {
metatileId,
fromLayout ? fromLayout->tileset_primary : this->tileset_primary,
fromLayout ? fromLayout->tileset_secondary : this->tileset_secondary,
- metatileLayerOrder,
- metatileLayerOpacity
+ metatileLayerOrder(),
+ metatileLayerOpacity()
);
imageCache.insert(metatileId, metatileImage);
}
-
- QPoint metatileOrigin = QPoint(map_x * 16, map_y * 16);
- painter.drawImage(metatileOrigin, metatileImage);
+ painter.drawImage(x, y, metatileImage);
changed_any = true;
}
painter.end();
@@ -401,13 +399,11 @@ QPixmap Layout::render(bool ignoreCache, Layout *fromLayout, QRect bounds) {
QPixmap Layout::renderCollision(bool ignoreCache) {
bool changed_any = false;
- int width_ = getWidth();
- int height_ = getHeight();
- if (collision_image.isNull() || collision_image.width() != width_ * 16 || collision_image.height() != height_ * 16) {
- collision_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
+ if (collision_image.isNull() || collision_image.width() != pixelWidth() || collision_image.height() != pixelHeight()) {
+ collision_image = QImage(pixelWidth(), pixelHeight(), QImage::Format_RGBA8888);
changed_any = true;
}
- if (this->blockdata.isEmpty() || !width_ || !height_) {
+ if (this->blockdata.isEmpty() || this->width == 0 || this->height == 0) {
collision_pixmap = collision_pixmap.fromImage(collision_image);
return collision_pixmap;
}
@@ -419,10 +415,9 @@ QPixmap Layout::renderCollision(bool ignoreCache) {
changed_any = true;
Block block = this->blockdata.at(i);
QImage collision_metatile_image = getCollisionMetatileImage(block);
- int map_y = width_ ? i / width_ : 0;
- int map_x = width_ ? i % width_ : 0;
- QPoint metatile_origin = QPoint(map_x * 16, map_y * 16);
- painter.drawImage(metatile_origin, collision_metatile_image);
+ int x = this->width ? ((i % this->width) * Metatile::pixelWidth()) : 0;
+ int y = this->width ? ((i / this->width) * Metatile::pixelHeight()) : 0;
+ painter.drawImage(x, y, collision_metatile_image);
}
painter.end();
cacheCollision();
@@ -434,14 +429,14 @@ QPixmap Layout::renderCollision(bool ignoreCache) {
QPixmap Layout::renderBorder(bool ignoreCache) {
bool changed_any = false, border_resized = false;
- int width_ = getBorderWidth();
- int height_ = getBorderHeight();
+ int pixelWidth = this->border_width * Metatile::pixelWidth();
+ int pixelHeight = this->border_height * Metatile::pixelHeight();
if (this->border_image.isNull()) {
- this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
+ this->border_image = QImage(pixelWidth, pixelHeight, QImage::Format_RGBA8888);
changed_any = true;
}
- if (this->border_image.width() != width_ * 16 || this->border_image.height() != height_ * 16) {
- this->border_image = QImage(width_ * 16, height_ * 16, QImage::Format_RGBA8888);
+ if (this->border_image.width() != pixelWidth || this->border_image.height() != pixelHeight) {
+ this->border_image = QImage(pixelWidth, pixelHeight, QImage::Format_RGBA8888);
border_resized = true;
}
if (this->border.isEmpty()) {
@@ -457,10 +452,10 @@ QPixmap Layout::renderBorder(bool ignoreCache) {
changed_any = true;
Block block = this->border.at(i);
uint16_t metatileId = block.metatileId();
- QImage metatile_image = getMetatileImage(metatileId, this->tileset_primary, this->tileset_secondary, metatileLayerOrder, metatileLayerOpacity);
- int map_y = width_ ? i / width_ : 0;
- int map_x = width_ ? i % width_ : 0;
- painter.drawImage(QPoint(map_x * 16, map_y * 16), metatile_image);
+ QImage metatile_image = getMetatileImage(metatileId, this);
+ int x = this->border_width ? ((i % this->border_width) * Metatile::pixelWidth()) : 0;
+ int y = this->border_width ? ((i / this->border_width) * Metatile::pixelHeight()) : 0;
+ painter.drawImage(x, y, metatile_image);
}
painter.end();
if (changed_any) {
@@ -535,6 +530,7 @@ bool Layout::loadBorder(const QString &root) {
logError(QString("Failed to load border for %1 from '%2': %3").arg(this->name).arg(path).arg(error));
return false;
}
+ this->border = blockdata;
// 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG
if (this->border_width <= 0) {
@@ -544,10 +540,6 @@ bool Layout::loadBorder(const QString &root) {
this->border_height = DEFAULT_BORDER_HEIGHT;
}
- this->border = blockdata;
- this->lastCommitBlocks.border = blockdata;
- this->lastCommitBlocks.borderDimensions = QSize(this->border_width, this->border_height);
-
int expectedSize = this->border_width * this->border_height;
if (this->border.count() != expectedSize) {
logWarn(QString("%1 border blockdata length %2 does not match dimensions %3x%4 (should be %5). Resizing border blockdata.")
@@ -558,6 +550,10 @@ bool Layout::loadBorder(const QString &root) {
.arg(expectedSize));
this->border.resize(expectedSize);
}
+
+ this->lastCommitBlocks.border = this->border;
+ this->lastCommitBlocks.borderDimensions = QSize(this->border_width, this->border_height);
+
return true;
}
@@ -574,10 +570,7 @@ bool Layout::loadBlockdata(const QString &root) {
logError(QString("Failed to load blockdata for %1 from '%2': %3").arg(this->name).arg(path).arg(error));
return false;
}
-
this->blockdata = blockdata;
- this->lastCommitBlocks.blocks = blockdata;
- this->lastCommitBlocks.layoutDimensions = QSize(this->width, this->height);
int expectedSize = this->width * this->height;
if (expectedSize <= 0) {
@@ -593,6 +586,10 @@ bool Layout::loadBlockdata(const QString &root) {
.arg(expectedSize));
this->blockdata.resize(expectedSize);
}
+
+ this->lastCommitBlocks.blocks = this->blockdata;
+ this->lastCommitBlocks.layoutDimensions = QSize(this->width, this->height);
+
return true;
}
diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp
index 73ff9c86..f6c0d382 100644
--- a/src/core/metatile.cpp
+++ b/src/core/metatile.cpp
@@ -36,8 +36,8 @@ int Metatile::getIndexInTileset(int metatileId) {
}
QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
- int x = static_cast(pixelCoord.x()) / 16;
- int y = static_cast(pixelCoord.y()) / 16;
+ int x = static_cast(pixelCoord.x()) / pixelWidth();
+ int y = static_cast(pixelCoord.y()) / pixelHeight();
if (pixelCoord.x() < 0) x--;
if (pixelCoord.y() < 0) y--;
return QPoint(x, y);
@@ -55,6 +55,11 @@ QString Metatile::getMetatileIdStrings(const QList metatileIds) {
return metatiles.join(",");
};
+QString Metatile::getLayerName(int layerNum) {
+ static const QStringList layerTitles = { "Bottom", "Middle", "Top"};
+ return layerTitles.value(layerNum);
+}
+
// Read and pack together this metatile's attributes.
uint32_t Metatile::getAttributes() const {
uint32_t data = 0;
diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp
index cc7efb55..5f7ac20b 100644
--- a/src/core/tileset.cpp
+++ b/src/core/tileset.cpp
@@ -20,15 +20,15 @@ Tileset::Tileset(const Tileset &other)
metatile_attrs_label(other.metatile_attrs_label),
metatile_attrs_path(other.metatile_attrs_path),
tilesImagePath(other.tilesImagePath),
- tilesImage(other.tilesImage.copy()),
palettePaths(other.palettePaths),
metatileLabels(other.metatileLabels),
palettes(other.palettes),
palettePreviews(other.palettePreviews),
+ m_tilesImage(other.m_tilesImage.copy()),
m_hasUnsavedTilesImage(other.m_hasUnsavedTilesImage)
{
- for (auto tile : other.tiles) {
- tiles.append(tile.copy());
+ for (auto tile : other.m_tiles) {
+ m_tiles.append(tile.copy());
}
for (auto *metatile : other.m_metatiles) {
@@ -46,15 +46,15 @@ Tileset &Tileset::operator=(const Tileset &other) {
metatile_attrs_label = other.metatile_attrs_label;
metatile_attrs_path = other.metatile_attrs_path;
tilesImagePath = other.tilesImagePath;
- tilesImage = other.tilesImage.copy();
+ m_tilesImage = other.m_tilesImage.copy();
palettePaths = other.palettePaths;
metatileLabels = other.metatileLabels;
palettes = other.palettes;
palettePreviews = other.palettePreviews;
- tiles.clear();
- for (auto tile : other.tiles) {
- tiles.append(tile.copy());
+ m_tiles.clear();
+ for (auto tile : other.m_tiles) {
+ m_tiles.append(tile.copy());
}
clearMetatiles();
@@ -94,6 +94,22 @@ void Tileset::resizeMetatiles(int newNumMetatiles) {
}
}
+uint16_t Tileset::firstMetatileId() const {
+ return this->is_secondary ? Project::getNumMetatilesPrimary() : 0;
+}
+
+uint16_t Tileset::lastMetatileId() const {
+ return qMax(1, firstMetatileId() + m_metatiles.length()) - 1;
+}
+
+int Tileset::maxMetatiles() const {
+ return this->is_secondary ? Project::getNumMetatilesSecondary() : Project::getNumMetatilesPrimary();
+}
+
+int Tileset::maxTiles() const {
+ return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary();
+}
+
Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
if (tileId < Project::getNumTilesPrimary()) {
return primaryTileset;
@@ -200,34 +216,34 @@ QString Tileset::getMetatileLabelPrefix()
QString Tileset::getMetatileLabelPrefix(const QString &name)
{
// Default is "gTileset_Name" --> "METATILE_Name_"
- const QString tilesetPrefix = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix);
const QString labelPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix);
- return QString("%1%2_").arg(labelPrefix).arg(QString(name).replace(tilesetPrefix, ""));
+ return QString("%1%2_").arg(labelPrefix).arg(Tileset::stripPrefix(name));
}
bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
- if (metatileId >= Project::getNumMetatilesTotal())
- return false;
-
- if (metatileId < Project::getNumMetatilesPrimary() && metatileId >= primaryTileset->numMetatiles())
- return false;
-
- if (metatileId >= Project::getNumMetatilesPrimary() + secondaryTileset->numMetatiles())
- return false;
-
- return true;
+ return (primaryTileset && primaryTileset->contains(metatileId))
+ || (secondaryTileset && secondaryTileset->contains(metatileId));
}
QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) {
QList> palettes;
- auto primaryPalettes = useTruePalettes ? primaryTileset->palettes : primaryTileset->palettePreviews;
+
+ QList> primaryPalettes;
+ if (primaryTileset) {
+ primaryPalettes = useTruePalettes ? primaryTileset->palettes : primaryTileset->palettePreviews;
+ }
for (int i = 0; i < Project::getNumPalettesPrimary(); i++) {
- palettes.append(primaryPalettes.at(i));
+ palettes.append(primaryPalettes.value(i));
+ }
+
+ QList> secondaryPalettes;
+ if (secondaryTileset) {
+ secondaryPalettes = useTruePalettes ? secondaryTileset->palettes : secondaryTileset->palettePreviews;
}
- auto secondaryPalettes = useTruePalettes ? secondaryTileset->palettes : secondaryTileset->palettePreviews;
for (int i = Project::getNumPalettesPrimary(); i < Project::getNumPalettesTotal(); i++) {
- palettes.append(secondaryPalettes.at(i));
+ palettes.append(secondaryPalettes.value(i));
}
+
return palettes;
}
@@ -236,10 +252,12 @@ QList Tileset::getPalette(int paletteId, Tileset *primaryTileset, Tileset
Tileset *tileset = paletteId < Project::getNumPalettesPrimary()
? primaryTileset
: secondaryTileset;
- auto palettes = useTruePalettes ? tileset->palettes : tileset->palettePreviews;
+ if (!tileset) {
+ return paletteTable;
+ }
- if (paletteId < 0 || paletteId >= palettes.length()){
- logError(QString("Invalid tileset palette id '%1' requested.").arg(paletteId));
+ auto palettes = useTruePalettes ? tileset->palettes : tileset->palettePreviews;
+ if (paletteId < 0 || paletteId >= palettes.length()) {
return paletteTable;
}
@@ -373,8 +391,7 @@ QString Tileset::getExpectedDir(QString tilesetName, bool isSecondary)
: projectConfig.getFilePath(ProjectFilePath::data_primary_tilesets_folders);
static const QRegularExpression re("([a-z])([A-Z0-9])");
- const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix);
- return basePath + tilesetName.replace(prefix, "").replace(re, "\\1_\\2").toLower();
+ return basePath + Tileset::stripPrefix(tilesetName).replace(re, "\\1_\\2").toLower();
}
// Get the expected positions of the members in struct Tileset.
@@ -400,17 +417,25 @@ QHash Tileset::getHeaderMemberMap(bool usingAsm)
bool Tileset::loadMetatiles() {
clearMetatiles();
- QFile metatiles_file(this->metatiles_path);
- if (!metatiles_file.open(QIODevice::ReadOnly)) {
- logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(metatiles_file.errorString()));
+ QFile file(this->metatiles_path);
+ if (!file.open(QIODevice::ReadOnly)) {
+ logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(file.errorString()));
return false;
}
- QByteArray data = metatiles_file.readAll();
+ QByteArray data = file.readAll();
int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
- int bytesPerMetatile = 2 * tilesPerMetatile;
- int num_metatiles = data.length() / bytesPerMetatile;
- for (int i = 0; i < num_metatiles; i++) {
+ int bytesPerMetatile = Tile::sizeInBytes() * tilesPerMetatile;
+ int numMetatiles = data.length() / bytesPerMetatile;
+ if (numMetatiles > maxMetatiles()) {
+ logWarn(QString("%1 metatile count %2 exceeds limit of %3. Additional metatiles will be ignored.")
+ .arg(this->name)
+ .arg(numMetatiles)
+ .arg(maxMetatiles()));
+ numMetatiles = maxMetatiles();
+ }
+
+ for (int i = 0; i < numMetatiles; i++) {
auto metatile = new Metatile;
int index = i * bytesPerMetatile;
for (int j = 0; j < tilesPerMetatile; j++) {
@@ -424,9 +449,9 @@ bool Tileset::loadMetatiles() {
}
bool Tileset::saveMetatiles() {
- QFile metatiles_file(this->metatiles_path);
- if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
- logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(metatiles_file.errorString()));
+ QFile file(this->metatiles_path);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(file.errorString()));
return false;
}
@@ -434,31 +459,40 @@ bool Tileset::saveMetatiles() {
int numTiles = projectConfig.getNumTilesInMetatile();
for (const auto &metatile : m_metatiles) {
for (int i = 0; i < numTiles; i++) {
- uint16_t tile = metatile->tiles.at(i).rawValue();
+ uint16_t tile = metatile->tiles.value(i).rawValue();
data.append(static_cast(tile));
data.append(static_cast(tile >> 8));
}
}
- metatiles_file.write(data);
+ file.write(data);
return true;
}
bool Tileset::loadMetatileAttributes() {
- QFile attrs_file(this->metatile_attrs_path);
- if (!attrs_file.open(QIODevice::ReadOnly)) {
- logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString()));
+ QFile file(this->metatile_attrs_path);
+ if (!file.open(QIODevice::ReadOnly)) {
+ logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(file.errorString()));
return false;
}
- QByteArray data = attrs_file.readAll();
+ QByteArray data = file.readAll();
int attrSize = projectConfig.metatileAttributesSize;
int numMetatiles = m_metatiles.length();
int numMetatileAttrs = data.length() / attrSize;
- if (numMetatiles != numMetatileAttrs) {
- logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(numMetatiles).arg(numMetatileAttrs).arg(this->name));
+ if (numMetatileAttrs > numMetatiles) {
+ logWarn(QString("%1 metatile attributes count %2 exceeds metatile count of %3. Additional attributes will be ignored.")
+ .arg(this->name)
+ .arg(numMetatileAttrs)
+ .arg(numMetatiles));
+ numMetatileAttrs = numMetatiles;
+ } else if (numMetatileAttrs < numMetatiles) {
+ logWarn(QString("%1 metatile attributes count %2 is fewer than the metatile count of %3. Missing attributes will default to 0.")
+ .arg(this->name)
+ .arg(numMetatileAttrs)
+ .arg(numMetatiles));
}
- for (int i = 0; i < qMin(numMetatiles, numMetatileAttrs); i++) {
+ for (int i = 0; i < numMetatileAttrs; i++) {
uint32_t attributes = 0;
for (int j = 0; j < attrSize; j++)
attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j);
@@ -468,9 +502,9 @@ bool Tileset::loadMetatileAttributes() {
}
bool Tileset::saveMetatileAttributes() {
- QFile attrs_file(this->metatile_attrs_path);
- if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
- logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString()));
+ QFile file(this->metatile_attrs_path);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(file.errorString()));
return false;
}
@@ -480,21 +514,36 @@ bool Tileset::saveMetatileAttributes() {
for (int i = 0; i < projectConfig.metatileAttributesSize; i++)
data.append(static_cast(attributes >> (8 * i)));
}
- attrs_file.write(data);
+ file.write(data);
return true;
}
bool Tileset::loadTilesImage(QImage *importedImage) {
QImage image;
+ bool imported = false;
if (importedImage) {
image = *importedImage;
- m_hasUnsavedTilesImage = true;
+ imported = true;
} else if (QFile::exists(this->tilesImagePath)) {
// No image provided, load from file path.
image = QImage(this->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither);
- } else {
- // Use default image
- image = QImage(8, 8, QImage::Format_Indexed8);
+ }
+
+ if (image.isNull()) {
+ logWarn(QString("Failed to load tiles image for %1. Using default tiles image.").arg(this->name));
+ image = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_Indexed8);
+ image.fill(0);
+ }
+
+ // Validate image dimensions
+ if (image.width() % Tile::pixelWidth() || image.height() % Tile::pixelHeight()) {
+ logError(QString("%1 tiles image has invalid dimensions %2x%3. Dimensions must be a multiple of %4x%5.")
+ .arg(this->name)
+ .arg(image.width())
+ .arg(image.height())
+ .arg(Tile::pixelWidth())
+ .arg(Tile::pixelHeight()));
+ return false;
}
// Validate image contains 16 colors.
@@ -508,17 +557,31 @@ bool Tileset::loadTilesImage(QImage *importedImage) {
}
image.setColorTable(colorTable);
}
+ m_tilesImage = image;
- QList tiles;
- int w = 8;
- int h = 8;
- for (int y = 0; y < image.height(); y += h)
- for (int x = 0; x < image.width(); x += w) {
- QImage tile = image.copy(x, y, w, h);
- tiles.append(tile);
+ // Cut up the full tiles image into individual tile images.
+ m_tiles.clear();
+ for (int y = 0; y < image.height(); y += Tile::pixelHeight())
+ for (int x = 0; x < image.width(); x += Tile::pixelWidth()) {
+ m_tiles.append(image.copy(x, y, Tile::pixelWidth(), Tile::pixelHeight()));
}
- this->tilesImage = image;
- this->tiles = tiles;
+
+ if (m_tiles.length() > maxTiles()) {
+ logWarn(QString("%1 tile count of %2 exceeds limit of %3. Additional tiles will not be displayed.")
+ .arg(this->name)
+ .arg(m_tiles.length())
+ .arg(maxTiles()));
+
+ // Just resize m_tiles so that numTiles() reports the correct tile count.
+ // We'll leave m_tilesImage alone (it doesn't get displayed, and we don't want to delete the user's image data).
+ m_tiles = m_tiles.mid(0, maxTiles());
+ }
+
+ if (imported) {
+ // Only set this flag once we've successfully loaded the tiles image.
+ m_hasUnsavedTilesImage = true;
+ }
+
return true;
}
@@ -528,7 +591,7 @@ bool Tileset::saveTilesImage() {
if (!m_hasUnsavedTilesImage)
return true;
- if (!this->tilesImage.save(this->tilesImagePath, "PNG")) {
+ if (!m_tilesImage.save(this->tilesImagePath, "PNG")) {
logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath));
return false;
}
@@ -575,19 +638,23 @@ bool Tileset::savePalettes() {
bool Tileset::load() {
bool success = true;
+ if (!loadPalettes()) success = false;
+ if (!loadTilesImage()) success = false;
if (!loadMetatiles()) success = false;
if (!loadMetatileAttributes()) success = false;
- if (!loadTilesImage()) success = false;
- if (!loadPalettes()) success = false;
return success;
}
// Because metatile labels are global (and handled by the project) we don't save them here.
bool Tileset::save() {
bool success = true;
+ if (!savePalettes()) success = false;
+ if (!saveTilesImage()) success = false;
if (!saveMetatiles()) success = false;
if (!saveMetatileAttributes()) success = false;
- if (!saveTilesImage()) success = false;
- if (!savePalettes()) success = false;
return success;
}
+
+QString Tileset::stripPrefix(const QString &fullName) {
+ return QString(fullName).replace(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix), "");
+}
diff --git a/src/core/utility.cpp b/src/core/utility.cpp
index 0ecfd13e..ff06838f 100644
--- a/src/core/utility.cpp
+++ b/src/core/utility.cpp
@@ -14,7 +14,7 @@ void Util::numericalModeSort(QStringList &list) {
std::sort(list.begin(), list.end(), collator);
}
-int Util::roundUp(int numToRound, int multiple) {
+int Util::roundUpToMultiple(int numToRound, int multiple) {
if (multiple <= 0)
return numToRound;
diff --git a/src/editor.cpp b/src/editor.cpp
index a0635d38..5f1dfe0f 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -30,7 +30,7 @@ Editor::Editor(Ui::MainWindow* ui)
{
this->ui = ui;
this->settings = new Settings();
- this->cursorMapTileRect = new CursorTileRect(QSize(16,16), qRgb(255, 255, 255));
+ this->cursorMapTileRect = new CursorTileRect(Metatile::pixelSize(), qRgb(255, 255, 255));
this->map_ruler = new MapRuler(4);
connect(this->map_ruler, &MapRuler::statusChanged, this, &Editor::mapRulerStatusChanged);
@@ -1171,13 +1171,13 @@ bool Editor::isMouseInMap() const {
void Editor::setPlayerViewRect(const QRectF &rect) {
delete this->playerViewRect;
- this->playerViewRect = new MovableRect(rect, qRgb(255, 255, 255));
+ this->playerViewRect = new MovableRect(rect, Metatile::pixelSize(), qRgb(255, 255, 255));
updateCursorRectVisibility();
}
void Editor::setCursorRectPos(const QPoint &pos) {
- int x = qMax(0, qMin(pos.x(), this->layout ? this->layout->getWidth() - 1 : 0));
- int y = qMax(0, qMin(pos.y(), this->layout ? this->layout->getHeight() - 1 : 0));
+ int x = qBound(0, pos.x(), this->layout ? this->layout->getWidth() - 1 : 0);
+ int y = qBound(0, pos.y(), this->layout ? this->layout->getHeight() - 1 : 0);
if (this->playerViewRect)
this->playerViewRect->updateLocation(x, y);
@@ -1357,12 +1357,13 @@ bool Editor::setLayout(QString layoutId) {
map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight()));
connect(this->layout, &Layout::dimensionsChanged, map_ruler, &MapRuler::setMapDimensions);
- ui->comboBox_PrimaryTileset->blockSignals(true);
- ui->comboBox_SecondaryTileset->blockSignals(true);
+ QString prevPrimaryTileset = ui->comboBox_PrimaryTileset->currentText();
+ QString prevSecondaryTileset = ui->comboBox_SecondaryTileset->currentText();
+
+ const QSignalBlocker b_PrimaryTilest(ui->comboBox_PrimaryTileset);
+ const QSignalBlocker b_SecondaryTilest(ui->comboBox_SecondaryTileset);
ui->comboBox_PrimaryTileset->setTextItem(this->layout->tileset_primary_label);
ui->comboBox_SecondaryTileset->setTextItem(this->layout->tileset_secondary_label);
- ui->comboBox_PrimaryTileset->blockSignals(false);
- ui->comboBox_SecondaryTileset->blockSignals(false);
const QSignalBlocker b0(this->ui->comboBox_LayoutSelector);
int index = this->ui->comboBox_LayoutSelector->findText(layoutId);
@@ -1371,6 +1372,10 @@ bool Editor::setLayout(QString layoutId) {
if (this->layout->name != prevLayoutName)
Scripting::cb_LayoutOpened(this->layout->name);
+ if (this->layout->tileset_primary_label != prevPrimaryTileset)
+ Scripting::cb_TilesetUpdated(this->layout->tileset_primary_label);
+ if (this->layout->tileset_secondary_label != prevSecondaryTileset)
+ Scripting::cb_TilesetUpdated(this->layout->tileset_secondary_label);
return true;
}
@@ -1641,7 +1646,7 @@ void Editor::displayMetatileSelector() {
scene_metatiles = new QGraphicsScene;
if (!metatile_selector_item) {
- metatile_selector_item = new MetatileSelector(8, this->layout);
+ metatile_selector_item = new MetatileSelector(projectConfig.metatileSelectorWidth, this->layout);
connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionChanged,
this, &Editor::onHoveredMetatileSelectionChanged);
connect(metatile_selector_item, &MetatileSelector::hoveredMetatileSelectionCleared,
@@ -1651,13 +1656,6 @@ void Editor::displayMetatileSelector() {
metatile_selector_item->select(0);
} else {
metatile_selector_item->setLayout(this->layout);
- if (metatile_selector_item->primaryTileset
- && metatile_selector_item->primaryTileset != this->layout->tileset_primary)
- emit tilesetUpdated(this->layout->tileset_primary->name);
- if (metatile_selector_item->secondaryTileset
- && metatile_selector_item->secondaryTileset != this->layout->tileset_secondary)
- emit tilesetUpdated(this->layout->tileset_secondary->name);
- metatile_selector_item->setTilesets(this->layout->tileset_primary, this->layout->tileset_secondary);
}
scene_metatiles->addItem(metatile_selector_item);
@@ -1917,8 +1915,8 @@ void Editor::displayMapBorder() {
for (int y = -borderMargins.top(); y < this->layout->getHeight() + borderMargins.bottom(); y += this->layout->getBorderHeight())
for (int x = -borderMargins.left(); x < this->layout->getWidth() + borderMargins.right(); x += this->layout->getBorderWidth()) {
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
- item->setX(x * 16);
- item->setY(y * 16);
+ item->setX(x * Metatile::pixelWidth());
+ item->setY(y * Metatile::pixelHeight());
item->setZValue(ZValue::MapBorder);
scene->addItem(item);
borderItems.append(item);
@@ -1966,8 +1964,8 @@ void Editor::displayMapGrid() {
// elements of the scripting API, so they're painted manually in MapView::drawForeground.
this->mapGrid = new QGraphicsItemGroup();
- const int pixelMapWidth = this->layout->getWidth() * 16;
- const int pixelMapHeight = this->layout->getHeight() * 16;
+ const int pixelMapWidth = this->layout->pixelWidth();
+ const int pixelMapHeight = this->layout->pixelHeight();
// The grid can be moved with a user-specified x/y offset. The grid's dash patterns will only wrap in full pattern increments,
// so we draw an additional row/column outside the map that can be revealed as the offset changes.
@@ -2488,7 +2486,7 @@ void Editor::setCollisionGraphics() {
// Use the image sheet to create an icon for each collision/elevation combination.
// Any icons for combinations that aren't provided by the image sheet are also created now using default graphics.
- const int w = 16, h = 16;
+ const int w = Metatile::pixelWidth(), h = Metatile::pixelHeight();
imgSheet = imgSheet.scaled(w * imgColumns, h * imgRows);
for (int collision = 0; collision <= Block::getMaxCollision(); collision++) {
// If (collision >= imgColumns) here, it's a valid collision value, but it is not represented with an icon on the image sheet.
diff --git a/src/main.cpp b/src/main.cpp
index d85c1b38..dfd3bff2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -6,6 +6,8 @@
int main(int argc, char *argv[])
{
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round);
+ QCoreApplication::setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true);
+
QApplication a(argc, argv);
a.setStyle("fusion");
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 7a316459..c191ea07 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -367,7 +367,6 @@ void MainWindow::initEditor() {
connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged);
connect(this->editor, &Editor::wildMonTableEdited, [this] { markMapEdited(this->editor->map); });
connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged);
- connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated);
connect(this->editor, &Editor::editActionSet, this, &MainWindow::setEditActionUi);
connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent);
connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents);
@@ -1409,7 +1408,7 @@ bool MainWindow::setProjectUI() {
ui->newEventToolButton->setEventTypeVisible(Event::Type::SecretBase, projectConfig.eventSecretBaseEnabled);
ui->newEventToolButton->setEventTypeVisible(Event::Type::CloneObject, projectConfig.eventCloneObjectEnabled);
- this->editor->setPlayerViewRect(QRectF(0, 0, 16, 16).marginsAdded(projectConfig.playerViewDistance));
+ this->editor->setPlayerViewRect(QRectF(QPoint(0,0), Metatile::pixelSize()).marginsAdded(projectConfig.playerViewDistance));
editor->setCollisionGraphics();
ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation());
@@ -1810,7 +1809,7 @@ void MainWindow::scrollMetatileSelectorToSelection() {
QPoint pos = editor->metatile_selector_item->getMetatileIdCoordsOnWidget(selection.metatileItems.first().metatileId);
QPoint size = editor->metatile_selector_item->getSelectionDimensions();
- pos += QPoint(size.x() - 1, size.y() - 1) * 16 / 2; // We want to focus on the center of the whole selection
+ pos += QPoint((size.x() - 1) * Metatile::pixelWidth(), (size.y() - 1) * Metatile::pixelHeight()) / 2; // We want to focus on the center of the whole selection
pos *= getMetatilesZoomScale();
auto viewport = ui->scrollArea_MetatileSelector->viewport();
@@ -1821,7 +1820,9 @@ void MainWindow::currentMetatilesSelectionChanged() {
redrawMetatileSelection();
if (this->tilesetEditor) {
MetatileSelection selection = editor->metatile_selector_item->getMetatileSelection();
- this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId);
+ if (!selection.metatileItems.isEmpty()) {
+ this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId);
+ }
}
// Don't scroll to internal selections here, it will disrupt the user while they make their selection.
@@ -2845,7 +2846,9 @@ void MainWindow::on_actionTileset_Editor_triggered()
openSubWindow(this->tilesetEditor);
MetatileSelection selection = this->editor->metatile_selector_item->getMetatileSelection();
- this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId);
+ if (!selection.metatileItems.isEmpty()) {
+ this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId);
+ }
}
void MainWindow::initTilesetEditor() {
diff --git a/src/project.cpp b/src/project.cpp
index 7188b5ef..b76821f3 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1558,9 +1558,8 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
tileset->loadTilesImage(&tilesImage);
// Create default metatiles
- const int numMetatiles = tileset->is_secondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary();
const int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
- for (int i = 0; i < numMetatiles; ++i) {
+ for (int i = 0; i < tileset->maxMetatiles(); ++i) {
auto metatile = new Metatile();
for(int j = 0; j < tilesPerMetatile; ++j){
Tile tile = Tile();
@@ -1618,7 +1617,7 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
metatilesFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles));
}
ignoreWatchedFilesTemporarily({headersFilepath, graphicsFilepath, metatilesFilepath});
- name.remove(0, prefix.length()); // Strip prefix from name to get base name for use in other symbols.
+ name = Tileset::stripPrefix(name);
tileset->appendToHeaders(headersFilepath, name, this->usingAsmTilesets);
tileset->appendToGraphics(graphicsFilepath, name, this->usingAsmTilesets);
tileset->appendToMetatiles(metatilesFilepath, name, this->usingAsmTilesets);
@@ -2400,12 +2399,11 @@ bool Project::readFieldmapProperties() {
// We can determine whether triple-layer metatiles are in-use by reading this constant.
// If the constant is missing (or is using a value other than 8 or 12) the user must tell
// us whether they're using triple-layer metatiles under Project Settings.
- static const int numTilesPerLayer = 4;
int numTilesPerMetatile = it.value();
- if (numTilesPerMetatile == 2 * numTilesPerLayer) {
+ if (numTilesPerMetatile == 2 * Metatile::tilesPerLayer()) {
projectConfig.tripleLayerMetatilesEnabled = false;
this->disabledSettingsNames.insert(numTilesPerMetatileName);
- } else if (numTilesPerMetatile == 3 * numTilesPerLayer) {
+ } else if (numTilesPerMetatile == 3 * Metatile::tilesPerLayer()) {
projectConfig.tripleLayerMetatilesEnabled = true;
this->disabledSettingsNames.insert(numTilesPerMetatileName);
}
@@ -2462,6 +2460,7 @@ bool Project::readFieldmapMasks() {
projectConfig.blockCollisionMask = blockMask;
if (readBlockMask(elevationMaskName, &blockMask))
projectConfig.blockElevationMask = blockMask;
+ Block::setLayout();
// Read RSE metatile attribute masks
auto it = defines.find(behaviorMaskName);
@@ -3444,14 +3443,25 @@ QString Project::getEmptySpeciesName() {
return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty);
}
-// Get the distance in metatiles (rounded up) that the player is able to see in each direction in-game.
+// Get the distance in pixels that the player is able to see in each direction in-game,
+// rounded up to a multiple of a metatile's pixel size.
+QMargins Project::getPixelViewDistance() {
+ QMargins viewDistance = projectConfig.playerViewDistance;
+ viewDistance.setTop(Util::roundUpToMultiple(viewDistance.top(), Metatile::pixelHeight()));
+ viewDistance.setBottom(Util::roundUpToMultiple(viewDistance.bottom(), Metatile::pixelHeight()));
+ viewDistance.setLeft(Util::roundUpToMultiple(viewDistance.left(), Metatile::pixelWidth()));
+ viewDistance.setRight(Util::roundUpToMultiple(viewDistance.right(), Metatile::pixelWidth()));
+ return viewDistance;
+}
+
+// Get the distance in metatiles that the player is able to see in each direction in-game.
// For the default view distance (i.e. assuming the player is centered in a 240x160 pixel GBA screen) this is 7x5 metatiles.
QMargins Project::getMetatileViewDistance() {
- QMargins viewDistance = projectConfig.playerViewDistance;
- viewDistance.setTop(qCeil(viewDistance.top() / 16.0));
- viewDistance.setBottom(qCeil(viewDistance.bottom() / 16.0));
- viewDistance.setLeft(qCeil(viewDistance.left() / 16.0));
- viewDistance.setRight(qCeil(viewDistance.right() / 16.0));
+ QMargins viewDistance = getPixelViewDistance();
+ viewDistance.setTop(viewDistance.top() / Metatile::pixelHeight());
+ viewDistance.setBottom(viewDistance.bottom() / Metatile::pixelHeight());
+ viewDistance.setLeft(viewDistance.left() / Metatile::pixelWidth());
+ viewDistance.setRight(viewDistance.right() / Metatile::pixelWidth());
return viewDistance;
}
@@ -3481,15 +3491,14 @@ void Project::applyParsedLimits() {
projectConfig.metatileEncounterTypeMask &= maxMask;
projectConfig.metatileLayerTypeMask &= maxMask;
- Block::setLayout();
Metatile::setLayout(this);
- Project::num_metatiles_primary = qMin(qMax(Project::num_metatiles_primary, 1), Block::getMaxMetatileId() + 1);
+ Project::num_metatiles_primary = qBound(1, Project::num_metatiles_primary, Block::getMaxMetatileId() + 1);
projectConfig.defaultMetatileId = qMin(projectConfig.defaultMetatileId, Block::getMaxMetatileId());
projectConfig.defaultElevation = qMin(projectConfig.defaultElevation, Block::getMaxElevation());
projectConfig.defaultCollision = qMin(projectConfig.defaultCollision, Block::getMaxCollision());
- projectConfig.collisionSheetSize.setHeight(qMin(qMax(projectConfig.collisionSheetSize.height(), 1), Block::getMaxElevation() + 1));
- projectConfig.collisionSheetSize.setWidth(qMin(qMax(projectConfig.collisionSheetSize.width(), 1), Block::getMaxCollision() + 1));
+ projectConfig.collisionSheetSize.setHeight(qBound(1, projectConfig.collisionSheetSize.height(), Block::getMaxElevation() + 1));
+ projectConfig.collisionSheetSize.setWidth(qBound(1, projectConfig.collisionSheetSize.width(), Block::getMaxCollision() + 1));
}
bool Project::hasUnsavedChanges() {
diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp
index 08bfc9c5..a1730791 100644
--- a/src/scriptapi/apimap.cpp
+++ b/src/scriptapi/apimap.cpp
@@ -4,6 +4,7 @@
#include "editcommands.h"
#include "config.h"
#include "imageproviders.h"
+#include "scriptutility.h"
// TODO: "tilesetNeedsRedraw" is used when redrawing the map after
// changing a metatile's tiles via script. It is unnecessarily
@@ -26,7 +27,7 @@ void MainWindow::tryRedrawMapArea(bool forceRedraw) {
if (this->tilesetEditor)
this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label);
if (this->editor->metatile_selector_item)
- this->editor->metatile_selector_item->draw();
+ this->editor->metatile_selector_item->refresh();
if (this->editor->selected_border_metatiles_item)
this->editor->selected_border_metatiles_item->draw();
if (this->editor->current_metatile_selection_item)
@@ -332,7 +333,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) {
if (this->tilesetEditor) {
this->tilesetEditor->updateTilesets(this->editor->layout->tileset_primary_label, this->editor->layout->tileset_secondary_label);
}
- this->editor->metatile_selector_item->draw();
+ this->editor->metatile_selector_item->refresh();
this->editor->selected_border_metatiles_item->draw();
this->editor->map_item->draw(true);
this->editor->updateMapBorder();
@@ -444,7 +445,7 @@ QJSValue MainWindow::getSecondaryTilesetPalettes() {
}
void MainWindow::refreshAfterPalettePreviewChange() {
- this->editor->metatile_selector_item->draw();
+ this->editor->metatile_selector_item->refresh();
this->editor->selected_border_metatiles_item->draw();
this->editor->map_item->draw(true);
this->editor->updateMapBorder();
@@ -545,13 +546,13 @@ int MainWindow::getNumSecondaryTilesetMetatiles() {
int MainWindow::getNumPrimaryTilesetTiles() {
if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary)
return 0;
- return this->editor->layout->tileset_primary->tiles.length();
+ return this->editor->layout->tileset_primary->numTiles();
}
int MainWindow::getNumSecondaryTilesetTiles() {
if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary)
return 0;
- return this->editor->layout->tileset_secondary->tiles.length();
+ return this->editor->layout->tileset_secondary->numTiles();
}
QString MainWindow::getPrimaryTileset() {
@@ -796,17 +797,46 @@ void MainWindow::setMetatileTile(int metatileId, int tileIndex, QJSValue tileObj
QJSValue MainWindow::getTilePixels(int tileId) {
if (tileId < 0 || !this->editor || !this->editor->layout)
return QJSValue();
+
+ const int numPixels = Tile::pixelWidth() * Tile::pixelHeight();
QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary);
- if (tileImage.isNull() || tileImage.sizeInBytes() < 64)
+ if (tileImage.isNull() || tileImage.sizeInBytes() < numPixels)
return QJSValue();
+
const uchar * pixels = tileImage.constBits();
- QJSValue pixelArray = Scripting::getEngine()->newArray(64);
- for (int i = 0; i < 64; i++) {
+ QJSValue pixelArray = Scripting::getEngine()->newArray(numPixels);
+ for (int i = 0; i < numPixels; i++) {
pixelArray.setProperty(i, pixels[i]);
}
return pixelArray;
}
+QList MainWindow::getMetatileLayerOrder() const {
+ if (!this->editor || !this->editor->layout)
+ return QList();
+ return this->editor->layout->metatileLayerOrder();
+}
+
+void MainWindow::setMetatileLayerOrder(const QList &order) {
+ if (!this->editor || !this->editor->layout || !ScriptUtility::validateMetatileLayerOrder(order))
+ return;
+ this->editor->layout->setMetatileLayerOrder(order);
+ this->refreshAfterPalettePreviewChange();
+}
+
+QList MainWindow::getMetatileLayerOpacity() const {
+ if (!this->editor || !this->editor->layout)
+ return QList();
+ return this->editor->layout->metatileLayerOpacity();
+}
+
+void MainWindow::setMetatileLayerOpacity(const QList &opacities) {
+ if (!this->editor || !this->editor->layout)
+ return;
+ this->editor->layout->setMetatileLayerOpacity(opacities);
+ this->refreshAfterPalettePreviewChange();
+}
+
//=====================
// Editing map header
//=====================
diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp
index 4e183659..ffe7743a 100644
--- a/src/scriptapi/apioverlay.cpp
+++ b/src/scriptapi/apioverlay.cpp
@@ -294,11 +294,7 @@ void MapView::addTileImage(int x, int y, const Tile &tile, bool setTransparency,
void MapView::addMetatileImage(int x, int y, int metatileId, bool setTransparency, int layer) {
if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary || !this->editor->layout->tileset_secondary)
return;
- QImage image = getMetatileImage(static_cast(metatileId),
- this->editor->layout->tileset_primary,
- this->editor->layout->tileset_secondary,
- this->editor->layout->metatileLayerOrder,
- this->editor->layout->metatileLayerOpacity);
+ QImage image = getMetatileImage(static_cast(metatileId), this->editor->layout);
if (setTransparency)
image.setColor(0, qRgba(0, 0, 0, 0));
if (this->getOverlay(layer)->addImage(x, y, image))
diff --git a/src/scriptapi/apiutility.cpp b/src/scriptapi/apiutility.cpp
index dfc65617..5058e2de 100644
--- a/src/scriptapi/apiutility.cpp
+++ b/src/scriptapi/apiutility.cpp
@@ -201,46 +201,36 @@ QList ScriptUtility::getCustomScripts() {
}
QList ScriptUtility::getMetatileLayerOrder() {
- if (!window || !window->editor || !window->editor->layout)
- return QList();
- return window->editor->layout->metatileLayerOrder;
+ return Layout::globalMetatileLayerOrder();
}
-void ScriptUtility::setMetatileLayerOrder(QList order) {
- if (!window || !window->editor || !window->editor->layout)
- return;
-
+bool ScriptUtility::validateMetatileLayerOrder(const QList &order) {
const int numLayers = 3;
- int size = order.size();
- if (size < numLayers) {
- logError(QString("Metatile layer order has insufficient elements (%1), needs at least %2.").arg(size).arg(numLayers));
- return;
- }
- bool invalid = false;
- for (int i = 0; i < numLayers; i++) {
+ bool valid = true;
+ for (int i = 0; i < qMin(order.length(), numLayers); i++) {
int layer = order.at(i);
if (layer < 0 || layer >= numLayers) {
logError(QString("'%1' is not a valid metatile layer order value, must be in range 0-%2.").arg(layer).arg(numLayers - 1));
- invalid = true;
+ valid = false;
}
}
- if (invalid) return;
+ return valid;
+}
- window->editor->layout->metatileLayerOrder = order;
- window->refreshAfterPalettePreviewChange();
+void ScriptUtility::setMetatileLayerOrder(const QList &order) {
+ if (!validateMetatileLayerOrder(order))
+ return;
+ Layout::setGlobalMetatileLayerOrder(order);
+ if (window) window->refreshAfterPalettePreviewChange();
}
QList ScriptUtility::getMetatileLayerOpacity() {
- if (!window || !window->editor || !window->editor->layout)
- return QList();
- return window->editor->layout->metatileLayerOpacity;
+ return Layout::globalMetatileLayerOpacity();
}
-void ScriptUtility::setMetatileLayerOpacity(QList order) {
- if (!window || !window->editor || !window->editor->layout)
- return;
- window->editor->layout->metatileLayerOpacity = order;
- window->refreshAfterPalettePreviewChange();
+void ScriptUtility::setMetatileLayerOpacity(const QList &opacities) {
+ Layout::setGlobalMetatileLayerOpacity(opacities);
+ if (window) window->refreshAfterPalettePreviewChange();
}
QList ScriptUtility::getMapNames() {
diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp
index 93823de2..148a75fe 100644
--- a/src/scriptapi/scripting.cpp
+++ b/src/scriptapi/scripting.cpp
@@ -99,15 +99,12 @@ void Scripting::populateGlobalObject(MainWindow *mainWindow) {
constants.setProperty("version", version);
// Get basic tileset information
- int numTilesPrimary = Project::getNumTilesPrimary();
- int numMetatilesPrimary = Project::getNumMetatilesPrimary();
- int numPalettesPrimary = Project::getNumPalettesPrimary();
- constants.setProperty("max_primary_tiles", numTilesPrimary);
- constants.setProperty("max_secondary_tiles", Project::getNumTilesTotal() - numTilesPrimary);
- constants.setProperty("max_primary_metatiles", numMetatilesPrimary);
- constants.setProperty("max_secondary_metatiles", Project::getNumMetatilesTotal() - numMetatilesPrimary);
- constants.setProperty("num_primary_palettes", numPalettesPrimary);
- constants.setProperty("num_secondary_palettes", Project::getNumPalettesTotal() - numPalettesPrimary);
+ constants.setProperty("max_primary_tiles", Project::getNumTilesPrimary());
+ constants.setProperty("max_secondary_tiles", Project::getNumTilesSecondary());
+ constants.setProperty("max_primary_metatiles", Project::getNumMetatilesPrimary());
+ constants.setProperty("max_secondary_metatiles", Project::getNumMetatilesSecondary());
+ constants.setProperty("num_primary_palettes", Project::getNumPalettesPrimary());
+ constants.setProperty("num_secondary_palettes", Project::getNumPalettesSecondary());
constants.setProperty("layers_per_metatile", projectConfig.getNumLayersInMetatile());
constants.setProperty("tiles_per_metatile", projectConfig.getNumTilesInMetatile());
@@ -301,7 +298,7 @@ void Scripting::cb_MapShifted(int xDelta, int yDelta) {
instance->invokeCallback(OnMapShifted, args);
}
-void Scripting::cb_TilesetUpdated(QString tilesetName) {
+void Scripting::cb_TilesetUpdated(const QString &tilesetName) {
if (!instance) return;
QJSValueList args {
diff --git a/src/ui/bordermetatilespixmapitem.cpp b/src/ui/bordermetatilespixmapitem.cpp
index e793e466..ef3c33db 100644
--- a/src/ui/bordermetatilespixmapitem.cpp
+++ b/src/ui/bordermetatilespixmapitem.cpp
@@ -39,21 +39,15 @@ void BorderMetatilesPixmapItem::draw() {
int width = layout->getBorderWidth();
int height = layout->getBorderHeight();
- QImage image(16 * width, 16 * height, QImage::Format_RGBA8888);
+ QImage image(width * Metatile::pixelWidth(), height * Metatile::pixelHeight(), QImage::Format_RGBA8888);
QPainter painter(&image);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
- int x = i * 16;
- int y = j * 16;
- QImage metatile_image = getMetatileImage(
- layout->getBorderMetatileId(i, j),
- layout->tileset_primary,
- layout->tileset_secondary,
- layout->metatileLayerOrder,
- layout->metatileLayerOpacity);
- QPoint metatile_origin = QPoint(x, y);
- painter.drawImage(metatile_origin, metatile_image);
+ QImage metatile_image = getMetatileImage(layout->getBorderMetatileId(i, j), layout);
+ int x = i * Metatile::pixelWidth();
+ int y = j * Metatile::pixelHeight();
+ painter.drawImage(x, y, metatile_image);
}
}
diff --git a/src/ui/checkeredbgscene.cpp b/src/ui/checkeredbgscene.cpp
new file mode 100644
index 00000000..9a32dff7
--- /dev/null
+++ b/src/ui/checkeredbgscene.cpp
@@ -0,0 +1,32 @@
+#include "checkeredbgscene.h"
+
+#include
+
+void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) {
+ QRect r = rect.toRect();
+ int w = this->gridSize.width();
+ int h = this->gridSize.height();
+ int xMin = r.left() - (r.left() % w) - w;
+ int yMin = r.top() - (r.top() % h) - h;
+ int xMax = r.right() - (r.right() % w) + w;
+ int yMax = r.bottom() - (r.bottom() % h) + h;
+
+ // draw grid from top to bottom of scene
+ QColor paintColor;
+ for (int x = xMin; x <= xMax; x += w) {
+ for (int y = yMin; y <= yMax; y += h) {
+ if ((x/w + y/h) % 2) {
+ if (this->validRect.contains(x, y))
+ paintColor = QColor(132, 217, 165); // green light color
+ else
+ paintColor = 0xbcbcbc; // normal light color
+ } else {
+ if (this->validRect.contains(x, y))
+ paintColor = QColor(76, 178, 121); // green dark color
+ else
+ paintColor = 0x969696; // normal dark color
+ }
+ painter->fillRect(QRect(x, y, w, h), paintColor);
+ }
+ }
+}
diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp
index b4fbfa12..db05ffab 100644
--- a/src/ui/connectionpixmapitem.cpp
+++ b/src/ui/connectionpixmapitem.cpp
@@ -100,9 +100,9 @@ void ConnectionPixmapItem::updatePos() {
void ConnectionPixmapItem::updateOrigin() {
if (this->connection->isVertical()) {
this->originX = 0;
- this->originY = this->connection->relativePos(true).y() * this->mHeight;
+ this->originY = this->connection->relativePixelPos(true).y();
} else if (this->connection->isHorizontal()) {
- this->originX = this->connection->relativePos(true).x() * this->mWidth;
+ this->originX = this->connection->relativePixelPos(true).x();
this->originY = 0;
}
updatePos();
diff --git a/src/ui/currentselectedmetatilespixmapitem.cpp b/src/ui/currentselectedmetatilespixmapitem.cpp
index e8b16f49..33425131 100644
--- a/src/ui/currentselectedmetatilespixmapitem.cpp
+++ b/src/ui/currentselectedmetatilespixmapitem.cpp
@@ -3,26 +3,21 @@
#include
QPixmap drawMetatileSelection(MetatileSelection selection, Layout *layout) {
- int width = selection.dimensions.x() * 16;
- int height = selection.dimensions.y() * 16;
+ int width = selection.dimensions.x() * Metatile::pixelWidth();
+ int height = selection.dimensions.y() * Metatile::pixelHeight();
QImage image(width, height, QImage::Format_RGBA8888);
image.fill(QColor(0, 0, 0, 0));
QPainter painter(&image);
for (int i = 0; i < selection.dimensions.x(); i++) {
for (int j = 0; j < selection.dimensions.y(); j++) {
- int x = i * 16;
- int y = j * 16;
+ int x = i * Metatile::pixelWidth();
+ int y = j * Metatile::pixelHeight();
QPoint metatile_origin = QPoint(x, y);
int index = j * selection.dimensions.x() + i;
MetatileSelectionItem item = selection.metatileItems.at(index);
if (item.enabled) {
- QImage metatile_image = getMetatileImage(
- item.metatileId,
- layout->tileset_primary,
- layout->tileset_secondary,
- layout->metatileLayerOrder,
- layout->metatileLayerOpacity);
+ QImage metatile_image = getMetatileImage(item.metatileId, layout);
painter.drawImage(metatile_origin, metatile_image);
}
}
diff --git a/src/ui/cursortilerect.cpp b/src/ui/cursortilerect.cpp
index ef1de285..e62bcdc8 100644
--- a/src/ui/cursortilerect.cpp
+++ b/src/ui/cursortilerect.cpp
@@ -65,6 +65,6 @@ void CursorTileRect::updateLocation(int coordX, int coordY) {
}
}
- this->setX(coordX * 16);
- this->setY(coordY * 16);
+ this->setX(coordX * m_tileSize.width());
+ this->setY(coordY * m_tileSize.height());
}
diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp
index c8fc4495..667e0aab 100644
--- a/src/ui/graphicsview.cpp
+++ b/src/ui/graphicsview.cpp
@@ -33,7 +33,7 @@ void MapView::drawForeground(QPainter *painter, const QRectF&) {
painter->save();
if (editor->layout) {
// We're clipping here to hide parts of the grid that are outside the map.
- const QRectF mapRect(-0.5, -0.5, editor->layout->getWidth() * 16 + 1.5, editor->layout->getHeight() * 16 + 1.5);
+ const QRectF mapRect(-0.5, -0.5, editor->layout->pixelWidth() + 1.5, editor->layout->pixelHeight() + 1.5);
painter->setClipping(true);
painter->setClipRect(mapRect);
}
diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp
index 3f7b8fb8..ce233066 100644
--- a/src/ui/imageproviders.cpp
+++ b/src/ui/imageproviders.cpp
@@ -1,6 +1,5 @@
#include "config.h"
#include "imageproviders.h"
-#include "log.h"
#include "editor.h"
#include
@@ -9,10 +8,29 @@ QImage getCollisionMetatileImage(Block block) {
}
QImage getCollisionMetatileImage(int collision, int elevation) {
- const QImage * image = Editor::collisionIcons.at(collision).at(elevation);
+ const QImage * image = Editor::collisionIcons.value(collision).value(elevation);
return image ? *image : QImage();
}
+QImage getMetatileImage(uint16_t metatileId, Layout *layout, bool useTruePalettes) {
+ Metatile* metatile = Tileset::getMetatile(metatileId,
+ layout ? layout->tileset_primary : nullptr,
+ layout ? layout->tileset_secondary : nullptr);
+ return getMetatileImage(metatile, layout, useTruePalettes);
+}
+
+QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes) {
+ if (!layout) {
+ return getMetatileImage(metatile, nullptr, nullptr, {}, {}, useTruePalettes);
+ }
+ return getMetatileImage(metatile,
+ layout->tileset_primary,
+ layout->tileset_secondary,
+ layout->metatileLayerOrder(),
+ layout->metatileLayerOpacity(),
+ useTruePalettes);
+}
+
QImage getMetatileImage(
uint16_t metatileId,
Tileset *primaryTileset,
@@ -21,13 +39,18 @@ QImage getMetatileImage(
const QList &layerOpacity,
bool useTruePalettes)
{
- Metatile* metatile = Tileset::getMetatile(metatileId, primaryTileset, secondaryTileset);
- if (!metatile) {
- QImage metatile_image(16, 16, QImage::Format_RGBA8888);
- metatile_image.fill(Qt::magenta);
- return metatile_image;
- }
- return getMetatileImage(metatile, primaryTileset, secondaryTileset, layerOrder, layerOpacity, useTruePalettes);
+ return getMetatileImage(Tileset::getMetatile(metatileId, primaryTileset, secondaryTileset),
+ primaryTileset,
+ secondaryTileset,
+ layerOrder,
+ layerOpacity,
+ useTruePalettes);
+}
+
+// The color to use when we want to show some portion of the image request was invalid.
+// Normally this is Qt::magenta, but we'll use Qt::transparent if we think the image allows it.
+QColor getInvalidImageColor() {
+ return (projectConfig.transparencyColor == QColor(Qt::transparent)) ? QColor(Qt::transparent) : QColor(Qt::magenta);
}
QImage getMetatileImage(
@@ -38,10 +61,10 @@ QImage getMetatileImage(
const QList &layerOpacity,
bool useTruePalettes)
{
- QImage metatile_image(16, 16, QImage::Format_RGBA8888);
+ QImage metatileImage(Metatile::pixelSize(), QImage::Format_RGBA8888);
if (!metatile) {
- metatile_image.fill(Qt::magenta);
- return metatile_image;
+ metatileImage.fill(getInvalidImageColor());
+ return metatileImage;
}
QList> palettes = Tileset::getBlockPalettes(primaryTileset, secondaryTileset, useTruePalettes);
@@ -50,22 +73,20 @@ QImage getMetatileImage(
// tile pixels line up across layers we will still have something to render.
// The GBA renders transparent pixels using palette 0 color 0. We have this color,
// but all 3 games actually overwrite it with black when loading the tileset palettes,
- // so we have a setting to choose between these two behaviors.
- metatile_image.fill(projectConfig.setTransparentPixelsBlack ? QColor("black") : QColor(palettes.value(0).value(0)));
+ // so we have a setting to specify an override transparency color.
+ metatileImage.fill(projectConfig.transparencyColor.isValid() ? projectConfig.transparencyColor : QColor(palettes.value(0).value(0)));
+
+ QPainter painter(&metatileImage);
- QPainter metatile_painter(&metatile_image);
- const int numLayers = 3; // When rendering, metatiles always have 3 layers
uint32_t layerType = metatile->layerType();
- for (int layer = 0; layer < numLayers; layer++)
- for (int y = 0; y < 2; y++)
- for (int x = 0; x < 2; x++) {
- int l = layerOrder.size() >= numLayers ? layerOrder[layer] : layer;
-
+ for (const auto &layer : layerOrder)
+ for (int y = 0; y < Metatile::tileHeight(); y++)
+ for (int x = 0; x < Metatile::tileWidth(); x++) {
// Get the tile to render next
Tile tile;
- int tileOffset = (y * 2) + x;
+ int tileOffset = (y * Metatile::tileWidth()) + x;
if (projectConfig.tripleLayerMetatilesEnabled) {
- tile = metatile->tiles.value(tileOffset + (l * 4));
+ tile = metatile->tiles.value(tileOffset + (layer * Metatile::tilesPerLayer()));
} else {
// "Vanilla" metatiles only have 8 tiles, but render 12.
// The remaining 4 tiles are rendered using user-specified tiles depending on layer type.
@@ -73,88 +94,71 @@ QImage getMetatileImage(
{
default:
case Metatile::LayerType::Normal:
- if (l == 0)
+ if (layer == 0)
tile = Tile(projectConfig.unusedTileNormal);
else // Tiles are on layers 1 and 2
- tile = metatile->tiles.value(tileOffset + ((l - 1) * 4));
+ tile = metatile->tiles.value(tileOffset + ((layer - 1) * Metatile::tilesPerLayer()));
break;
case Metatile::LayerType::Covered:
- if (l == 2)
+ if (layer == 2)
tile = Tile(projectConfig.unusedTileCovered);
else // Tiles are on layers 0 and 1
- tile = metatile->tiles.value(tileOffset + (l * 4));
+ tile = metatile->tiles.value(tileOffset + (layer * Metatile::tilesPerLayer()));
break;
case Metatile::LayerType::Split:
- if (l == 1)
+ if (layer == 1)
tile = Tile(projectConfig.unusedTileSplit);
else // Tiles are on layers 0 and 2
- tile = metatile->tiles.value(tileOffset + ((l == 0 ? 0 : 1) * 4));
+ tile = metatile->tiles.value(tileOffset + ((layer == 0 ? 0 : 1) * Metatile::tilesPerLayer()));
break;
}
}
- QImage tile_image = getTileImage(tile.tileId, primaryTileset, secondaryTileset);
- if (tile_image.isNull()) {
- // Some metatiles specify tiles that are outside the valid range.
- // The way the GBA will render these depends on what's in memory (which Porymap can't know)
- // so we treat them as if they were transparent.
- continue;
- }
+ QImage tileImage = getColoredTileImage(tile.tileId, primaryTileset, secondaryTileset, palettes.value(tile.palette));
- // Colorize the metatile tiles with its palette.
- if (tile.palette < palettes.length()) {
- const QList palette = palettes.value(tile.palette);
- for (int j = 0; j < palette.length(); j++) {
- tile_image.setColor(j, palette.value(j));
- }
- } else {
- logWarn(QString("Tile '%1' is referring to invalid palette number: '%2'").arg(tile.tileId).arg(tile.palette));
- }
-
- QPoint origin = QPoint(x*8, y*8);
- float opacity = layerOpacity.size() >= numLayers ? layerOpacity[l] : 1.0;
+ float opacity = layerOpacity.value(layer, 1.0);
if (opacity < 1.0) {
int alpha = 255 * opacity;
- for (int c = 0; c < tile_image.colorCount(); c++) {
- QColor color(tile_image.color(c));
+ for (int c = 0; c < tileImage.colorCount(); c++) {
+ QColor color(tileImage.color(c));
color.setAlpha(alpha);
- tile_image.setColor(c, color.rgba());
+ tileImage.setColor(c, color.rgba());
}
}
// Color 0 is displayed as transparent.
- QColor color(tile_image.color(0));
- color.setAlpha(0);
- tile_image.setColor(0, color.rgba());
- tile.flip(&tile_image);
- metatile_painter.drawImage(origin, tile_image);
- }
- metatile_painter.end();
+ if (tileImage.colorCount()) {
+ QColor color(tileImage.color(0));
+ color.setAlpha(0);
+ tileImage.setColor(0, color.rgba());
+ }
- return metatile_image;
+ tile.flip(&tileImage);
+ painter.drawImage(x * Tile::pixelWidth(), y * Tile::pixelHeight(), tileImage);
+ }
+ painter.end();
+
+ return metatileImage;
}
QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset);
- int index = Tile::getIndexInTileset(tileId);
- if (!tileset) {
- return QImage();
- }
- return tileset->tiles.value(index, QImage());
+ return tileset ? tileset->tileImage(tileId) : QImage();
}
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList &palette) {
QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset);
if (tileImage.isNull()) {
- tileImage = QImage(8, 8, QImage::Format_RGBA8888);
- QPainter painter(&tileImage);
- painter.fillRect(0, 0, 8, 8, palette.at(0));
+ // Some tiles specify tile IDs or palette IDs that are outside the valid range.
+ // The way the GBA will render these depends on what's in memory (which Porymap can't know)
+ // so we render them using the invalid color
+ tileImage = QImage(Tile::pixelSize(), QImage::Format_RGBA8888);
+ tileImage.fill(getInvalidImageColor());
} else {
for (int i = 0; i < 16; i++) {
- tileImage.setColor(i, palette.at(i));
+ tileImage.setColor(i, palette.value(i, getInvalidImageColor().rgb()));
}
}
-
return tileImage;
}
@@ -175,3 +179,94 @@ void flattenTo4bppImage(QImage * image) {
for (int i = 0; i < image->sizeInBytes(); i++, pixel++)
*pixel %= 16;
}
+
+// Constructs a grid image of the metatiles in the specified ID range.
+QImage getMetatileSheetImage(Tileset *primaryTileset,
+ Tileset *secondaryTileset,
+ uint16_t metatileIdStart,
+ uint16_t metatileIdEnd,
+ int numMetatilesWide,
+ const QList &layerOrder,
+ const QList &layerOpacity,
+ const QSize &metatileSize,
+ bool useTruePalettes)
+{
+ if (metatileIdEnd < metatileIdStart || numMetatilesWide <= 0)
+ return QImage();
+
+ int numMetatilesToDraw = metatileIdEnd - metatileIdStart + 1;
+
+ // Round up image height for incomplete last row.
+ int numMetatilesTall = Util::roundUpToMultiple(numMetatilesToDraw, numMetatilesWide) / numMetatilesWide;
+
+ QImage image(numMetatilesWide * metatileSize.width(), numMetatilesTall * metatileSize.height(), QImage::Format_RGBA8888);
+ image.fill(getInvalidImageColor());
+
+ QPainter painter(&image);
+ for (int i = 0; i < numMetatilesToDraw; i++) {
+ uint16_t metatileId = i + metatileIdStart;
+ QImage metatileImage = getMetatileImage(metatileId, primaryTileset, secondaryTileset, layerOrder, layerOpacity, useTruePalettes)
+ .scaled(metatileSize);
+
+ int x = (i % numMetatilesWide) * metatileSize.width();
+ int y = (i / numMetatilesWide) * metatileSize.height();
+ painter.drawImage(x, y, metatileImage);
+ }
+ painter.end();
+ return image;
+}
+
+// Constructs a grid image of the metatiles in the primary and secondary tileset,
+// rounding as necessary to keep the two tilesets on separate rows.
+// The unused metatiles (if any) between the primary and secondary tilesets are skipped.
+QImage getMetatileSheetImage(Tileset *primaryTileset,
+ Tileset *secondaryTileset,
+ int numMetatilesWide,
+ const QList &layerOrder,
+ const QList &layerOpacity,
+ const QSize &metatileSize,
+ bool useTruePalettes)
+{
+ auto createSheetImage = [=](uint16_t start, Tileset *tileset) {
+ uint16_t end = start;
+ if (tileset) {
+ if (tileset->numMetatiles() == 0)
+ return QImage();
+ end += tileset->numMetatiles() - 1;
+ }
+ return getMetatileSheetImage(primaryTileset,
+ secondaryTileset,
+ start,
+ end,
+ numMetatilesWide,
+ layerOrder,
+ layerOpacity,
+ metatileSize,
+ useTruePalettes);
+ };
+
+ QImage primaryImage = createSheetImage(0, primaryTileset);
+ QImage secondaryImage = createSheetImage(Project::getNumMetatilesPrimary(), secondaryTileset);
+
+ QImage image(qMax(primaryImage.width(), secondaryImage.width()), primaryImage.height() + secondaryImage.height(), QImage::Format_RGBA8888);
+ image.fill(getInvalidImageColor());
+
+ QPainter painter(&image);
+ painter.drawImage(0, 0, primaryImage);
+ painter.drawImage(0, primaryImage.height(), secondaryImage);
+ painter.end();
+
+ return image;
+}
+
+QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) {
+ if (!layout)
+ return QImage();
+ return getMetatileSheetImage(layout->tileset_primary,
+ layout->tileset_secondary,
+ numMetatilesWide,
+ layout->metatileLayerOrder(),
+ layout->metatileLayerOpacity(),
+ Metatile::pixelSize(),
+ useTruePalettes);
+}
diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp
index 59efbe4b..30c6ff72 100644
--- a/src/ui/mapimageexporter.cpp
+++ b/src/ui/mapimageexporter.cpp
@@ -46,7 +46,7 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map,
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
- m_scene = new QGraphicsScene(this);
+ m_scene = new CheckeredBgScene(QSize(8,8), this);
m_preview = m_scene->addPixmap(QPixmap());
ui->graphicsView_Preview->setScene(m_scene);
@@ -333,8 +333,8 @@ QGifImage* MapImageExporter::createTimelapseGifImage(QProgressDialog *progress)
if (currentHistoryAppliesToFrame(step.historyStack) || step.historyStack->index() == step.initialStackIndex) {
// Either this is relevant edit history, or it's the final frame (which is always rendered). Record the size of the map at this point.
QMargins margins = getMargins(m_map);
- canvasSize = canvasSize.expandedTo(QSize(m_layout->getWidth() * 16 + margins.left() + margins.right(),
- m_layout->getHeight() * 16 + margins.top() + margins.bottom()));
+ canvasSize = canvasSize.expandedTo(QSize(m_layout->pixelWidth() + margins.left() + margins.right(),
+ m_layout->pixelHeight() + margins.top() + margins.bottom()));
}
if (step.historyStack->canUndo()){
step.historyStack->undo();
@@ -434,8 +434,8 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress) {
if (!connection->isCardinal()) continue;
Map *connectedMap = connection->targetMap();
if (!connectedMap) continue;
- QPoint pos = connection->relativePos();
- unvisited.append(StitchedMap{cur.x + (pos.x() * 16), cur.y + (pos.y() * 16), connectedMap});
+ QPoint pos = connection->relativePixelPos();
+ unvisited.append(StitchedMap{cur.x + pos.x(), cur.y + pos.y(), connectedMap});
}
}
if (stitchedMaps.isEmpty())
@@ -447,7 +447,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress) {
// Determine the overall dimensions of the stitched maps.
QRect dimensions = QRect(0, 0, m_map->getWidth(), m_map->getHeight()) + getMargins(m_map);
for (const StitchedMap &map : stitchedMaps) {
- dimensions |= (QRect(map.x, map.y, map.map->getWidth() * 16, map.map->getHeight() * 16) + getMargins(map.map));
+ dimensions |= (QRect(map.x, map.y, map.map->pixelWidth(), map.map->pixelHeight()) + getMargins(map.map));
}
QPixmap stitchedPixmap(dimensions.width(), dimensions.height());
@@ -602,7 +602,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap() {
QMargins MapImageExporter::getMargins(const Map *map) {
QMargins margins;
if (m_settings.showBorder) {
- margins = m_project->getMetatileViewDistance() * 16;
+ margins = m_project->getPixelViewDistance();
} else if (map && connectionsEnabled()) {
for (const auto &connection : map->getConnections()) {
const QString dir = connection->direction();
@@ -612,10 +612,10 @@ QMargins MapImageExporter::getMargins(const Map *map) {
if (!targetMap) continue;
QRect rect = targetMap->getConnectionRect(dir);
- if (dir == "up") margins.setTop(rect.height() * 16);
- else if (dir == "down") margins.setBottom(rect.height() * 16);
- else if (dir == "left") margins.setLeft(rect.width() * 16);
- else if (dir == "right") margins.setRight(rect.width() * 16);
+ if (dir == "up") margins.setTop(rect.height());
+ else if (dir == "down") margins.setBottom(rect.height());
+ else if (dir == "left") margins.setLeft(rect.width());
+ else if (dir == "right") margins.setRight(rect.width());
}
}
if (m_settings.showGrid) {
@@ -652,7 +652,7 @@ void MapImageExporter::paintBorder(QPainter *painter, Layout *layout) {
// Skip border painting if it would be fully covered by the rest of the map
if (layout->isWithinBounds(QRect(x, y, layout->getBorderWidth(), layout->getBorderHeight())))
continue;
- painter->drawPixmap(x * 16, y * 16, layout->border_pixmap);
+ painter->drawPixmap(x * Metatile::pixelWidth(), y * Metatile::pixelHeight(), layout->border_pixmap);
}
painter->restore();
@@ -665,7 +665,7 @@ void MapImageExporter::paintConnections(QPainter *painter, const Map *map) {
for (const auto &connection : map->getConnections()) {
if (!m_settings.showConnections.contains(connection->direction()))
continue;
- painter->drawImage(connection->relativePos(true) * 16, connection->render().toImage());
+ painter->drawImage(connection->relativePixelPos(true), connection->render().toImage());
}
}
@@ -693,12 +693,12 @@ void MapImageExporter::paintGrid(QPainter *painter, const Layout *layout) {
if (!m_settings.showGrid)
return;
- int w = layout->getWidth() * 16;
- int h = layout->getHeight() * 16;
- for (int x = 0; x <= w; x += 16) {
+ int w = layout->pixelWidth();
+ int h = layout->pixelHeight();
+ for (int x = 0; x <= w; x += Metatile::pixelWidth()) {
painter->drawLine(x, 0, x, h);
}
- for (int y = 0; y <= h; y += 16) {
+ for (int y = 0; y <= h; y += Metatile::pixelHeight()) {
painter->drawLine(0, y, w, y);
}
}
diff --git a/src/ui/mapruler.cpp b/src/ui/mapruler.cpp
index 908e0a46..da83821c 100644
--- a/src/ui/mapruler.cpp
+++ b/src/ui/mapruler.cpp
@@ -37,9 +37,11 @@ QPainterPath MapRuler::shape() const {
ruler.addRect(xRuler);
ruler.addRect(yRuler);
ruler = ruler.simplified();
- for (int x = 16; x < pixWidth(); x += 16)
+ int w = Metatile::pixelWidth();
+ int h = Metatile::pixelHeight();
+ for (int x = w; x < pixWidth(); x += w)
ruler.addRect(x, xRuler.y(), 0, thickness);
- for (int y = 16; y < pixHeight(); y += 16)
+ for (int y = h; y < pixHeight(); y += h)
ruler.addRect(yRuler.x(), y, thickness, 0);
if (deltaX() && deltaY())
ruler.addPolygon(QVector({ cornerTick.p1(), cornerTick.p2() }));
@@ -131,7 +133,9 @@ QPoint MapRuler::snapToWithinBounds(QPoint pos) const {
void MapRuler::updateGeometry() {
prepareGeometryChange();
- setPos(QPoint(left() * 16 + 8, top() * 16 + 8));
+ int w = Metatile::pixelWidth();
+ int h = Metatile::pixelHeight();
+ setPos(QPoint(left() * w + w/2, top() * h + h/2));
/* Determine what quadrant the end point is in relative to the anchor point. The anchor
* point is the top-left corner of the metatile the ruler starts in, so a zero-length
* ruler is considered to be in the bottom-right quadrant from the anchor point. */
diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp
new file mode 100644
index 00000000..92dc5079
--- /dev/null
+++ b/src/ui/metatileimageexporter.cpp
@@ -0,0 +1,364 @@
+#include "metatileimageexporter.h"
+#include "ui_metatileimageexporter.h"
+#include "filedialog.h"
+#include "imageproviders.h"
+#include "utility.h"
+#include "project.h"
+#include "metatile.h"
+
+#include
+
+MetatileImageExporter::MetatileImageExporter(QWidget *parent, Tileset *primaryTileset, Tileset *secondaryTileset, Settings *savedSettings) :
+ QDialog(parent),
+ ui(new Ui::MetatileImageExporter),
+ m_primaryTileset(primaryTileset),
+ m_secondaryTileset(secondaryTileset),
+ m_savedSettings(savedSettings)
+{
+ ui->setupUi(this);
+ m_transparencyButtons = {
+ ui->radioButton_TransparencyNormal,
+ ui->radioButton_TransparencyBlack,
+ ui->radioButton_TransparencyFirst,
+ };
+
+ m_scene = new CheckeredBgScene(QSize(8,8), this);
+ m_preview = m_scene->addPixmap(QPixmap());
+ ui->graphicsView_Preview->setScene(m_scene);
+
+ if (projectConfig.tripleLayerMetatilesEnabled) {
+ // When triple-layer metatiles are enabled there is no unused layer,
+ // so this setting becomes pointless. Disable it.
+ ui->checkBox_Placeholders->setVisible(false);
+ }
+
+ uint16_t maxMetatileId = Block::getMaxMetatileId();
+ ui->spinBox_MetatileStart->setMaximum(maxMetatileId);
+ ui->spinBox_MetatileEnd->setMaximum(maxMetatileId);
+ ui->spinBox_WidthMetatiles->setRange(1, maxMetatileId);
+ ui->spinBox_WidthPixels->setRange(1 * Metatile::pixelWidth(), maxMetatileId * Metatile::pixelWidth());
+
+ if (m_primaryTileset) {
+ ui->comboBox_PrimaryTileset->setTextItem(m_primaryTileset->name);
+ }
+ if (m_secondaryTileset) {
+ ui->comboBox_SecondaryTileset->setTextItem(m_secondaryTileset->name);
+ }
+
+ if (m_savedSettings) {
+ populate(*m_savedSettings);
+ } else {
+ populate({});
+ }
+
+ connect(ui->listWidget_Layers, &ReorderableListWidget::itemChanged, this, &MetatileImageExporter::updatePreview);
+ connect(ui->listWidget_Layers, &ReorderableListWidget::reordered, this, &MetatileImageExporter::updatePreview);
+
+ connect(ui->pushButton_Save, &QPushButton::pressed, [this] { saveImage(); });
+ connect(ui->pushButton_Close, &QPushButton::pressed, this, &MetatileImageExporter::close);
+ connect(ui->pushButton_Reset, &QPushButton::pressed, this, &MetatileImageExporter::reset);
+
+ connect(ui->spinBox_WidthMetatiles, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::syncPixelWidth);
+ connect(ui->spinBox_WidthMetatiles, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate);
+ connect(ui->spinBox_WidthMetatiles, &UIntSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview);
+
+ connect(ui->spinBox_WidthPixels, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::syncMetatileWidth);
+ connect(ui->spinBox_WidthPixels, &UIntSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate);
+ connect(ui->spinBox_WidthPixels, &UIntSpinBox::editingFinished, this, &MetatileImageExporter::syncPixelWidth); // Round pixel width to multiple of 16
+ connect(ui->spinBox_WidthPixels, &UIntSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview);
+
+ connect(ui->spinBox_MetatileStart, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::validateMetatileEnd);
+ connect(ui->spinBox_MetatileStart, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate);
+ connect(ui->spinBox_MetatileStart, &UIntHexSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview);
+
+ connect(ui->spinBox_MetatileEnd, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::validateMetatileStart);
+ connect(ui->spinBox_MetatileEnd, &UIntHexSpinBox::valueChanged, this, &MetatileImageExporter::queuePreviewUpdate);
+ connect(ui->spinBox_MetatileEnd, &UIntHexSpinBox::editingFinished, this, &MetatileImageExporter::tryUpdatePreview);
+
+ // If we used toggled instead of clicked we'd get two preview updates instead of one when the setting changes.
+ connect(ui->radioButton_TransparencyNormal, &QRadioButton::clicked, this, &MetatileImageExporter::updatePreview);
+ connect(ui->radioButton_TransparencyBlack, &QRadioButton::clicked, this, &MetatileImageExporter::updatePreview);
+ connect(ui->radioButton_TransparencyFirst, &QRadioButton::clicked, this, &MetatileImageExporter::updatePreview);
+
+ connect(ui->checkBox_Placeholders, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview);
+ connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::tryEnforceMetatileRange);
+ connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview);
+ connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::tryEnforceMetatileRange);
+ connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview);
+
+ ui->graphicsView_Preview->setFocus();
+}
+
+MetatileImageExporter::~MetatileImageExporter() {
+ delete ui;
+}
+
+// Allow the window to open before displaying the preview.
+// Metatile sheet image creation is generally quick, so this only
+// really matters so that the graphics view can adjust the scale properly.
+void MetatileImageExporter::showEvent(QShowEvent *event) {
+ QDialog::showEvent(event);
+ if (!event->spontaneous()) {
+ QTimer::singleShot(0, this, &MetatileImageExporter::updatePreview);
+ }
+}
+
+void MetatileImageExporter::closeEvent(QCloseEvent *event) {
+ if (m_savedSettings) {
+ m_savedSettings->metatileStart = ui->spinBox_MetatileStart->value();
+ m_savedSettings->metatileEnd = ui->spinBox_MetatileEnd->value();
+ m_savedSettings->numMetatilesWide = ui->spinBox_WidthMetatiles->value();
+ m_savedSettings->usePrimaryTileset = ui->checkBox_PrimaryTileset->isChecked();
+ m_savedSettings->useSecondaryTileset = ui->checkBox_SecondaryTileset->isChecked();
+ m_savedSettings->renderPlaceholders = ui->checkBox_Placeholders->isChecked();
+ for (int i = 0; i < m_transparencyButtons.length(); i++) {
+ if (m_transparencyButtons.at(i)->isChecked()) {
+ m_savedSettings->transparencyMode = i;
+ break;
+ }
+ }
+ m_savedSettings->layerOrder.clear();
+ for (int i = 0; i < ui->listWidget_Layers->count(); i++) {
+ auto item = ui->listWidget_Layers->item(i);
+ int layerNum = item->data(Qt::UserRole).toInt();
+ m_savedSettings->layerOrder[layerNum] = (item->checkState() == Qt::Checked);
+ }
+ }
+ QDialog::closeEvent(event);
+}
+
+void MetatileImageExporter::populate(const Settings &settings) {
+ const QSignalBlocker b_MetatileStart(ui->spinBox_MetatileStart);
+ ui->spinBox_MetatileStart->setValue(settings.metatileStart);
+
+ const QSignalBlocker b_MetatileEnd(ui->spinBox_MetatileStart);
+ ui->spinBox_MetatileEnd->setValue(settings.metatileEnd);
+
+ const QSignalBlocker b_WidthMetatiles(ui->spinBox_MetatileStart);
+ ui->spinBox_WidthMetatiles->setValue(settings.numMetatilesWide);
+
+ const QSignalBlocker b_WidthPixels(ui->spinBox_MetatileStart);
+ ui->spinBox_WidthPixels->setValue(settings.numMetatilesWide * Metatile::pixelWidth());
+
+ const QSignalBlocker b_PrimaryTileset(ui->spinBox_MetatileStart);
+ ui->checkBox_PrimaryTileset->setChecked(settings.usePrimaryTileset);
+
+ const QSignalBlocker b_SecondaryTileset(ui->spinBox_MetatileStart);
+ ui->checkBox_SecondaryTileset->setChecked(settings.useSecondaryTileset);
+
+ const QSignalBlocker b_Placeholders(ui->spinBox_MetatileStart);
+ ui->checkBox_Placeholders->setChecked(settings.renderPlaceholders);
+
+ if (m_transparencyButtons.value(settings.transparencyMode)) {
+ auto button = m_transparencyButtons[settings.transparencyMode];
+ const QSignalBlocker b_Transparency(button);
+ button->setChecked(true);
+ }
+
+ // Build layer list from settings
+ const QSignalBlocker b_Layers(ui->listWidget_Layers);
+ ui->listWidget_Layers->clear();
+ for (auto it = settings.layerOrder.cbegin(); it != settings.layerOrder.cend(); it++) {
+ int layerNum = it.key();
+ bool enabled = it.value();
+
+ auto item = new QListWidgetItem(Metatile::getLayerName(layerNum));
+ item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren);
+ item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
+ item->setData(Qt::UserRole, layerNum); // Save the original index to identify the layer
+ ui->listWidget_Layers->addItem(item);
+ }
+ // Don't give extra unnecessary space to the list
+ ui->listWidget_Layers->setFixedHeight(ui->listWidget_Layers->sizeHintForRow(0) * ui->listWidget_Layers->count() + 4);
+
+ tryEnforceMetatileRange();
+}
+
+void MetatileImageExporter::applySettings(const Settings &settings) {
+ populate(settings);
+ updatePreview();
+}
+
+void MetatileImageExporter::reset() {
+ applySettings({});
+}
+
+QImage MetatileImageExporter::getImage() {
+ tryUpdatePreview();
+ return m_preview->pixmap().toImage();
+}
+
+bool MetatileImageExporter::saveImage(QString filepath) {
+ tryUpdatePreview();
+ if (filepath.isEmpty()) {
+ QString defaultFilepath = QString("%1/%2").arg(FileDialog::getDirectory()).arg(getDefaultFileName());
+ filepath = FileDialog::getSaveFileName(this, windowTitle(), defaultFilepath, QStringLiteral("Image Files (*.png *.jpg *.bmp)"));
+ if (filepath.isEmpty()) {
+ return false;
+ }
+ }
+ return m_preview->pixmap().save(filepath);
+}
+
+QString MetatileImageExporter::getDefaultFileName() const {
+ if (m_layerOrder.length() == 1) {
+ // Exporting a metatile layer image is an expected use case for Porytiles, which expects certain file names.
+ // We can make the process a little easier by setting the default file name to those expected file names.
+ static const QStringList layerFilenames = { "bottom", "middle", "top" };
+ return layerFilenames.at(m_layerOrder.constFirst()) + ".png";
+ }
+
+ QString defaultFilename;
+ if (ui->checkBox_PrimaryTileset->isChecked() && m_primaryTileset) {
+ defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_primaryTileset->name)));
+ }
+ if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) {
+ defaultFilename.append(QString("%1_").arg(Tileset::stripPrefix(m_secondaryTileset->name)));
+ }
+ if (!m_layerOrder.isEmpty() && m_layerOrder != QList({0,1,2})) {
+ for (int i = m_layerOrder.length() - 1; i >= 0; i--) {
+ defaultFilename.append(Metatile::getLayerName(m_layerOrder.at(i)));
+ }
+ defaultFilename.append("_");
+ }
+ defaultFilename.append("Metatiles.png");
+ return defaultFilename;
+}
+
+void MetatileImageExporter::queuePreviewUpdate() {
+ m_previewUpdateQueued = true;
+}
+
+// For updating only when a change has been recorded.
+// Useful for something that might happen often, like an input widget losing focus.
+void MetatileImageExporter::tryUpdatePreview() {
+ if (m_preview->pixmap().isNull() || m_previewUpdateQueued) {
+ updatePreview();
+ }
+}
+
+void MetatileImageExporter::updatePreview() {
+ copyRenderSettings();
+
+ m_layerOrder.clear();
+ for (int i = 0; i < ui->listWidget_Layers->count(); i++) {
+ auto item = ui->listWidget_Layers->item(i);
+ if (item->checkState() == Qt::Checked) {
+ int layerNum = item->data(Qt::UserRole).toInt();
+ m_layerOrder.prepend(qBound(0, layerNum, 2));
+ }
+ }
+
+ QImage previewImage;
+ if (ui->checkBox_PrimaryTileset->isChecked() && ui->checkBox_SecondaryTileset->isChecked()) {
+ // Special behavior to combine the two tilesets while skipping the unused region between tilesets.
+ previewImage = getMetatileSheetImage(m_primaryTileset,
+ m_secondaryTileset,
+ ui->spinBox_WidthMetatiles->value(),
+ m_layerOrder);
+ } else {
+ previewImage = getMetatileSheetImage(m_primaryTileset,
+ m_secondaryTileset,
+ ui->spinBox_MetatileStart->value(),
+ ui->spinBox_MetatileEnd->value(),
+ ui->spinBox_WidthMetatiles->value(),
+ m_layerOrder);
+ }
+
+ m_preview->setPixmap(QPixmap::fromImage(previewImage));
+ m_scene->setSceneRect(m_scene->itemsBoundingRect());
+ m_previewUpdateQueued = false;
+
+ restoreRenderSettings();
+}
+
+void MetatileImageExporter::validateMetatileStart() {
+ const QSignalBlocker b(ui->spinBox_MetatileStart);
+ ui->spinBox_MetatileStart->setValue(qMin(ui->spinBox_MetatileStart->value(),
+ ui->spinBox_MetatileEnd->value()));
+}
+
+void MetatileImageExporter::validateMetatileEnd() {
+ const QSignalBlocker b(ui->spinBox_MetatileEnd);
+ ui->spinBox_MetatileEnd->setValue(qMax(ui->spinBox_MetatileStart->value(),
+ ui->spinBox_MetatileEnd->value()));
+}
+
+void MetatileImageExporter::updateMetatileRange() {
+ uint16_t min;
+ uint16_t max;
+ if (ui->checkBox_PrimaryTileset->isChecked() && m_primaryTileset) {
+ if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) {
+ // Both tilesets enforced
+ min = qMin(m_primaryTileset->firstMetatileId(), m_secondaryTileset->firstMetatileId());
+ max = qMax(m_primaryTileset->lastMetatileId(), m_secondaryTileset->lastMetatileId());
+ } else {
+ // Primary enforced
+ min = m_primaryTileset->firstMetatileId();
+ max = m_primaryTileset->lastMetatileId();
+ }
+ } else if (ui->checkBox_SecondaryTileset->isChecked() && m_secondaryTileset) {
+ // Secondary enforced
+ min = m_secondaryTileset->firstMetatileId();
+ max = m_secondaryTileset->lastMetatileId();
+ } else {
+ // No tilesets enforced
+ return;
+ }
+
+ const QSignalBlocker b_MetatileStart(ui->spinBox_MetatileStart);
+ const QSignalBlocker b_MetatileEnd(ui->spinBox_MetatileEnd);
+ ui->spinBox_MetatileStart->setValue(min);
+ ui->spinBox_MetatileEnd->setValue(max);
+}
+
+void MetatileImageExporter::tryEnforceMetatileRange() {
+ // Users can either specify which tileset(s) to render, or specify a range of metatiles, but not both.
+ if (ui->checkBox_PrimaryTileset->isChecked() || ui->checkBox_SecondaryTileset->isChecked()) {
+ updateMetatileRange();
+ ui->groupBox_MetatileRange->setDisabled(true);
+ } else {
+ ui->groupBox_MetatileRange->setDisabled(false);
+ }
+}
+
+void MetatileImageExporter::syncPixelWidth() {
+ const QSignalBlocker b(ui->spinBox_WidthPixels);
+ ui->spinBox_WidthPixels->setValue(ui->spinBox_WidthMetatiles->value() * Metatile::pixelWidth());
+}
+
+void MetatileImageExporter::syncMetatileWidth() {
+ const QSignalBlocker b(ui->spinBox_WidthMetatiles);
+ ui->spinBox_WidthMetatiles->setValue(Util::roundUpToMultiple(ui->spinBox_WidthPixels->value(), Metatile::pixelWidth()) / Metatile::pixelWidth());
+}
+
+// These settings control some rendering behavior that make metatiles render accurately to their in-game appearance,
+// which may be undesirable when exporting metatile images for editing.
+// The settings are buried in getMetatileImage at the moment, to change them we'll temporarily overwrite them.
+void MetatileImageExporter::copyRenderSettings() {
+ m_savedConfig.transparencyColor = projectConfig.transparencyColor;
+ m_savedConfig.unusedTileNormal = projectConfig.unusedTileNormal;
+ m_savedConfig.unusedTileCovered = projectConfig.unusedTileCovered;
+ m_savedConfig.unusedTileSplit = projectConfig.unusedTileSplit;
+
+ if (ui->radioButton_TransparencyNormal->isChecked()) {
+ projectConfig.transparencyColor = QColor(Qt::transparent);
+ } else if (ui->radioButton_TransparencyBlack->isChecked()) {
+ projectConfig.transparencyColor = QColor(Qt::black);
+ } else {
+ projectConfig.transparencyColor = QColor();
+ }
+
+ if (!ui->checkBox_Placeholders->isChecked()) {
+ projectConfig.unusedTileNormal = 0;
+ projectConfig.unusedTileCovered = 0;
+ projectConfig.unusedTileSplit = 0;
+ }
+}
+
+void MetatileImageExporter::restoreRenderSettings() {
+ projectConfig.transparencyColor = m_savedConfig.transparencyColor;
+ projectConfig.unusedTileNormal = m_savedConfig.unusedTileNormal;
+ projectConfig.unusedTileCovered = m_savedConfig.unusedTileCovered;
+ projectConfig.unusedTileSplit = m_savedConfig.unusedTileSplit;
+}
diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp
index a26ed14b..bae09ee6 100644
--- a/src/ui/metatilelayersitem.cpp
+++ b/src/ui/metatilelayersitem.cpp
@@ -4,7 +4,7 @@
#include
MetatileLayersItem::MetatileLayersItem(Metatile *metatile, Tileset *primaryTileset, Tileset *secondaryTileset)
- : SelectablePixmapItem(16, 16, 2 * projectConfig.getNumLayersInMetatile(), 2),
+ : SelectablePixmapItem(16, 16, Metatile::tileWidth() * projectConfig.getNumLayersInMetatile(), Metatile::tileHeight()),
metatile(metatile),
primaryTileset(primaryTileset),
secondaryTileset(secondaryTileset)
@@ -31,8 +31,8 @@ static const QList tilePositions = {
void MetatileLayersItem::draw() {
const int numLayers = projectConfig.getNumLayersInMetatile();
- const int layerWidth = this->cellWidth * 2;
- const int layerHeight = this->cellHeight * 2;
+ const int layerWidth = this->cellWidth * Metatile::tileWidth();
+ const int layerHeight = this->cellHeight * Metatile::tileHeight();
QPixmap pixmap(numLayers * layerWidth, layerHeight);
QPainter painter(&pixmap);
@@ -129,7 +129,7 @@ void MetatileLayersItem::hoverMoveEvent(QGraphicsSceneHoverEvent * event) {
if (tileIndex < 0 || tileIndex >= this->metatile->tiles.length())
return;
- emit this->hoveredTileChanged(this->metatile->tiles.at(tileIndex).tileId);
+ emit this->hoveredTileChanged(this->metatile->tiles.at(tileIndex));
}
void MetatileLayersItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
@@ -148,6 +148,6 @@ void MetatileLayersItem::clearLastHoveredCoords() {
QPoint MetatileLayersItem::getBoundedPos(const QPointF &pos) {
int x = static_cast(pos.x()) / this->cellWidth;
int y = static_cast(pos.y()) / this->cellHeight;
- return QPoint( qMax(0, qMin(x, this->maxSelectionWidth - 1)),
- qMax(0, qMin(y, this->maxSelectionHeight - 1)) );
+ return QPoint(qBound(0, x, this->maxSelectionWidth - 1),
+ qBound(0, y, this->maxSelectionHeight - 1));
}
diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp
index 828c73cd..38cc50be 100644
--- a/src/ui/metatileselector.cpp
+++ b/src/ui/metatileselector.cpp
@@ -3,40 +3,20 @@
#include "project.h"
#include
-QPoint MetatileSelector::getSelectionDimensions() {
+QPoint MetatileSelector::getSelectionDimensions() const {
if (this->prefabSelection || this->externalSelection)
return selection.dimensions;
return SelectablePixmapItem::getSelectionDimensions();
}
int MetatileSelector::numPrimaryMetatilesRounded() const {
- // We round up the number of primary metatiles to keep the tilesets on separate rows.
- return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide;
+ if (!primaryTileset())
+ return 0;
+ return Util::roundUpToMultiple(primaryTileset()->numMetatiles(), this->numMetatilesWide);
}
void MetatileSelector::updateBasePixmap() {
- int primaryLength = this->numPrimaryMetatilesRounded();
- int length_ = primaryLength + this->secondaryTileset->numMetatiles();
- int height_ = length_ / this->numMetatilesWide;
- if (length_ % this->numMetatilesWide != 0) {
- height_++;
- }
- QImage image(this->numMetatilesWide * this->cellWidth, height_ * this->cellHeight, QImage::Format_RGBA8888);
- image.fill(Qt::magenta);
- QPainter painter(&image);
- for (int i = 0; i < length_; i++) {
- int tile = i;
- if (i >= primaryLength) {
- tile += Project::getNumMetatilesPrimary() - primaryLength;
- }
- QImage metatile_image = getMetatileImage(tile, this->primaryTileset, this->secondaryTileset, layout->metatileLayerOrder, layout->metatileLayerOpacity);
- int map_y = i / this->numMetatilesWide;
- int map_x = i % this->numMetatilesWide;
- QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight);
- painter.drawImage(metatile_origin, metatile_image);
- }
- painter.end();
- this->basePixmap = QPixmap::fromImage(image);
+ this->basePixmap = QPixmap::fromImage(getMetatileSheetImage(this->layout, this->numMetatilesWide));
}
void MetatileSelector::draw() {
@@ -53,7 +33,11 @@ void MetatileSelector::drawSelection() {
}
bool MetatileSelector::select(uint16_t metatileId) {
- if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) return false;
+ bool ok;
+ QPoint pos = metatileIdToPos(metatileId, &ok);
+ if (!ok) {
+ return false;
+ }
this->externalSelection = false;
this->prefabSelection = false;
this->selection = MetatileSelection{
@@ -62,8 +46,7 @@ bool MetatileSelector::select(uint16_t metatileId) {
QList({MetatileSelectionItem{true, metatileId}}),
QList(),
};
- QPoint coords = this->getMetatileIdCoords(metatileId);
- SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0);
+ SelectablePixmapItem::select(pos);
this->updateSelectedMetatiles();
return true;
}
@@ -73,9 +56,8 @@ void MetatileSelector::selectFromMap(uint16_t metatileId, uint16_t collision, ui
this->setExternalSelection(1, 1, {metatileId}, {movePermissions});
}
-void MetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
- this->primaryTileset = primaryTileset;
- this->secondaryTileset = secondaryTileset;
+void MetatileSelector::setLayout(Layout *layout) {
+ this->layout = layout;
if (this->externalSelection)
this->updateExternalSelectedMetatiles();
else
@@ -85,11 +67,11 @@ void MetatileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTi
draw();
}
-MetatileSelection MetatileSelector::getMetatileSelection() {
- return selection;
+void MetatileSelector::refresh() {
+ setLayout(this->layout);
}
-void MetatileSelector::setExternalSelection(int width, int height, QList metatiles, QList> collisions) {
+void MetatileSelector::setExternalSelection(int width, int height, const QList &metatiles, const QList> &collisions) {
this->prefabSelection = false;
this->externalSelection = true;
this->externalSelectionWidth = width;
@@ -99,18 +81,18 @@ void MetatileSelector::setExternalSelection(int width, int height, QListselection.collisionItems.clear();
this->selection.hasCollision = true;
this->selection.dimensions = QPoint(width, height);
- for (int i = 0; i < metatiles.length(); i++) {
- auto collision = collisions.at(i);
- this->selection.collisionItems.append(CollisionSelectionItem{true, collision.first, collision.second});
+ for (int i = 0; i < qMin(metatiles.length(), collisions.length()); i++) {
uint16_t metatileId = metatiles.at(i);
+ uint16_t collision = collisions.at(i).first;
+ uint16_t elevation = collisions.at(i).second;
+ this->selection.collisionItems.append(CollisionSelectionItem{true, collision, elevation});
this->externalSelectedMetatiles.append(metatileId);
- if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
+ if (!this->layout->metatileIsValid(metatileId))
metatileId = 0;
this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId});
}
if (this->selection.metatileItems.length() == 1) {
- QPoint coords = this->getMetatileIdCoords(this->selection.metatileItems.first().metatileId);
- SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0);
+ SelectablePixmapItem::select(metatileIdToPos(this->selection.metatileItems.first().metatileId));
}
this->draw();
@@ -127,7 +109,9 @@ void MetatileSelector::setPrefabSelection(MetatileSelection selection) {
}
bool MetatileSelector::positionIsValid(const QPoint &pos) const {
- return Tileset::metatileIsValid(getMetatileId(pos.x(), pos.y()), this->primaryTileset, this->secondaryTileset);
+ bool ok;
+ posToMetatileId(pos, &ok);
+ return ok;
}
void MetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) {
@@ -168,8 +152,14 @@ void MetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
}
void MetatileSelector::hoverChanged() {
- uint16_t metatileId = this->getMetatileId(this->cellPos.x(), this->cellPos.y());
- emit this->hoveredMetatileSelectionChanged(metatileId);
+ bool ok;
+ uint16_t metatileId = posToMetatileId(this->cellPos, &ok);
+ if (ok) {
+ emit this->hoveredMetatileSelectionChanged(metatileId);
+ } else {
+ emit this->hoveredMetatileSelectionCleared();
+ this->cellPos = QPoint(-1, -1);
+ }
}
void MetatileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) {
@@ -187,9 +177,7 @@ void MetatileSelector::updateSelectedMetatiles() {
QPoint origin = this->getSelectionStart();
for (int j = 0; j < this->selection.dimensions.y(); j++) {
for (int i = 0; i < this->selection.dimensions.x(); i++) {
- uint16_t metatileId = this->getMetatileId(origin.x() + i, origin.y() + j);
- if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
- metatileId = 0;
+ uint16_t metatileId = posToMetatileId(origin.x() + i, origin.y() + j);
this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId});
}
}
@@ -201,43 +189,59 @@ void MetatileSelector::updateExternalSelectedMetatiles() {
this->selection.dimensions = QPoint(this->externalSelectionWidth, this->externalSelectionHeight);
for (int i = 0; i < this->externalSelectedMetatiles.count(); ++i) {
uint16_t metatileId = this->externalSelectedMetatiles.at(i);
- if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
+ if (!this->layout->metatileIsValid(metatileId))
metatileId = 0;
this->selection.metatileItems.append(MetatileSelectionItem{true, metatileId});
}
emit selectedMetatilesChanged();
}
-uint16_t MetatileSelector::getMetatileId(int x, int y) const {
+uint16_t MetatileSelector::posToMetatileId(const QPoint &pos, bool *ok) const {
+ return posToMetatileId(pos.x(), pos.y(), ok);
+}
+
+uint16_t MetatileSelector::posToMetatileId(int x, int y, bool *ok) const {
+ if (ok) *ok = true;
int index = y * this->numMetatilesWide + x;
- int numPrimary = this->numPrimaryMetatilesRounded();
- if (index < numPrimary) {
- return static_cast(index);
- } else {
- return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary);
- }
-}
-
-QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) {
- if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
- {
- // Invalid metatile id.
- return QPoint(0, 0);
+ uint16_t metatileId = static_cast(index);
+ if (primaryTileset() && primaryTileset()->contains(metatileId)) {
+ return metatileId;
}
- int index = metatileId < Project::getNumMetatilesPrimary()
- ? metatileId
- : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded();
- return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
+ // There's some extra handling here because we round the tilesets to keep them on separate rows.
+ // This means if the maximum number of primary metatiles is not divisible by the metatile width
+ // then the metatiles we used to round the primary tileset would have the index of valid secondary metatiles.
+ // These need to be ignored, or they'll appear to be duplicates of the subseqeunt secondary metatiles.
+ int numPrimaryRounded = numPrimaryMetatilesRounded();
+ int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide;
+ metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded);
+ if (secondaryTileset() && secondaryTileset()->contains(metatileId) && y >= firstSecondaryRow) {
+ return metatileId;
+ }
+
+ if (ok) *ok = false;
+ return 0;
}
-QPoint MetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) {
- QPoint pos = getMetatileIdCoords(metatileId);
+QPoint MetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const {
+ if (primaryTileset() && primaryTileset()->contains(metatileId)) {
+ if (ok) *ok = true;
+ int index = metatileId;
+ return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
+ }
+ if (secondaryTileset() && secondaryTileset()->contains(metatileId)) {
+ if (ok) *ok = true;
+ int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded();
+ return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
+ }
+
+ if (ok) *ok = false;
+ return QPoint(0,0);
+}
+
+QPoint MetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) const {
+ QPoint pos = metatileIdToPos(metatileId);
pos.rx() = (pos.x() * this->cellWidth) + (this->cellWidth / 2);
pos.ry() = (pos.y() * this->cellHeight) + (this->cellHeight / 2);
return pos;
}
-
-void MetatileSelector::setLayout(Layout *layout) {
- this->layout = layout;
-}
diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp
index 3fc73934..1e751aa9 100644
--- a/src/ui/movablerect.cpp
+++ b/src/ui/movablerect.cpp
@@ -5,16 +5,17 @@
#include "movablerect.h"
#include "utility.h"
-MovableRect::MovableRect(const QRectF &rect, const QRgb &color)
+MovableRect::MovableRect(const QRectF &rect, const QSize &cellSize, const QRgb &color)
: QGraphicsRectItem(rect),
baseRect(rect),
+ cellSize(cellSize),
color(color)
{ }
/// Center rect on grid position (x, y)
void MovableRect::updateLocation(int x, int y) {
- setRect(this->baseRect.x() + (x * 16),
- this->baseRect.y() + (y * 16),
+ setRect(this->baseRect.x() + (x * this->cellSize.width()),
+ this->baseRect.y() + (y * this->cellSize.height()),
this->baseRect.width(),
this->baseRect.height());
}
@@ -24,9 +25,9 @@ void MovableRect::updateLocation(int x, int y) {
************************************************************************
******************************************************************************/
-ResizableRect::ResizableRect(QObject *parent, int width, int height, QRgb color)
+ResizableRect::ResizableRect(QObject *parent, const QSize &cellSize, const QSize &size, const QRgb &color)
: QObject(parent),
- MovableRect(QRect(0, 0, width * 16, height * 16), color)
+ MovableRect(QRect(0, 0, size.width(), size.height()), cellSize, color)
{
setAcceptHoverEvents(true);
setFlags(this->flags() | QGraphicsItem::ItemIsMovable);
@@ -114,8 +115,8 @@ void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) {
}
void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
- int dx = Util::roundUp(event->scenePos().x() - this->clickedPos.x(), 16);
- int dy = Util::roundUp(event->scenePos().y() - this->clickedPos.y(), 16);
+ int dx = Util::roundUpToMultiple(event->scenePos().x() - this->clickedPos.x(), this->cellSize.width());
+ int dy = Util::roundUpToMultiple(event->scenePos().y() - this->clickedPos.y(), this->cellSize.height());
QRect resizedRect = this->clickedRect;
@@ -149,20 +150,20 @@ void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
break;
}
- // Lower limits: smallest possible size is 16x16 square
- if (resizedRect.width() < 16) {
+ // Lower limits: smallest possible size is 1 cell
+ if (resizedRect.width() < this->cellSize.width()) {
if (dx < 0) { // right sided adjustment made
- resizedRect.setWidth(16);
+ resizedRect.setWidth(this->cellSize.width());
} else { // left sided adjustment slightly more complicated
- int dxMax = this->clickedRect.right() - this->clickedRect.left() - 16;
+ int dxMax = this->clickedRect.right() - this->clickedRect.left() - this->cellSize.width();
resizedRect.adjust(dxMax - dx, 0, 0, 0);
}
}
- if (resizedRect.height() < 16) {
+ if (resizedRect.height() < this->cellSize.height()) {
if (dy < 0) { // bottom
- resizedRect.setHeight(16);
+ resizedRect.setHeight(this->cellSize.height());
} else { // top
- int dyMax = this->clickedRect.bottom() - this->clickedRect.top() - 16;
+ int dyMax = this->clickedRect.bottom() - this->clickedRect.top() - this->cellSize.height();
resizedRect.adjust(0, dyMax - dy, 0, 0);
}
}
diff --git a/src/ui/prefabcreationdialog.cpp b/src/ui/prefabcreationdialog.cpp
index ef6e6a0f..76c71f39 100644
--- a/src/ui/prefabcreationdialog.cpp
+++ b/src/ui/prefabcreationdialog.cpp
@@ -23,12 +23,12 @@ PrefabCreationDialog::PrefabCreationDialog(QWidget *parent, MetatileSelector *me
QObject::connect(this->ui->graphicsView_Prefab, &ClickableGraphicsView::clicked, [=](QMouseEvent *event){
auto pos = event->pos();
- int selectionWidth = this->selection.dimensions.x() * 16;
- int selectionHeight = this->selection.dimensions.y() * 16;
+ int selectionWidth = this->selection.dimensions.x() * Metatile::pixelWidth();
+ int selectionHeight = this->selection.dimensions.y() * Metatile::pixelHeight();
if (pos.x() < 0 || pos.x() >= selectionWidth || pos.y() < 0 || pos.y() >= selectionHeight)
return;
- int metatileX = pos.x() / 16;
- int metatileY = pos.y() / 16;
+ int metatileX = pos.x() / Metatile::pixelWidth();
+ int metatileY = pos.y() / Metatile::pixelHeight();
int index = metatileY * this->selection.dimensions.x() + metatileX;
bool toggledState = !this->selection.metatileItems[index].enabled;
this->selection.metatileItems[index].enabled = toggledState;
diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp
index c4becaa5..dadaefd7 100644
--- a/src/ui/projectsettingseditor.cpp
+++ b/src/ui/projectsettingseditor.cpp
@@ -161,6 +161,7 @@ void ProjectSettingsEditor::initUi() {
ui->spinBox_PlayerViewDistance_North->setMaximum(INT_MAX);
ui->spinBox_PlayerViewDistance_East->setMaximum(INT_MAX);
ui->spinBox_PlayerViewDistance_South->setMaximum(INT_MAX);
+ ui->spinBox_MetatileSelectorWidth->setMaximum(maxMetatileId + 1);
// The values for some of the settings we provide in this window can be determined using constants in the user's projects.
// If the user has these constants we disable these settings in the UI -- they can modify them using their constants.
@@ -232,7 +233,7 @@ QList ProjectSettingsEditor::getBorderMetatileIds(bool customSize) {
// Custom border size, read metatiles from line edit
for (auto s : ui->lineEdit_BorderMetatiles->text().split(",")) {
uint16_t metatileId = s.toUInt(nullptr, 0);
- metatileIds.append(qMin(metatileId, static_cast(Project::getNumMetatilesTotal() - 1)));
+ metatileIds.append(qMin(metatileId, Block::getMaxMetatileId()));
}
} else {
// Default border size, read metatiles from spin boxes
@@ -475,7 +476,7 @@ void ProjectSettingsEditor::refresh() {
ui->checkBox_PreserveMatchingOnlyData->setChecked(projectConfig.preserveMatchingOnlyData);
// Radio buttons
- if (projectConfig.setTransparentPixelsBlack)
+ if (projectConfig.transparencyColor == QColor(Qt::black))
ui->radioButton_RenderBlack->setChecked(true);
else
ui->radioButton_RenderFirstPalColor->setChecked(true);
@@ -503,6 +504,7 @@ void ProjectSettingsEditor::refresh() {
ui->spinBox_PlayerViewDistance_North->setValue(projectConfig.playerViewDistance.top());
ui->spinBox_PlayerViewDistance_East->setValue(projectConfig.playerViewDistance.right());
ui->spinBox_PlayerViewDistance_South->setValue(projectConfig.playerViewDistance.bottom());
+ ui->spinBox_MetatileSelectorWidth->setValue(projectConfig.metatileSelectorWidth);
// Set (and sync) border metatile IDs
this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds);
@@ -574,7 +576,7 @@ void ProjectSettingsEditor::save() {
projectConfig.tilesetsHaveCallback = ui->checkBox_OutputCallback->isChecked();
projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked();
porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked();
- projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked();
+ projectConfig.transparencyColor = ui->radioButton_RenderBlack->isChecked() ? QColor(Qt::black) : QColor();
projectConfig.preserveMatchingOnlyData = ui->checkBox_PreserveMatchingOnlyData->isChecked();
// Save spin box settings
@@ -598,6 +600,7 @@ void ProjectSettingsEditor::save() {
ui->spinBox_PlayerViewDistance_North->value(),
ui->spinBox_PlayerViewDistance_East->value(),
ui->spinBox_PlayerViewDistance_South->value());
+ projectConfig.metatileSelectorWidth = ui->spinBox_MetatileSelectorWidth->value();
// Save line edit settings
projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text();
diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp
index 7f11836f..679e9ee2 100644
--- a/src/ui/resizelayoutpopup.cpp
+++ b/src/ui/resizelayoutpopup.cpp
@@ -7,42 +7,9 @@
#include "ui_resizelayoutpopup.h"
-CheckeredBgScene::CheckeredBgScene(QObject *parent) : QGraphicsScene(parent) { }
-
-void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) {
- QRect r = rect.toRect();
- int xMin = r.left() - r.left() % this->gridSize - this->gridSize;
- int yMin = r.top() - r.top() % this->gridSize - this->gridSize;
- int xMax = r.right() - r.right() % this->gridSize + this->gridSize;
- int yMax = r.bottom() - r.bottom() % this->gridSize + this->gridSize;
-
- // draw grid 16x16 from top to bottom of scene
- QColor paintColor(0x00ff00);
- for (int x = xMin, xTile = 0; x <= xMax; x += this->gridSize, xTile++) {
- for (int y = yMin, yTile = 0; y <= yMax; y += this->gridSize, yTile++) {
- if (!((xTile ^ yTile) & 1)) { // tile numbers have same parity (evenness)
- if (this->validRect.contains(x, y))
- paintColor = QColor(132, 217, 165); // green light color
- else
- paintColor = 0xbcbcbc; // normal light color
- }
- else {
- if (this->validRect.contains(x, y))
- paintColor = QColor(76, 178, 121); // green dark color
- else
- paintColor = 0x969696; // normal dark color
- }
- painter->fillRect(QRect(x, y, this->gridSize, this->gridSize), paintColor);
- }
- }
-}
-
-/******************************************************************************
- ************************************************************************
- ******************************************************************************/
-
-BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) {
+BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, const QSize &cellSize, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) {
setFlags(this->flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsSelectable);
+ this->cellSize = cellSize;
}
void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) {
@@ -61,7 +28,8 @@ void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem
QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) {
if (change == ItemPositionChange && scene()) {
QPointF newPos = value.toPointF();
- return QPointF(Util::roundUp(newPos.x(), 16), Util::roundUp(newPos.y(), 16));
+ return QPointF(Util::roundUpToMultiple(newPos.x(), this->cellSize.width()),
+ Util::roundUpToMultiple(newPos.y(), this->cellSize.height()));
}
else
return QGraphicsItem::itemChange(change, value);
@@ -83,7 +51,7 @@ ResizeLayoutPopup::ResizeLayoutPopup(QWidget *parent, Layout *layout, Project *p
this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint);
this->setWindowModality(Qt::ApplicationModal);
- this->scene = new CheckeredBgScene(this);
+ this->scene = new CheckeredBgScene(Metatile::pixelSize(), this);
this->ui->graphicsView->setScene(this->scene);
this->ui->graphicsView->setRenderHints(QPainter::Antialiasing);
this->ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
@@ -124,11 +92,13 @@ void ResizeLayoutPopup::setupLayoutView() {
this->ui->spinBox_borderHeight->setValue(this->layout->getBorderHeight());
// Layout stuff
- this->layoutPixmap = new BoundedPixmapItem(this->layout->pixmap);
+ this->layoutPixmap = new BoundedPixmapItem(this->layout->pixmap, Metatile::pixelSize());
this->scene->addItem(layoutPixmap);
int maxWidth = this->project->getMaxMapWidth();
int maxHeight = this->project->getMaxMapHeight();
- QGraphicsRectItem *cover = new QGraphicsRectItem(-maxWidth * 8, -maxHeight * 8, maxWidth * 16, maxHeight * 16);
+ int maxPixelWidth = maxWidth * Metatile::pixelWidth() * 2; // *2 to allow reaching max dimension by expanding from 0,0 in either direction
+ int maxPixelHeight = maxHeight * Metatile::pixelHeight() * 2;
+ QGraphicsRectItem *cover = new QGraphicsRectItem(-(maxPixelWidth / 2), -(maxPixelHeight / 2), maxPixelWidth, maxPixelHeight);
this->scene->addItem(cover);
this->ui->spinBox_width->setMinimum(1);
@@ -136,12 +106,14 @@ void ResizeLayoutPopup::setupLayoutView() {
this->ui->spinBox_height->setMinimum(1);
this->ui->spinBox_height->setMaximum(maxHeight);
- this->outline = new ResizableRect(this, this->layout->getWidth(), this->layout->getHeight(), qRgb(255, 0, 255));
+ this->outline = new ResizableRect(this, Metatile::pixelSize(), this->layout->pixelSize(), qRgb(255, 0, 255));
this->outline->setZValue(Editor::ZValue::ResizeLayoutPopup); // Ensure on top of view
this->outline->setLimit(cover->rect().toAlignedRect());
connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){
// Note: this extra limit check needs access to the project values, so it is done here and not ResizableRect::mouseMoveEvent
- int size = this->project->getMapDataSize(rect.width() / 16, rect.height() / 16);
+ int metatilesWide = rect.width() / Metatile::pixelWidth();
+ int metatilesTall = rect.height() / Metatile::pixelHeight();
+ int size = this->project->getMapDataSize(metatilesWide, metatilesTall);
int maxSize = this->project->getMaxMapDataSize();
if (size > maxSize) {
QSize addition = this->project->getMapSizeAddition();
@@ -151,8 +123,8 @@ void ResizeLayoutPopup::setupLayoutView() {
.arg(addition.width())
.arg(addition.height())
.arg(maxSize)
- .arg(rect.width() / 16)
- .arg(rect.height() / 16)
+ .arg(metatilesWide)
+ .arg(metatilesTall)
.arg(size),
this);
// adjust rect to last accepted size
@@ -160,8 +132,11 @@ void ResizeLayoutPopup::setupLayoutView() {
}
this->scene->setValidRect(rect);
this->outline->setRect(rect);
- this->ui->spinBox_width->setValue(rect.width() / 16);
- this->ui->spinBox_height->setValue(rect.height() / 16);
+
+ const QSignalBlocker b_Width(this->ui->spinBox_width);
+ const QSignalBlocker b_Height(this->ui->spinBox_height);
+ this->ui->spinBox_width->setValue(metatilesWide);
+ this->ui->spinBox_height->setValue(metatilesTall);
});
scene->addItem(outline);
@@ -171,7 +146,7 @@ void ResizeLayoutPopup::setupLayoutView() {
this->scale = 1.0;
QRectF rect = this->outline->rect();
- const int marginSize = 10 * 16; // Leave a margin of 10 metatiles around the map
+ const int marginSize = 10 * Metatile::pixelWidth(); // Leave a margin of 10 metatiles around the map
rect += QMargins(marginSize, marginSize, marginSize, marginSize);
this->ui->graphicsView->fitInView(rect, Qt::KeepAspectRatio);
}
@@ -179,25 +154,23 @@ void ResizeLayoutPopup::setupLayoutView() {
void ResizeLayoutPopup::on_spinBox_width_valueChanged(int value) {
if (!this->outline) return;
QRectF rect = this->outline->rect();
- this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), value * 16, rect.height()));
+ this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), value * Metatile::pixelWidth(), rect.height()));
}
void ResizeLayoutPopup::on_spinBox_height_valueChanged(int value) {
if (!this->outline) return;
QRectF rect = this->outline->rect();
- this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), rect.width(), value * 16));
+ this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), rect.width(), value * Metatile::pixelHeight()));
}
/// Result is the number of metatiles to add (or subtract) to each side of the map after dimension changes
QMargins ResizeLayoutPopup::getResult() {
QMargins result = QMargins();
-
- result.setLeft(this->layoutPixmap->x() - this->outline->rect().left());
- result.setTop(this->layoutPixmap->y() - this->outline->rect().top());
- result.setRight(this->outline->rect().right() - (this->layoutPixmap->x() + this->layoutPixmap->pixmap().width()));
- result.setBottom(this->outline->rect().bottom() - (this->layoutPixmap->y() + this->layoutPixmap->pixmap().height()));
-
- return result / 16;
+ result.setLeft((this->layoutPixmap->x() - this->outline->rect().left()) / Metatile::pixelWidth());
+ result.setTop((this->layoutPixmap->y() - this->outline->rect().top()) / Metatile::pixelHeight());
+ result.setRight((this->outline->rect().right() - (this->layoutPixmap->x() + this->layoutPixmap->pixmap().width())) / Metatile::pixelWidth());
+ result.setBottom((this->outline->rect().bottom() - (this->layoutPixmap->y() + this->layoutPixmap->pixmap().height())) / Metatile::pixelHeight());
+ return result;
}
QSize ResizeLayoutPopup::getBorderResult() {
diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp
index e15824ad..bd001e59 100644
--- a/src/ui/selectablepixmapitem.cpp
+++ b/src/ui/selectablepixmapitem.cpp
@@ -1,7 +1,7 @@
#include "selectablepixmapitem.h"
#include
-QPoint SelectablePixmapItem::getSelectionDimensions()
+QPoint SelectablePixmapItem::getSelectionDimensions() const
{
return QPoint(abs(this->selectionOffsetX) + 1, abs(this->selectionOffsetY) + 1);
}
@@ -19,8 +19,8 @@ void SelectablePixmapItem::select(int x, int y, int width, int height)
{
this->selectionInitialX = x;
this->selectionInitialY = y;
- this->selectionOffsetX = qMax(0, qMin(width, this->maxSelectionWidth));
- this->selectionOffsetY = qMax(0, qMin(height, this->maxSelectionHeight));
+ this->selectionOffsetX = qBound(0, width, this->maxSelectionWidth);
+ this->selectionOffsetY = qBound(0, height, this->maxSelectionHeight);
this->draw();
emit this->selectionChanged(x, y, width, height);
}
diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp
index 6fe96f0f..4df24065 100644
--- a/src/ui/tileseteditor.cpp
+++ b/src/ui/tileseteditor.cpp
@@ -24,8 +24,10 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
hasUnsavedChanges(false)
{
setAttribute(Qt::WA_DeleteOnClose);
- setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
ui->setupUi(this);
+
+ setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
+
connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::setXFlip);
connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::setYFlip);
@@ -35,6 +37,20 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save);
+ connect(ui->actionImport_Primary_Tiles_Image, &QAction::triggered, [this] { importTilesetTiles(this->primaryTileset); });
+ connect(ui->actionImport_Secondary_Tiles_Image, &QAction::triggered, [this] { importTilesetTiles(this->secondaryTileset); });
+
+ connect(ui->actionImport_Primary_AdvanceMap_Metatiles, &QAction::triggered, [this] { importAdvanceMapMetatiles(this->primaryTileset); });
+ connect(ui->actionImport_Secondary_AdvanceMap_Metatiles, &QAction::triggered, [this] { importAdvanceMapMetatiles(this->secondaryTileset); });
+
+ connect(ui->actionExport_Primary_Tiles_Image, &QAction::triggered, [this] { exportTilesImage(this->primaryTileset); });
+ connect(ui->actionExport_Secondary_Tiles_Image, &QAction::triggered, [this] { exportTilesImage(this->secondaryTileset); });
+
+ connect(ui->actionExport_Primary_Porytiles_Layer_Images, &QAction::triggered, [this] { exportPorytilesLayerImages(this->primaryTileset); });
+ connect(ui->actionExport_Secondary_Porytiles_Layer_Images, &QAction::triggered, [this] { exportPorytilesLayerImages(this->secondaryTileset); });
+
+ connect(ui->actionExport_Metatiles_Image, &QAction::triggered, [this] { exportMetatilesImage(); });
+
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
@@ -74,6 +90,7 @@ TilesetEditor::~TilesetEditor()
delete selectedTileScene;
delete metatileLayersScene;
delete copiedMetatile;
+ delete metatileImageExportSettings;
this->metatileHistory.clear();
}
@@ -198,7 +215,7 @@ void TilesetEditor::setRawAttributesVisible(bool visible) {
void TilesetEditor::initMetatileSelector()
{
- this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout);
+ this->metatileSelector = new TilesetEditorMetatileSelector(projectConfig.metatileSelectorWidth, this->primaryTileset, this->secondaryTileset, this->layout);
connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileChanged,
this, &TilesetEditor::onHoveredMetatileChanged);
connect(this->metatileSelector, &TilesetEditorMetatileSelector::hoveredMetatileCleared,
@@ -227,8 +244,9 @@ void TilesetEditor::initMetatileLayersItem() {
this, &TilesetEditor::onMetatileLayerTileChanged);
connect(this->metatileLayersItem, &MetatileLayersItem::selectedTilesChanged,
this, &TilesetEditor::onMetatileLayerSelectionChanged);
- connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged,
- this, &TilesetEditor::onHoveredTileChanged);
+ connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileChanged, [this](const Tile &tile) {
+ onHoveredTileChanged(tile);
+ });
connect(this->metatileLayersItem, &MetatileLayersItem::hoveredTileCleared,
this, &TilesetEditor::onHoveredTileCleared);
@@ -244,8 +262,9 @@ void TilesetEditor::initMetatileLayersItem() {
void TilesetEditor::initTileSelector()
{
this->tileSelector = new TilesetEditorTileSelector(this->primaryTileset, this->secondaryTileset, projectConfig.getNumLayersInMetatile());
- connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged,
- this, &TilesetEditor::onHoveredTileChanged);
+ connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) {
+ onHoveredTileChanged(tileId);
+ });
connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared,
this, &TilesetEditor::onHoveredTileCleared);
connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged,
@@ -374,19 +393,21 @@ void TilesetEditor::drawSelectedTiles() {
return;
}
+ const int imgTileWidth = 16;
+ const int imgTileHeight = 16;
this->selectedTileScene->clear();
QList tiles = this->tileSelector->getSelectedTiles();
QPoint dimensions = this->tileSelector->getSelectionDimensions();
- QImage selectionImage(16 * dimensions.x(), 16 * dimensions.y(), QImage::Format_RGBA8888);
+ QImage selectionImage(imgTileWidth * dimensions.x(), imgTileHeight * dimensions.y(), QImage::Format_RGBA8888);
QPainter painter(&selectionImage);
int tileIndex = 0;
for (int j = 0; j < dimensions.y(); j++) {
for (int i = 0; i < dimensions.x(); i++) {
auto tile = tiles.at(tileIndex);
- QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(16, 16);
+ QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true).scaled(imgTileWidth, imgTileHeight);
tile.flip(&tileImage);
tileIndex++;
- painter.drawImage(i * 16, j * 16, tileImage);
+ painter.drawImage(i * imgTileWidth, j * imgTileHeight, tileImage);
}
}
@@ -437,8 +458,17 @@ void TilesetEditor::queueMetatileReload(uint16_t metatileId) {
this->metatileReloadQueue.insert(metatileId);
}
-void TilesetEditor::onHoveredTileChanged(uint16_t tile) {
- this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tile, 3)));
+void TilesetEditor::onHoveredTileChanged(const Tile &tile) {
+ this->ui->statusbar->showMessage(QString("Tile: %1, Palette: %2%3%4")
+ .arg(Util::toHexString(tile.tileId, 3))
+ .arg(QString::number(tile.palette))
+ .arg(tile.xflip ? ", X-flipped" : "")
+ .arg(tile.yflip ? ", Y-flipped" : "")
+ );
+}
+
+void TilesetEditor::onHoveredTileChanged(uint16_t tileId) {
+ this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tileId, 3)));
}
void TilesetEditor::onHoveredTileCleared() {
@@ -694,17 +724,8 @@ bool TilesetEditor::save() {
return success;
}
-void TilesetEditor::on_actionImport_Primary_Tiles_triggered()
-{
- this->importTilesetTiles(this->primaryTileset, true);
-}
-
-void TilesetEditor::on_actionImport_Secondary_Tiles_triggered()
-{
- this->importTilesetTiles(this->secondaryTileset, false);
-}
-
-void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) {
+void TilesetEditor::importTilesetTiles(Tileset *tileset) {
+ bool primary = !tileset->is_secondary;
QString descriptor = primary ? "primary" : "secondary";
QString descriptorCaps = primary ? "Primary" : "Secondary";
@@ -725,18 +746,20 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) {
} else {
logError(QString("Failed to open image file: '%1'").arg(filepath));
}
- if (image.width() == 0 || image.height() == 0 || image.width() % 8 != 0 || image.height() % 8 != 0) {
+ if (image.width() == 0 || image.height() == 0 || image.width() % Tile::pixelWidth() != 0 || image.height() % Tile::pixelHeight() != 0) {
ErrorMessage::show(QStringLiteral("Failed to import tiles."),
- QString("The image dimensions (%1 x %2) are invalid. Width and height must be multiples of 8 pixels.")
+ QString("The image dimensions (%1x%2) are invalid. The dimensions must be a multiple of %3x%4 pixels.")
.arg(image.width())
- .arg(image.height()),
+ .arg(image.height())
+ .arg(Tile::pixelWidth())
+ .arg(Tile::pixelHeight()),
this);
return;
}
// Validate total number of tiles in image.
- int numTilesWide = image.width() / 8;
- int numTilesHigh = image.height() / 8;
+ int numTilesWide = image.width() / Tile::pixelWidth();
+ int numTilesHigh = image.height() / Tile::pixelHeight();
int totalTiles = numTilesHigh * numTilesWide;
int maxAllowedTiles = primary ? Project::getNumTilesPrimary() : Project::getNumTilesTotal() - Project::getNumTilesPrimary();
if (totalTiles > maxAllowedTiles) {
@@ -774,7 +797,10 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) {
image = image.convertToFormat(QImage::Format::Format_Indexed8, colorTable);
}
- tileset->loadTilesImage(&image);
+ if (!tileset->loadTilesImage(&image)) {
+ RecentErrorMessage::show(QStringLiteral("Failed to import tiles."), this);
+ return;
+ }
this->refresh();
this->hasUnsavedChanges = true;
}
@@ -822,7 +848,7 @@ void TilesetEditor::on_actionChange_Metatiles_Count_triggered()
primarySpinBox->setMinimum(1);
secondarySpinBox->setMinimum(1);
primarySpinBox->setMaximum(Project::getNumMetatilesPrimary());
- secondarySpinBox->setMaximum(Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary());
+ secondarySpinBox->setMaximum(Project::getNumMetatilesSecondary());
primarySpinBox->setValue(this->primaryTileset->numMetatiles());
secondarySpinBox->setValue(this->secondaryTileset->numMetatiles());
form.addRow(new QLabel("Primary Tileset"), primarySpinBox);
@@ -968,62 +994,70 @@ void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel)
this->commitMetatileAndLabelChange(prevMetatile, prevLabel);
}
-void TilesetEditor::on_actionExport_Primary_Tiles_Image_triggered()
-{
- QString defaultName = QString("%1_Tiles_Pal%2").arg(this->primaryTileset->name).arg(this->paletteId);
- QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
- QString filepath = FileDialog::getSaveFileName(this, "Export Primary Tiles Image", defaultFilepath, "Image Files (*.png)");
+void TilesetEditor::exportTilesImage(Tileset *tileset) {
+ bool primary = !tileset->is_secondary;
+ QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId);
+ QString filepath = FileDialog::getSaveFileName(this, QString("Export %1 Tiles Image").arg(primary ? "Primary" : "Secondary"), defaultFilepath, "Image Files (*.png)");
if (!filepath.isEmpty()) {
- QImage image = this->tileSelector->buildPrimaryTilesIndexedImage();
+ QImage image = primary ? this->tileSelector->buildPrimaryTilesIndexedImage() : this->tileSelector->buildSecondaryTilesIndexedImage();
exportIndexed4BPPPng(image, filepath);
}
}
-void TilesetEditor::on_actionExport_Secondary_Tiles_Image_triggered()
-{
- QString defaultName = QString("%1_Tiles_Pal%2").arg(this->secondaryTileset->name).arg(this->paletteId);
- QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
- QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Tiles Image", defaultFilepath, "Image Files (*.png)");
- if (!filepath.isEmpty()) {
- QImage image = this->tileSelector->buildSecondaryTilesIndexedImage();
- exportIndexed4BPPPng(image, filepath);
+// There are many more options for exporting metatile images than tile images, so we open a separate dialog to ask the user for settings.
+void TilesetEditor::exportMetatilesImage() {
+ if (!this->metatileImageExportSettings) {
+ this->metatileImageExportSettings = new MetatileImageExporter::Settings;
+ }
+ auto dialog = new MetatileImageExporter(this, this->primaryTileset, this->secondaryTileset, this->metatileImageExportSettings);
+ dialog->setAttribute(Qt::WA_DeleteOnClose);
+ dialog->open();
+}
+
+void TilesetEditor::exportPorytilesLayerImages(Tileset *tileset) {
+ QString dir = FileDialog::getExistingDirectory(this, QStringLiteral("Choose Folder to Export Images"));
+ if (dir.isEmpty()) {
+ return;
+ }
+
+ MetatileImageExporter layerExporter(this, this->primaryTileset, this->secondaryTileset);
+ MetatileImageExporter::Settings settings = {};
+ settings.usePrimaryTileset = !tileset->is_secondary;
+ settings.useSecondaryTileset = tileset->is_secondary;
+
+ QMap images;
+ QStringList pathCollisions;
+ for (int i = 0; i < 3; i++) {
+ settings.layerOrder.clear();
+ settings.layerOrder[i] = true;
+ layerExporter.applySettings(settings);
+
+ QString filename = layerExporter.getDefaultFileName();
+ QString path = QString("%1/%2").arg(dir).arg(filename);
+ if (QFileInfo::exists(path)) {
+ pathCollisions.append(filename);
+ }
+ images[path] = layerExporter.getImage();
+ }
+
+ if (!pathCollisions.isEmpty()) {
+ QString message = QString("The following files will be overwritten, are you sure you want to export?\n\n%1").arg(pathCollisions.join("\n"));
+ auto reply = QuestionMessage::show(message, this);
+ if (reply != QMessageBox::Yes) {
+ return;
+ }
+ }
+
+ for (auto it = images.constBegin(); it != images.constEnd(); it++) {
+ QString path = it.key();
+ if (!it.value().save(path)) {
+ logError(QString("Failed to save Porytiles layer image '%1'.").arg(path));
+ }
}
}
-void TilesetEditor::on_actionExport_Primary_Metatiles_Image_triggered()
-{
- QString defaultName = QString("%1_Metatiles").arg(this->primaryTileset->name);
- QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
- QString filepath = FileDialog::getSaveFileName(this, "Export Primary Metatiles Image", defaultFilepath, "Image Files (*.png)");
- if (!filepath.isEmpty()) {
- QImage image = this->metatileSelector->buildPrimaryMetatilesImage();
- image.save(filepath, "PNG");
- }
-}
-
-void TilesetEditor::on_actionExport_Secondary_Metatiles_Image_triggered()
-{
- QString defaultName = QString("%1_Metatiles").arg(this->secondaryTileset->name);
- QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultName);
- QString filepath = FileDialog::getSaveFileName(this, "Export Secondary Metatiles Image", defaultFilepath, "Image Files (*.png)");
- if (!filepath.isEmpty()) {
- QImage image = this->metatileSelector->buildSecondaryMetatilesImage();
- image.save(filepath, "PNG");
- }
-}
-
-void TilesetEditor::on_actionImport_Primary_Metatiles_triggered()
-{
- this->importTilesetMetatiles(this->primaryTileset, true);
-}
-
-void TilesetEditor::on_actionImport_Secondary_Metatiles_triggered()
-{
- this->importTilesetMetatiles(this->secondaryTileset, false);
-}
-
-void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary)
-{
+void TilesetEditor::importAdvanceMapMetatiles(Tileset *tileset) {
+ bool primary = !tileset->is_secondary;
QString descriptorCaps = primary ? "Primary" : "Secondary";
QString filepath = FileDialog::getOpenFileName(this, QString("Import %1 Tileset Metatiles from Advance Map 1.92").arg(descriptorCaps), "", "Advance Map 1.92 Metatile Files (*.bvd)");
@@ -1041,7 +1075,7 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary)
// TODO: This is crude because it makes a history entry for every newly-imported metatile.
// Revisit this when tiles and num metatiles are added to tileset editory history.
- int metatileIdBase = primary ? 0 : Project::getNumMetatilesPrimary();
+ uint16_t metatileIdBase = tileset->firstMetatileId();
for (int i = 0; i < metatiles.length(); i++) {
if (i >= tileset->numMetatiles()) {
break;
diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp
index 5026a49b..bba51e77 100644
--- a/src/ui/tileseteditormetatileselector.cpp
+++ b/src/ui/tileseteditormetatileselector.cpp
@@ -3,17 +3,20 @@
#include "project.h"
#include
-TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout)
- : SelectablePixmapItem(16, 16, 1, 1) {
+// TODO: This class has a decent bit of overlap with the MetatileSelector class.
+// They should be refactored to inherit from a single parent class.
+
+TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(int numMetatilesWide, Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout)
+ : SelectablePixmapItem(32, 32, 1, 1),
+ numMetatilesWide(qMax(numMetatilesWide, 1)) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
- this->numMetatilesWide = 8;
this->layout = layout;
setAcceptHoverEvents(true);
this->usedMetatiles.resize(Project::getNumMetatilesTotal());
}
-int TilesetEditorMetatileSelector::numRows(int numMetatiles) {
+int TilesetEditorMetatileSelector::numRows(int numMetatiles) const {
int numMetatilesHigh = numMetatiles / this->numMetatilesWide;
if (numMetatiles % this->numMetatilesWide != 0) {
// Round up height for incomplete last row
@@ -22,67 +25,29 @@ int TilesetEditorMetatileSelector::numRows(int numMetatiles) {
return numMetatilesHigh;
}
-int TilesetEditorMetatileSelector::numRows() {
+int TilesetEditorMetatileSelector::numRows() const {
return this->numRows(this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles());
}
int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const {
- // We round up the number of primary metatiles to keep the tilesets on separate rows.
- return ceil((double)this->primaryTileset->numMetatiles() / this->numMetatilesWide) * this->numMetatilesWide;
-}
-
-QImage TilesetEditorMetatileSelector::buildAllMetatilesImage() {
- return this->buildImage(0, this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles());
-}
-
-QImage TilesetEditorMetatileSelector::buildPrimaryMetatilesImage() {
- return this->buildImage(0, this->primaryTileset->numMetatiles());
-}
-
-QImage TilesetEditorMetatileSelector::buildSecondaryMetatilesImage() {
- return this->buildImage(Project::getNumMetatilesPrimary(), this->secondaryTileset->numMetatiles());
-}
-
-QImage TilesetEditorMetatileSelector::buildImage(int metatileIdStart, int numMetatiles) {
- int numMetatilesHigh = this->numRows(numMetatiles);
- int numPrimary = this->numPrimaryMetatilesRounded();
- int maxPrimary = Project::getNumMetatilesPrimary();
- bool includesPrimary = metatileIdStart < maxPrimary;
-
- QImage image(this->numMetatilesWide * this->cellWidth, numMetatilesHigh * this->cellHeight, QImage::Format_RGBA8888);
- image.fill(Qt::magenta);
- QPainter painter(&image);
- for (int i = 0; i < numMetatiles; i++) {
- int metatileId = i + metatileIdStart;
- if (includesPrimary && metatileId >= numPrimary)
- metatileId += maxPrimary - numPrimary; // Skip over unused region of primary tileset
- QImage metatile_image = getMetatileImage(
- metatileId,
- this->primaryTileset,
- this->secondaryTileset,
- this->layout->metatileLayerOrder,
- this->layout->metatileLayerOpacity,
- true)
- .scaled(this->cellWidth, this->cellHeight);
- int map_y = i / this->numMetatilesWide;
- int map_x = i % this->numMetatilesWide;
- QPoint metatile_origin = QPoint(map_x * this->cellWidth, map_y * this->cellHeight);
- painter.drawImage(metatile_origin, metatile_image);
- }
- painter.end();
- return image;
+ if (!this->primaryTileset)
+ return 0;
+ return Util::roundUpToMultiple(this->primaryTileset->numMetatiles(), this->numMetatilesWide);
}
void TilesetEditorMetatileSelector::drawMetatile(uint16_t metatileId) {
- QPoint pos = getMetatileIdCoords(metatileId);
+ bool ok;
+ QPoint pos = metatileIdToPos(metatileId, &ok);
+ if (!ok)
+ return;
QPainter painter(&this->baseImage);
QImage metatile_image = getMetatileImage(
metatileId,
this->primaryTileset,
this->secondaryTileset,
- this->layout->metatileLayerOrder,
- this->layout->metatileLayerOpacity,
+ this->layout->metatileLayerOrder(),
+ this->layout->metatileLayerOpacity(),
true)
.scaled(this->cellWidth, this->cellHeight);
painter.drawImage(QPoint(pos.x() * this->cellWidth, pos.y() * this->cellHeight), metatile_image);
@@ -97,7 +62,13 @@ void TilesetEditorMetatileSelector::drawSelectedMetatile() {
}
void TilesetEditorMetatileSelector::updateBasePixmap() {
- this->baseImage = buildAllMetatilesImage();
+ this->baseImage = getMetatileSheetImage(this->primaryTileset,
+ this->secondaryTileset,
+ this->numMetatilesWide,
+ this->layout->metatileLayerOrder(),
+ this->layout->metatileLayerOpacity(),
+ QSize(this->cellWidth, this->cellHeight),
+ true);
this->basePixmap = QPixmap::fromImage(this->baseImage);
}
@@ -114,9 +85,11 @@ void TilesetEditorMetatileSelector::draw() {
}
bool TilesetEditorMetatileSelector::select(uint16_t metatileId) {
- if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) return false;
- QPoint coords = this->getMetatileIdCoords(metatileId);
- SelectablePixmapItem::select(coords.x(), coords.y(), 0, 0);
+ bool ok;
+ QPoint pos = metatileIdToPos(metatileId, &ok);
+ if (!ok)
+ return false;
+ SelectablePixmapItem::select(pos);
this->selectedMetatileId = metatileId;
emit selectedMetatileChanged(metatileId);
return true;
@@ -131,32 +104,19 @@ void TilesetEditorMetatileSelector::setTilesets(Tileset *primaryTileset, Tileset
}
void TilesetEditorMetatileSelector::updateSelectedMetatile() {
- QPoint origin = this->getSelectionStart();
- uint16_t metatileId = this->getMetatileId(origin.x(), origin.y());
- if (Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
- this->selectedMetatileId = metatileId;
- else
- this->selectedMetatileId = Project::getNumMetatilesPrimary() + this->secondaryTileset->numMetatiles() - 1;
+ bool ok;
+ uint16_t metatileId = posToMetatileId(getSelectionStart(), &ok);
+ if (!ok)
+ return;
+
+ this->selectedMetatileId = metatileId;
emit selectedMetatileChanged(this->selectedMetatileId);
}
-uint16_t TilesetEditorMetatileSelector::getSelectedMetatileId() {
- return this->selectedMetatileId;
-}
-
-uint16_t TilesetEditorMetatileSelector::getMetatileId(int x, int y) {
- int index = y * this->numMetatilesWide + x;
- int numPrimary = numPrimaryMetatilesRounded();
- if (index < numPrimary) {
- return static_cast(index);
- } else {
- return static_cast(Project::getNumMetatilesPrimary() + index - numPrimary);
- }
-}
-
bool TilesetEditorMetatileSelector::shouldAcceptEvent(QGraphicsSceneMouseEvent *event) {
- QPoint pos = this->getCellPos(event->pos());
- return Tileset::metatileIsValid(getMetatileId(pos.x(), pos.y()), this->primaryTileset, this->secondaryTileset);
+ bool ok;
+ posToMetatileId(getCellPos(event->pos()), &ok);
+ return ok;
}
void TilesetEditorMetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) {
@@ -179,29 +139,64 @@ void TilesetEditorMetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *
}
void TilesetEditorMetatileSelector::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
- QPoint pos = this->getCellPos(event->pos());
- uint16_t metatileId = this->getMetatileId(pos.x(), pos.y());
- emit this->hoveredMetatileChanged(metatileId);
+ bool ok;
+ uint16_t metatileId = posToMetatileId(getCellPos(event->pos()), &ok);
+ if (ok) {
+ emit this->hoveredMetatileChanged(metatileId);
+ } else {
+ emit this->hoveredMetatileCleared();
+ }
}
void TilesetEditorMetatileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) {
emit this->hoveredMetatileCleared();
}
-QPoint TilesetEditorMetatileSelector::getMetatileIdCoords(uint16_t metatileId) {
- if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset))
- {
- // Invalid metatile id.
- return QPoint(0, 0);
- }
- int index = metatileId < Project::getNumMetatilesPrimary()
- ? metatileId
- : metatileId - Project::getNumMetatilesPrimary() + this->numPrimaryMetatilesRounded();
- return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
+uint16_t TilesetEditorMetatileSelector::posToMetatileId(const QPoint &pos, bool *ok) const {
+ return posToMetatileId(pos.x(), pos.y(), ok);
}
-QPoint TilesetEditorMetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) {
- QPoint pos = getMetatileIdCoords(metatileId);
+uint16_t TilesetEditorMetatileSelector::posToMetatileId(int x, int y, bool *ok) const {
+ if (ok) *ok = true;
+ int index = y * this->numMetatilesWide + x;
+ uint16_t metatileId = static_cast(index);
+ if (this->primaryTileset && this->primaryTileset->contains(metatileId)) {
+ return metatileId;
+ }
+
+ // There's some extra handling here because we round the tilesets to keep them on separate rows.
+ // This means if the maximum number of primary metatiles is not divisible by the metatile width
+ // then the metatiles we used to round the primary tileset would have the index of valid secondary metatiles.
+ // These need to be ignored, or they'll appear to be duplicates of the subseqeunt secondary metatiles.
+ int numPrimaryRounded = numPrimaryMetatilesRounded();
+ int firstSecondaryRow = numPrimaryRounded / this->numMetatilesWide;
+ metatileId = static_cast(Project::getNumMetatilesPrimary() + index - numPrimaryRounded);
+ if (this->secondaryTileset && this->secondaryTileset->contains(metatileId) && y >= firstSecondaryRow) {
+ return metatileId;
+ }
+
+ if (ok) *ok = false;
+ return 0;
+}
+
+QPoint TilesetEditorMetatileSelector::metatileIdToPos(uint16_t metatileId, bool *ok) const {
+ if (this->primaryTileset && this->primaryTileset->contains(metatileId)) {
+ if (ok) *ok = true;
+ int index = metatileId;
+ return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
+ }
+ if (this->secondaryTileset && this->secondaryTileset->contains(metatileId)) {
+ if (ok) *ok = true;
+ int index = metatileId - Project::getNumMetatilesPrimary() + numPrimaryMetatilesRounded();
+ return QPoint(index % this->numMetatilesWide, index / this->numMetatilesWide);
+ }
+
+ if (ok) *ok = false;
+ return QPoint(0,0);
+}
+
+QPoint TilesetEditorMetatileSelector::getMetatileIdCoordsOnWidget(uint16_t metatileId) const {
+ QPoint pos = metatileIdToPos(metatileId);
pos.rx() = (pos.x() * this->cellWidth) + (this->cellWidth / 2);
pos.ry() = (pos.y() * this->cellHeight) + (this->cellHeight / 2);
return pos;
diff --git a/src/ui/tileseteditortileselector.cpp b/src/ui/tileseteditortileselector.cpp
index 034a86a7..95d5be6c 100644
--- a/src/ui/tileseteditortileselector.cpp
+++ b/src/ui/tileseteditortileselector.cpp
@@ -4,7 +4,7 @@
#include
#include
-QPoint TilesetEditorTileSelector::getSelectionDimensions() {
+QPoint TilesetEditorTileSelector::getSelectionDimensions() const {
if (this->externalSelection) {
return QPoint(this->externalSelectionWidth, this->externalSelectionHeight);
} else {
@@ -12,53 +12,40 @@ QPoint TilesetEditorTileSelector::getSelectionDimensions() {
}
}
-void TilesetEditorTileSelector::draw() {
- if (!this->primaryTileset || !this->secondaryTileset) {
- this->setPixmap(QPixmap());
+void TilesetEditorTileSelector::updateBasePixmap() {
+ if (!this->primaryTileset || !this->secondaryTileset || this->numTilesWide == 0) {
+ this->basePixmap = QPixmap();
+ return;
}
int totalTiles = Project::getNumTilesTotal();
- int primaryLength = this->primaryTileset->tiles.length();
- int secondaryLength = this->secondaryTileset->tiles.length();
int height = totalTiles / this->numTilesWide;
- QList palette = Tileset::getPalette(this->paletteId, this->primaryTileset, this->secondaryTileset, true);
QImage image(this->numTilesWide * this->cellWidth, height * this->cellHeight, QImage::Format_RGBA8888);
QPainter painter(&image);
- for (uint16_t tile = 0; tile < totalTiles; tile++) {
- QImage tileImage;
- if (tile < primaryLength) {
- tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight);
- } else if (tile < Project::getNumTilesPrimary()) {
- tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888);
- tileImage.fill(palette.at(0));
- } else if (tile < Project::getNumTilesPrimary() + secondaryLength) {
- tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight);
- } else {
- tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888);
- QPainter painter(&tileImage);
- painter.fillRect(0, 0, this->cellWidth, this->cellHeight, palette.at(0));
- }
-
- int y = tile / this->numTilesWide;
- int x = tile % this->numTilesWide;
- QPoint origin = QPoint(x * this->cellWidth, y * this->cellHeight);
- painter.drawImage(origin, tileImage);
+ for (uint16_t tileId = 0; tileId < totalTiles; tileId++) {
+ QImage tileImage = getPalettedTileImage(tileId, this->primaryTileset, this->secondaryTileset, this->paletteId, true)
+ .scaled(this->cellWidth, this->cellHeight);
+ int x = (tileId % this->numTilesWide) * this->cellWidth;
+ int y = (tileId / this->numTilesWide) * this->cellHeight;
+ painter.drawImage(x, y, tileImage);
}
if (this->showDivider) {
- int row = this->primaryTileset->tiles.length() / this->numTilesWide;
- if (this->primaryTileset->tiles.length() % this->numTilesWide != 0) {
- // Round up height for incomplete last row
- row++;
- }
+ int row = Util::roundUpToMultiple(Project::getNumTilesPrimary(), this->numTilesWide) / this->numTilesWide;
const int y = row * this->cellHeight;
painter.setPen(Qt::white);
painter.drawLine(0, y, this->numTilesWide * this->cellWidth, y);
}
painter.end();
- this->setPixmap(QPixmap::fromImage(image));
+ this->basePixmap = QPixmap::fromImage(image);
+}
+
+void TilesetEditorTileSelector::draw() {
+ if (this->basePixmap.isNull())
+ updateBasePixmap();
+ setPixmap(this->basePixmap);
if (!this->externalSelection || (this->externalSelectionWidth == 1 && this->externalSelectionHeight == 1)) {
this->drawSelection();
@@ -82,12 +69,14 @@ void TilesetEditorTileSelector::highlight(uint16_t tile) {
void TilesetEditorTileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
+ this->updateBasePixmap();
this->draw();
}
void TilesetEditorTileSelector::setPaletteId(int paletteId) {
this->paletteId = paletteId;
this->paletteChanged = true;
+ this->updateBasePixmap();
this->draw();
}
@@ -213,7 +202,7 @@ void TilesetEditorTileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) {
}
QPoint TilesetEditorTileSelector::getTileCoords(uint16_t tile) {
- if (tile >= Project::getNumTilesTotal())
+ if (tile >= Project::getNumTilesTotal() || this->numTilesWide == 0)
{
// Invalid tile.
return QPoint(0, 0);
@@ -233,30 +222,30 @@ QImage TilesetEditorTileSelector::buildPrimaryTilesIndexedImage() {
if (!this->primaryTileset)
return QImage();
- return buildImage(0, this->primaryTileset->tiles.length());
+ return buildImage(0, this->primaryTileset->numTiles());
}
QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() {
if (!this->secondaryTileset)
return QImage();
- return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->tiles.length());
+ return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->numTiles());
}
QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) {
- const int tileWidth = 8;
- const int tileHeight = 8;
+ if (this->numTilesWide == 0)
+ return QImage();
+
int height = qCeil(numTiles / static_cast(this->numTilesWide));
- QImage image(this->numTilesWide * tileWidth, height * tileHeight, QImage::Format_RGBA8888);
+ QImage image(this->numTilesWide * Tile::pixelWidth(), height * Tile::pixelHeight(), QImage::Format_RGBA8888);
image.fill(0);
QPainter painter(&image);
for (int i = 0; i < numTiles; i++) {
QImage tileImage = getGreyscaleTileImage(tileIdStart + i, this->primaryTileset, this->secondaryTileset);
- int y = i / this->numTilesWide;
- int x = i % this->numTilesWide;
- QPoint origin = QPoint(x * tileWidth, y * tileHeight);
- painter.drawImage(origin, tileImage);
+ int x = (i % this->numTilesWide) * Tile::pixelWidth();
+ int y = (i / this->numTilesWide) * Tile::pixelHeight();
+ painter.drawImage(x, y, tileImage);
}
painter.end();
diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp
index 095d754e..7155aa67 100644
--- a/src/ui/uintspinbox.cpp
+++ b/src/ui/uintspinbox.cpp
@@ -2,24 +2,24 @@
#include
UIntSpinBox::UIntSpinBox(QWidget *parent)
- : QAbstractSpinBox(parent)
+ : QAbstractSpinBox(parent),
+ m_minimum(0),
+ m_maximum(99),
+ m_value(m_minimum),
+ m_singleStep(1),
+ m_displayIntegerBase(10),
+ m_hasPadding(false),
+ m_numChars(2)
{
// Don't let scrolling hijack focus.
setFocusPolicy(Qt::StrongFocus);
- m_minimum = 0;
- m_maximum = 99;
- m_value = m_minimum;
- m_displayIntegerBase = 10;
- m_numChars = 2;
- m_hasPadding = false;
-
this->updateEdit();
connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(onEditFinished()));
};
void UIntSpinBox::setValue(uint32_t val) {
- val = qMax(m_minimum, qMin(m_maximum, val));
+ val = qBound(m_minimum, val, m_maximum);
if (m_value != val) {
m_value = val;
emit valueChanged(m_value);
@@ -69,6 +69,12 @@ void UIntSpinBox::setRange(uint32_t min, uint32_t max) {
this->updateEdit();
}
+void UIntSpinBox::setSingleStep(uint32_t val) {
+ if (m_singleStep != val) {
+ m_singleStep = val;
+ }
+}
+
void UIntSpinBox::setPrefix(const QString &prefix) {
if (m_prefix != prefix) {
m_prefix = prefix;
@@ -132,6 +138,7 @@ void UIntSpinBox::onEditFinished() {
}
void UIntSpinBox::stepBy(int steps) {
+ steps *= m_singleStep;
auto newValue = m_value;
if (steps < 0 && newValue + steps > newValue) {
newValue = 0;
diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp
index fd2d9216..ea5b4814 100644
--- a/src/ui/wildmonchart.cpp
+++ b/src/ui/wildmonchart.cpp
@@ -367,7 +367,7 @@ QChart* WildMonChart::createLevelDistributionChart() {
series->attachAxis(axisY);
// We round the y-axis max up to a multiple of 5.
- axisY->setMax(Util::roundUp(qCeil(axisY->max()), 5));
+ axisY->setMax(Util::roundUpToMultiple(qCeil(axisY->max()), 5));
return chart;
}