pytilities.geometry.rectangle

Package pytilities :: Package geometry :: Module rectangle |
[hide private]
[frames] | no frames]

# Source Code for Module pytilities.geometry.rectangle

      1  # Copyright (C) 2010 Tim Diels <limyreth@users.sourceforge.net>
      2  #
      3  # This file is part of pytilities.
      4  #
      5  # pytilities is free software: you can redistribute it and/or modify
      6  # it under the terms of the GNU General Public License as published by
      7  # the Free Software Foundation, either version 3 of the License, or
      8  # (at your option) any later version.
      9  #
     10  # pytilities is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  # GNU General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU General Public License
     16  # along with pytilities.  If not, see <http://www.gnu.org/licenses/>.
     17  #
     18
     19  __docformat__ = 'reStructuredText'
     20
     21  from pytilities.overloading import \
     22      overloaded, Overload, Param, CompositeParam
     23  from pytilities.types import NumberType
     24  from . import Vector, BoundVector
     25
     26  # NOTE: be careful with the awesome ways python int vs float math works
     27  # As with statically typed languages, even here 5 / 2 == 2. As values do still
     28  # have types. So, try not to mix ints and floats.
     29  # If you'd like it to autoconvert or assert for ints/floats, go decorator in
     30  # this class (and vector)
     31 -class Rectangle(object):
     32
     33      """
     34      A rectangle identified by two points.
     35
     36      The rectangle stores left, top, right, and bottom values.
     37
     38      Coordinates are based on screen coordinates.
     39
     40      origin                               top
     41         +-----> x increases                |
     42         |                           left  -+-  right
     43         v                                  |
     44      y increases                         bottom
     45
     46      Instance methods:
     47
     48          - `copy`: Make a shallow copy
     49          - `contains`: Is a vector inside?
     50          - `overlaps`: Does a rectangle overlap?
     51          - `inflate`: grow (or shrink)
     52          - `move_to`: move entire rectangle to a spot
     53          - `move_by`: move entire rectangle by an amount
     54
     55      Instance properties:
     56
     57          - `bounds`: Read-write, all rectangle coordinates
     58          - `center`: Read-write, center of the rectangle
     59          - `width`: Read-write, width of the rectangle
     60          - `height`: Read-write, height of the rectangle
     61          - `size`: Read-write, size of the rectangle
     62          - `left`: Read-write, left edge of rectangle
     63          - `top`: Read-write, top edge of rectangle
     64          - `right`: Read-write, right edge of rectangle
     65          - `bottom`: Read-write, bottom edge of rectangle
     66          - `top_left`: Read-write, top-left corner
     67          - `top_right`: Read-write, top-right corner
     68          - `bottom_left`: Read-write, bottom-left corner
     69          - `bottom_right`: Read-write, bottom-right corner
     70
     71      Operators:
     72
     73          str(s)
     74          repr(s)
     75      """
     76
     77 -    def __init__(self, *args):
     78          """
     79          Construct a rectangle.
     80
     81          Overloaded, parameters:
     82
     83          :a:
     84              `left` :: float | int
     85              `top` :: float | int
     86              `right` :: float | int
     87              `bottom` :: float | int
     88
     89          :b:
     90              `top_left` :: Vector
     91              `bottom_right` :: Vector
     92          """
     93
     94          self.bounds = args
     95
     96          self.__size = BoundVector(
     97              self, "width", "height")
     98
     99          self.__center = BoundVector(
    100              self, "__center_x", "__center_y")
    101
    102          self.__top_left = BoundVector(
    103              self, "left", "top")
    104
    105          self.__top_right = BoundVector(
    106              self, "right", "top")
    107
    108          self.__bottom_left = BoundVector(
    109              self, "left", "bottom")
    110
    111          self.__bottom_right = BoundVector(
    112              self, "right", "bottom")
    113
    114 -    def __copy__(self):
    115          return Rectangle(*(self.bounds))
    116
    117 -    def copy(self):
    118          """Returns shallow copy"""
    119          return self.__copy__
    120
    121      @property
    122 -    def bounds(self):
    123          """Read-write, all rectangle coordinates
    124
    125          Returns (left, top, right, bottom)
    126
    127          Overloaded, set parameters:
    128
    129          :a:
    130              `left` :: float | int
    131              `top` :: float | int
    132              `right` :: float | int
    133              `bottom` :: float | int
    134
    135          :b:
    136              `top_left` :: Vector
    137              `bottom_right` :: Vector
    138          """
    139          return (self.left, self.top, self.right, self.bottom)
    140
    141 -    def __set_bounds_numbers(self, left, top, right, bottom):
    142          (self.left, self.top, self.right, self.bottom) = \
    143              (left, top, right, bottom)
    144
    145          assert self.left < self.right, \
    146              "given top_left isn't the top left corner"
    147          assert self.top < self.bottom, \
    148              "given top_left isn't the top left corner"
    149
    150 -    def __set_bounds_points(self, top_left, bottom_right):
    151          (self.left, self.top) = top_left.xy
    152          (self.right, self.bottom) = bottom_right.xy
    153
    154          assert self.left < self.right, \
    155              "given top_left isn't the top left corner"
    156          assert self.top < self.bottom, \
    157              "given top_left isn't the top left corner"
    158
    159      @bounds.setter
    160      @overloaded((
    161          Overload(__set_bounds_numbers,
    162              CompositeParam("args", (
    163                  Param("left", NumberType),
    164                  Param("top", NumberType),
    165                  Param("right", NumberType),
    166                  Param("bottom", NumberType)))),
    167          Overload(__set_bounds_points,
    168              CompositeParam("args", (
    169                  Param("top_left"),
    170                  Param("bottom_right"))))))
    171 -    def bounds(self):
    172          pass
    173
    174      @property
    175 -    def __center_x(self):
    176          return (self.left + self.right) / 2
    177
    178      @__center_x.setter
    179 -    def __center_x(self, value):
    180          cx = self.size.x
    181          self.left = value - cx/2
    182          self.right = self.left + cx  # this way we maintain our size
    183
    184      @property
    185 -    def __center_y(self):
    186          return (self.top + self.bottom) / 2
    187
    188      @__center_y.setter
    189 -    def __center_y(self, value):
    190          cy = self.size.y
    191          self.top = value - cy/2
    192          self.bottom = self.top + cy  # this way we maintain our size
    193
    194      @property
    195 -    def center(self):
    196          """
    197          Read-write, center of the rectangle :: bound Vector
    198
    199          Changing the center does not change the size.
    200          """
    201          return self.__center
    202
    203      @center.setter
    204 -    def center(self, v):
    205          self.__center.assign(v)
    206
    207      @property
    208 -    def width(self):
    209          """Read-write, width of the rectangle :: float | int"""
    210          return self.right - self.left
    211
    212      @width.setter
    213 -    def width(self, value):
    214          self.right = self.left + value
    215
    216      @property
    217 -    def height(self):
    218          """Read-write, height of the rectangle :: float | int"""
    219          return self.bottom - self.top
    220
    221      @height.setter
    222 -    def height(self, value):
    223          self.bottom = self.top + value
    224
    225      @property
    226 -    def size(self):
    227          """Read-write, size of the rectangle :: bound Vector"""
    228          return self.__size
    229
    230      @size.setter
    231 -    def size(self, value):
    232          self.__size.assign(value)
    233
    234      @property
    235 -    def left(self):
    236          """Read-write, left edge of rectangle :: float | int"""
    237          return self.__left
    238
    239      @left.setter
    240 -    def left(self, value):
    241          self.__left = value
    242
    243      @property
    244 -    def top(self):
    245          """Read-write, top edge of rectangle :: float | int"""
    246          return self.__top
    247
    248      @top.setter
    249 -    def top(self, value):
    250          self.__top = value
    251
    252      @property
    253 -    def right(self):
    254          """Read-write, right edge of rectangle :: float | int"""
    255          return self.__right
    256
    257      @right.setter
    258 -    def right(self, value):
    259          self.__right = value
    260
    261      @property
    262 -    def bottom(self):
    263          """Read-write, bottom edge of rectangle :: float | int"""
    264          return self.__bottom
    265
    266      @bottom.setter
    267 -    def bottom(self, value):
    268          self.__bottom = value
    269
    270  # TODO: think of way of writing bound props like size easier
    271  # Call it @bound_property or something...
    272
    273      @property
    274 -    def top_left(self):
    275          """Read-write, top-left corner :: bound Vector"""
    276          return self.__top_left
    277
    278      @top_left.setter
    279 -    def top_left(self, value):
    280          return self.__top_left.assign(value)
    281
    282      @property
    283 -    def top_right(self):
    284          """Read-write, top-right corner :: bound Vector"""
    285          return self.__top_right
    286
    287      @top_right.setter
    288 -    def top_right(self, value):
    289          return self.__top_right.assign(value)
    290
    291      @property
    292 -    def bottom_left(self):
    293          """Read-write, bottom-left corner :: bound Vector"""
    294          return self.__bottom_left
    295
    296      @bottom_left.setter
    297 -    def bottom_left(self, value):
    298          return self.__bottom_left.assign(value)
    299
    300      @property
    301 -    def bottom_right(self):
    302          """Read-write, bottom-right corner :: bound Vector"""
    303          return self.__bottom_right
    304
    305      @bottom_right.setter
    306 -    def bottom_right(self, value):
    307          return self.__bottom_right.assign(value)
    308
    309 -    def contains(self, v):
    310          """
    311          Returns `True` if `v` is inside the rectangle.
    312
    313          Parameters:
    314
    315              `v` :: Vector
    316          """
    317          x,y = v.xy
    318          return (self.left <= x <= self.right and
    319                  self.top <= y <= self.bottom)
    320
    321 -    def overlaps(self, rect):
    322          """
    323          Returns `True` if `rect` overlaps with this rectangle.
    324
    325          Parameters:
    326
    327              `rect` :: Rectangle
    328          """
    329          return (self.right > rect.left and self.left < rect.right and
    330                  self.top < rect.bottom and self.bottom > rect.top)
    331
    332 -    def __inflate_number(self, n):
    333          self.left -= n
    334          self.top -= n
    335          self.right += n
    336          self.bottom += n
    337
    338 -    def __inflate_vector(self, v):
    339          x, y = v.xy
    340          self.left -= x
    341          self.top -= y
    342          self.right += x
    343          self.bottom += y
    344
    345      @overloaded((
    346          Overload(__inflate_number,
    347                  Param("n", NumberType)),
    348          Overload(__inflate_vector,
    349                  Param("v"))))
    350 -    def inflate(self):
    351          """
    352          Inflate the rectangle
    353
    354          Overloaded, parameters:
    355
    356          :a:
    357              n :: number
    358                  Extend all sides by n points
    359          :b:
    360              v :: Vector
    361                  extend left and right sides by v.x and top and bottom sides by
    362                  v.y
    363          """
    364
    365 -    def move_to(self, v):
    366          """
    367          Move the top_left corner to `v`, without changing size
    368
    369          Parameters:
    370
    371              `v` :: Vector
    372                  the spot to move to
    373          """
    374          bottom_right = v + self.size
    375          (self.left, self.top) = v.xy
    376          (self.right, self.bottom) = bottom_right.xy
    377
    378 -    def move_by(self, v):
    379          """
    380          Move the top_left corner by `v`, without changing size
    381
    382          Parameters:
    383
    384              `v` :: Vector
    385                  the amount to move by
    386          """
    387          self.left += v.x
    388          self.right += v.x
    389          self.top += v.y
    390          self.bottom += v.y
    391
    392 -    def __str__( self ):
    393          return "<%s (%s,%s)-(%s,%s)>" % (self.__class__.__name__,
    394                                           self.left, self.top,
    395                                             self.right, self.bottom)
    396
    397 -    def __repr__(self):
    398          return "%s(%r, %r)" % (self.__class__.__name__,
    399                                 Vector(self.left, self.top),
    400                                 Vector(self.right, self.bottom))
    401
    402  # TODO: try to write code of bound props in a more succint way (using some
    403  # awesome lib func/decorator/class)
    404