GridLayout: basic implementation.
class Point:
"""Point in cartesian space.
Implements attribute access (.x, .y) and list-like access([0],[1]).
This is heavy-weight mutable object. See also ImmutablePoint.
"""
def __init__(self, *args, **kwargs):
self.x = 0
self.y = 0
self.update(*args, **kwargs)
def move(self, relx, rely):
self.x += relx
self.y += rely
def moved(self, relx, rely):
return Point(self.x + relx, self.y + rely)
def update(self, *args, **kwargs):
"""Update point.
Accepts positional or keyword arguments:
- (point: Point)
- (x: int, y: int)
"""
if len(args) == 2:
self.x, self.y = args
elif len(args) == 1:
self.x, self.y = args[0]
elif len(args):
raise ValueError('Too many args.')
for key, val in kwargs.items():
if key == 'point':
self.x, self.y = val
elif key in ('x', 'y'):
setattr(self, key, val)
else:
raise ValueError('Bad keyword arg: %r' % key)
# sequence interface
def __len__(self):
return 2
def __getitem__(self, key):
return (self.x, self.y)[key]
# point arithmetics
def __add__(self, other):
return Point(self.x + other[0], self.y + other[1])
def __sub__(self, other):
return Point(self.x - other[0], self.y - other[1])
def __eq__(self, other):
"""Comparison operator.
Point can be compared to any sequence of at least two elements:
>>> p = Point(1, 2)
>>> p == Point(1, 2)
True
>>> p == (1, 2)
True
>>> p == (0, 0)
False
>>> p == None
False
"""
try:
return self.x == other[0] and self.y == other[1]
except (TypeError, IndexError):
return False
# string representation
def __repr__(self):
return '{0.__class__.__name__}(x={0.x}, y={0.y})'.format(self)
def immutable(self):
return ImmutablePoint(*self)
class ImmutablePoint:
"""Point class without write access."""
__slots__ = ('_x', '_y')
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def moved(self, relx, rely):
return ImmutablePoint(self.x + relx, self.y + rely)
def __getitem__(self, key):
return (self.x, self.y)[key]
def __repr__(self):
return '{0.__class__.__name__}(x={0.x}, y={0.y})'.format(self)
class Size:
"""Size class.
Implements attribute access (.w, .h) and list-like access([0],[1]).
This is heavy-weight mutable object. See also ImmutableSize.
"""
def __init__(self, *args, **kwargs):
self.w = 0
self.h = 0
self.update(*args, **kwargs)
def __getitem__(self, key):
return (self.w, self.h)[key]
def __repr__(self):
return '{0.__class__.__name__}(w={0.w}, h={0.h})'.format(self)
def update(self, *args, **kwargs):
"""Update size.
Accepts positional or keyword arguments:
- (size: Size)
- (w: int, h: int)
"""
if len(args) == 2:
self.w, self.h = args
elif len(args) == 1:
self.w, self.h = args[0]
elif len(args):
raise ValueError('Too many args.')
for key, val in kwargs.items():
if key == 'size':
self.w, self.h = val
elif key in ('w', 'h'):
setattr(self, key, val)
else:
raise ValueError('Bad keyword arg: %r' % key)
def immutable(self):
return ImmutableSize(*self)
class ImmutableSize:
"""Size class without write access.
When using reference to (mutable) Size class, properties are not fixed.
They can still be changed by original owner and these changes will become
visible to other references of the object. ImmutableSize should be used
when this is not intended.
"""
__slots__ = ('_w', '_h')
def __init__(self, w, h):
self._w = w
self._h = h
@property
def w(self):
return self._w
@property
def h(self):
return self._h
def __getitem__(self, key):
return (self.w, self.h)[key]
def __repr__(self):
return '{0.__class__.__name__}(w={0.w}, h={0.h})'.format(self)
class Rect:
"""Rectangle is defined by coordinates and size."""
def __init__(self, x=0, y=0, w=0, h=0):
self.x = x
self.y = y
self.w = w
self.h = h
@classmethod
def _make(cls, origin, size):
"""Make new Rect instance with origin and size as specified.
`origin` should be Point or pair of coordinates,
`size` should be Size or pair of integers
"""
x, y = origin
w, h = size
return Rect(x, y, w, h)
def __repr__(self):
return '{0.__class__.__name__}(x={0.x}, y={0.y}, w={0.w}, h={0.h})'.format(self)
def __contains__(self, point):
"""Test if point is positioned inside rectangle.
`point` should be either Point or pair of int values.
"""
x, y = point
return (self.x <= x < self.x + self.w
and self.y <= y < self.y + self.h)
def moved(self, relx, rely):
"""Return new Rect with same size, moved by `relx`, `rely`."""
return Rect(self.x + relx, self.y + rely, self.w, self.h)
def intersect(self, other):
x1 = max(self.x, other.x)
y1 = max(self.y, other.y)
x2 = min(self.x + self.w, other.x + other.w)
y2 = min(self.y + self.h, other.y + other.h)
if x1 >= x2 or y1 >= y2:
return Rect()
return Rect(x1, y1, x2-x1, y2-y1)
def union(self, other):
x = min(self.x, other.x)
y = min(self.y, other.y)
w = max(self.x + self.w, other.x + other.w) - x
h = max(self.y + self.h, other.y + other.h) - y
return Rect(x, y, w, h)