# SPDX-License-Identifier: GPL-3.0
# Copyright (c) 2014-2023 William Edwards <shadowapex@gmail.com>, Benjamin Bean <superman2k5@gmail.com>
from __future__ import annotations
from abc import abstractmethod
from typing import (
Protocol,
Sequence,
Tuple,
TypeVar,
Union,
cast,
runtime_checkable,
)
SelfRectType = TypeVar("SelfRectType", bound="ReadOnlyRect")
RectType = TypeVar("RectType", bound="ReadOnlyRect", covariant=True)
[docs]@runtime_checkable
class HasRectAttribute(Protocol[RectType]):
@property
def rect(self) -> RectType:
pass
RectLike = Union[
SelfRectType,
Tuple[int, int, int, int],
Tuple[Tuple[int, int], Tuple[int, int]],
]
[docs]class ReadOnlyRect(Protocol):
"""Interface of a PyGame compatible rect."""
def __init__(
self: SelfRectType,
__arg: Union[HasRectAttribute[SelfRectType], RectLike[SelfRectType]],
) -> None:
pass
[docs] def copy(self: SelfRectType) -> SelfRectType:
return type(self)((self.x, self.y, self.w, self.h))
[docs] def move(self: SelfRectType, x: int, y: int) -> SelfRectType:
return type(self)((self.x + x, self.y + y, self.w, self.h))
[docs] def inflate(self: SelfRectType, x: int, y: int) -> SelfRectType:
return type(self)(
(
self.x - type(self.x)(x / 2),
self.y - type(self.y)(y / 2),
self.w + x,
self.h + y,
)
)
[docs] def union(
self: SelfRectType,
__other: RectLike[SelfRectType],
) -> SelfRectType:
return self.unionall((__other,))
[docs] def unionall(
self: SelfRectType,
__rects: Sequence[RectLike[SelfRectType]],
) -> SelfRectType:
rects_list = [type(self)(r) for r in __rects] + [self]
left = min(r.left for r in rects_list)
top = min(r.top for r in rects_list)
right = max(r.right for r in rects_list)
bottom = max(r.bottom for r in rects_list)
return type(self)((left, top, right - left, bottom - top))
[docs] def contains(
self: SelfRectType,
__other: RectLike[SelfRectType],
) -> bool:
other = type(self)(__other)
return (
(self.left <= other.left)
and (self.top <= other.top)
and (self.right >= other.right)
and (self.bottom >= other.bottom)
and (self.right > other.left)
and (self.bottom > other.top)
)
[docs] def collidepoint(
self,
__point: Tuple[int, int],
) -> bool:
x, y = __point
return self.left <= x < self.right and self.top <= y < self.bottom
[docs] def colliderect(
self: SelfRectType,
__other: RectLike[SelfRectType],
) -> bool:
return intersect(self, type(self)(__other))
[docs] def collidelist(
self: SelfRectType,
__rects: Sequence[RectLike[SelfRectType]],
) -> int:
for i, rect in enumerate(__rects):
if intersect(self, type(self)(rect)):
return i
return -1
[docs] def collidelistall(
self: SelfRectType,
__l: Sequence[RectLike[SelfRectType]],
) -> Sequence[int]:
return [
i
for i, rect in enumerate(__l)
if intersect(self, type(self)(rect))
]
@property
def top(self) -> int:
return self.y
@property
def left(self) -> int:
return self.x
@property
def bottom(self) -> int:
return self.y + self.h
@property
def right(self) -> int:
return self.x + self.w
@property
def topleft(self) -> Tuple[int, int]:
return self.x, self.y
@property
def bottomleft(self) -> Tuple[int, int]:
return self.x, self.y + self.h
@property
def topright(self) -> Tuple[int, int]:
return self.x + self.w, self.y
@property
def bottomright(self) -> Tuple[int, int]:
return self.x + self.w, self.y + self.h
@property
def midtop(self) -> Tuple[int, int]:
return self.centerx, self.y
@property
def midleft(self) -> Tuple[int, int]:
return self.x, self.centery
@property
def midbottom(self) -> Tuple[int, int]:
return self.centerx, self.y + self.h
@property
def midright(self) -> Tuple[int, int]:
return self.x + self.w, self.centery
@property
def center(self) -> Tuple[int, int]:
return self.centerx, self.centery
@property
def centerx(self) -> int:
return self.x + self.w // 2
@property
def centery(self) -> int:
return self.y + self.h // 2
@property
def size(self) -> Tuple[int, int]:
return self.w, self.h
@property
def width(self) -> int:
return self.w
@property
def height(self) -> int:
return self.h
@property
@abstractmethod
def w(self) -> int:
pass
@property
@abstractmethod
def h(self) -> int:
pass
@property
@abstractmethod
def x(self) -> int:
pass
@property
@abstractmethod
def y(self) -> int:
pass
[docs]def intersect(r1: ReadOnlyRect, r2: ReadOnlyRect) -> bool:
return (
(r2.left <= r1.left < r2.right) or (r1.left <= r2.left < r1.right)
) and ((r2.top <= r1.top < r2.bottom) or (r1.top <= r2.top < r1.bottom))
[docs]class Rect(ReadOnlyRect):
"""
Pure Python Rect class that follows the PyGame API
GIANT WARNING: This is completely in python and will be much slower than
PyGame's built in Rect class. This rect should be used only
if needed!
These rects support floating point and are hashable.
"""
__slots__ = ["_x", "_y", "_w", "_h"]
def __init__(
self,
arg: Union[HasRectAttribute[Rect], RectLike[Rect]],
) -> None:
"""
should accept rect like object or tuple of two tuples or one tuple
of four numbers, store :x,y,h,w
"""
if isinstance(arg, HasRectAttribute):
arg = arg.rect
if isinstance(arg, Rect):
self._x = arg.x
self._y = arg.y
self._w = arg.w
self._h = arg.h
elif isinstance(arg, (list, tuple)):
if len(arg) == 2:
arg = cast(Tuple[Tuple[int, int], Tuple[int, int]], arg)
self._x, self._y = arg[0]
self._w, self._h = arg[1]
elif len(arg) == 4:
arg = cast(Tuple[int, int, int, int], arg)
self._x, self._y, self._w, self._h = arg
else:
self._x, self._y, self._w, self._h = arg
@property
def w(self) -> int:
return self._w
@property
def h(self) -> int:
return self._h
@property
def x(self) -> int:
return self._x
@property
def y(self) -> int:
return self._y