tuikit/core/coords.py
author Radek Brich <radek.brich@devl.cz>
Sun, 22 Feb 2015 09:53:13 +0100
changeset 119 dd91747504dd
parent 115 b4ff7392003a
permissions -rw-r--r--
Redraw widgets on request. Add scrollbar demo.


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, *args, **kwargs):
        self.x = 0
        self.y = 0
        self.w = 0
        self.h = 0
        if len(args) == 4:
            self.x, self.y, self.w, self.h = args
        elif len(args) == 2:
            self.set_origin_and_size(*args)
        for key, val in kwargs.items():
            setattr(self, key, val)

    def set_origin_and_size(self, origin, size):
        """Set rect origin and size as specified.

        `origin` should be Point or pair of coordinates,
        `size` should be Size or pair of integers

        """
        self.x, self.y = origin
        self.w, self.h = size

    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)