import doctest
import collections
# TODO - finish doc tests
# TODO - unit tests needed for get/set and Box named tuple
__version__ = ' 0.1.4 '
# Constants for rectangle attributes:
TOP = ' top '
BOTTOM = ' bottom '
LEFT = ' left '
RIGHT = ' right '
TOPLEFT = ' topleft '
TOPRIGHT = ' topright '
BOTTOMLEFT = ' bottomleft '
BOTTOMRIGHT = ' bottomright '
MIDTOP = ' midtop '
MIDRIGHT = ' midright '
MIDLEFT = ' midleft '
MIDBOTTOM = ' midbottom '
CENTER = ' center '
CENTERX = ' centerx '
CENTERY = ' centery '
WIDTH = ' width '
HEIGHT = ' height '
SIZE = ' size '
BOX = ' box '
AREA = ' area '
Box = collections . namedtuple ( ' Box ' , ' left top width height ' )
Point = collections . namedtuple ( ' Point ' , ' x y ' )
Size = collections . namedtuple ( ' Size ' , ' width height ' )
class PyRectException ( Exception ) :
"""
This class exists for PyRect exceptions . If the PyRect module raises any
non - PyRectException exceptions , this indicates there ' s a bug in PyRect.
"""
pass
def _checkForIntOrFloat ( arg ) :
""" Raises an exception if arg is not an int or float. Always returns None. """
if not isinstance ( arg , ( int , float ) ) :
raise PyRectException ( ' argument must be int or float, not %s ' % ( arg . __class__ . __name__ ) )
def _checkForInt ( arg ) :
""" Raises an exception if arg is not an int. Always returns None. """
if not isinstance ( arg , int ) :
raise PyRectException ( ' argument must be int or float, not %s ' % ( arg . __class__ . __name__ ) )
def _checkForTwoIntOrFloatTuple ( arg ) :
try :
if not isinstance ( arg [ 0 ] , ( int , float ) ) or \
not isinstance ( arg [ 1 ] , ( int , float ) ) :
raise PyRectException ( ' argument must be a two-item tuple containing int or float values ' )
except :
raise PyRectException ( ' argument must be a two-item tuple containing int or float values ' )
def _checkForFourIntOrFloatTuple ( arg ) :
try :
if not isinstance ( arg [ 0 ] , ( int , float ) ) or \
not isinstance ( arg [ 1 ] , ( int , float ) ) or \
not isinstance ( arg [ 2 ] , ( int , float ) ) or \
not isinstance ( arg [ 3 ] , ( int , float ) ) :
raise PyRectException ( ' argument must be a four-item tuple containing int or float values ' )
except :
raise PyRectException ( ' argument must be a four-item tuple containing int or float values ' )
def _collides ( rectOrPoint1 , rectOrPoint2 ) :
""" Returns True if rectOrPoint1 and rectOrPoint2 collide with each other. """
def _getRectsAndPoints ( rectsOrPoints ) :
points = [ ]
rects = [ ]
for rectOrPoint in rectsOrPoints :
try :
_checkForTwoIntOrFloatTuple ( rectOrPoint )
points . append ( rectOrPoint )
except PyRectException :
try :
_checkForFourIntOrFloatTuple ( rectOrPoint )
except :
raise PyRectException ( ' argument is not a point or a rect tuple ' )
rects . append ( rectOrPoint )
return ( rects , points )
'''
def collideAnyBetween ( rectsOrPoints ) :
""" Returns True if any of the (x, y) or (left, top, width, height) tuples
in rectsOrPoints collides with any other point or box tuple in rectsOrPoints .
>> > p1 = ( 50 , 50 )
>> > p2 = ( 100 , 100 )
>> > p3 = ( 50 , 200 )
>> > r1 = ( - 50 , - 50 , 20 , 20 )
>> > r2 = ( 25 , 25 , 50 , 50 )
>> > collideAnyBetween ( [ p1 , p2 , p3 , r1 , r2 ] ) # p1 and r2 collide
True
>> > collideAnyBetween ( [ p1 , p2 , p3 , r1 ] )
False
"""
# TODO - needs to be complete
# split up
rects , points = _getRectsAndPoints ( rectsOrPoints )
# compare points with each other
if len ( points ) > 1 :
for point in points :
if point != points [ 0 ] :
return False
# TODO finish
'''
'''
def collideAllBetween ( rectsOrPoints ) :
""" Returns True if any of the (x, y) or (left, top, width, height) tuples
in rectsOrPoints collides with any other point or box tuple in rectsOrPoints .
>> > p1 = ( 50 , 50 )
>> > p2 = ( 100 , 100 )
>> > p3 = ( 50 , 200 )
>> > r1 = ( - 50 , - 50 , 20 , 20 )
>> > r2 = ( 25 , 25 , 50 , 50 )
>> > collideAllBetween ( [ p1 , p2 , p3 , r1 , r2 ] )
False
>> > collideAllBetween ( [ p1 , p2 , p3 , r1 ] )
False
>> > collideAllBetween ( [ p1 , r2 ] ) # Everything in the list collides with each other.
True
"""
# Check for valid arguments
try :
for rectOrPoint in rectsOrPoints :
if len ( rectOrPoint ) == 2 :
_checkForTwoIntOrFloatTuple ( rectOrPoint )
elif len ( rectOrPoint ) == 4 :
_checkForFourIntOrFloatTuple ( rectOrPoint )
else :
raise PyRectException ( )
except :
raise PyRectException ( ' Arguments in rectsOrPoints must be 2- or 4-integer/float tuples. ' )
raise NotImplementedError # return a list of all rects or points that collide with any other in the argument
'''
class Rect ( object ) :
def __init__ ( self , left = 0 , top = 0 , width = 0 , height = 0 , enableFloat = False , readOnly = False , onChange = None , onRead = None ) :
_checkForIntOrFloat ( width )
_checkForIntOrFloat ( height )
_checkForIntOrFloat ( left )
_checkForIntOrFloat ( top )
self . _enableFloat = bool ( enableFloat )
self . _readOnly = bool ( readOnly )
if onChange is not None and not callable ( onChange ) :
raise PyRectException ( ' onChange argument must be None or callable (function, method, etc.) ' )
self . onChange = onChange
if onRead is not None and not callable ( onRead ) :
raise PyRectException ( ' onRead argument must be None or callable (function, method, etc.) ' )
self . onRead = onRead
if enableFloat :
self . _width = float ( width )
self . _height = float ( height )
self . _left = float ( left )
self . _top = float ( top )
else :
self . _width = int ( width )
self . _height = int ( height )
self . _left = int ( left )
self . _top = int ( top )
# OPERATOR OVERLOADING / DUNDER METHODS
def __repr__ ( self ) :
""" Return a string of the constructor function call to create this Rect object. """
return ' %s (left= %s , top= %s , width= %s , height= %s ) ' % ( self . __class__ . __name__ , self . _left , self . _top , self . _width , self . _height )
def __str__ ( self ) :
""" Return a string representation of this Rect object. """
return ' (x= %s , y= %s , w= %s , h= %s ) ' % ( self . _left , self . _top , self . _width , self . _height )
def callOnChange ( self , oldLeft , oldTop , oldWidth , oldHeight ) :
# Note: callOnChange() should be called *after* the attribute has been changed.
# Note: This isn't thread safe; the attributes can change between the calling of this function and the code in the function running.
if self . onChange is not None :
self . onChange ( Box ( oldLeft , oldTop , oldWidth , oldHeight ) , Box ( self . _left , self . _top , self . _width , self . _height ) )
@property
def enableFloat ( self ) :
"""
A Boolean attribute that determines if this rectangle uses floating point
numbers for its position and size . False , by default .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . enableFloat
False
>> > r . enableFloat = True
>> > r . top = 3.14
>> > r
Rect ( left = 0.0 , top = 3.14 , width = 10.0 , height = 20.0 )
"""
return self . _enableFloat
@enableFloat.setter
def enableFloat ( self , value ) :
if not isinstance ( value , bool ) :
raise PyRectException ( ' enableFloat must be set to a bool value ' )
self . _enableFloat = value
if self . _enableFloat :
self . _left = float ( self . _left )
self . _top = float ( self . _top )
self . _width = float ( self . _width )
self . _height = float ( self . _height )
else :
self . _left = int ( self . _left )
self . _top = int ( self . _top )
self . _width = int ( self . _width )
self . _height = int ( self . _height )
# LEFT SIDE PROPERTY
@property
def left ( self ) :
"""
The x coordinate for the left edge of the rectangle . ` x ` is an alias for ` left ` .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . left
0
>> > r . left = 50
>> > r
Rect ( left = 50 , top = 0 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( LEFT )
return self . _left
@left.setter
def left ( self , newLeft ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newLeft )
if newLeft != self . _left : # Only run this code if the size/position has changed.
originalLeft = self . _left
if self . _enableFloat :
self . _left = newLeft
else :
self . _left = int ( newLeft )
self . callOnChange ( originalLeft , self . _top , self . _width , self . _height )
x = left # x is an alias for left
# TOP SIDE PROPERTY
@property
def top ( self ) :
"""
The y coordinate for the top edge of the rectangle . ` y ` is an alias for ` top ` .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . top
0
>> > r . top = 50
>> > r
Rect ( left = 0 , top = 50 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( TOP )
return self . _top
@top.setter
def top ( self , newTop ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newTop )
if newTop != self . _top : # Only run this code if the size/position has changed.
originalTop = self . _top
if self . _enableFloat :
self . _top = newTop
else :
self . _top = int ( newTop )
self . callOnChange ( self . _left , originalTop , self . _width , self . _height )
y = top # y is an alias for top
# RIGHT SIDE PROPERTY
@property
def right ( self ) :
"""
The x coordinate for the right edge of the rectangle .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . right
10
>> > r . right = 50
>> > r
Rect ( left = 40 , top = 0 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( RIGHT )
return self . _left + self . _width
@right.setter
def right ( self , newRight ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newRight )
if newRight != self . _left + self . _width : # Only run this code if the size/position has changed.
originalLeft = self . _left
if self . _enableFloat :
self . _left = newRight - self . _width
else :
self . _left = int ( newRight ) - self . _width
self . callOnChange ( originalLeft , self . _top , self . _width , self . _height )
# BOTTOM SIDE PROPERTY
@property
def bottom ( self ) :
""" The y coordinate for the bottom edge of the rectangle.
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . bottom
20
>> > r . bottom = 30
>> > r
Rect ( left = 0 , top = 10 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( BOTTOM )
return self . _top + self . _height
@bottom.setter
def bottom ( self , newBottom ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newBottom )
if newBottom != self . _top + self . _height : # Only run this code if the size/position has changed.
originalTop = self . _top
if self . _enableFloat :
self . _top = newBottom - self . _height
else :
self . _top = int ( newBottom ) - self . _height
self . callOnChange ( self . _left , originalTop , self . _width , self . _height )
# TOP LEFT CORNER PROPERTY
@property
def topleft ( self ) :
"""
The x and y coordinates for the top right corner of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . topleft
( 0 , 0 )
>> > r . topleft = ( 30 , 30 )
>> > r
Rect ( left = 30 , top = 30 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( TOPLEFT )
return Point ( x = self . _left , y = self . _top )
@topleft.setter
def topleft ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newLeft , newTop = value
if ( newLeft != self . _left ) or ( newTop != self . _top ) : # Only run this code if the size/position has changed.
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
self . _left = newLeft
self . _top = newTop
else :
self . _left = int ( newLeft )
self . _top = int ( newTop )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# BOTTOM LEFT CORNER PROPERTY
@property
def bottomleft ( self ) :
"""
The x and y coordinates for the bottom right corner of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . bottomleft
( 0 , 20 )
>> > r . bottomleft = ( 30 , 30 )
>> > r
Rect ( left = 30 , top = 10 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( BOTTOMLEFT )
return Point ( x = self . _left , y = self . _top + self . _height )
@bottomleft.setter
def bottomleft ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newLeft , newBottom = value
if ( newLeft != self . _left ) or ( newBottom != self . _top + self . _height ) : # Only run this code if the size/position has changed.
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
self . _left = newLeft
self . _top = newBottom - self . _height
else :
self . _left = int ( newLeft )
self . _top = int ( newBottom ) - self . _height
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# TOP RIGHT CORNER PROPERTY
@property
def topright ( self ) :
"""
The x and y coordinates for the top right corner of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . topright
( 10 , 0 )
>> > r . topright = ( 30 , 30 )
>> > r
Rect ( left = 20 , top = 30 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( TOPRIGHT )
return Point ( x = self . _left + self . _width , y = self . _top )
@topright.setter
def topright ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newRight , newTop = value
if ( newRight != self . _left + self . _width ) or ( newTop != self . _top ) : # Only run this code if the size/position has changed.
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
self . _left = newRight - self . _width
self . _top = newTop
else :
self . _left = int ( newRight ) - self . _width
self . _top = int ( newTop )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# BOTTOM RIGHT CORNER PROPERTY
@property
def bottomright ( self ) :
"""
The x and y coordinates for the bottom right corner of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . bottomright
( 10 , 20 )
>> > r . bottomright = ( 30 , 30 )
>> > r
Rect ( left = 20 , top = 10 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( BOTTOMRIGHT )
return Point ( x = self . _left + self . _width , y = self . _top + self . _height )
@bottomright.setter
def bottomright ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newRight , newBottom = value
if ( newBottom != self . _top + self . _height ) or ( newRight != self . _left + self . _width ) : # Only run this code if the size/position has changed.
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
self . _left = newRight - self . _width
self . _top = newBottom - self . _height
else :
self . _left = int ( newRight ) - self . _width
self . _top = int ( newBottom ) - self . _height
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# MIDDLE OF TOP SIDE PROPERTY
@property
def midtop ( self ) :
"""
The x and y coordinates for the midpoint of the top edge of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . midtop
( 5 , 0 )
>> > r . midtop = ( 40 , 50 )
>> > r
Rect ( left = 35 , top = 50 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( MIDTOP )
if self . _enableFloat :
return Point ( x = self . _left + ( self . _width / 2.0 ) , y = self . _top )
else :
return Point ( x = self . _left + ( self . _width / / 2 ) , y = self . _top )
@midtop.setter
def midtop ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newMidTop , newTop = value
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
if ( newMidTop != self . _left + self . _width / 2.0 ) or ( newTop != self . _top ) : # Only run this code if the size/position has changed.
self . _left = newMidTop - ( self . _width / 2.0 )
self . _top = newTop
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
else :
if ( newMidTop != self . _left + self . _width / / 2 ) or ( newTop != self . _top ) : # Only run this code if the size/position has changed.
self . _left = int ( newMidTop ) - ( self . _width / / 2 )
self . _top = int ( newTop )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# MIDDLE OF BOTTOM SIDE PROPERTY
@property
def midbottom ( self ) :
"""
The x and y coordinates for the midpoint of the bottom edge of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . midbottom
( 5 , 20 )
>> > r . midbottom = ( 40 , 50 )
>> > r
Rect ( left = 35 , top = 30 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( MIDBOTTOM )
if self . _enableFloat :
return Point ( x = self . _left + ( self . _width / 2.0 ) , y = self . _top + self . _height )
else :
return Point ( x = self . _left + ( self . _width / / 2 ) , y = self . _top + self . _height )
@midbottom.setter
def midbottom ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newMidBottom , newBottom = value
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
if ( newMidBottom != self . _left + self . _width / 2.0 ) or ( newBottom != self . _top + self . _height ) : # Only run this code if the size/position has changed.
self . _left = newMidBottom - ( self . _width / 2.0 )
self . _top = newBottom - self . _height
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
else :
if ( newMidBottom != self . _left + self . _width / / 2 ) or ( newBottom != self . _top + self . _height ) : # Only run this code if the size/position has changed.
self . _left = int ( newMidBottom ) - ( self . _width / / 2 )
self . _top = int ( newBottom ) - self . _height
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# MIDDLE OF LEFT SIDE PROPERTY
@property
def midleft ( self ) :
"""
The x and y coordinates for the midpoint of the left edge of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . midleft
( 0 , 10 )
>> > r . midleft = ( 40 , 50 )
>> > r
Rect ( left = 40 , top = 40 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( MIDLEFT )
if self . _enableFloat :
return Point ( x = self . _left , y = self . _top + ( self . _height / 2.0 ) )
else :
return Point ( x = self . _left , y = self . _top + ( self . _height / / 2 ) )
@midleft.setter
def midleft ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newLeft , newMidLeft = value
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
if ( newLeft != self . _left ) or ( newMidLeft != self . _top + ( self . _height / 2.0 ) ) : # Only run this code if the size/position has changed.
self . _left = newLeft
self . _top = newMidLeft - ( self . _height / 2.0 )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
else :
if ( newLeft != self . _left ) or ( newMidLeft != self . _top + ( self . _height / / 2 ) ) : # Only run this code if the size/position has changed.
self . _left = int ( newLeft )
self . _top = int ( newMidLeft ) - ( self . _height / / 2 )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# MIDDLE OF RIGHT SIDE PROPERTY
@property
def midright ( self ) :
"""
The x and y coordinates for the midpoint of the right edge of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . midright
( 10 , 10 )
>> > r . midright = ( 40 , 50 )
>> > r
Rect ( left = 30 , top = 40 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( MIDRIGHT )
if self . _enableFloat :
return Point ( x = self . _left + self . _width , y = self . _top + ( self . _height / 2.0 ) )
else :
return Point ( x = self . _left + self . _width , y = self . _top + ( self . _height / / 2 ) )
@midright.setter
def midright ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newRight , newMidRight = value
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
if ( newRight != self . _left + self . _width ) or ( newMidRight != self . _top + self . _height / 2.0 ) : # Only run this code if the size/position has changed.
self . _left = newRight - self . _width
self . _top = newMidRight - ( self . _height / 2.0 )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
else :
if ( newRight != self . _left + self . _width ) or ( newMidRight != self . _top + self . _height / / 2 ) : # Only run this code if the size/position has changed.
self . _left = int ( newRight ) - self . _width
self . _top = int ( newMidRight ) - ( self . _height / / 2 )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# CENTER POINT PROPERTY
@property
def center ( self ) :
"""
The x and y coordinates for the center of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . center
( 5 , 10 )
>> > r . center = ( 40 , 50 )
>> > r
Rect ( left = 35 , top = 40 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( CENTER )
if self . _enableFloat :
return Point ( x = self . _left + ( self . _width / 2.0 ) , y = self . _top + ( self . _height / 2.0 ) )
else :
return Point ( x = self . _left + ( self . _width / / 2 ) , y = self . _top + ( self . _height / / 2 ) )
@center.setter
def center ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newCenterx , newCentery = value
originalLeft = self . _left
originalTop = self . _top
if self . _enableFloat :
if ( newCenterx != self . _left + self . _width / 2.0 ) or ( newCentery != self . _top + self . _height / 2.0 ) : # Only run this code if the size/position has changed.
self . _left = newCenterx - ( self . _width / 2.0 )
self . _top = newCentery - ( self . _height / 2.0 )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
else :
if ( newCenterx != self . _left + self . _width / / 2 ) or ( newCentery != self . _top + self . _height / / 2 ) : # Only run this code if the size/position has changed.
self . _left = int ( newCenterx ) - ( self . _width / / 2 )
self . _top = int ( newCentery ) - ( self . _height / / 2 )
self . callOnChange ( originalLeft , originalTop , self . _width , self . _height )
# X COORDINATE OF CENTER POINT PROPERTY
@property
def centerx ( self ) :
"""
The x coordinate for the center of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . centerx
5
>> > r . centerx = 50
>> > r
Rect ( left = 45 , top = 0 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( CENTERX )
if self . _enableFloat :
return self . _left + ( self . _width / 2.0 )
else :
return self . _left + ( self . _width / / 2 )
@centerx.setter
def centerx ( self , newCenterx ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newCenterx )
originalLeft = self . _left
if self . _enableFloat :
if ( newCenterx != self . _left + self . _width / 2.0 ) : # Only run this code if the size/position has changed.
self . _left = newCenterx - ( self . _width / 2.0 )
self . callOnChange ( originalLeft , self . _top , self . _width , self . _height )
else :
if ( newCenterx != self . _left + self . _width / / 2 ) : # Only run this code if the size/position has changed.
self . _left = int ( newCenterx ) - ( self . _width / / 2 )
self . callOnChange ( originalLeft , self . _top , self . _width , self . _height )
# Y COORDINATE OF CENTER POINT PROPERTY
@property
def centery ( self ) :
"""
The y coordinate for the center of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . centery
10
>> > r . centery = 50
>> > r
Rect ( left = 0 , top = 40 , width = 10 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( CENTERY )
if self . _enableFloat :
return self . _top + ( self . _height / 2.0 )
else :
return self . _top + ( self . _height / / 2 )
@centery.setter
def centery ( self , newCentery ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newCentery )
originalTop = self . _top
if self . _enableFloat :
if ( newCentery != self . _top + self . _height / 2.0 ) : # Only run this code if the size/position has changed.
self . _top = newCentery - ( self . _height / 2.0 )
self . callOnChange ( self . _left , originalTop , self . _width , self . _height )
else :
if ( newCentery != self . _top + self . _height / / 2 ) : # Only run this code if the size/position has changed.
self . _top = int ( newCentery ) - ( self . _height / / 2 )
self . callOnChange ( self . _left , originalTop , self . _width , self . _height )
# SIZE PROPERTY (i.e. (width, height))
@property
def size ( self ) :
"""
The width and height of the rectangle , as a tuple .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . size
( 10 , 20 )
>> > r . size = ( 40 , 50 )
>> > r
Rect ( left = 0 , top = 0 , width = 40 , height = 50 )
"""
if self . onRead is not None :
self . onRead ( SIZE )
return Size ( width = self . _width , height = self . _height )
@size.setter
def size ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForTwoIntOrFloatTuple ( value )
newWidth , newHeight = value
if newWidth != self . _width or newHeight != self . _height :
originalWidth = self . _width
originalHeight = self . _height
if self . _enableFloat :
self . _width = newWidth
self . _height = newHeight
else :
self . _width = int ( newWidth )
self . _height = int ( newHeight )
self . callOnChange ( self . _left , self . _top , originalWidth , originalHeight )
# WIDTH PROPERTY
@property
def width ( self ) :
"""
The width of the rectangle . ` w ` is an alias for ` width ` .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . width
10
>> > r . width = 50
>> > r
Rect ( left = 0 , top = 0 , width = 50 , height = 20 )
"""
if self . onRead is not None :
self . onRead ( WIDTH )
return self . _width
@width.setter
def width ( self , newWidth ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newWidth )
if ( newWidth != self . _width ) : # Only run this code if the size/position has changed.
originalWidth = self . _width
if self . _enableFloat :
self . _width = newWidth
else :
self . _width = int ( newWidth )
self . callOnChange ( self . _left , self . _top , originalWidth , self . _height )
w = width
# HEIGHT PROPERTY
@property
def height ( self ) :
"""
The height of the rectangle . ` h ` is an alias for ` height `
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . height
20
>> > r . height = 50
>> > r
Rect ( left = 0 , top = 0 , width = 10 , height = 50 )
"""
if self . onRead is not None :
self . onRead ( HEIGHT )
return self . _height
@height.setter
def height ( self , newHeight ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( newHeight )
if ( newHeight != self . _height ) : # Only run this code if the size/position has changed.
originalHeight = self . _height
if self . _enableFloat :
self . _height = newHeight
else :
self . _height = int ( newHeight )
self . callOnChange ( self . _left , self . _top , self . _width , originalHeight )
h = height
# AREA PROPERTY
@property
def area ( self ) :
""" The area of the `Rect`, which is simply the width times the height.
This is a read - only attribute .
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . area
200
"""
if self . onRead is not None :
self . onRead ( AREA )
return self . _width * self . _height
# BOX PROPERTY
@property
def box ( self ) :
""" A tuple of four integers: (left, top, width, height).
>> > r = Rect ( 0 , 0 , 10 , 20 )
>> > r . box
( 0 , 0 , 10 , 20 )
>> > r . box = ( 5 , 15 , 100 , 200 )
>> > r . box
( 5 , 15 , 100 , 200 ) """
if self . onRead is not None :
self . onRead ( BOX )
return Box ( left = self . _left , top = self . _top , width = self . _width , height = self . _height )
@box.setter
def box ( self , value ) :
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForFourIntOrFloatTuple ( value )
newLeft , newTop , newWidth , newHeight = value
if ( newLeft != self . _left ) or ( newTop != self . _top ) or ( newWidth != self . _width ) or ( newHeight != self . _height ) :
originalLeft = self . _left
originalTop = self . _top
originalWidth = self . _width
originalHeight = self . _height
if self . _enableFloat :
self . _left = float ( newLeft )
self . _top = float ( newTop )
self . _width = float ( newWidth )
self . _height = float ( newHeight )
else :
self . _left = int ( newLeft )
self . _top = int ( newTop )
self . _width = int ( newWidth )
self . _height = int ( newHeight )
self . callOnChange ( originalLeft , originalTop , originalWidth , originalHeight )
def get ( self , rectAttrName ) :
# Access via the properties so that it triggers onRead().
if rectAttrName == TOP :
return self . top
elif rectAttrName == BOTTOM :
return self . bottom
elif rectAttrName == LEFT :
return self . left
elif rectAttrName == RIGHT :
return self . right
elif rectAttrName == TOPLEFT :
return self . topleft
elif rectAttrName == TOPRIGHT :
return self . topright
elif rectAttrName == BOTTOMLEFT :
return self . bottomleft
elif rectAttrName == BOTTOMRIGHT :
return self . bottomright
elif rectAttrName == MIDTOP :
return self . midtop
elif rectAttrName == MIDBOTTOM :
return self . midbottom
elif rectAttrName == MIDLEFT :
return self . midleft
elif rectAttrName == MIDRIGHT :
return self . midright
elif rectAttrName == CENTER :
return self . center
elif rectAttrName == CENTERX :
return self . centerx
elif rectAttrName == CENTERY :
return self . centery
elif rectAttrName == WIDTH :
return self . width
elif rectAttrName == HEIGHT :
return self . height
elif rectAttrName == SIZE :
return self . size
elif rectAttrName == AREA :
return self . area
elif rectAttrName == BOX :
return self . box
else :
raise PyRectException ( " ' %s ' is not a valid attribute name " % ( rectAttrName ) )
def set ( self , rectAttrName , value ) :
# Set via the properties so that it triggers onChange().
if rectAttrName == TOP :
self . top = value
elif rectAttrName == BOTTOM :
self . bottom = value
elif rectAttrName == LEFT :
self . left = value
elif rectAttrName == RIGHT :
self . right = value
elif rectAttrName == TOPLEFT :
self . topleft = value
elif rectAttrName == TOPRIGHT :
self . topright = value
elif rectAttrName == BOTTOMLEFT :
self . bottomleft = value
elif rectAttrName == BOTTOMRIGHT :
self . bottomright = value
elif rectAttrName == MIDTOP :
self . midtop = value
elif rectAttrName == MIDBOTTOM :
self . midbottom = value
elif rectAttrName == MIDLEFT :
self . midleft = value
elif rectAttrName == MIDRIGHT :
self . midright = value
elif rectAttrName == CENTER :
self . center = value
elif rectAttrName == CENTERX :
self . centerx = value
elif rectAttrName == CENTERY :
self . centery = value
elif rectAttrName == WIDTH :
self . width = value
elif rectAttrName == HEIGHT :
self . height = value
elif rectAttrName == SIZE :
self . size = value
elif rectAttrName == AREA :
raise PyRectException ( ' area is a read-only attribute ' )
elif rectAttrName == BOX :
self . box = value
else :
raise PyRectException ( " ' %s ' is not a valid attribute name " % ( rectAttrName ) )
def move ( self , xOffset , yOffset ) :
""" Moves this Rect object by the given offsets. The xOffset and yOffset
arguments can be any integer value , positive or negative .
>> > r = Rect ( 0 , 0 , 100 , 100 )
>> > r . move ( 10 , 20 )
>> > r
Rect ( left = 10 , top = 20 , width = 100 , height = 100 )
"""
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
_checkForIntOrFloat ( xOffset )
_checkForIntOrFloat ( yOffset )
if self . _enableFloat :
self . _left + = xOffset
self . _top + = yOffset
else :
self . _left + = int ( xOffset )
self . _top + = int ( yOffset )
def copy ( self ) :
""" Return a copied `Rect` object with the same position and size as this
` Rect ` object .
>> > r1 = Rect ( 0 , 0 , 100 , 150 )
>> > r2 = r1 . copy ( )
>> > r1 == r2
True
>> > r2
Rect ( left = 0 , top = 0 , width = 100 , height = 150 )
"""
return Rect ( self . _left , self . _top , self . _width , self . _height , self . _enableFloat , self . _readOnly )
def inflate ( self , widthChange = 0 , heightChange = 0 ) :
""" Increases the size of this Rect object by the given offsets. The
rectangle ' s center doesn ' t move . Negative values will shrink the
rectangle .
>> > r = Rect ( 0 , 0 , 100 , 150 )
>> > r . inflate ( 20 , 40 )
>> > r
Rect ( left = - 10 , top = - 20 , width = 120 , height = 190 )
"""
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
originalCenter = self . center
self . width + = widthChange
self . height + = heightChange
self . center = originalCenter
def clamp ( self , otherRect ) :
""" Centers this Rect object at the center of otherRect.
>> > r1 = Rect ( 0 , 0 , 100 , 100 )
>> > r2 = Rect ( - 20 , - 90 , 50 , 50 )
>> > r2 . clamp ( r1 )
>> > r2
Rect ( left = 25 , top = 25 , width = 50 , height = 50 )
>> > r1 . center == r2 . center
True
"""
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
self . center = otherRect . center
'''
def intersection ( self , otherRect ) :
""" Returns a new Rect object of the overlapping area between this
Rect object and otherRect .
` clip ( ) ` is an alias for ` intersection ( ) ` .
"""
pass
clip = intersection
'''
def union ( self , otherRect ) :
""" Adjusts the width and height to also cover the area of `otherRect`.
>> > r1 = Rect ( 0 , 0 , 100 , 100 )
>> > r2 = Rect ( - 10 , - 10 , 100 , 100 )
>> > r1 . union ( r2 )
>> > r1
Rect ( left = - 10 , top = - 10 , width = 110 , height = 110 )
"""
# TODO - Change otherRect so that it could be a point as well.
unionLeft = min ( self . _left , otherRect . _left )
unionTop = min ( self . _top , otherRect . _top )
unionRight = max ( self . right , otherRect . right )
unionBottom = max ( self . bottom , otherRect . bottom )
self . _left = unionLeft
self . _top = unionTop
self . _width = unionRight - unionLeft
self . _height = unionBottom - unionTop
def unionAll ( self , otherRects ) :
""" Adjusts the width and height to also cover all the `Rect` objects in
the ` otherRects ` sequence .
>> > r = Rect ( 0 , 0 , 100 , 100 )
>> > r1 = Rect ( 0 , 0 , 150 , 100 )
>> > r2 = Rect ( - 10 , - 10 , 100 , 100 )
>> > r . unionAll ( [ r1 , r2 ] )
>> > r
Rect ( left = - 10 , top = - 10 , width = 160 , height = 110 )
"""
# TODO - Change otherRect so that it could be a point as well.
otherRects = list ( otherRects )
otherRects . append ( self )
unionLeft = min ( [ r . _left for r in otherRects ] )
unionTop = min ( [ r . _top for r in otherRects ] )
unionRight = max ( [ r . right for r in otherRects ] )
unionBottom = max ( [ r . bottom for r in otherRects ] )
self . _left = unionLeft
self . _top = unionTop
self . _width = unionRight - unionLeft
self . _height = unionBottom - unionTop
"""
def fit ( self , other ) :
pass # TODO - needs to be complete
"""
def normalize ( self ) :
""" Rect objects with a negative width or height cover a region where the
right / bottom edge is to the left / above of the left / top edge , respectively .
The ` normalize ( ) ` method sets the ` width ` and ` height ` to positive if they
were negative .
The Rect stays in the same place , though with the ` top ` and ` left `
attributes representing the true top and left side .
>> > r = Rect ( 0 , 0 , - 10 , - 20 )
>> > r . normalize ( )
>> > r
Rect ( left = - 10 , top = - 20 , width = 10 , height = 20 )
"""
if self . _readOnly :
raise PyRectException ( ' Rect object is read-only ' )
if self . _width < 0 :
self . _width = - self . _width
self . _left - = self . _width
if self . _height < 0 :
self . _height = - self . _height
self . _top - = self . _height
# Note: No need to intify here, since the four attributes should already be ints and no multiplication was done.
def __contains__ ( self , value ) : # for either points or other Rect objects. For Rects, the *entire* Rect must be in this Rect.
if isinstance ( value , Rect ) :
return value . topleft in self and value . topright in self and value . bottomleft in self and value . bottomright in self
# Check if value is an (x, y) sequence or a (left, top, width, height) sequence.
try :
len ( value )
except :
raise PyRectException ( ' in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s ' % ( value . __class__ . __name__ ) )
if len ( value ) == 2 :
# Assume that value is an (x, y) sequence.
_checkForTwoIntOrFloatTuple ( value )
x , y = value
return self . _left < x < self . _left + self . _width and self . _top < y < self . _top + self . _height
elif len ( value ) == 4 :
# Assume that value is an (x, y) sequence.
_checkForFourIntOrFloatTuple ( value )
left , top , width , height = value
return ( left , top ) in self and ( left + width , top ) in self and ( left , top + height ) in self and ( left + width , top + height ) in self
else :
raise PyRectException ( ' in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s ' % ( value . __class__ . __name__ ) )
def collide ( self , value ) :
""" Returns `True` if value collides with this `Rect` object, where value can
be an ( x , y ) tuple , a ( left , top , width , height ) box tuple , or another ` Rect `
object . If value represents a rectangular area , any part of that area
can collide with this ` Rect ` object to make ` collide ( ) ` return ` True ` .
Otherwise , returns ` False ` . """
# Note: This code is similar to __contains__(), with some minor changes
# because __contains__() requires the rectangular are to be COMPELTELY
# within the Rect object.
if isinstance ( value , Rect ) :
return value . topleft in self or value . topright in self or value . bottomleft in self or value . bottomright in self
# Check if value is an (x, y) sequence or a (left, top, width, height) sequence.
try :
len ( value )
except :
raise PyRectException ( ' in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s ' % ( value . __class__ . __name__ ) )
if len ( value ) == 2 :
# Assume that value is an (x, y) sequence.
_checkForTwoIntOrFloatTuple ( value )
x , y = value
return self . _left < x < self . _left + self . _width and self . _top < y < self . _top + self . _height
elif len ( value ) == 4 :
# Assume that value is an (x, y) sequence.
left , top , width , height = value
return ( left , top ) in self or ( left + width , top ) in self or ( left , top + height ) in self or ( left + width , top + height ) in self
else :
raise PyRectException ( ' in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s ' % ( value . __class__ . __name__ ) )
'''
def collideAny ( self , rectsOrPoints ) :
""" Returns True if any of the (x, y) or (left, top, width, height)
tuples in rectsOrPoints is inside this Rect object .
>> r = Rect ( 0 , 0 , 100 , 100 )
>> p1 = ( 150 , 80 )
>> p2 = ( 100 , 100 ) # This point collides.
>> r . collideAny ( [ p1 , p2 ] )
True
>> r1 = Rect ( 50 , 50 , 10 , 20 ) # This Rect collides.
>> r . collideAny ( [ r1 ] )
True
>> r . collideAny ( [ p1 , p2 , r1 ] )
True
"""
# TODO - needs to be complete
pass # returns True or False
raise NotImplementedError
'''
'''
def collideAll ( self , rectsOrPoints ) :
""" Returns True if all of the (x, y) or (left, top, width, height)
tuples in rectsOrPoints is inside this Rect object .
"""
pass # return a list of all rects or points that collide with any other in the argument
raise NotImplementedError
'''
# TODO - Add overloaded operators for + - * / and others once we can determine actual use cases for them.
""" NOTE: All of the comparison magic methods compare the box tuple of Rect
objects . This is the behavior of the pygame Rect objects . Originally ,
I thought about having the < , < = , > , and > = operators compare the area
of Rect objects . But at the same time , I wanted to have == and != compare
not just area , but all four left , top , width , and height attributes .
But that ' s weird to have different comparison operators comparing different
features of a rectangular area . So I just defaulted to what Pygame does
and compares the box tuple . This means that the == and != operators are
the only really useful comparison operators , so I decided to ditch the
other operators altogether and just have Rect only support == and != .
"""
def __eq__ ( self , other ) :
if isinstance ( other , Rect ) :
return other . box == self . box
else :
raise PyRectException ( ' Rect objects can only be compared with other Rect objects ' )
def __ne__ ( self , other ) :
if isinstance ( other , Rect ) :
return other . box != self . box
else :
raise PyRectException ( ' Rect objects can only be compared with other Rect objects ' )
if __name__ == ' __main__ ' :
print ( doctest . testmod ( ) )