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/tileseteditor.ui b/forms/tileseteditor.ui
index 6c696bf3..36aa92f7 100644
--- a/forms/tileseteditor.ui
+++ b/forms/tileseteditor.ui
@@ -11,7 +11,7 @@
- Qt::FocusPolicy::ClickFocus
+ Qt::ClickFocus
Tileset Editor
@@ -21,7 +21,7 @@
-
- Qt::Orientation::Horizontal
+ Qt::Horizontal
false
@@ -34,10 +34,10 @@
- QFrame::Shape::NoFrame
+ QFrame::NoFrame
- QFrame::Shadow::Plain
+ QFrame::Plain
@@ -58,14 +58,14 @@
true
- Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop
+ Qt::AlignHCenter|Qt::AlignTop
0
0
- 239
+ 253
659
@@ -88,17 +88,17 @@
-
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
-
- Qt::Orientation::Vertical
+ Qt::Vertical
@@ -121,7 +121,7 @@
- Qt::Orientation::Horizontal
+ Qt::Horizontal
@@ -129,10 +129,10 @@
- QFrame::Shape::NoFrame
+ QFrame::NoFrame
- QFrame::Shadow::Raised
+ QFrame::Raised
@@ -162,14 +162,14 @@
- QFrame::Shape::NoFrame
+ QFrame::NoFrame
- QFrame::Shadow::Raised
+ QFrame::Raised
- QLayout::SizeConstraint::SetMinimumSize
+ QLayout::SetMinimumSize
0
@@ -255,7 +255,7 @@
- QComboBox::InsertPolicy::NoInsert
+ QComboBox::NoInsert
@@ -274,10 +274,10 @@
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
@@ -290,17 +290,17 @@
- QComboBox::InsertPolicy::NoInsert
+ QComboBox::NoInsert
-
- Qt::Orientation::Horizontal
+ Qt::Horizontal
- QSizePolicy::Policy::Maximum
+ QSizePolicy::Maximum
@@ -319,7 +319,7 @@
- QComboBox::InsertPolicy::NoInsert
+ QComboBox::NoInsert
@@ -361,7 +361,7 @@
-
- Qt::Orientation::Vertical
+ Qt::Vertical
@@ -412,7 +412,7 @@
-
- Qt::LayoutDirection::LeftToRight
+ Qt::LeftToRight
@@ -436,10 +436,10 @@
-
- Qt::Orientation::Vertical
+ Qt::Vertical
- QSizePolicy::Policy::Fixed
+ QSizePolicy::Fixed
@@ -464,10 +464,10 @@
- QFrame::Shape::NoFrame
+ QFrame::NoFrame
- QFrame::Shadow::Plain
+ QFrame::Plain
@@ -510,13 +510,13 @@
- QFrame::Shape::StyledPanel
+ QFrame::StyledPanel
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
@@ -526,7 +526,7 @@
-
- Qt::Orientation::Vertical
+ Qt::Vertical
@@ -548,14 +548,14 @@
true
- Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop
+ Qt::AlignHCenter|Qt::AlignTop
0
0
- 499
+ 446
241
@@ -575,17 +575,17 @@
-
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
- Qt::ScrollBarPolicy::ScrollBarAlwaysOff
+ Qt::ScrollBarAlwaysOff
-
- Qt::Orientation::Vertical
+ Qt::Vertical
@@ -602,7 +602,7 @@
-
- Qt::Orientation::Horizontal
+ Qt::Horizontal
@@ -625,18 +625,34 @@
File
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
diff --git a/include/config.h b/include/config.h
index 234fa76d..51c11a74 100644
--- a/include/config.h
+++ b/include/config.h
@@ -54,7 +54,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;
@@ -82,7 +83,7 @@ public:
this->collisionOpacity = 50;
this->collisionZoom = 30;
this->metatilesZoom = 30;
- this->tilesetEditorMetatilesZoom = 45;
+ this->tilesetEditorMetatilesZoom = 30;
this->tilesetEditorTilesZoom = 30;
this->showPlayerView = false;
this->showCursorTile = true;
@@ -351,7 +352,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();
@@ -426,7 +427,7 @@ public:
bool prefabImportPrompted;
bool tilesetsHaveCallback;
bool tilesetsHaveIsCompressed;
- bool setTransparentPixelsBlack;
+ QColor transparencyColor;
bool preserveMatchingOnlyData;
int metatileAttributesSize;
uint32_t metatileBehaviorMask;
diff --git a/include/core/maplayout.h b/include/core/maplayout.h
index 280f3006..8091469a 100644
--- a/include/core/maplayout.h
+++ b/include/core/maplayout.h
@@ -65,14 +65,24 @@ public:
} lastCommitBlocks; // to track map changes
void setMetatileLayerOrder(const QList &layerOrder) { m_metatileLayerOrder = layerOrder; }
- QList metatileLayerOrder() const;
- static void setDefaultMetatileLayerOrder(const QList &layerOrder) { s_defaultMetatileLayerOrder = layerOrder; }
- static QList defaultMetatileLayerOrder();
+ 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; }
- QList metatileLayerOpacity() const;
- static void setDefaultMetatileLayerOpacity(const QList &layerOpacity) { s_defaultMetatileLayerOpacity = layerOpacity; }
- static QList defaultMetatileLayerOpacity();
+ 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;
@@ -166,8 +176,8 @@ private:
QList m_metatileLayerOrder;
QList m_metatileLayerOpacity;
- static QList s_defaultMetatileLayerOrder;
- static QList s_defaultMetatileLayerOpacity;
+ static QList s_globalMetatileLayerOrder;
+ static QList s_globalMetatileLayerOpacity;
signals:
void dimensionsChanged(const QSize &size);
diff --git a/include/core/metatile.h b/include/core/metatile.h
index a1c805d1..a57717fe 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 int tileWidth() { return 2; }
+ static int tileHeight() { return 2; }
+ static int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); }
+ static int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); }
+ static int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); }
+ static 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..d7ae5061 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,10 @@ public:
static int getIndexInTileset(int);
static const uint16_t maxValue;
+
+ static int pixelWidth() { return 8; }
+ static int pixelHeight() { return 8; }
+ static QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); }
};
inline bool operator==(const Tile &a, const Tile &b) {
diff --git a/include/core/tileset.h b/include/core/tileset.h
index abb31ab1..80fef000 100644
--- a/include/core/tileset.h
+++ b/include/core/tileset.h
@@ -38,6 +38,7 @@ public:
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*);
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/ui/imageproviders.h b/include/ui/imageproviders.h
index d3136580..0cfe8511 100644
--- a/include/ui/imageproviders.h
+++ b/include/ui/imageproviders.h
@@ -12,8 +12,10 @@ QImage getCollisionMetatileImage(Block);
QImage getCollisionMetatileImage(int, int);
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 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 *, int, bool useTruePalettes = false);
+QImage getMetatileSheetImage(Tileset *, Tileset *, uint16_t, int, int, const QList &, const QList & = {}, const QSize &size = 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);
diff --git a/include/ui/metatileimageexporter.h b/include/ui/metatileimageexporter.h
new file mode 100644
index 00000000..a6b8f6cd
--- /dev/null
+++ b/include/ui/metatileimageexporter.h
@@ -0,0 +1,101 @@
+#ifndef METATILEIMAGEEXPORTER_H
+#define METATILEIMAGEEXPORTER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "config.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 = 8;
+ bool usePrimaryTileset = true;
+ bool useSecondaryTileset = false;
+ bool renderPlaceholders = false;
+ int transparencyMode = 0;
+ };
+
+ explicit MetatileImageExporter(QWidget *parent, Tileset *primaryTileset, Tileset *secondaryTileset, Settings *savedSettings = nullptr);
+ ~MetatileImageExporter();
+
+protected:
+ virtual void showEvent(QShowEvent *) override;
+ virtual void closeEvent(QCloseEvent *) override;
+
+private:
+ Ui::MetatileImageExporter *ui;
+
+ Tileset *m_primaryTileset;
+ Tileset *m_secondaryTileset;
+ Settings *m_savedSettings;
+
+ QGraphicsScene *m_scene = nullptr;
+ QGraphicsPixmapItem *m_preview = nullptr;
+ bool m_previewUpdateQueued = false;
+ QList m_layerOrder;
+ ProjectConfig m_savedConfig;
+ QList m_transparencyButtons;
+
+ void applySettings(const Settings &settings);
+ void updatePreview();
+ void tryUpdatePreview();
+ void queuePreviewUpdate();
+ void updateTilesetUI();
+ void syncPixelWidth();
+ void syncMetatileWidth();
+ void validateMetatileStart();
+ void validateMetatileEnd();
+ uint16_t getExpectedMetatileStart();
+ uint16_t getExpectedMetatileEnd();
+ void updateMetatileRange();
+ void copyRenderSettings();
+ void restoreRenderSettings();
+ void saveImage();
+ void reset();
+};
+
+#endif // METATILEIMAGEEXPORTER_H
diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h
index b6a60a61..b89709df 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;
@@ -71,10 +72,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 +88,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 +111,10 @@ 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 exportMetatilesImage();
void refresh();
void commitMetatileLabel();
void closeEvent(QCloseEvent*);
@@ -170,6 +161,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..77a5c4e4 100644
--- a/include/ui/tileseteditormetatileselector.h
+++ b/include/ui/tileseteditormetatileselector.h
@@ -21,8 +21,6 @@ public:
uint16_t getSelectedMetatileId();
void updateSelectedMetatile();
QPoint getMetatileIdCoordsOnWidget(uint16_t metatileId);
- QImage buildPrimaryMetatilesImage();
- QImage buildSecondaryMetatilesImage();
QVector usedMetatiles;
bool selectorShowUnused = false;
@@ -56,8 +54,6 @@ private:
void drawFilters();
void drawUnused();
void drawCounts();
- QImage buildAllMetatilesImage();
- QImage buildImage(int metatileIdStart, int numMetatiles);
int numPrimaryMetatilesRounded() const;
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..982fbd37 100644
--- a/porymap.pro
+++ b/porymap.pro
@@ -124,6 +124,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 \
@@ -240,6 +241,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 +291,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 4f5188bd..8b2f24a9 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -283,7 +283,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,7 +293,7 @@ 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) {
@@ -305,6 +305,10 @@ QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &val
return color;
}
+QString KeyValueConfigBase::toConfigColor(const QColor &color) {
+ return color.name().remove("#"); // Our text config treats '#' as the start of a comment.
+}
+
PorymapConfig porymapConfig;
PorymapConfig::PorymapConfig() : KeyValueConfigBase(QStringLiteral("porymap.cfg")) {
@@ -571,7 +575,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) {
@@ -898,8 +902,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") {
@@ -1005,7 +1009,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));
diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp
index a9885042..7ab13ec7 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);
}
@@ -612,23 +615,3 @@ Blockdata Layout::readBlockdata(const QString &path, QString *error) {
return blockdata;
}
-
-QList Layout::metatileLayerOrder() const {
- return !m_metatileLayerOrder.isEmpty() ? m_metatileLayerOrder : Layout::defaultMetatileLayerOrder();
-}
-
-QList Layout::s_defaultMetatileLayerOrder;
-QList Layout::defaultMetatileLayerOrder() {
- static const QList initialDefault = {0, 1, 2};
- return !s_defaultMetatileLayerOrder.isEmpty() ? s_defaultMetatileLayerOrder : initialDefault;
-}
-
-QList Layout::metatileLayerOpacity() const {
- return !m_metatileLayerOpacity.isEmpty() ? m_metatileLayerOpacity : Layout::defaultMetatileLayerOpacity();
-}
-
-QList Layout::s_defaultMetatileLayerOpacity;
-QList Layout::defaultMetatileLayerOpacity() {
- static const QList initialDefault = {1.0, 1.0, 1.0};
- return !s_defaultMetatileLayerOpacity.isEmpty() ? s_defaultMetatileLayerOpacity : initialDefault;
-}
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 0ae8512f..75c9c362 100644
--- a/src/core/tileset.cpp
+++ b/src/core/tileset.cpp
@@ -200,9 +200,8 @@ 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) {
@@ -382,8 +381,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.
@@ -600,3 +598,7 @@ bool Tileset::save() {
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 bd15f4c5..d301d60e 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -1176,8 +1176,8 @@ void Editor::setPlayerViewRect(const QRectF &rect) {
}
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);
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/project.cpp b/src/project.cpp
index 75b32474..9a65ac38 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1618,7 +1618,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);
@@ -3462,12 +3462,12 @@ void Project::applyParsedLimits() {
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/apiutility.cpp b/src/scriptapi/apiutility.cpp
index 900c8701..5058e2de 100644
--- a/src/scriptapi/apiutility.cpp
+++ b/src/scriptapi/apiutility.cpp
@@ -201,7 +201,7 @@ QList ScriptUtility::getCustomScripts() {
}
QList ScriptUtility::getMetatileLayerOrder() {
- return Layout::defaultMetatileLayerOrder();
+ return Layout::globalMetatileLayerOrder();
}
bool ScriptUtility::validateMetatileLayerOrder(const QList &order) {
@@ -220,16 +220,16 @@ bool ScriptUtility::validateMetatileLayerOrder(const QList &order) {
void ScriptUtility::setMetatileLayerOrder(const QList &order) {
if (!validateMetatileLayerOrder(order))
return;
- Layout::setDefaultMetatileLayerOrder(order);
+ Layout::setGlobalMetatileLayerOrder(order);
if (window) window->refreshAfterPalettePreviewChange();
}
QList ScriptUtility::getMetatileLayerOpacity() {
- return Layout::defaultMetatileLayerOpacity();
+ return Layout::globalMetatileLayerOpacity();
}
void ScriptUtility::setMetatileLayerOpacity(const QList &opacities) {
- Layout::setDefaultMetatileLayerOpacity(opacities);
+ Layout::setGlobalMetatileLayerOpacity(opacities);
if (window) window->refreshAfterPalettePreviewChange();
}
diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp
index 4072baf8..0997c1e1 100644
--- a/src/ui/imageproviders.cpp
+++ b/src/ui/imageproviders.cpp
@@ -56,11 +56,9 @@ QImage getMetatileImage(
const QList &layerOpacity,
bool useTruePalettes)
{
- const int numTilesWide = 2;
- const int numTilesTall = 2;
- QImage metatile_image(8 * numTilesWide, 8 * numTilesTall, QImage::Format_RGBA8888);
+ QImage metatile_image(Metatile::pixelWidth(), Metatile::pixelHeight(), QImage::Format_RGBA8888);
if (!metatile) {
- metatile_image.fill(Qt::magenta);
+ metatile_image.fill(projectConfig.transparencyColor == QColor(Qt::transparent) ? projectConfig.transparencyColor : QColor(Qt::magenta));
return metatile_image;
}
@@ -70,21 +68,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.
+ metatile_image.fill(projectConfig.transparencyColor.isValid() ? projectConfig.transparencyColor : QColor(palettes.value(0).value(0)));
QPainter metatile_painter(&metatile_image);
uint32_t layerType = metatile->layerType();
- const int numTilesPerLayer = numTilesWide * numTilesTall;
for (const auto &layer : layerOrder)
- for (int y = 0; y < numTilesTall; y++)
- for (int x = 0; x < numTilesWide; x++) {
+ 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 * numTilesWide) + x;
+ int tileOffset = (y * Metatile::tileWidth()) + x;
if (projectConfig.tripleLayerMetatilesEnabled) {
- tile = metatile->tiles.value(tileOffset + (layer * numTilesPerLayer));
+ 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.
@@ -95,19 +92,19 @@ QImage getMetatileImage(
if (layer == 0)
tile = Tile(projectConfig.unusedTileNormal);
else // Tiles are on layers 1 and 2
- tile = metatile->tiles.value(tileOffset + ((layer - 1) * numTilesPerLayer));
+ tile = metatile->tiles.value(tileOffset + ((layer - 1) * Metatile::tilesPerLayer()));
break;
case Metatile::LayerType::Covered:
if (layer == 2)
tile = Tile(projectConfig.unusedTileCovered);
else // Tiles are on layers 0 and 1
- tile = metatile->tiles.value(tileOffset + (layer * numTilesPerLayer));
+ tile = metatile->tiles.value(tileOffset + (layer * Metatile::tilesPerLayer()));
break;
case Metatile::LayerType::Split:
if (layer == 1)
tile = Tile(projectConfig.unusedTileSplit);
else // Tiles are on layers 0 and 2
- tile = metatile->tiles.value(tileOffset + ((layer == 0 ? 0 : 1) * numTilesPerLayer));
+ tile = metatile->tiles.value(tileOffset + ((layer == 0 ? 0 : 1) * Metatile::tilesPerLayer()));
break;
}
}
@@ -130,7 +127,7 @@ QImage getMetatileImage(
logWarn(QString("Tile '%1' is referring to invalid palette number: '%2'").arg(tile.tileId).arg(tile.palette));
}
- QPoint origin = QPoint(x*8, y*8);
+ QPoint origin = QPoint(x * Tile::pixelWidth(), y * Tile::pixelHeight());
float opacity = layerOpacity.value(layer, 1.0);
if (opacity < 1.0) {
int alpha = 255 * opacity;
@@ -165,9 +162,9 @@ QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondary
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);
+ tileImage = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_RGBA8888);
QPainter painter(&tileImage);
- painter.fillRect(0, 0, 8, 8, palette.at(0));
+ painter.fillRect(0, 0, tileImage.width(), tileImage.height(), palette.at(0));
} else {
for (int i = 0; i < 16; i++) {
tileImage.setColor(i, palette.at(i));
@@ -194,3 +191,57 @@ void flattenTo4bppImage(QImage * image) {
for (int i = 0; i < image->sizeInBytes(); i++, pixel++)
*pixel %= 16;
}
+
+QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) {
+ return getMetatileSheetImage(layout->tileset_primary,
+ layout->tileset_secondary,
+ 0,
+ -1,
+ numMetatilesWide,
+ layout->metatileLayerOrder(),
+ layout->metatileLayerOpacity(),
+ Metatile::pixelSize(),
+ useTruePalettes);
+}
+
+QImage getMetatileSheetImage(Tileset *primaryTileset,
+ Tileset *secondaryTileset,
+ uint16_t metatileIdStart,
+ int numMetatilesToDraw,
+ int numMetatilesWide,
+ const QList &layerOrder,
+ const QList &layerOpacity,
+ const QSize &metatileSize,
+ bool useTruePalettes)
+{
+ // We round up the number of primary metatiles to keep the tilesets on separate rows.
+ int numPrimary = Util::roundUpToMultiple(primaryTileset ? primaryTileset->numMetatiles() : 0, numMetatilesWide);
+ int maxPrimary = Project::getNumMetatilesPrimary();
+ bool includesPrimary = metatileIdStart < maxPrimary;
+
+ // Negative values are used to indicate 'draw all metatiles'
+ if (numMetatilesToDraw < 0) {
+ numMetatilesToDraw = numPrimary + (secondaryTileset ? secondaryTileset->numMetatiles() : 0) - metatileIdStart;
+ }
+
+ // Round up height for incomplete last row
+ int numMetatilesTall = ceil((double)numMetatilesToDraw / numMetatilesWide);
+
+ QImage image(numMetatilesWide * metatileSize.width(), numMetatilesTall * metatileSize.height(), QImage::Format_RGBA8888);
+ image.fill(projectConfig.transparencyColor == QColor(Qt::transparent) ? projectConfig.transparencyColor : QColor(Qt::magenta));
+
+ QPainter painter(&image);
+ for (int i = 0; i < numMetatilesToDraw; i++) {
+ uint16_t metatileId = i + metatileIdStart;
+ if (includesPrimary && metatileId >= numPrimary)
+ metatileId += maxPrimary - numPrimary; // Skip over unused region of primary tileset
+ QImage metatile_image = getMetatileImage(metatileId, primaryTileset, secondaryTileset, layerOrder, layerOpacity, useTruePalettes)
+ .scaled(metatileSize.width(), metatileSize.height());
+ int map_y = i / numMetatilesWide;
+ int map_x = i % numMetatilesWide;
+ QPoint metatile_origin = QPoint(map_x * metatileSize.width(), map_y * metatileSize.height());
+ painter.drawImage(metatile_origin, metatile_image);
+ }
+ painter.end();
+ return image;
+}
diff --git a/src/ui/metatileimageexporter.cpp b/src/ui/metatileimageexporter.cpp
new file mode 100644
index 00000000..d5e94f13
--- /dev/null
+++ b/src/ui/metatileimageexporter.cpp
@@ -0,0 +1,321 @@
+#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)
+{
+ setAttribute(Qt::WA_DeleteOnClose);
+ ui->setupUi(this);
+ m_transparencyButtons = {
+ ui->radioButton_TransparencyNormal,
+ ui->radioButton_TransparencyBlack,
+ ui->radioButton_TransparencyFirst,
+ };
+
+ m_scene = new QGraphicsScene(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) {
+ applySettings(*m_savedSettings);
+ } else {
+ applySettings({});
+ }
+
+ 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, &MetatileImageExporter::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::updateTilesetUI);
+ connect(ui->checkBox_PrimaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updatePreview);
+ connect(ui->checkBox_SecondaryTileset, &QCheckBox::toggled, this, &MetatileImageExporter::updateTilesetUI);
+ 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::applySettings(const Settings &settings) {
+ ui->spinBox_MetatileStart->setValue(settings.metatileStart);
+ ui->spinBox_MetatileEnd->setValue(settings.metatileEnd);
+ ui->spinBox_WidthMetatiles->setValue(settings.numMetatilesWide);
+ ui->spinBox_WidthPixels->setValue(settings.numMetatilesWide * Metatile::pixelWidth());
+ ui->checkBox_PrimaryTileset->setChecked(settings.usePrimaryTileset);
+ ui->checkBox_SecondaryTileset->setChecked(settings.useSecondaryTileset);
+ ui->checkBox_Placeholders->setChecked(settings.renderPlaceholders);
+ if (m_transparencyButtons.value(settings.transparencyMode)) {
+ m_transparencyButtons[settings.transparencyMode]->setChecked(true);
+ }
+
+ // Build layer list from settings
+ 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);
+
+ updateTilesetUI();
+}
+
+void MetatileImageExporter::reset() {
+ applySettings({});
+ updatePreview();
+}
+
+void MetatileImageExporter::saveImage() {
+ // Ensure the image in the preview is up-to-date before exporting.
+ updatePreview();
+
+ QString defaultFilename;
+ 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" };
+ defaultFilename = (layerFilenames.at(m_layerOrder.constFirst()));
+ } else {
+ 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");
+ }
+
+ QString defaultFilepath = QString("%1/%2.png").arg(FileDialog::getDirectory()).arg(defaultFilename);
+ QString filepath = FileDialog::getSaveFileName(this, windowTitle(), defaultFilepath, QStringLiteral("Image Files (*.png *.jpg *.bmp)"));
+ if (!filepath.isEmpty()) {
+ m_preview->pixmap().save(filepath);
+ }
+}
+
+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();
+
+ int numMetatilesWide = ui->spinBox_WidthMetatiles->value();
+ int metatileStart = ui->spinBox_MetatileStart->value();
+ int numMetatiles = Util::roundUpToMultiple(ui->spinBox_MetatileEnd->value() - metatileStart + 1, numMetatilesWide);
+
+ 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 = getMetatileSheetImage(m_primaryTileset,
+ m_secondaryTileset,
+ metatileStart,
+ numMetatiles,
+ numMetatilesWide,
+ 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()));
+}
+
+uint16_t MetatileImageExporter::getExpectedMetatileStart() {
+ if (ui->checkBox_PrimaryTileset->isChecked()) return 0;
+ if (ui->checkBox_SecondaryTileset->isChecked()) return Project::getNumMetatilesPrimary();
+ return ui->spinBox_MetatileStart->value();
+}
+
+// TODO: Combining tilesets is not rendering the correct range of metatiles
+uint16_t MetatileImageExporter::getExpectedMetatileEnd() {
+ if (ui->checkBox_SecondaryTileset->isChecked()) return Project::getNumMetatilesPrimary() + (m_secondaryTileset ? (m_secondaryTileset->numMetatiles() - 1) : 0);
+ if (ui->checkBox_PrimaryTileset->isChecked()) return m_primaryTileset ? (m_primaryTileset->numMetatiles() - 1) : 0;
+ return ui->spinBox_MetatileEnd->value();
+}
+
+void MetatileImageExporter::updateMetatileRange() {
+ const QSignalBlocker b_MetatileStart(ui->spinBox_MetatileStart);
+ const QSignalBlocker b_MetatileEnd(ui->spinBox_MetatileEnd);
+ ui->spinBox_MetatileStart->setValue(getExpectedMetatileStart());
+ ui->spinBox_MetatileEnd->setValue(getExpectedMetatileEnd());
+}
+
+void MetatileImageExporter::updateTilesetUI() {
+ // 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..b0279e4e 100644
--- a/src/ui/metatilelayersitem.cpp
+++ b/src/ui/metatilelayersitem.cpp
@@ -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 321f2925..d8f50410 100644
--- a/src/ui/metatileselector.cpp
+++ b/src/ui/metatileselector.cpp
@@ -15,28 +15,7 @@ int MetatileSelector::numPrimaryMetatilesRounded() const {
}
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 metatileId = i;
- if (i >= primaryLength) {
- metatileId += Project::getNumMetatilesPrimary() - primaryLength;
- }
- QImage metatile_image = getMetatileImage(metatileId, this->layout);
- 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() {
diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp
index 3fc73934..70e08e46 100644
--- a/src/ui/movablerect.cpp
+++ b/src/ui/movablerect.cpp
@@ -114,8 +114,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(), 16);
+ int dy = Util::roundUpToMultiple(event->scenePos().y() - this->clickedPos.y(), 16);
QRect resizedRect = this->clickedRect;
diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp
index c4becaa5..f9f820ab 100644
--- a/src/ui/projectsettingseditor.cpp
+++ b/src/ui/projectsettingseditor.cpp
@@ -475,10 +475,11 @@ void ProjectSettingsEditor::refresh() {
ui->checkBox_PreserveMatchingOnlyData->setChecked(projectConfig.preserveMatchingOnlyData);
// Radio buttons
- if (projectConfig.setTransparentPixelsBlack)
+ // TODO: Replace
+ /*if (projectConfig.setTransparentPixelsBlack)
ui->radioButton_RenderBlack->setChecked(true);
else
- ui->radioButton_RenderFirstPalColor->setChecked(true);
+ ui->radioButton_RenderFirstPalColor->setChecked(true);*/
// Set spin box values
ui->spinBox_Elevation->setValue(projectConfig.defaultElevation);
@@ -574,7 +575,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.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); // TODO
projectConfig.preserveMatchingOnlyData = ui->checkBox_PreserveMatchingOnlyData->isChecked();
// Save spin box settings
diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp
index 7f11836f..e6b814b1 100644
--- a/src/ui/resizelayoutpopup.cpp
+++ b/src/ui/resizelayoutpopup.cpp
@@ -61,7 +61,7 @@ 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(), 16), Util::roundUpToMultiple(newPos.y(), 16));
}
else
return QGraphicsItem::itemChange(change, value);
diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp
index e15824ad..3b212579 100644
--- a/src/ui/selectablepixmapitem.cpp
+++ b/src/ui/selectablepixmapitem.cpp
@@ -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..55545616 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,17 @@ 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_Metatiles_Image, &QAction::triggered, [this] { exportMetatilesImage(); });
+
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
@@ -74,6 +87,7 @@ TilesetEditor::~TilesetEditor()
delete selectedTileScene;
delete metatileLayersScene;
delete copiedMetatile;
+ delete metatileImageExportSettings;
this->metatileHistory.clear();
}
@@ -694,17 +708,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";
@@ -968,62 +973,27 @@ 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->open();
}
-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)");
diff --git a/src/ui/tileseteditormetatileselector.cpp b/src/ui/tileseteditormetatileselector.cpp
index 79888681..60f4b0f5 100644
--- a/src/ui/tileseteditormetatileselector.cpp
+++ b/src/ui/tileseteditormetatileselector.cpp
@@ -4,7 +4,7 @@
#include
TilesetEditorMetatileSelector::TilesetEditorMetatileSelector(Tileset *primaryTileset, Tileset *secondaryTileset, Layout *layout)
- : SelectablePixmapItem(16, 16, 1, 1) {
+ : SelectablePixmapItem(32, 32, 1, 1) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
this->numMetatilesWide = 8;
@@ -31,48 +31,6 @@ int TilesetEditorMetatileSelector::numPrimaryMetatilesRounded() const {
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;
-}
-
void TilesetEditorMetatileSelector::drawMetatile(uint16_t metatileId) {
QPoint pos = getMetatileIdCoords(metatileId);
@@ -97,7 +55,15 @@ void TilesetEditorMetatileSelector::drawSelectedMetatile() {
}
void TilesetEditorMetatileSelector::updateBasePixmap() {
- this->baseImage = buildAllMetatilesImage();
+ this->baseImage = getMetatileSheetImage(this->primaryTileset,
+ this->secondaryTileset,
+ 0,
+ this->numPrimaryMetatilesRounded() + this->secondaryTileset->numMetatiles(),
+ this->numMetatilesWide,
+ this->layout->metatileLayerOrder(),
+ this->layout->metatileLayerOpacity(),
+ QSize(this->cellWidth, this->cellHeight),
+ true);
this->basePixmap = QPixmap::fromImage(this->baseImage);
}
diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp
index 53f6df78..cfe19b72 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;
@@ -127,6 +133,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;
}