"""Rects all act the same, but take four of the possible eight attributes and calculate the rest."""
from __future__ import annotations
import math
from typing import Any, NamedTuple, TypedDict
from pyglet.math import Vec2
from arcade.types.numbers import AsFloat
from arcade.types.vector_like import AnchorPoint, Point2
RectParams = tuple[AsFloat, AsFloat, AsFloat, AsFloat]
IntRectParams = tuple[int, int, int, int]
[docs]
class RectKwargs(TypedDict):
"""Annotates a plain :py:class:`dict` of :py:class:`Rect` arguments.
This is only meaningful as a type annotation during type checking.
For example, the :py:meth:`Rect.kwargs <arcade.types.Rect.kwargs>`
property returns an ordinary will actually be a :py:class:`dict`
of :py:class:`Rect` field names to :py:class:`float` values.
To learn more, please see:
* :py:class:`.Rect`
* :py:class:`typing.TypedDict`
"""
left: float
right: float
bottom: float
top: float
width: float
height: float
x: float
y: float
[docs]
class Rect(NamedTuple):
"""A rectangle, with several convenience properties and functions.
This object is immutable by design. It provides no setters, and is a NamedTuple
subclass.
Attempts to implement all Rectangle functions used in the library, and to be a
helpful tool for developers storing/manipulating rectangle and rectangle-like
constructs.
Rectangles cannot rotate by design, since this complicates their implementation
a lot.
You probably don't want to create one of these directly, and should instead use
a helper method, like :py:func:`.LBWH`, :py:func:`.LRBT`, :py:func:`.XYWH`, or
:py:func:`.Viewport`.
You can also use :py:meth:`.from_kwargs` to create a Rect from keyword arguments.
"""
#: The X position of the rectangle's left edge.
left: float
#: The X position of the rectangle's right edge.
right: float
#: The Y position of the rectangle's bottom edge.
bottom: float
#: The Y position of the rectangle's top edge.
top: float
#: The total width of the rectangle along the X axis.
#: To get the rectangle's :py:attr:`.height` well, use
#: :py:attr:`.size`
width: float
#: The total height of the rectangle along the Y axis.
#: To get the rectangle's :py:attr:`.width` as well, use
#: :py:attr:`.size`.
height: float
#: The center of the rectangle along the X axis. To get its
#: center :py:attr:`.y` as well, use :py:attr:`.center`.
x: float
#: The center of the rectangle along the Y axis. To get its
#: center :py:attr:`.x` as well, use :py:attr:`.center`.
y: float
@property
def center_x(self) -> float:
"""Backwards-compatible alias for :py:attr:`.x`."""
return self.x
@property
def center_y(self) -> float:
"""Backwards-compatible alias for :py:attr:`.y`."""
return self.y
@property
def center(self) -> Vec2:
"""Returns a :py:class:`~pyglet.math.Vec2` representing the center of the rectangle."""
return Vec2(self.x, self.y)
@property
def bottom_left(self) -> Vec2:
"""
Returns a :py:class:`~pyglet.math.Vec2` representing the
bottom-left of the rectangle.
"""
return Vec2(self.left, self.bottom)
@property
def bottom_right(self) -> Vec2:
"""
Returns a :py:class:`~pyglet.math.Vec2` representing the
bottom-right of the rectangle.
"""
return Vec2(self.right, self.bottom)
@property
def top_left(self) -> Vec2:
"""
Returns a :py:class:`~pyglet.math.Vec2` representing the
top-left of the rectangle.
"""
return Vec2(self.left, self.top)
@property
def top_right(self) -> Vec2:
"""
Returns a :py:class:`~pyglet.math.Vec2` representing the
top-right of the rectangle.
"""
return Vec2(self.right, self.top)
@property
def bottom_center(self) -> Vec2:
"""
Returns a :py:class:`~pyglet.math.Vec2` representing the
bottom-center of the rectangle.
"""
return Vec2(self.x, self.bottom)
@property
def center_right(self) -> Vec2:
"""
Returns a :py:class:`~pyglet.math.Vec2` representing the
center-right of the rectangle.
"""
return Vec2(self.right, self.y)
@property
def top_center(self) -> Vec2:
"""
Returns a :py:class:`~pyglet.math.Vec2` representing the
top-center of the rectangle.
"""
return Vec2(self.x, self.top)
@property
def center_left(self) -> Vec2:
"""Returns a :py:class:`~pyglet.math.Vec2` representing the center-left of the rectangle."""
return Vec2(self.left, self.y)
@property
def size(self) -> Vec2:
"""Returns a :py:class:`~pyglet.math.Vec2` representing the size of the rectangle."""
return Vec2(self.width, self.height)
@property
def area(self) -> float:
"""The area of the rectangle in square pixels."""
return self.width * self.height
@property
def aspect_ratio(self) -> float:
"""Returns the ratio between the width and the height."""
return self.width / self.height
[docs]
def at_position(self, position: Point2) -> Rect:
"""Returns a new :py:class:`Rect` which is moved to put `position` at its center."""
x, y = position
return XYWH(x, y, self.width, self.height)
[docs]
def move(self, dx: AsFloat = 0.0, dy: AsFloat = 0.0) -> Rect:
"""
Returns a new :py:class:`Rect` which is moved by `dx` in the
x-direction and `dy` in the y-direction.
"""
return XYWH(self.x + dx, self.y + dy, self.width, self.height)
[docs]
def resize(
self,
width: AsFloat | None = None,
height: AsFloat | None = None,
anchor: Vec2 = AnchorPoint.CENTER,
) -> Rect:
"""
Returns a new :py:class:`Rect` at the current Rect's position,
but with a new width and height, anchored at a point (default center.)
"""
width = width if width is not None else self.width
height = height if height is not None else self.height
anchor_x = self.left + anchor.x * self.width
anchor_y = self.bottom + anchor.y * self.height
# ratio_x = width / (self.width or 1.0)
# ratio_y = height / (self.height or 1.0)
# adjusted_left = anchor_x - (self.left - anchor_x) * ratio_x
# adjusted_bottom = anchor_y + (self.bottom - anchor_y) * ratio_y
# return LBWH(adjusted_left, adjusted_bottom, width, height)
return XYWH(anchor_x, anchor_y, width, height, anchor)
[docs]
def scale(self, new_scale: AsFloat, anchor: Vec2 = AnchorPoint.CENTER) -> Rect:
"""
Returns a new :py:class:`Rect` scaled by a factor of ``new_scale``,
anchored at a point (default center.)
"""
anchor_x = self.left + anchor.x * self.width
anchor_y = self.bottom + anchor.y * self.height
adjusted_left = anchor_x + (self.left - anchor_x) * new_scale
adjusted_right = anchor_x + (self.right - anchor_x) * new_scale
adjusted_top = anchor_y + (self.top - anchor_y) * new_scale
adjusted_bottom = anchor_y + (self.bottom - anchor_y) * new_scale
return LRBT(adjusted_left, adjusted_right, adjusted_bottom, adjusted_top)
[docs]
def scale_axes(self, new_scale: Point2, anchor: Vec2 = AnchorPoint.CENTER) -> Rect:
"""
Return a new :py:class:`Rect`
scaled by a factor of `new_scale.x` in the width
and `new_scale.y` in the height, anchored at a point (default center.)
"""
anchor_x = self.left + anchor.x * self.width
anchor_y = self.bottom + anchor.y * self.height
nsx, nsy = new_scale
adjusted_left = anchor_x + (self.left - anchor_x) * nsx
adjusted_right = anchor_x + (self.right - anchor_x) * nsx
adjusted_top = anchor_y + (self.top - anchor_y) * nsy
adjusted_bottom = anchor_y + (self.bottom - anchor_y) * nsy
return LRBT(adjusted_left, adjusted_right, adjusted_bottom, adjusted_top)
[docs]
def __mul__(self, scale: AsFloat) -> Rect: # type: ignore[override]
"""Scale the Rect by ``scale`` relative to ``(0, 0)``."""
return Rect(
self.left * scale,
self.right * scale,
self.bottom * scale,
self.top * scale,
self.width * scale,
self.height * scale,
self.x * scale,
self.y * scale,
)
[docs]
def __truediv__(self, scale: AsFloat) -> Rect:
"""Scale the rectangle by 1/``scale`` relative to ``(0, 0)``."""
return Rect(
self.left / scale,
self.right / scale,
self.bottom / scale,
self.top / scale,
self.width / scale,
self.height / scale,
self.x / scale,
self.y / scale,
)
[docs]
def align_top(self, value: AsFloat) -> Rect:
"""Returns a new :py:class:`Rect`, which is aligned to the top at `value`."""
return LBWH(self.left, value - self.height, self.width, self.height)
[docs]
def align_bottom(self, value: AsFloat) -> Rect:
"""Returns a new :py:class:`Rect`, which is aligned to the bottom at `value`."""
return LBWH(self.left, value, self.width, self.height)
[docs]
def align_left(self, value: AsFloat) -> Rect:
"""Returns a new :py:class:`Rect`, which is aligned to the left at `value`."""
return LBWH(value, self.bottom, self.width, self.height)
[docs]
def align_right(self, value: AsFloat) -> Rect:
"""Returns a new :py:class:`Rect`, which is aligned to the right at `value`."""
return LBWH(value - self.width, self.bottom, self.width, self.height)
[docs]
def align_center(self, value: Point2) -> Rect:
"""Returns a new :py:class:`Rect`, which is aligned to the center x and y at `value`."""
cx, cy = value
return XYWH(cx, cy, self.width, self.height)
[docs]
def align_x(self, value: AsFloat) -> Rect:
"""Returns a new :py:class:`Rect`, which is aligned to the x at `value`."""
return XYWH(value, self.y, self.width, self.height)
[docs]
def align_center_x(self, value: AsFloat) -> Rect:
"""Backwards-compatible alias for `Rect.x`."""
return self.align_x(value)
[docs]
def align_y(self, value: AsFloat) -> Rect:
"""Get a new :py:class:`Rect`, which is aligned to the y at `value`."""
return XYWH(self.x, value, self.width, self.height)
[docs]
def align_center_y(self, value: AsFloat) -> Rect:
"""Backwards-compatible alias for `Rect.x`."""
return self.align_y(value)
[docs]
def min_size(
self,
width: AsFloat | None = None,
height: AsFloat | None = None,
anchor: Vec2 = AnchorPoint.CENTER,
) -> Rect:
"""
Return a :py:class:`Rect` that is at least size `width` by `height`, positioned at
the current position and anchored to a point (default center.)
"""
width = max(width or 0.0, self.width)
height = max(height or 0.0, self.height)
return self.resize(width, height, anchor)
[docs]
def max_size(
self,
width: AsFloat | None = None,
height: AsFloat | None = None,
anchor: Vec2 = AnchorPoint.CENTER,
) -> Rect:
"""
Return a :py:class:`Rect` that is at most size `width` by `height`, positioned at
the current position and anchored to a point (default center.)
"""
width = min(width or float("inf"), self.width)
height = min(height or float("inf"), self.height)
return self.resize(width, height, anchor)
[docs]
def clamp_height(
self,
min_height: AsFloat | None = None,
max_height: AsFloat | None = None,
anchor: Vec2 = AnchorPoint.CENTER,
) -> Rect:
"""
Return a :py:class:`Rect` that has a height between `min_height` and `max_height`,
positioned at the current position and anchored to a point (default center.)
"""
height = min(max_height or float("inf"), max(min_height or 0.0, self.height))
return self.resize(self.width, height, anchor)
[docs]
def clamp_width(
self,
min_width: AsFloat | None = None,
max_width: AsFloat | None = None,
anchor: Vec2 = AnchorPoint.CENTER,
) -> Rect:
"""Return a :py:class:`.Rect` constrained to the passed dimension.
It will be created as follows:
* Its :py:attr:`.width` will be between any provided ``min_width``
and ``max_width``
* It will be positioned at the current position using the passed
``anchor``
Args:
min_width:
An optional minimum width.
max_width:
An optional maximum width.
anchor:
A :py:class:`~pyglet.math.Vec2` of the fractional
percentage of the rectangle's total :py:attr:`.size` along
both axes. It defaults to the center.
"""
width = min(max_width or float("inf"), max(min_width or 0.0, self.width))
return self.resize(width, self.height, anchor)
[docs]
def clamp_size(
self,
min_width: AsFloat | None = None,
max_width: AsFloat | None = None,
min_height: AsFloat | None = None,
max_height: AsFloat | None = None,
anchor: Vec2 = AnchorPoint.CENTER,
) -> Rect:
"""Get a new clamped-size rectangle at the same position and anchored at ``anchor_point``.
This combines the effects of :py:meth:`clamp_width` and :py:meth:`clamp_height` into
one call.
"""
width = min(max_width or float("inf"), max(min_width or 0.0, self.width))
height = min(max_height or float("inf"), max(min_height or 0.0, self.height))
return self.resize(width, height, anchor)
[docs]
def union(self, other: Rect) -> Rect:
"""Get the smallest rectangle covering both this one and ``other``."""
left = min(self.left, other.left)
right = max(self.right, other.right)
bottom = min(self.bottom, other.bottom)
top = max(self.top, other.top)
return LRBT(left, right, bottom, top)
[docs]
def __or__(self, other: Rect) -> Rect:
"""Shorthand for :py:meth:`rect.union(other) <union>`.
Args:
other: Another :py:class:`Rect` instance.
"""
return self.union(other)
[docs]
def intersection(self, other: Rect) -> Rect | None:
"""Return a :py:class:`Rect` of the overlap if any exists.
If the two :py:class:`Rect` instances do not intersect, this
method will return ``None`` instead.
Args:
other: Another :py:class:`Rect` instance.
"""
intersecting = self.overlaps(other)
if not intersecting:
return None
left = max(self.left, other.left)
right = min(self.right, other.right)
bottom = max(self.bottom, other.bottom)
top = min(self.top, other.top)
return LRBT(left, right, bottom, top)
[docs]
def __and__(self, other: Rect) -> Rect | None:
"""Shorthand for :py:meth:`rect.intersection(other) <interesection>`.
Args:
other: Another :py:class:`Rect` instance.
"""
return self.intersection(other)
[docs]
def overlaps(self, other: Rect) -> bool:
"""Returns ``True`` if `other` overlaps with ``self``.
Args:
other: Another :py:class:`Rect` instance.
"""
return (other.width + self.width) / 2.0 > abs(self.x - other.x) and (
other.height + self.height
) / 2.0 > abs(self.y - other.y)
[docs]
def point_in_rect(self, point: Point2) -> bool:
"""Returns ``True`` if ``point`` is inside this rectangle.
Args:
point: A tuple of :py:class:`int` or :py:class:`float` values.
"""
px, py = point
return (self.left <= px <= self.right) and (self.bottom <= py <= self.top)
[docs]
def point_in_bounds(self, point: Point2) -> bool:
"""Returns ``True`` if ``point`` is inside this rectangle excluding the boundaries.
Args:
point: A tuple of :py:class:`int` or :py:class:`float` values.
"""
px, py = point
return (self.left < px < self.right) and (self.bottom < py < self.top)
[docs]
def __contains__(self, point: Point2 | Any) -> bool:
"""Shorthand for :py:meth:`rect.point_in_rect(point) <point_in_rect>`.
Args:
point: A tuple of :py:class:`int` or :py:class:`float` values.
"""
from arcade.utils import is_iterable
if not is_iterable(point):
return False
return self.point_in_rect(point)
[docs]
def distance_from_bounds(self, point: Point2) -> float:
"""Returns the point's distance from the boundary of this rectangle."""
px, py = point
diff = Vec2(px - self.x, py - self.y)
dx = abs(diff.x) - self.width / 2.0
dy = abs(diff.y) - self.height / 2.0
d = (max(dx, 0.0) ** 2 + max(dy, 0.0) ** 2) ** 0.5 + min(max(dx, dy), 0.0)
return d
[docs]
def point_on_bounds(self, point: Point2, tolerance: float) -> bool:
"""Returns ``True`` if ``point`` is within ``tolerance`` of the bounds.
The ``point``'s distance from the bounds is computed by through
:py:meth:`distance_from_bounds`.
Args:
point: The point to check.
tolerance: The maximum distance the point can be from the bounds.
"""
return abs(self.distance_from_bounds(point)) < tolerance
[docs]
def position_to_uv(self, point: Point2) -> Vec2:
"""Convert a point to UV space values inside the rectangle.
This is like a pair of ratios which measure how far the ``point``
is from the rectangle's :py:meth:`bottom_left` up toward its
:py:meth:`top_right` along each axis.
.. warning:: This method does not clamp output!
Since ``point`` is absolute pixels, one or both axes
of the returned :py:class:`~pyglet.math.Vec2` can be:
* less than ``0.0``
* greater than ``1.0``
Each axis of the return value measures how far into
the rectangle's ``size`` the ``point`` is relative
to the :py:meth:`bottom_left`:
.. code-block:: python
# consult the diagram below
Vec2(
(point.x - rect.left) / rect.width,
(point.y - rect.bottom) / rect.height
)
.. code-block::
|------- rect.width ------|
The rectangle (rect.top_right)
+-------------------------T ---
| | |
- - - - - - - P (Point x, y)| |
| | | rect.height
| | | | |
y | | |
| B----------|--------------+ ---
| (rect.bottom_right)
|
O----- x -----|
Args:
point: A point relative to the rectangle's :py:meth:`bottom_left` corner.
"""
x, y = point
return Vec2(
(x - self.left) / self.width,
(y - self.bottom) / self.height,
)
[docs]
def uv_to_position(self, uv: Point2) -> Vec2:
"""Convert a point in UV-space to a point within the rectangle.
The ``uv`` is a pair of ratios which describe how far a point
extends across the rectangle's :py:attr:`width` and
:py:attr:`height` from the :py:attr:`bottom_left` toward its
:py:attr:`top_right`.
.. warning:: This method does not clamp output!
Since one or both of ``uv``'s components can be
less than ``0.0`` or greater than ``1.0``, the
returned point can fall outside the rectangle.
The following can be used as arguments to this function:
#. Values in :py:class:`~arcade.types.AnchorPoint`
#. Returned values from :py:meth:`position_to_uv`
#. Rescaled input data from controllers
Args:
uv:
A pair of ratio values describing how far a
a point falls from a rectangle's :py:attr:`bottom_left`
toward its :py:attr:`top_right`.
"""
x, y = uv
return Vec2(self.left + x * self.width, self.bottom + y * self.height)
[docs]
def get_relative_to_anchor(self, point: Point2, anchor: Vec2 = AnchorPoint.CENTER) -> Vec2:
"""Convert a point to a relative offset from the anchor point.
Args:
point:
The point to make relative.
anchor:
The anchor point to make the point relative to.
"""
x, y = point
rx = x - (self.left + (self.width * anchor.x))
ry = y - (self.bottom + (self.height * anchor.y))
return Vec2(rx, ry)
[docs]
def to_points(self) -> tuple[Vec2, Vec2, Vec2, Vec2]:
"""Return a new :py:class:`tuple` of this rectangle's corner points.
The points will be ordered as follows:
#. :py:meth:`bottom_left`
#. :py:meth:`top_left`
#. :py:meth:`top_right`
#. :py:meth:`bottom_right`
"""
left = self.left
bottom = self.bottom
right = self.right
top = self.top
return (Vec2(left, bottom), Vec2(left, top), Vec2(right, top), Vec2(right, bottom))
@property
def lbwh(self) -> RectParams:
"""Provides a tuple in the format of (left, bottom, width, height)."""
return (self.left, self.bottom, self.width, self.height)
@property
def lrbt(self) -> RectParams:
"""Provides a tuple in the format of (left, right, bottom, top)."""
return (self.left, self.right, self.bottom, self.top)
@property
def xywh(self) -> RectParams:
"""Provides a tuple in the format of (x, y, width, height)."""
return (self.x, self.y, self.width, self.height)
@property
def xyrr(self) -> RectParams:
"""Provides a tuple in the format of (x, y, width / 2, height / 2)."""
return (self.x, self.y, self.width / 2, self.height / 2)
@property
def lbwh_int(self) -> IntRectParams:
"""Provides a tuple in the format of (left, bottom, width, height), casted to ints."""
return (int(self.left), int(self.bottom), int(self.width), int(self.height))
@property
def lrbt_int(self) -> IntRectParams:
"""Provides a tuple in the format of (left, right, bottom, top), casted to ints."""
return (int(self.left), int(self.right), int(self.bottom), int(self.top))
@property
def xywh_int(self) -> IntRectParams:
"""Provides a tuple in the format of (x, y, width, height), casted to ints."""
return (int(self.x), int(self.y), int(self.width), int(self.height))
@property
def xyrr_int(self) -> RectParams:
"""Provides a tuple in the format of (x, y, width / 2, height / 2), casted to ints."""
return (int(self.x), int(self.y), int(self.width) / 2, int(self.height) / 2)
[docs]
@classmethod
def from_kwargs(cls, **kwargs: AsFloat) -> Rect:
"""Creates a new Rect from keyword arguments. Throws ValueError if not enough are provided.
Expected forms are:
* LRBT (providing ``left``, ``right``, ``bottom``, and ``top``)
* LBWH (providing ``left``, ``bottom``, ``width``, and ``height``)
* XYWH (providing ``x``, ``y``, ``width``, and ``height``)
"""
# Perform iteration only once and store it as a set literal
specified: set[str] = {k for k, v in kwargs.items() if v is not None}
have_lb = "left" in specified and "bottom" in specified
# LRBT
if have_lb and "top" in specified and "right" in specified:
return LRBT(kwargs["left"], kwargs["right"], kwargs["bottom"], kwargs["top"]) # type: ignore
# LBWH
have_wh = "width" in specified and "height" in specified
if have_wh and have_lb:
return LBWH(kwargs["left"], kwargs["bottom"], kwargs["width"], kwargs["height"]) # type: ignore
# XYWH
if have_wh and "x" in specified and "y" in specified:
return XYWH(kwargs["x"], kwargs["y"], kwargs["width"], kwargs["height"]) # type: ignore
raise ValueError("Not enough attributes defined for a valid rectangle!")
@property
def kwargs(self) -> RectKwargs:
"""Get this rectangle as a :py:class:`dict` of field names to values.
.. _tomli: https://github.com/hukkin/tomli
Many data formats have corresponding Python modules with write
support. Such modules often one or more functions which convert
a passed :py:class:`dict` to a :py:class:`str` or write the result
of such a conversion to a file.
For example, the built-in :py:mod:`json` module offers the
following functions on all Python versions currently supported
by Arcade:
.. list-table::
:header-rows: 1
* - Function
- Summary
- Useful For
* - :py:func:`json.dump`
- Write a :py:class:`dict` to a file
- Saving game progress or edited levels
* - :py:func:`json.dumps`
- Get a :py:class:`dict` as a :py:class:`str` of JSON
- Calls to a Web API
.. note::
The return value is an ordinary :py:class:`dict`.
Although the return type is annotated as a
:py:class:`.RectKwargs`, it is only meaningful when type
checking. See :py:class:`typing.TypedDict` to learn more.
"""
return {
"left": self.left,
"right": self.right,
"bottom": self.bottom,
"top": self.top,
"x": self.x,
"y": self.y,
"width": self.width,
"height": self.height,
}
# Since __repr__ is handled automatically by NamedTuple, we focus on
# human-readable spot-check values for __str__ instead.
def __str__(self) -> str:
return (
f"<{self.__class__.__name__} LRBT({self.left}, {self.right}, {self.bottom}, {self.top})"
f" XYWH({self.x}, {self.y}, {self.width}, {self.height})>"
)
[docs]
def __bool__(self) -> bool:
"""Returns True if area is not 0, else False."""
return self.width != 0 and self.height != 0
[docs]
def __round__(self, n: int) -> Rect:
"""Rounds the left, right, bottom, and top to `n` decimals."""
return LRBT(
round(self.left, n), round(self.right, n), round(self.bottom, n), round(self.top, n)
)
[docs]
def __floor__(self) -> Rect:
"""Floors the left, right, bottom, and top."""
return LRBT(
math.floor(self.left),
math.floor(self.right),
math.floor(self.bottom),
math.floor(self.top),
)
[docs]
def __ceil__(self) -> Rect:
"""Floors the left, right, bottom, and top."""
return LRBT(
math.ceil(self.left), math.ceil(self.right), math.ceil(self.bottom), math.ceil(self.top)
)
# Shorthand creation helpers
[docs]
def LRBT(left: AsFloat, right: AsFloat, bottom: AsFloat, top: AsFloat) -> Rect:
"""Creates a new :py:class:`.Rect` from left, right, bottom, and top parameters."""
width = right - left
height = top - bottom
x = left + (width / 2)
y = bottom + (height / 2)
return Rect(left, right, bottom, top, width, height, x, y)
[docs]
def LBWH(left: AsFloat, bottom: AsFloat, width: AsFloat, height: AsFloat) -> Rect:
"""Creates a new :py:class:`.Rect` from left, bottom, width, and height parameters."""
right = left + width
top = bottom + height
x = left + (width / 2)
y = bottom + (height / 2)
return Rect(left, right, bottom, top, width, height, x, y)
[docs]
def XYWH(
x: AsFloat, y: AsFloat, width: AsFloat, height: AsFloat, anchor: Vec2 = AnchorPoint.CENTER
) -> Rect:
"""
Creates a new :py:class:`.Rect` from x, y, width, and height parameters,
anchored at a relative point (default center).
"""
left = x - anchor.x * width
right = left + width
bottom = y - anchor.y * height
top = bottom + height
c_x = left + width / 2.0
c_y = bottom + height / 2.0
return Rect(left, right, bottom, top, width, height, c_x, c_y)
[docs]
def XYRR(x: AsFloat, y: AsFloat, half_width: AsFloat, half_height: AsFloat) -> Rect:
"""
Creates a new :py:class:`.Rect` from center x, center y, half width, and half height parameters.
This is mainly used by OpenGL.
"""
left = x - half_width
right = x + half_width
bottom = y - half_height
top = y + half_height
return Rect(left, right, bottom, top, half_width * 2, half_height * 2, x, y)
[docs]
def Viewport(left: int, bottom: int, width: int, height: int) -> Rect:
"""
Creates a new :py:class:`.Rect` from left, bottom, width, and height parameters,
restricted to integers.
"""
right = left + width
top = bottom + height
x = left + int(width / 2)
y = bottom + int(height / 2)
return Rect(left, right, bottom, top, width, height, x, y)
__all__ = [
"IntRectParams",
"RectParams",
"RectKwargs",
"Rect",
"AnchorPoint",
"LBWH",
"LRBT",
"XYWH",
"XYRR",
"Viewport",
]