tuikit/core/coords.py
author Radek Brich <radek.brich@devl.cz>
Wed, 03 Sep 2014 08:53:44 +0200
changeset 106 abcadb7e2ef1
parent 104 742e504ec053
child 107 1822c37b2688
permissions -rw-r--r--
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)