Merge pull request #758 from GriffinRichards/metatile-layers

New metatile export options, metatile/tile image fixes
This commit is contained in:
GriffinR
2025-07-25 13:22:12 -04:00
committed by GitHub
71 changed files with 2427 additions and 989 deletions

View File

@@ -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

View File

@@ -0,0 +1,526 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MetatileImageExporter</class>
<widget class="QDialog" name="MetatileImageExporter">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>649</width>
<height>601</height>
</rect>
</property>
<property name="windowTitle">
<string>Export Metatiles Image</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QFrame" name="frame_Options">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::FocusPolicy::ClickFocus</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_Options">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea_Options">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_Options">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>304</width>
<height>532</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_Tilesets">
<property name="title">
<string>Tilesets</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="topMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="1" column="0">
<widget class="QCheckBox" name="checkBox_SecondaryTileset">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, automatically update the metatile range to include the full secondary tileset.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Secondary Tileset</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollComboBox" name="comboBox_PrimaryTileset">
<property name="enabled">
<bool>false</bool>
</property>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="NoScrollComboBox" name="comboBox_SecondaryTileset">
<property name="enabled">
<bool>false</bool>
</property>
<property name="editable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="checkBox_PrimaryTileset">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, automatically update the metatile range to include the full primary tileset.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Primary Tileset</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>1</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_MetatileRange">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Metatile Range</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="topMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_MetatileStart">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="UIntHexSpinBox" name="spinBox_MetatileStart">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The metatile ID to start the rendered image at.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_MetatileEnd">
<property name="text">
<string>End</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="UIntHexSpinBox" name="spinBox_MetatileEnd">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The metatile ID to end the rendered image at.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_Layers">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="title">
<string>Layers</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ReorderableListWidget" name="listWidget_Layers">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow</enum>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragDropMode::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::DropAction::MoveAction</enum>
</property>
<property name="resizeMode">
<enum>QListView::ResizeMode::Adjust</enum>
</property>
<property name="itemAlignment">
<set>Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_Transparency">
<property name="title">
<string>Transparency</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="radioButton_TransparencyNormal">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, transparent pixels in the image will be rendered with alpha of 0.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Normal</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_TransparencyBlack">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, transparent pixels in the image will be rendered as black. This is the default in-game behavior.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Black</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_TransparencyFirst">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>First palette color</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_Misc">
<property name="title">
<string>Miscellaneous</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="topMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="checkBox_Placeholders">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Render placeholder metatiles</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_WidthMetatiles">
<property name="text">
<string>Width (metatiles)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="UIntSpinBox" name="spinBox_WidthMetatiles">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width of the output image in metatiles.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_WidthPixels">
<property name="text">
<string>Width (pixels)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="UIntSpinBox" name="spinBox_WidthPixels">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width of the output image in pixels. Automatically rounded up to a multiple of a metatile's pixel width.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum" stdset="0">
<number>16</number>
</property>
<property name="maximum" stdset="0">
<number>128</number>
</property>
<property name="singleStep" stdset="0">
<number>16</number>
</property>
<property name="value" stdset="0">
<number>128</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_Buttons">
<item>
<widget class="QPushButton" name="pushButton_Reset">
<property name="text">
<string>Reset</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_Close">
<property name="text">
<string>Close</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_Save">
<property name="text">
<string>Save</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_Preview">
<property name="title">
<string>Preview</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QScrollArea" name="scrollArea_Preview">
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_Preview">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>285</width>
<height>551</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="2" column="3">
<widget class="QGraphicsView" name="graphicsView_Preview">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="mouseTracking">
<bool>false</bool>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored</enum>
</property>
<property name="dragMode">
<enum>QGraphicsView::DragMode::NoDrag</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NoScrollComboBox</class>
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
<customwidget>
<class>UIntSpinBox</class>
<extends>QAbstractSpinBox</extends>
<header>uintspinbox.h</header>
</customwidget>
<customwidget>
<class>UIntHexSpinBox</class>
<extends>UIntSpinBox</extends>
<header location="global">uintspinbox.h</header>
</customwidget>
<customwidget>
<class>ReorderableListWidget</class>
<extends>QListWidget</extends>
<header>metatileimageexporter.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -852,7 +852,7 @@
<x>0</x>
<y>0</y>
<width>570</width>
<height>798</height>
<height>837</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
@@ -973,6 +973,27 @@
</layout>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_MetatileSelectorWidth">
<item row="0" column="0">
<widget class="QLabel" name="label_MetatileSelectorWidth">
<property name="text">
<string>Metatile Selector Width</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="NoScrollSpinBox" name="spinBox_MetatileSelectorWidth">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The width (in metatiles) of the metatile selectors on the Map tab and in the Tileset Editor.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame_WarningTilesetsTab">
<property name="styleSheet">
@@ -1873,16 +1894,6 @@
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
<customwidget>
<class>NoScrollTextEdit</class>
<extends>QTextEdit</extends>
<header>noscrolltextedit.h</header>
</customwidget>
<customwidget>
<class>UIntSpinBox</class>
<extends>QAbstractSpinBox</extends>
@@ -1893,6 +1904,16 @@
<extends>UIntSpinBox</extends>
<header location="global">uintspinbox.h</header>
</customwidget>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
<customwidget>
<class>NoScrollTextEdit</class>
<extends>QTextEdit</extends>
<header>noscrolltextedit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/images.qrc"/>

View File

@@ -65,7 +65,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>239</width>
<width>253</width>
<height>659</height>
</rect>
</property>
@@ -555,7 +555,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>499</width>
<width>446</width>
<height>241</height>
</rect>
</property>
@@ -625,18 +625,42 @@
<property name="title">
<string>File</string>
</property>
<widget class="QMenu" name="menuExport_Tiles_Image">
<property name="title">
<string>Export Tiles Image</string>
</property>
<addaction name="actionExport_Primary_Tiles_Image"/>
<addaction name="actionExport_Secondary_Tiles_Image"/>
</widget>
<widget class="QMenu" name="menuImport_AdvanceMap_Metatiles">
<property name="title">
<string>Import Metatiles from Advance Map 1.92</string>
</property>
<addaction name="actionImport_Primary_AdvanceMap_Metatiles"/>
<addaction name="actionImport_Secondary_AdvanceMap_Metatiles"/>
</widget>
<widget class="QMenu" name="menuImport_Tiles_Image">
<property name="title">
<string>Import Tiles Image</string>
</property>
<addaction name="actionImport_Primary_Tiles_Image"/>
<addaction name="actionImport_Secondary_Tiles_Image"/>
</widget>
<widget class="QMenu" name="menuExport_porytiles_Layer_Images">
<property name="title">
<string>Export Porytiles Layer Images...</string>
</property>
<addaction name="actionExport_Primary_Porytiles_Layer_Images"/>
<addaction name="actionExport_Secondary_Porytiles_Layer_Images"/>
</widget>
<addaction name="actionSave_Tileset"/>
<addaction name="separator"/>
<addaction name="actionImport_Primary_Tiles"/>
<addaction name="actionImport_Secondary_Tiles"/>
<addaction name="menuImport_Tiles_Image"/>
<addaction name="menuImport_AdvanceMap_Metatiles"/>
<addaction name="separator"/>
<addaction name="actionImport_Primary_Metatiles"/>
<addaction name="actionImport_Secondary_Metatiles"/>
<addaction name="separator"/>
<addaction name="actionExport_Primary_Tiles_Image"/>
<addaction name="actionExport_Secondary_Tiles_Image"/>
<addaction name="actionExport_Primary_Metatiles_Image"/>
<addaction name="actionExport_Secondary_Metatiles_Image"/>
<addaction name="menuExport_Tiles_Image"/>
<addaction name="actionExport_Metatiles_Image"/>
<addaction name="menuExport_porytiles_Layer_Images"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
@@ -685,16 +709,6 @@
<string>Ctrl+S</string>
</property>
</action>
<action name="actionImport_Primary_Tiles">
<property name="text">
<string>Import Primary Tiles Image...</string>
</property>
</action>
<action name="actionImport_Secondary_Tiles">
<property name="text">
<string>Import Secondary Tiles Image...</string>
</property>
</action>
<action name="actionChange_Metatiles_Count">
<property name="text">
<string>Change Number of Metatiles...</string>
@@ -745,36 +759,6 @@
<string>Ctrl+Y</string>
</property>
</action>
<action name="actionExport_Primary_Tiles_Image">
<property name="text">
<string>Export Primary Tiles Image...</string>
</property>
</action>
<action name="actionExport_Secondary_Tiles_Image">
<property name="text">
<string>Export Secondary Tiles Image...</string>
</property>
</action>
<action name="actionImport_Primary_Metatiles">
<property name="text">
<string>Import Primary Metatiles from Advance Map 1.92...</string>
</property>
</action>
<action name="actionImport_Secondary_Metatiles">
<property name="text">
<string>Import Secondary Metatiles from Advance Map 1.92...</string>
</property>
</action>
<action name="actionExport_Primary_Metatiles_Image">
<property name="text">
<string>Export Primary Metatiles Image...</string>
</property>
</action>
<action name="actionExport_Secondary_Metatiles_Image">
<property name="text">
<string>Export Secondary Metatiles Image...</string>
</property>
</action>
<action name="actionCut">
<property name="text">
<string>Cut</string>
@@ -834,6 +818,51 @@
<string>Show Raw Metatile Attributes</string>
</property>
</action>
<action name="actionExport_Primary_Tiles_Image">
<property name="text">
<string>Primary...</string>
</property>
</action>
<action name="actionExport_Secondary_Tiles_Image">
<property name="text">
<string>Secondary...</string>
</property>
</action>
<action name="actionImport_Primary_AdvanceMap_Metatiles">
<property name="text">
<string>Primary...</string>
</property>
</action>
<action name="actionImport_Secondary_AdvanceMap_Metatiles">
<property name="text">
<string>Secondary...</string>
</property>
</action>
<action name="actionImport_Primary_Tiles_Image">
<property name="text">
<string>Primary...</string>
</property>
</action>
<action name="actionImport_Secondary_Tiles_Image">
<property name="text">
<string>Secondary...</string>
</property>
</action>
<action name="actionExport_Metatiles_Image">
<property name="text">
<string>Export Metatiles Image...</string>
</property>
</action>
<action name="actionExport_Primary_Porytiles_Layer_Images">
<property name="text">
<string>Primary...</string>
</property>
</action>
<action name="actionExport_Secondary_Porytiles_Layer_Images">
<property name="text">
<string>Secondary...</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@@ -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<uint32_t> warpBehaviors;
int maxEventsPerGroup;
int forcedMajorVersion;
int metatileSelectorWidth;
QStringList globalConstantsFilepaths;
QMap<QString,QString> globalConstants;

View File

@@ -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<Event::Group, QList<Event *>> m_events;
QSet<Event *> m_ownedEvents; // for memory management
QList<int> m_metatileLayerOrder;
QList<float> m_metatileLayerOpacity;
void trackConnection(MapConnection*);
// MapConnections in 'ownedConnections' but not 'connections' persist in the edit history.

View File

@@ -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> project;
static const QMap<QString, QString> oppositeDirections;

View File

@@ -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<int> metatileLayerOrder;
QList<float> metatileLayerOpacity;
void setMetatileLayerOrder(const QList<int> &layerOrder) { m_metatileLayerOrder = layerOrder; }
const QList<int> &metatileLayerOrder() const {
return !m_metatileLayerOrder.isEmpty() ? m_metatileLayerOrder : Layout::globalMetatileLayerOrder();
}
static void setGlobalMetatileLayerOrder(const QList<int> &layerOrder) { s_globalMetatileLayerOrder = layerOrder; }
static const QList<int> &globalMetatileLayerOrder() {
static const QList<int> defaultLayerOrder = {0, 1, 2};
return !s_globalMetatileLayerOrder.isEmpty() ? s_globalMetatileLayerOrder : defaultLayerOrder;
}
void setMetatileLayerOpacity(const QList<float> &layerOpacity) { m_metatileLayerOpacity = layerOpacity; }
const QList<float> &metatileLayerOpacity() const {
return !m_metatileLayerOpacity.isEmpty() ? m_metatileLayerOpacity : Layout::globalMetatileLayerOpacity();
}
static void setGlobalMetatileLayerOpacity(const QList<float> &layerOpacity) { s_globalMetatileLayerOpacity = layerOpacity; }
static const QList<float> &globalMetatileLayerOpacity() {
static const QList<float> 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<int> m_metatileLayerOrder;
QList<float> m_metatileLayerOpacity;
static QList<int> s_globalMetatileLayerOrder;
static QList<float> s_globalMetatileLayerOpacity;
signals:
void dimensionsChanged(const QSize &size);
void needsRedrawing();

View File

@@ -62,6 +62,14 @@ public:
static void setLayout(Project*);
static QString getMetatileIdString(uint16_t metatileId);
static QString getMetatileIdStrings(const QList<uint16_t> 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;

View File

@@ -3,6 +3,7 @@
#define TILE_H
#include <QObject>
#include <QSize>
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) {

View File

@@ -30,14 +30,13 @@ public:
QString metatile_attrs_label;
QString metatile_attrs_path;
QString tilesImagePath;
QImage tilesImage;
QStringList palettePaths;
QList<QImage> tiles;
QHash<int, QString> metatileLabels;
QList<QList<QRgb>> palettes;
QList<QList<QRgb>> 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<Metatile*> &metatiles);
void addMetatile(Metatile* metatile);
QList<Metatile*> metatiles() const { return m_metatiles; }
Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); }
const QList<Metatile*> &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<Metatile*> m_metatiles;
QList<QImage> m_tiles;
QImage m_tilesImage;
bool m_hasUnsavedTilesImage = false;
};

View File

@@ -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);

View File

@@ -285,7 +285,6 @@ signals:
void wildMonTableEdited();
void currentMetatilesSelectionChanged();
void mapRulerStatusChanged(const QString &);
void tilesetUpdated(QString);
void gridToggled(bool);
void editActionSet(EditAction newEditAction);
};

View File

@@ -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<int> getMetatileLayerOrder() const;
Q_INVOKABLE void setMetatileLayerOrder(const QList<int> &order);
Q_INVOKABLE QList<float> getMetatileLayerOpacity() const;
Q_INVOKABLE void setMetatileLayerOpacity(const QList<float> &opacities);
Q_INVOKABLE QString getSong();
Q_INVOKABLE void setSong(QString song);
Q_INVOKABLE QString getLocation();

View File

@@ -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();

View File

@@ -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);

View File

@@ -38,9 +38,9 @@ public:
Q_INVOKABLE bool getSmartPathsEnabled();
Q_INVOKABLE QList<QString> getCustomScripts();
Q_INVOKABLE QList<int> getMetatileLayerOrder();
Q_INVOKABLE void setMetatileLayerOrder(QList<int> order);
Q_INVOKABLE void setMetatileLayerOrder(const QList<int> &order);
Q_INVOKABLE QList<float> getMetatileLayerOpacity();
Q_INVOKABLE void setMetatileLayerOpacity(QList<float> order);
Q_INVOKABLE void setMetatileLayerOpacity(const QList<float> &order);
Q_INVOKABLE QList<QString> getMapNames();
Q_INVOKABLE QList<QString> getMapConstants();
Q_INVOKABLE QList<QString> getLayoutNames();
@@ -57,6 +57,8 @@ public:
Q_INVOKABLE bool isPrimaryTileset(QString tilesetName);
Q_INVOKABLE bool isSecondaryTileset(QString tilesetName);
static bool validateMetatileLayerOrder(const QList<int> &order);
private:
void callTimeoutFunction(QJSValue callback);
void runMessageBox(QString text, QString informativeText, QString detailedText, QMessageBox::Icon icon);

View File

@@ -0,0 +1,39 @@
#ifndef CHECKEREDBGSCENE_H
#define CHECKEREDBGSCENE_H
#include <QGraphicsScene>
// 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

View File

@@ -2,6 +2,7 @@
#define CONNECTIONPIXMAPITEM_H
#include "mapconnection.h"
#include "metatile.h"
#include <QGraphicsPixmapItem>
#include <QPainter>
#include <QPointer>
@@ -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();

View File

@@ -6,13 +6,40 @@
#include <QImage>
#include <QPixmap>
class Layout;
QImage getCollisionMetatileImage(Block);
QImage getCollisionMetatileImage(int, int);
QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList<int>&, const QList<float>&, bool useTruePalettes = false);
QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList<int>&, const QList<float>&, 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<int>&, const QList<float>& = {}, bool useTruePalettes = false);
QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList<int>&, const QList<float>& = {}, 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<int> &layerOrder,
const QList<float> &layerOpacity = {},
const QSize &metatileSize = Metatile::pixelSize(),
bool useTruePalettes = false);
QImage getMetatileSheetImage(Tileset *primaryTileset,
Tileset *secondaryTileset,
int numMetatilesWide,
const QList<int> &layerOrder,
const QList<float> &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<QRgb> &palette);
QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset);
void flattenTo4bppImage(QImage * image);
static QList<QRgb> greyscalePalette({

View File

@@ -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;

View File

@@ -3,6 +3,7 @@
#include <QGraphicsObject>
#include <QLine>
#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

View File

@@ -0,0 +1,103 @@
#ifndef METATILEIMAGEEXPORTER_H
#define METATILEIMAGEEXPORTER_H
#include <QDialog>
#include <QShowEvent>
#include <QCloseEvent>
#include <QListWidget>
#include <QDropEvent>
#include <QRadioButton>
#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<int,bool> 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<int> m_layerOrder;
ProjectConfig m_savedConfig;
QList<QRadioButton*> 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

View File

@@ -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*);

View File

@@ -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<uint16_t>, QList<QPair<uint16_t, uint16_t>>);
QPoint getMetatileIdCoordsOnWidget(uint16_t);
void setExternalSelection(int, int, const QList<uint16_t>&, const QList<QPair<uint16_t, uint16_t>>&);
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();

View File

@@ -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));

View File

@@ -3,10 +3,10 @@
#include "maplayout.h"
#include "project.h"
#include "checkeredbgscene.h"
#include <QDialog>
#include <QPointer>
#include <QGraphicsScene>
#include <QGraphicsLineItem>
#include <QGraphicsRectItem>
#include <QDialogButtonBox>
@@ -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;
};

View File

@@ -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;

View File

@@ -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<uint16_t> metatileReloadQueue;
MetatileImageExporter::Settings *metatileImageExportSettings = nullptr;
bool save();

View File

@@ -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<uint16_t> 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:

View File

@@ -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<QRgb> getCurPaletteTable();
QList<Tile> buildSelectedTiles(int, int, QList<Tile>);
QImage buildImage(int tileIdStart, int numTiles);
void updateBasePixmap();
void drawUnused();
signals:

View File

@@ -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;

View File

@@ -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 \

View File

@@ -4,6 +4,7 @@
#include "map.h"
#include "validator.h"
#include "utility.h"
#include "metatile.h"
#include <QDir>
#include <QFile>
#include <QFormLayout>
@@ -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<QString, QString> 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<QString, QString> 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<QString, QString> 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) {

View File

@@ -131,7 +131,7 @@ QList<Metatile*> 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<unsigned char>(in.at(0)) |
(static_cast<unsigned char>(in.at(1)) << 8) |
(static_cast<unsigned char>(in.at(2)) << 16) |

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -6,6 +6,9 @@
#include "imageproviders.h"
#include "utility.h"
QList<int> Layout::s_globalMetatileLayerOrder;
QList<float> 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;
}

View File

@@ -36,8 +36,8 @@ int Metatile::getIndexInTileset(int metatileId) {
}
QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
int x = static_cast<int>(pixelCoord.x()) / 16;
int y = static_cast<int>(pixelCoord.y()) / 16;
int x = static_cast<int>(pixelCoord.x()) / pixelWidth();
int y = static_cast<int>(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<uint16_t> 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;

View File

@@ -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<QList<QRgb>> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) {
QList<QList<QRgb>> palettes;
auto primaryPalettes = useTruePalettes ? primaryTileset->palettes : primaryTileset->palettePreviews;
QList<QList<QRgb>> 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<QList<QRgb>> 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<QRgb> 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<int, QString> 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<char>(tile));
data.append(static_cast<char>(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<unsigned char>(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<char>(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<QImage> 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), "");
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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");

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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<int> MainWindow::getMetatileLayerOrder() const {
if (!this->editor || !this->editor->layout)
return QList<int>();
return this->editor->layout->metatileLayerOrder();
}
void MainWindow::setMetatileLayerOrder(const QList<int> &order) {
if (!this->editor || !this->editor->layout || !ScriptUtility::validateMetatileLayerOrder(order))
return;
this->editor->layout->setMetatileLayerOrder(order);
this->refreshAfterPalettePreviewChange();
}
QList<float> MainWindow::getMetatileLayerOpacity() const {
if (!this->editor || !this->editor->layout)
return QList<float>();
return this->editor->layout->metatileLayerOpacity();
}
void MainWindow::setMetatileLayerOpacity(const QList<float> &opacities) {
if (!this->editor || !this->editor->layout)
return;
this->editor->layout->setMetatileLayerOpacity(opacities);
this->refreshAfterPalettePreviewChange();
}
//=====================
// Editing map header
//=====================

View File

@@ -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<uint16_t>(metatileId),
this->editor->layout->tileset_primary,
this->editor->layout->tileset_secondary,
this->editor->layout->metatileLayerOrder,
this->editor->layout->metatileLayerOpacity);
QImage image = getMetatileImage(static_cast<uint16_t>(metatileId), this->editor->layout);
if (setTransparency)
image.setColor(0, qRgba(0, 0, 0, 0));
if (this->getOverlay(layer)->addImage(x, y, image))

View File

@@ -201,46 +201,36 @@ QList<QString> ScriptUtility::getCustomScripts() {
}
QList<int> ScriptUtility::getMetatileLayerOrder() {
if (!window || !window->editor || !window->editor->layout)
return QList<int>();
return window->editor->layout->metatileLayerOrder;
return Layout::globalMetatileLayerOrder();
}
void ScriptUtility::setMetatileLayerOrder(QList<int> order) {
if (!window || !window->editor || !window->editor->layout)
return;
bool ScriptUtility::validateMetatileLayerOrder(const QList<int> &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<int> &order) {
if (!validateMetatileLayerOrder(order))
return;
Layout::setGlobalMetatileLayerOrder(order);
if (window) window->refreshAfterPalettePreviewChange();
}
QList<float> ScriptUtility::getMetatileLayerOpacity() {
if (!window || !window->editor || !window->editor->layout)
return QList<float>();
return window->editor->layout->metatileLayerOpacity;
return Layout::globalMetatileLayerOpacity();
}
void ScriptUtility::setMetatileLayerOpacity(QList<float> order) {
if (!window || !window->editor || !window->editor->layout)
return;
window->editor->layout->metatileLayerOpacity = order;
window->refreshAfterPalettePreviewChange();
void ScriptUtility::setMetatileLayerOpacity(const QList<float> &opacities) {
Layout::setGlobalMetatileLayerOpacity(opacities);
if (window) window->refreshAfterPalettePreviewChange();
}
QList<QString> ScriptUtility::getMapNames() {

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -0,0 +1,32 @@
#include "checkeredbgscene.h"
#include <QPainter>
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);
}
}
}

View File

@@ -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();

View File

@@ -3,26 +3,21 @@
#include <QPainter>
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);
}
}

View File

@@ -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());
}

View File

@@ -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);
}

View File

@@ -1,6 +1,5 @@
#include "config.h"
#include "imageproviders.h"
#include "log.h"
#include "editor.h"
#include <QPainter>
@@ -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<float> &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<float> &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<QList<QRgb>> 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<QRgb> 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<QRgb> &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<int> &layerOrder,
const QList<float> &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<int> &layerOrder,
const QList<float> &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);
}

View File

@@ -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);
}
}

View File

@@ -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<QPointF>({ 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. */

View File

@@ -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 <QTimer>
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<int>({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;
}

View File

@@ -4,7 +4,7 @@
#include <QPainter>
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<QPoint> 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<int>(pos.x()) / this->cellWidth;
int y = static_cast<int>(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));
}

View File

@@ -3,40 +3,20 @@
#include "project.h"
#include <QPainter>
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>({MetatileSelectionItem{true, metatileId}}),
QList<CollisionSelectionItem>(),
};
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<uint16_t> metatiles, QList<QPair<uint16_t, uint16_t>> collisions) {
void MetatileSelector::setExternalSelection(int width, int height, const QList<uint16_t> &metatiles, const QList<QPair<uint16_t, uint16_t>> &collisions) {
this->prefabSelection = false;
this->externalSelection = true;
this->externalSelectionWidth = width;
@@ -99,18 +81,18 @@ void MetatileSelector::setExternalSelection(int width, int height, QList<uint16_
this->selection.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<uint16_t>(index);
} else {
return static_cast<uint16_t>(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<uint16_t>(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<uint16_t>(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;
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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<uint16_t> 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<uint16_t>(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();

View File

@@ -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() {

View File

@@ -1,7 +1,7 @@
#include "selectablepixmapitem.h"
#include <QPainter>
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);
}

View File

@@ -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<Tile> 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<QString,QImage> 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;

View File

@@ -3,17 +3,20 @@
#include "project.h"
#include <QPainter>
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<uint16_t>(index);
} else {
return static_cast<uint16_t>(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<uint16_t>(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<uint16_t>(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;

View File

@@ -4,7 +4,7 @@
#include <QPainter>
#include <QVector>
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<QRgb> 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<double>(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();

View File

@@ -2,24 +2,24 @@
#include <QWheelEvent>
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;

View File

@@ -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;
}