diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index 0c25c82e14..4ffbfc742d 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -7,6 +7,7 @@ from typing import ( Sequence, ) +from pygame import Rect, FRect from ._common import Coordinate, RectValue _CanBeCircle = Union[Circle, Tuple[Coordinate, float], Sequence[float]] @@ -95,5 +96,7 @@ class Circle: def update(self, x: float, y: float, r: float, /) -> None: ... @overload def update(self, center: Coordinate, r: float, /) -> None: ... + def as_rect(self) -> Rect: ... + def as_frect(self) -> FRect: ... def __copy__(self) -> Circle: ... copy = __copy__ diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index 02b24eae38..747b75e1d9 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -255,6 +255,48 @@ .. ## Circle.update ## + .. method:: as_rect + + | :sl:`returns the smallest pygame.Rect object that contains the circle` + | :sg:`as_rect() -> Rect` + + The `as_rect` method returns a `pygame.Rect` object that represents the smallest + rectangle that completely contains the `Circle` object. This means that the `Rect` + object returned by as_rect will have dimensions such that it completely encloses + the `Circle`, with no part of the `Circle` extending outside of the `Rect`. + + .. note:: + This method is equivalent(behaviour wise) to the following code: + + .. code-block:: python + + Rect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2) + + .. versionadded:: 2.5.0 + + .. ## Circle.as_rect ## + + .. method:: as_frect + + | :sl:`returns the smallest pygame.FRect object that contains the circle` + | :sg:`as_frect() -> FRect` + + The `as_frect` method returns a `pygame.FRect` object that represents the smallest + rectangle that completely contains the `Circle` object. This means that the `FRect` + object returned by as_rect will have dimensions such that it completely encloses + the `Circle`, with no part of the `Circle` extending outside of the `FRect`. + + .. note:: + This method is equivalent(behaviour wise) to the following code: + + .. code-block:: python + + FRect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2) + + .. versionadded:: 2.5.0 + + .. ## Circle.as_frect ## + .. method:: copy | :sl:`returns a copy of the circle` diff --git a/src_c/circle.c b/src_c/circle.c index f393cab318..3d84a3e33f 100644 --- a/src_c/circle.c +++ b/src_c/circle.c @@ -388,6 +388,28 @@ pg_circle_colliderect(pgCircleObject *self, PyObject *const *args, return PyBool_FromLong(pgCollision_RectCircle(x, y, w, h, &self->circle)); } +static PyObject * +pg_circle_as_rect(pgCircleObject *self, PyObject *_null) +{ + pgCircleBase *scirc = &self->circle; + int diameter = (int)(scirc->r * 2.0); + int x = (int)(scirc->x - scirc->r); + int y = (int)(scirc->y - scirc->r); + + return pgRect_New4(x, y, diameter, diameter); +} + +static PyObject * +pg_circle_as_frect(pgCircleObject *self, PyObject *_null) +{ + pgCircleBase *scirc = &self->circle; + double diameter = scirc->r * 2.0; + double x = scirc->x - scirc->r; + double y = scirc->y - scirc->r; + + return pgFRect_New4((float)x, (float)y, (float)diameter, (float)diameter); +} + static struct PyMethodDef pg_circle_methods[] = { {"collidepoint", (PyCFunction)pg_circle_collidepoint, METH_FASTCALL, DOC_CIRCLE_COLLIDEPOINT}, @@ -400,6 +422,10 @@ static struct PyMethodDef pg_circle_methods[] = { DOC_CIRCLE_COLLIDERECT}, {"update", (PyCFunction)pg_circle_update, METH_FASTCALL, DOC_CIRCLE_UPDATE}, + {"as_rect", (PyCFunction)pg_circle_as_rect, METH_NOARGS, + DOC_CIRCLE_ASRECT}, + {"as_frect", (PyCFunction)pg_circle_as_frect, METH_NOARGS, + DOC_CIRCLE_ASFRECT}, {"__copy__", (PyCFunction)pg_circle_copy, METH_NOARGS, DOC_CIRCLE_COPY}, {"copy", (PyCFunction)pg_circle_copy, METH_NOARGS, DOC_CIRCLE_COPY}, {NULL, NULL, 0, NULL}}; diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h index 3b5cfa7140..bd8ffb53cb 100644 --- a/src_c/doc/geometry_doc.h +++ b/src_c/doc/geometry_doc.h @@ -15,4 +15,6 @@ #define DOC_CIRCLE_MOVEIP "move_ip((x, y), /) -> None\nmove_ip(x, y, /) -> None\nmove_ip(vector2, /) -> None\nmoves the circle by a given amount, in place" #define DOC_CIRCLE_COLLIDERECT "colliderect(rect, /) -> bool\ncolliderect((x, y, width, height), /) -> bool\ncolliderect(x, y, width, height, /) -> bool\ncolliderect((x, y), (width, height), /) -> bool\nchecks if a rectangle intersects the circle" #define DOC_CIRCLE_UPDATE "update((x, y), radius, /) -> None\nupdate(x, y, radius, /) -> None\nupdates the circle position and radius" +#define DOC_CIRCLE_ASRECT "as_rect() -> Rect\nreturns the smallest pygame.Rect object that contains the circle" +#define DOC_CIRCLE_ASFRECT "as_frect() -> FRect\nreturns the smallest pygame.FRect object that contains the circle" #define DOC_CIRCLE_COPY "copy() -> Circle\nreturns a copy of the circle" diff --git a/test/geometry_test.py b/test/geometry_test.py index 95d17b7c74..1dbe9b4332 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -696,6 +696,32 @@ def test_selfupdate(self): self.assertEqual(c.r, c_r) self.assertEqual(c.r_sqr, c_r_sqr) + def test_as_rect_invalid_args(self): + c = Circle(0, 0, 10) + + invalid_args = [None, [], "1", (1,), Vector2(1, 1), 1] + + with self.assertRaises(TypeError): + for arg in invalid_args: + c.as_rect(arg) + + def test_as_rect(self): + c = Circle(0, 0, 10) + self.assertEqual(c.as_rect(), Rect(-10, -10, 20, 20)) + + def test_as_frect_invalid_args(self): + c = Circle(0, 0, 10) + + invalid_args = [None, [], "1", (1,), Vector2(1, 1), 1] + + with self.assertRaises(TypeError): + for arg in invalid_args: + c.as_frect(arg) + + def test_as_frect(self): + c = Circle(0, 0, 10) + self.assertEqual(c.as_frect(), FRect(-10, -10, 20, 20)) + def test_circle_richcompare(self): """Ensures that the circle correctly compares itself to other circles""" c = Circle(0, 0, 10)