diff --git a/NEWS.md b/NEWS.md
index edb06b88b1..4fe0646254 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -2,6 +2,7 @@
* Rewritten Properties view to enable direct widget interaction (#4045)
* Added support for lists in custom properties (#1493)
+* Added capsule object shape (by Jocelyn, #2153)
* Allow filtering tilesets by name in the tileset dock (with dogboydog, #4239)
* Allow changing the values of number inputs using expressions (with dogboydog, #4234)
* Added support for SVG 1.2 / CSS blending modes to layers (#3932)
diff --git a/docs/manual/objects.rst b/docs/manual/objects.rst
index e9617a2576..3a391bf436 100644
--- a/docs/manual/objects.rst
+++ b/docs/manual/objects.rst
@@ -37,7 +37,7 @@ to switch to the :ref:`select-objects-tool` tool.
Insert Rectangle
~~~~~~~~~~~~~~~~
-Shortcut: ``R``
+Shortcut: ``R`` |insert-rectangle|
The rectangle was the first type of object supported by Tiled, which is why
objects are rectangles by default in the :doc:`/reference/tmx-map-format`. They
@@ -61,7 +61,7 @@ around its position. This is mainly to keep it visible and selectable.
Insert Point
~~~~~~~~~~~~~~
-Shortcut: ``I``
+Shortcut: ``I`` |insert-point|
Points are the simplest objects you can place on a map. They only represent a
location, and cannot be resized or rotated. Simply click on the map to position
@@ -72,18 +72,35 @@ a point object.
Insert Ellipse
~~~~~~~~~~~~~~
-Shortcut: ``C``
+Shortcut: ``C`` |insert-ellipse|
Ellipses work the same way as `rectangles <#insert-rectangle>`__, except
that they are rendered as an ellipse. Useful for when your area or
collision shape needs to represent a circle or ellipse.
+
+.. raw:: html
+
+
Since Tiled 1.12
+
+
+.. _insert-capsule-tool:
+
+Insert Capsule
+~~~~~~~~~~~~~~
+
+Shortcut: ``Shift+C`` |insert-capsule|
+
+Capsules work the same way as `rectangles <#insert-rectangle>`__, except
+that they are rendered as a capsule. Useful for when your area or
+collision shape needs to represent a circle or capsule.
+
.. _insert-polygon-tool:
Insert Polygon
~~~~~~~~~~~~~~
-Shortcut: ``P``
+Shortcut: ``P`` |insert-polygon|
Polygons are the most flexible way of defining the shape of an area.
They are most commonly used for defining collision shapes.
@@ -125,7 +142,7 @@ The :ref:`edit-polygons-tool` tool is used to edit polylines as well.
Insert Tile
~~~~~~~~~~~
-Shortcut: ``T``
+Shortcut: ``T`` |insert-tile|
Tiles can be inserted as objects to have full flexibility in placing,
scaling and rotating the tile image on your map. Like all objects, tile
@@ -168,7 +185,7 @@ objects consistent with that of :ref:`rectangle objects
Insert Template
~~~~~~~~~~~~~~~
-Shortcut: ``V``
+Shortcut: ``V`` |insert-template|
Can be used to quickly insert multiple instances of the template
selected in the Templates view. See :ref:`creating-template-instances`.
@@ -178,7 +195,7 @@ selected in the Templates view. See :ref:`creating-template-instances`.
Insert Text
~~~~~~~~~~~
-Shortcut: ``X``
+Shortcut: ``X`` |insert-text|
Text objects can be used to add arbitrary multi-line text to your maps.
You can configure various font properties and the wrapping / clipping
@@ -190,7 +207,7 @@ the game.
Select Objects
--------------
-Shortcut: ``S``
+Shortcut: ``S`` |tool-select-objects|
When you're not inserting new objects, you're generally using the Select
Objects tool. It packs a lot of functionality, which is outlined below.
@@ -384,3 +401,14 @@ jump there by right-clicking the property and selecting *Go to Object*.
faster by `sponsoring Tiled development `__. The
more support I receive the more time I can afford to spend improving
Tiled!
+
+
+.. |tool-select-objects| image:: ../../src/tiled/resources/images/22/tool-select-objects.png
+.. |insert-rectangle| image:: ../../src/tiled/resources/images/24/insert-rectangle.png
+.. |insert-point| image:: ../../src/tiled/resources/images/24/insert-point.png
+.. |insert-ellipse| image:: ../../src/tiled/resources/images/24/insert-ellipse.png
+.. |insert-capsule| image:: ../../src/tiled/resources/images/24/insert-capsule.png
+.. |insert-polygon| image:: ../../src/tiled/resources/images/24/insert-polygon.png
+.. |insert-tile| image:: ../../src/tiled/resources/images/24/insert-image.png
+.. |insert-template| image:: ../../src/tiled/resources/images/24/insert-template.png
+.. |insert-text| image:: ../../src/tiled/resources/images/24/insert-text.png
diff --git a/docs/reference/json-map-format.rst b/docs/reference/json-map-format.rst
index f7907cc57a..3b316854f1 100644
--- a/docs/reference/json-map-format.rst
+++ b/docs/reference/json-map-format.rst
@@ -233,6 +233,7 @@ Object
:header: Field, Type, Description
:widths: 1, 1, 4
+ capsule, bool, "Used to mark an object as a capsule"
ellipse, bool, "Used to mark an object as an ellipse"
gid, int, "Global tile ID, only if object represents a tile"
height, double, "Height in pixels."
@@ -492,10 +493,10 @@ Tileset
wangsets, array, "Array of :ref:`Wang sets ` (since 1.1.5)"
Each tileset has a ``firstgid`` (first global ID) property which
-tells you the global ID of its first tile (the one with local
-tile ID 0). This allows you to map the global IDs back to the
-right tileset, and then calculate the local tile ID by
-subtracting the ``firstgid`` from the global tile ID. The first
+tells you the global ID of its first tile (the one with local
+tile ID 0). This allows you to map the global IDs back to the
+right tileset, and then calculate the local tile ID by
+subtracting the ``firstgid`` from the global tile ID. The first
tileset always has a ``firstgid`` value of 1.
.. _json-tileset-grid:
@@ -769,6 +770,8 @@ Tiled 1.12
* Added ``mode`` property to :ref:`json-layer` to specify the blend mode to use
when rendering the layer.
+* Added ``capsule`` property to :ref:`json-object`.
+
Tiled 1.11.1
~~~~~~~~~~~~
diff --git a/docs/reference/tmx-changelog.rst b/docs/reference/tmx-changelog.rst
index 066eb11309..3855063b1a 100644
--- a/docs/reference/tmx-changelog.rst
+++ b/docs/reference/tmx-changelog.rst
@@ -9,6 +9,16 @@ Tiled 1.12
- Added ``mode`` attribute on :ref:`tmx-layer` to specific its blend mode.
+- Added capsule object shape. Same parameters as rectangular objects,
+ but marked as capsule with a child element:
+
+ .. code:: xml
+
+
+
+
Tiled 1.10
----------
diff --git a/docs/reference/tmx-map-format.rst b/docs/reference/tmx-map-format.rst
index 39b3586ce5..df50932cd0 100644
--- a/docs/reference/tmx-map-format.rst
+++ b/docs/reference/tmx-map-format.rst
@@ -578,9 +578,9 @@ properties from the specified template, properties saved with the object
will have higher priority, i.e. they will override the template
properties.
-Can contain at most one: :ref:`tmx-properties`, :ref:`tmx-ellipse` (since
-0.9), :ref:`tmx-point` (since 1.1), :ref:`tmx-polygon`, :ref:`tmx-polyline`,
-:ref:`tmx-text` (since 1.0)
+Can contain at most one: :ref:`tmx-properties`, :ref:`tmx-ellipse` (since 0.9),
+:ref:`tmx-capsule` (since 1.12), :ref:`tmx-point` (since 1.1),
+:ref:`tmx-polygon`, :ref:`tmx-polyline`, :ref:`tmx-text` (since 1.0)
.. _tmx-ellipse:
@@ -591,6 +591,15 @@ Used to mark an object as an ellipse. The existing ``x``, ``y``,
``width`` and ``height`` attributes are used to determine the size of
the ellipse.
+.. _tmx-capsule:
+
+
+~~~~~~~~~
+
+Used to mark an object as a capsule. The existing ``x``, ``y``,
+``width`` and ``height`` attributes are used to determine the size of
+the capsule.
+
.. _tmx-point:
diff --git a/src/libtiled/isometricrenderer.cpp b/src/libtiled/isometricrenderer.cpp
index a5b6587341..3f85f3feec 100644
--- a/src/libtiled/isometricrenderer.cpp
+++ b/src/libtiled/isometricrenderer.cpp
@@ -120,6 +120,15 @@ QPainterPath IsometricRenderer::shape(const MapObject *object) const
QPainterPath path;
switch (object->shape()) {
+ case MapObject::Capsule: {
+ QRectF bounds = object->bounds();
+ bounds.translate(-alignmentOffset(bounds, object->alignment(map())));
+ const qreal rad = std::min(std::abs(bounds.height()),
+ std::abs(bounds.width())) / 2;
+ path.addRoundedRect(bounds, rad, rad, Qt::SizeMode::AbsoluteSize);
+ path = transform().map(path);
+ break;
+ }
case MapObject::Ellipse: {
QRectF bounds = object->bounds();
bounds.translate(-alignmentOffset(bounds, object->alignment(map())));
@@ -166,7 +175,8 @@ QPainterPath IsometricRenderer::interactionShape(const MapObject *object) const
} else {
switch (object->shape()) {
case MapObject::Rectangle:
- case MapObject::Ellipse: {
+ case MapObject::Ellipse:
+ case MapObject::Capsule: {
QRectF bounds = object->bounds();
bounds.translate(-alignmentOffset(bounds, object->alignment(map())));
@@ -404,18 +414,19 @@ void IsometricRenderer::drawMapObject(QPainter *painter,
// TODO: Do something sensible to make null-sized objects usable
switch (object->shape()) {
- case MapObject::Ellipse: {
+ case MapObject::Ellipse:
+ case MapObject::Capsule: {
const QPolygonF rect = pixelRectToScreenPolygon(bounds);
- const QPainterPath ellipse = shape(object);
+ const QPainterPath path = shape(object);
- painter->drawPath(ellipse.translated(shadowOffset));
+ painter->drawPath(path.translated(shadowOffset));
painter->drawPolygon(rect.translated(shadowOffset));
painter->setPen(colorPen);
painter->drawPolygon(rect);
painter->setBrush(brush);
- painter->drawPath(ellipse);
+ painter->drawPath(path);
break;
}
case MapObject::Point:
diff --git a/src/libtiled/mapobject.cpp b/src/libtiled/mapobject.cpp
index a517801a98..be157ed768 100644
--- a/src/libtiled/mapobject.cpp
+++ b/src/libtiled/mapobject.cpp
@@ -178,6 +178,7 @@ QRectF MapObject::screenBounds(const MapRenderer &renderer) const
} else {
switch (mShape) {
case MapObject::Ellipse:
+ case MapObject::Capsule:
case MapObject::Rectangle: {
QRectF bounds(this->bounds());
align(bounds, alignment(renderer.map()));
@@ -491,8 +492,8 @@ void MapObject::flipInScreenCoordinates(FlipDirection direction, const QPointF &
}
/**
- * Rectangles, ellipses and polygons are flipped in "pixel space", the
- * coordinate space before isometric projection.
+ * Rectangles, ellipses, capsules and polygons are flipped in "pixel space",
+ * the coordinate space before isometric projection.
*
* NOTE: No attempt to handle rotated shapes on isometric maps is made. The
* expectation is that due to their weird handling, nobody really does that
diff --git a/src/libtiled/mapobject.h b/src/libtiled/mapobject.h
index 4e8ae8ab27..77f7b3e0d6 100644
--- a/src/libtiled/mapobject.h
+++ b/src/libtiled/mapobject.h
@@ -99,6 +99,7 @@ class TILEDSHARED_EXPORT MapObject : public Object
Polygon,
Polyline,
Ellipse,
+ Capsule,
Text,
Point,
};
diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp
index 5c6e646e42..ee58d5708a 100644
--- a/src/libtiled/mapreader.cpp
+++ b/src/libtiled/mapreader.cpp
@@ -1252,6 +1252,10 @@ std::unique_ptr MapReaderPrivate::readObject()
xml.skipCurrentElement();
object->setShape(MapObject::Ellipse);
object->setPropertyChanged(MapObject::ShapeProperty);
+ } else if (xml.name() == QLatin1String("capsule")) {
+ xml.skipCurrentElement();
+ object->setShape(MapObject::Capsule);
+ object->setPropertyChanged(MapObject::ShapeProperty);
} else if (xml.name() == QLatin1String("text")) {
object->setTextData(readObjectText());
object->setShape(MapObject::Text);
diff --git a/src/libtiled/maptovariantconverter.cpp b/src/libtiled/maptovariantconverter.cpp
index ac1dc93ff6..603a6639a2 100644
--- a/src/libtiled/maptovariantconverter.cpp
+++ b/src/libtiled/maptovariantconverter.cpp
@@ -622,6 +622,10 @@ QVariant MapToVariantConverter::toVariant(const MapObject &object) const
if (notTemplateInstance || object.propertyChanged(MapObject::ShapeProperty))
objectVariant[QStringLiteral("ellipse")] = true;
break;
+ case MapObject::Capsule:
+ if (notTemplateInstance || object.propertyChanged(MapObject::ShapeProperty))
+ objectVariant[QStringLiteral("capsule")] = true;
+ break;
case MapObject::Text:
if (notTemplateInstance || (object.propertyChanged(MapObject::TextProperty) ||
object.propertyChanged(MapObject::TextFontProperty) ||
diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp
index fc41107538..d90e00f85c 100644
--- a/src/libtiled/mapwriter.cpp
+++ b/src/libtiled/mapwriter.cpp
@@ -793,6 +793,10 @@ void MapWriterPrivate::writeObject(QXmlStreamWriter &w,
if (shouldWrite(true, isTemplateInstance, mapObject.propertyChanged(MapObject::ShapeProperty)))
w.writeEmptyElement(QLatin1String("ellipse"));
break;
+ case MapObject::Capsule:
+ if (shouldWrite(true, isTemplateInstance, mapObject.propertyChanged(MapObject::ShapeProperty)))
+ w.writeEmptyElement(QLatin1String("capsule"));
+ break;
case MapObject::Text: {
if (shouldWrite(true, isTemplateInstance,
mapObject.propertyChanged(MapObject::TextProperty) ||
diff --git a/src/libtiled/orthogonalrenderer.cpp b/src/libtiled/orthogonalrenderer.cpp
index e8096d1bcd..e5fd9360a1 100644
--- a/src/libtiled/orthogonalrenderer.cpp
+++ b/src/libtiled/orthogonalrenderer.cpp
@@ -77,6 +77,7 @@ QRectF OrthogonalRenderer::boundingRect(const MapObject *object) const
switch (object->shape()) {
case MapObject::Ellipse:
+ case MapObject::Capsule:
case MapObject::Rectangle:
if (bounds.isNull()) {
boundingRect = bounds.adjusted(-10 - extraSpace,
@@ -172,6 +173,15 @@ QPainterPath OrthogonalRenderer::shape(const MapObject *object) const
path.addEllipse(bounds);
break;
}
+ case MapObject::Capsule: {
+ const qreal rad = std::min(std::abs(bounds.height()),
+ std::abs(bounds.width())) / 2;
+ if (rad == 0.)
+ path.addEllipse(bounds.topLeft(), 10, 10);
+ else
+ path.addRoundedRect(bounds, rad, rad, Qt::SizeMode::AbsoluteSize);
+ break;
+ }
case MapObject::Point:
path = pointShape(object->position());
break;
@@ -203,6 +213,7 @@ QPainterPath OrthogonalRenderer::interactionShape(const MapObject *object) const
case MapObject::Rectangle:
case MapObject::Polygon:
case MapObject::Ellipse:
+ case MapObject::Capsule:
case MapObject::Text:
path = shape(object);
break;
@@ -394,7 +405,7 @@ void OrthogonalRenderer::drawMapObject(QPainter *painter,
// CoreGraphics when drawing the path requested by the
// QCoreGraphicsPaintEngine. Draw them as rectangle instead.
MapObject::Shape shape = object->shape();
- if (shape == MapObject::Ellipse &&
+ if ((shape == MapObject::Ellipse || shape == MapObject::Capsule) &&
((bounds.width() == qreal(0)) ^ (bounds.height() == qreal(0)))) {
shape = MapObject::Rectangle;
}
@@ -458,6 +469,23 @@ void OrthogonalRenderer::drawMapObject(QPainter *painter,
break;
}
+ case MapObject::Capsule: {
+ if (bounds.isNull())
+ bounds = QRectF(QPointF(-10, -10), QSizeF(20, 20));
+
+ const qreal rad = std::min(std::abs(bounds.height()),
+ std::abs(bounds.width())) / 2;
+
+ // Draw the shadow
+ painter->setPen(shadowPen);
+ painter->drawRoundedRect(bounds.translated(shadowOffset), rad, rad);
+
+ painter->setPen(linePen);
+ painter->setBrush(fillBrush);
+ painter->drawRoundedRect(bounds, rad, rad);
+ break;
+ }
+
case MapObject::Text: {
const auto& textData = object->textData();
painter->setFont(textData.font);
diff --git a/src/libtiled/varianttomapconverter.cpp b/src/libtiled/varianttomapconverter.cpp
index 0cff37447a..4e83959751 100644
--- a/src/libtiled/varianttomapconverter.cpp
+++ b/src/libtiled/varianttomapconverter.cpp
@@ -806,6 +806,7 @@ std::unique_ptr VariantToMapConverter::toMapObject(const QVariantMap
const QVariant polylineVariant = variantMap[QStringLiteral("polyline")];
const QVariant polygonVariant = variantMap[QStringLiteral("polygon")];
const QVariant ellipseVariant = variantMap[QStringLiteral("ellipse")];
+ const QVariant capsuleVariant = variantMap[QStringLiteral("capsule")];
const QVariant pointVariant = variantMap[QStringLiteral("point")];
const QVariant textVariant = variantMap[QStringLiteral("text")];
@@ -823,6 +824,10 @@ std::unique_ptr VariantToMapConverter::toMapObject(const QVariantMap
object->setShape(MapObject::Ellipse);
object->setPropertyChanged(MapObject::ShapeProperty);
}
+ if (capsuleVariant.toBool()) {
+ object->setShape(MapObject::Capsule);
+ object->setPropertyChanged(MapObject::ShapeProperty);
+ }
if (pointVariant.toBool()) {
object->setShape(MapObject::Point);
object->setPropertyChanged(MapObject::ShapeProperty);
diff --git a/src/plugins/lua/luaplugin.cpp b/src/plugins/lua/luaplugin.cpp
index 64db6cce8a..c088302b66 100644
--- a/src/plugins/lua/luaplugin.cpp
+++ b/src/plugins/lua/luaplugin.cpp
@@ -708,6 +708,8 @@ static const char *toString(MapObject::Shape shape)
return "polyline";
case MapObject::Ellipse:
return "ellipse";
+ case MapObject::Capsule:
+ return "capsule";
case MapObject::Text:
return "text";
case MapObject::Point:
@@ -739,6 +741,7 @@ void LuaWriter::writeMapObject(const Tiled::MapObject *mapObject)
switch (mapObject->shape()) {
case MapObject::Rectangle:
case MapObject::Ellipse:
+ case MapObject::Capsule:
case MapObject::Point:
break;
case MapObject::Polygon:
diff --git a/src/plugins/python/pythonbind.cpp b/src/plugins/python/pythonbind.cpp
index 18d5f5cb3d..52313f23f0 100644
--- a/src/plugins/python/pythonbind.cpp
+++ b/src/plugins/python/pythonbind.cpp
@@ -7675,6 +7675,10 @@ inittiled_Tiled(void)
// Tiled::MapObject::Ellipse
tmp_value = PyLong_FromLong(Tiled::MapObject::Ellipse);
PyDict_SetItemString((PyObject*) PyTiledMapObject_Type.tp_dict, "Ellipse", tmp_value);
+ Py_DECREF(tmp_value);
+ // Tiled::MapObject::Capsule
+ tmp_value = PyLong_FromLong(Tiled::MapObject::Capsule);
+ PyDict_SetItemString((PyObject*) PyTiledMapObject_Type.tp_dict, "Capsule", tmp_value);
Py_DECREF(tmp_value);
// Tiled::MapObject::Text
tmp_value = PyLong_FromLong(Tiled::MapObject::Text);
diff --git a/src/tiled/createcapsuleobjecttool.cpp b/src/tiled/createcapsuleobjecttool.cpp
new file mode 100644
index 0000000000..da012dfe5d
--- /dev/null
+++ b/src/tiled/createcapsuleobjecttool.cpp
@@ -0,0 +1,58 @@
+/*
+ * createcapsuleobjecttool.cpp
+ * Copyright 2025, Jocelyn
+ *
+ * This file is part of Tiled.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "createcapsuleobjecttool.h"
+
+#include "mapobject.h"
+#include "utils.h"
+
+using namespace Tiled;
+
+CreateCapsuleObjectTool::CreateCapsuleObjectTool(QObject *parent)
+ : CreateScalableObjectTool("CreateCapsuleObjectTool",
+ parent)
+{
+ QIcon icon(QLatin1String(":images/24/insert-capsule.png"));
+ icon.addFile(QLatin1String(":images/48/insert-capsule.png"));
+ setIcon(icon);
+ setShortcut(Qt::SHIFT + Qt::Key_C);
+ Utils::setThemeIcon(this, "insert-capsule");
+ languageChangedImpl();
+}
+
+void CreateCapsuleObjectTool::languageChanged()
+{
+ CreateScalableObjectTool::languageChanged();
+ languageChangedImpl();
+}
+
+void CreateCapsuleObjectTool::languageChangedImpl()
+{
+ setName(tr("Insert Capsule"));
+}
+
+MapObject *CreateCapsuleObjectTool::createNewMapObject()
+{
+ auto newMapObject = new MapObject;
+ newMapObject->setShape(MapObject::Capsule);
+ return newMapObject;
+}
+
+#include "moc_createcapsuleobjecttool.cpp"
diff --git a/src/tiled/createcapsuleobjecttool.h b/src/tiled/createcapsuleobjecttool.h
new file mode 100644
index 0000000000..b71834f64d
--- /dev/null
+++ b/src/tiled/createcapsuleobjecttool.h
@@ -0,0 +1,43 @@
+/*
+ * createcapsulebjecttool.h
+ * Copyright 2025, Jocelyn
+ *
+ * This file is part of Tiled.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "createscalableobjecttool.h"
+
+namespace Tiled {
+
+class CreateCapsuleObjectTool : public CreateScalableObjectTool
+{
+ Q_OBJECT
+
+public:
+ CreateCapsuleObjectTool(QObject *parent);
+
+ void languageChanged() override;
+
+protected:
+ MapObject *createNewMapObject() override;
+
+private:
+ void languageChangedImpl();
+};
+
+} // namespace Tiled
diff --git a/src/tiled/editablemapobject.h b/src/tiled/editablemapobject.h
index a43b971f1f..001f28b12a 100644
--- a/src/tiled/editablemapobject.h
+++ b/src/tiled/editablemapobject.h
@@ -86,6 +86,7 @@ class EditableMapObject : public EditableObject
Polygon,
Polyline,
Ellipse,
+ Capsule,
Text,
Point,
};
diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs
index 1733a758da..078531ca3b 100644
--- a/src/tiled/libtilededitor.qbs
+++ b/src/tiled/libtilededitor.qbs
@@ -178,6 +178,8 @@ DynamicLibrary {
"commandsedit.ui",
"consoledock.cpp",
"consoledock.h",
+ "createcapsuleobjecttool.cpp",
+ "createcapsuleobjecttool.h",
"createellipseobjecttool.cpp",
"createellipseobjecttool.h",
"createobjecttool.cpp",
diff --git a/src/tiled/mapeditor.cpp b/src/tiled/mapeditor.cpp
index c99388bace..80c529c6cd 100644
--- a/src/tiled/mapeditor.cpp
+++ b/src/tiled/mapeditor.cpp
@@ -23,6 +23,7 @@
#include "addremovetileset.h"
#include "bucketfilltool.h"
#include "changeselectedarea.h"
+#include "createcapsuleobjecttool.h"
#include "createellipseobjecttool.h"
#include "createobjecttool.h"
#include "createpointobjecttool.h"
@@ -179,6 +180,7 @@ MapEditor::MapEditor(QObject *parent)
CreateObjectTool *rectangleObjectsTool = new CreateRectangleObjectTool(this);
CreateObjectTool *pointObjectsTool = new CreatePointObjectTool(this);
CreateObjectTool *ellipseObjectsTool = new CreateEllipseObjectTool(this);
+ CreateObjectTool *capsuleObjectsTool = new CreateCapsuleObjectTool(this);
CreateObjectTool *polygonObjectsTool = new CreatePolygonObjectTool(this);
CreateObjectTool *textObjectsTool = new CreateTextObjectTool(this);
@@ -197,6 +199,7 @@ MapEditor::MapEditor(QObject *parent)
mToolsToolBar->addAction(mToolManager->registerTool(rectangleObjectsTool));
mToolsToolBar->addAction(mToolManager->registerTool(pointObjectsTool));
mToolsToolBar->addAction(mToolManager->registerTool(ellipseObjectsTool));
+ mToolsToolBar->addAction(mToolManager->registerTool(capsuleObjectsTool));
mToolsToolBar->addAction(mToolManager->registerTool(polygonObjectsTool));
mToolsToolBar->addAction(mToolManager->registerTool(tileObjectsTool));
mToolsToolBar->addAction(mToolManager->registerTool(templatesTool));
diff --git a/src/tiled/mapobjectmodel.cpp b/src/tiled/mapobjectmodel.cpp
index 26a644d403..4d06a54ba8 100644
--- a/src/tiled/mapobjectmodel.cpp
+++ b/src/tiled/mapobjectmodel.cpp
@@ -43,6 +43,7 @@ ObjectIconManager::ObjectIconManager()
, mPolygonIcon(QLatin1String(":images/24/object-polygon.png"))
, mPolylineIcon(QLatin1String(":images/24/object-polyline.png"))
, mEllipseIcon(QLatin1String(":images/24/object-ellipse.png"))
+ , mCapsuleIcon(QLatin1String(":images/24/object-capsule.png"))
, mTextIcon(QLatin1String(":images/24/object-text.png"))
, mPointIcon(QLatin1String(":images/24/object-point.png"))
{}
@@ -64,6 +65,8 @@ const QIcon &ObjectIconManager::iconForObject(const MapObject &object) const
return mPolylineIcon;
case MapObject::Ellipse:
return mEllipseIcon;
+ case MapObject::Capsule:
+ return mCapsuleIcon;
case MapObject::Text:
return mTextIcon;
case MapObject::Point:
diff --git a/src/tiled/mapobjectmodel.h b/src/tiled/mapobjectmodel.h
index aebbc64e47..9abe7fa2c5 100644
--- a/src/tiled/mapobjectmodel.h
+++ b/src/tiled/mapobjectmodel.h
@@ -52,6 +52,7 @@ class ObjectIconManager
const QIcon mPolygonIcon;
const QIcon mPolylineIcon;
const QIcon mEllipseIcon;
+ const QIcon mCapsuleIcon;
const QIcon mTextIcon;
const QIcon mPointIcon;
};
diff --git a/src/tiled/objectselectiontool.cpp b/src/tiled/objectselectiontool.cpp
index 2c6dd9187e..e5d4d7c5e6 100644
--- a/src/tiled/objectselectiontool.cpp
+++ b/src/tiled/objectselectiontool.cpp
@@ -828,6 +828,7 @@ static QRectF pixelBounds(const MapObject *object)
switch (object->shape()) {
case MapObject::Ellipse:
+ case MapObject::Capsule:
case MapObject::Rectangle:
case MapObject::Point: {
QRectF bounds(object->bounds());
@@ -864,6 +865,7 @@ static bool canResizeAbsolute(const MapObject *object)
switch (object->shape()) {
case MapObject::Rectangle:
case MapObject::Ellipse:
+ case MapObject::Capsule:
case MapObject::Text:
return true;
case MapObject::Point:
@@ -914,6 +916,7 @@ static QRectF objectBounds(const MapObject *object,
} else {
switch (object->shape()) {
case MapObject::Ellipse:
+ case MapObject::Capsule:
case MapObject::Rectangle: {
QRectF bounds(object->bounds());
align(bounds, object->alignment());
diff --git a/src/tiled/resources/images/24/insert-capsule.png b/src/tiled/resources/images/24/insert-capsule.png
new file mode 100644
index 0000000000..c58b057952
Binary files /dev/null and b/src/tiled/resources/images/24/insert-capsule.png differ
diff --git a/src/tiled/resources/images/24/object-capsule.png b/src/tiled/resources/images/24/object-capsule.png
new file mode 100644
index 0000000000..84f08092b8
Binary files /dev/null and b/src/tiled/resources/images/24/object-capsule.png differ
diff --git a/src/tiled/resources/images/48/insert-capsule.png b/src/tiled/resources/images/48/insert-capsule.png
new file mode 100644
index 0000000000..6a1550f1d9
Binary files /dev/null and b/src/tiled/resources/images/48/insert-capsule.png differ
diff --git a/src/tiled/resources/images/parts/icons.tmx b/src/tiled/resources/images/parts/icons.tmx
index 484d88ca2a..b425430e96 100644
--- a/src/tiled/resources/images/parts/icons.tmx
+++ b/src/tiled/resources/images/parts/icons.tmx
@@ -1,36 +1,41 @@
-