Use Point for mouse events, add them to Container and Widget.
class Point:
"""Point in cartesian space.
Implements attribute access (.x, .y) and list-like access([0],[1]).
"""
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 'Point(x={0.x},y={0.y})'.format(self)
class Size:
"""Size class.
Implements attribute access (.w, .h) and list-like access([0],[1]).
"""
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 'Size(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 readonly(self):
return ReadonlySize(self)
class ReadonlySize:
"""Wrapper for Size which makes it read-only."""
def __init__(self, size):
self._size = size
@property
def w(self):
return self._size.w
@property
def h(self):
return self._size.h
def __getitem__(self, key):
return self._size[key]
def __repr__(self):
return 'ReadonlySize(w={0.w},h={0.h})'.format(self._size)
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 'Rect(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)