__init__.py 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462
  1. import doctest
  2. import collections
  3. # TODO - finish doc tests
  4. # TODO - unit tests needed for get/set and Box named tuple
  5. __version__ = "0.2.0"
  6. # Constants for rectangle attributes:
  7. TOP = "top"
  8. BOTTOM = "bottom"
  9. LEFT = "left"
  10. RIGHT = "right"
  11. TOPLEFT = "topleft"
  12. TOPRIGHT = "topright"
  13. BOTTOMLEFT = "bottomleft"
  14. BOTTOMRIGHT = "bottomright"
  15. MIDTOP = "midtop"
  16. MIDRIGHT = "midright"
  17. MIDLEFT = "midleft"
  18. MIDBOTTOM = "midbottom"
  19. CENTER = "center"
  20. CENTERX = "centerx"
  21. CENTERY = "centery"
  22. WIDTH = "width"
  23. HEIGHT = "height"
  24. SIZE = "size"
  25. BOX = "box"
  26. AREA = "area"
  27. PERIMETER = "perimeter"
  28. Box = collections.namedtuple("Box", "left top width height")
  29. Point = collections.namedtuple("Point", "x y")
  30. Size = collections.namedtuple("Size", "width height")
  31. class PyRectException(Exception):
  32. """
  33. This class exists for PyRect exceptions. If the PyRect module raises any
  34. non-PyRectException exceptions, this indicates there's a bug in PyRect.
  35. """
  36. pass
  37. def _checkForIntOrFloat(arg):
  38. """Raises an exception if arg is not an int or float. Always returns None."""
  39. if not isinstance(arg, (int, float)):
  40. raise PyRectException(
  41. "argument must be int or float, not %s" % (arg.__class__.__name__)
  42. )
  43. def _checkForInt(arg):
  44. """Raises an exception if arg is not an int. Always returns None."""
  45. if not isinstance(arg, int):
  46. raise PyRectException(
  47. "argument must be int or float, not %s" % (arg.__class__.__name__)
  48. )
  49. def _checkForTwoIntOrFloatTuple(arg):
  50. try:
  51. if not isinstance(arg[0], (int, float)) or not isinstance(arg[1], (int, float)):
  52. raise PyRectException(
  53. "argument must be a two-item tuple containing int or float values"
  54. )
  55. except:
  56. raise PyRectException(
  57. "argument must be a two-item tuple containing int or float values"
  58. )
  59. def _checkForFourIntOrFloatTuple(arg):
  60. try:
  61. if (
  62. not isinstance(arg[0], (int, float))
  63. or not isinstance(arg[1], (int, float))
  64. or not isinstance(arg[2], (int, float))
  65. or not isinstance(arg[3], (int, float))
  66. ):
  67. raise PyRectException(
  68. "argument must be a four-item tuple containing int or float values"
  69. )
  70. except:
  71. raise PyRectException(
  72. "argument must be a four-item tuple containing int or float values"
  73. )
  74. def _collides(rectOrPoint1, rectOrPoint2):
  75. """Returns True if rectOrPoint1 and rectOrPoint2 collide with each other."""
  76. def _getRectsAndPoints(rectsOrPoints):
  77. points = []
  78. rects = []
  79. for rectOrPoint in rectsOrPoints:
  80. try:
  81. _checkForTwoIntOrFloatTuple(rectOrPoint)
  82. points.append(rectOrPoint)
  83. except PyRectException:
  84. try:
  85. _checkForFourIntOrFloatTuple(rectOrPoint)
  86. except:
  87. raise PyRectException("argument is not a point or a rect tuple")
  88. rects.append(rectOrPoint)
  89. return (rects, points)
  90. '''
  91. def collideAnyBetween(rectsOrPoints):
  92. """Returns True if any of the (x, y) or (left, top, width, height) tuples
  93. in rectsOrPoints collides with any other point or box tuple in rectsOrPoints.
  94. >>> p1 = (50, 50)
  95. >>> p2 = (100, 100)
  96. >>> p3 = (50, 200)
  97. >>> r1 = (-50, -50, 20, 20)
  98. >>> r2 = (25, 25, 50, 50)
  99. >>> collideAnyBetween([p1, p2, p3, r1, r2]) # p1 and r2 collide
  100. True
  101. >>> collideAnyBetween([p1, p2, p3, r1])
  102. False
  103. """
  104. # TODO - needs to be complete
  105. # split up
  106. rects, points = _getRectsAndPoints(rectsOrPoints)
  107. # compare points with each other
  108. if len(points) > 1:
  109. for point in points:
  110. if point != points[0]:
  111. return False
  112. # TODO finish
  113. '''
  114. '''
  115. def collideAllBetween(rectsOrPoints):
  116. """Returns True if any of the (x, y) or (left, top, width, height) tuples
  117. in rectsOrPoints collides with any other point or box tuple in rectsOrPoints.
  118. >>> p1 = (50, 50)
  119. >>> p2 = (100, 100)
  120. >>> p3 = (50, 200)
  121. >>> r1 = (-50, -50, 20, 20)
  122. >>> r2 = (25, 25, 50, 50)
  123. >>> collideAllBetween([p1, p2, p3, r1, r2])
  124. False
  125. >>> collideAllBetween([p1, p2, p3, r1])
  126. False
  127. >>> collideAllBetween([p1, r2]) # Everything in the list collides with each other.
  128. True
  129. """
  130. # Check for valid arguments
  131. try:
  132. for rectOrPoint in rectsOrPoints:
  133. if len(rectOrPoint) == 2:
  134. _checkForTwoIntOrFloatTuple(rectOrPoint)
  135. elif len(rectOrPoint) == 4:
  136. _checkForFourIntOrFloatTuple(rectOrPoint)
  137. else:
  138. raise PyRectException()
  139. except:
  140. raise PyRectException('Arguments in rectsOrPoints must be 2- or 4-integer/float tuples.')
  141. raise NotImplementedError # return a list of all rects or points that collide with any other in the argument
  142. '''
  143. class Rect(object):
  144. def __init__(
  145. self,
  146. left=0,
  147. top=0,
  148. width=0,
  149. height=0,
  150. enableFloat=False,
  151. readOnly=False,
  152. onChange=None,
  153. onRead=None,
  154. ):
  155. _checkForIntOrFloat(width)
  156. _checkForIntOrFloat(height)
  157. _checkForIntOrFloat(left)
  158. _checkForIntOrFloat(top)
  159. self._enableFloat = bool(enableFloat)
  160. self._readOnly = bool(readOnly)
  161. if onChange is not None and not callable(onChange):
  162. raise PyRectException(
  163. "onChange argument must be None or callable (function, method, etc.)"
  164. )
  165. self.onChange = onChange
  166. if onRead is not None and not callable(onRead):
  167. raise PyRectException(
  168. "onRead argument must be None or callable (function, method, etc.)"
  169. )
  170. self.onRead = onRead
  171. if enableFloat:
  172. self._width = float(width)
  173. self._height = float(height)
  174. self._left = float(left)
  175. self._top = float(top)
  176. else:
  177. self._width = int(width)
  178. self._height = int(height)
  179. self._left = int(left)
  180. self._top = int(top)
  181. # OPERATOR OVERLOADING / DUNDER METHODS
  182. def __repr__(self):
  183. """Return a string of the constructor function call to create this Rect object."""
  184. return "%s(left=%s, top=%s, width=%s, height=%s)" % (
  185. self.__class__.__name__,
  186. self._left,
  187. self._top,
  188. self._width,
  189. self._height,
  190. )
  191. def __str__(self):
  192. """Return a string representation of this Rect object."""
  193. return "(x=%s, y=%s, w=%s, h=%s)" % (
  194. self._left,
  195. self._top,
  196. self._width,
  197. self._height,
  198. )
  199. def callOnChange(self, oldLeft, oldTop, oldWidth, oldHeight):
  200. # Note: callOnChange() should be called *after* the attribute has been changed.
  201. # Note: This isn't thread safe; the attributes can change between the calling of this function and the code in the function running.
  202. if self.onChange is not None:
  203. self.onChange(
  204. Box(oldLeft, oldTop, oldWidth, oldHeight),
  205. Box(self._left, self._top, self._width, self._height),
  206. )
  207. @property
  208. def enableFloat(self):
  209. """
  210. A Boolean attribute that determines if this rectangle uses floating point
  211. numbers for its position and size. False, by default.
  212. >>> r = Rect(0, 0, 10, 20)
  213. >>> r.enableFloat
  214. False
  215. >>> r.enableFloat = True
  216. >>> r.top = 3.14
  217. >>> r
  218. Rect(left=0.0, top=3.14, width=10.0, height=20.0)
  219. """
  220. return self._enableFloat
  221. @enableFloat.setter
  222. def enableFloat(self, value):
  223. if not isinstance(value, bool):
  224. raise PyRectException("enableFloat must be set to a bool value")
  225. self._enableFloat = value
  226. if self._enableFloat:
  227. self._left = float(self._left)
  228. self._top = float(self._top)
  229. self._width = float(self._width)
  230. self._height = float(self._height)
  231. else:
  232. self._left = int(self._left)
  233. self._top = int(self._top)
  234. self._width = int(self._width)
  235. self._height = int(self._height)
  236. # LEFT SIDE PROPERTY
  237. @property
  238. def left(self):
  239. """
  240. The x coordinate for the left edge of the rectangle. `x` is an alias for `left`.
  241. >>> r = Rect(0, 0, 10, 20)
  242. >>> r.left
  243. 0
  244. >>> r.left = 50
  245. >>> r
  246. Rect(left=50, top=0, width=10, height=20)
  247. """
  248. if self.onRead is not None:
  249. self.onRead(LEFT)
  250. return self._left
  251. @left.setter
  252. def left(self, newLeft):
  253. if self._readOnly:
  254. raise PyRectException("Rect object is read-only")
  255. _checkForIntOrFloat(newLeft)
  256. if (
  257. newLeft != self._left
  258. ): # Only run this code if the size/position has changed.
  259. originalLeft = self._left
  260. if self._enableFloat:
  261. self._left = newLeft
  262. else:
  263. self._left = int(newLeft)
  264. self.callOnChange(originalLeft, self._top, self._width, self._height)
  265. x = left # x is an alias for left
  266. # TOP SIDE PROPERTY
  267. @property
  268. def top(self):
  269. """
  270. The y coordinate for the top edge of the rectangle. `y` is an alias for `top`.
  271. >>> r = Rect(0, 0, 10, 20)
  272. >>> r.top
  273. 0
  274. >>> r.top = 50
  275. >>> r
  276. Rect(left=0, top=50, width=10, height=20)
  277. """
  278. if self.onRead is not None:
  279. self.onRead(TOP)
  280. return self._top
  281. @top.setter
  282. def top(self, newTop):
  283. if self._readOnly:
  284. raise PyRectException("Rect object is read-only")
  285. _checkForIntOrFloat(newTop)
  286. if newTop != self._top: # Only run this code if the size/position has changed.
  287. originalTop = self._top
  288. if self._enableFloat:
  289. self._top = newTop
  290. else:
  291. self._top = int(newTop)
  292. self.callOnChange(self._left, originalTop, self._width, self._height)
  293. y = top # y is an alias for top
  294. # RIGHT SIDE PROPERTY
  295. @property
  296. def right(self):
  297. """
  298. The x coordinate for the right edge of the rectangle.
  299. >>> r = Rect(0, 0, 10, 20)
  300. >>> r.right
  301. 10
  302. >>> r.right = 50
  303. >>> r
  304. Rect(left=40, top=0, width=10, height=20)
  305. """
  306. if self.onRead is not None:
  307. self.onRead(RIGHT)
  308. return self._left + self._width
  309. @right.setter
  310. def right(self, newRight):
  311. if self._readOnly:
  312. raise PyRectException("Rect object is read-only")
  313. _checkForIntOrFloat(newRight)
  314. if (
  315. newRight != self._left + self._width
  316. ): # Only run this code if the size/position has changed.
  317. originalLeft = self._left
  318. if self._enableFloat:
  319. self._left = newRight - self._width
  320. else:
  321. self._left = int(newRight) - self._width
  322. self.callOnChange(originalLeft, self._top, self._width, self._height)
  323. # BOTTOM SIDE PROPERTY
  324. @property
  325. def bottom(self):
  326. """The y coordinate for the bottom edge of the rectangle.
  327. >>> r = Rect(0, 0, 10, 20)
  328. >>> r.bottom
  329. 20
  330. >>> r.bottom = 30
  331. >>> r
  332. Rect(left=0, top=10, width=10, height=20)
  333. """
  334. if self.onRead is not None:
  335. self.onRead(BOTTOM)
  336. return self._top + self._height
  337. @bottom.setter
  338. def bottom(self, newBottom):
  339. if self._readOnly:
  340. raise PyRectException("Rect object is read-only")
  341. _checkForIntOrFloat(newBottom)
  342. if (
  343. newBottom != self._top + self._height
  344. ): # Only run this code if the size/position has changed.
  345. originalTop = self._top
  346. if self._enableFloat:
  347. self._top = newBottom - self._height
  348. else:
  349. self._top = int(newBottom) - self._height
  350. self.callOnChange(self._left, originalTop, self._width, self._height)
  351. # TOP LEFT CORNER PROPERTY
  352. @property
  353. def topleft(self):
  354. """
  355. The x and y coordinates for the top right corner of the rectangle, as a tuple.
  356. >>> r = Rect(0, 0, 10, 20)
  357. >>> r.topleft
  358. (0, 0)
  359. >>> r.topleft = (30, 30)
  360. >>> r
  361. Rect(left=30, top=30, width=10, height=20)
  362. """
  363. if self.onRead is not None:
  364. self.onRead(TOPLEFT)
  365. return Point(x=self._left, y=self._top)
  366. @topleft.setter
  367. def topleft(self, value):
  368. if self._readOnly:
  369. raise PyRectException("Rect object is read-only")
  370. _checkForTwoIntOrFloatTuple(value)
  371. newLeft, newTop = value
  372. if (newLeft != self._left) or (
  373. newTop != self._top
  374. ): # Only run this code if the size/position has changed.
  375. originalLeft = self._left
  376. originalTop = self._top
  377. if self._enableFloat:
  378. self._left = newLeft
  379. self._top = newTop
  380. else:
  381. self._left = int(newLeft)
  382. self._top = int(newTop)
  383. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  384. # BOTTOM LEFT CORNER PROPERTY
  385. @property
  386. def bottomleft(self):
  387. """
  388. The x and y coordinates for the bottom right corner of the rectangle, as a tuple.
  389. >>> r = Rect(0, 0, 10, 20)
  390. >>> r.bottomleft
  391. (0, 20)
  392. >>> r.bottomleft = (30, 30)
  393. >>> r
  394. Rect(left=30, top=10, width=10, height=20)
  395. """
  396. if self.onRead is not None:
  397. self.onRead(BOTTOMLEFT)
  398. return Point(x=self._left, y=self._top + self._height)
  399. @bottomleft.setter
  400. def bottomleft(self, value):
  401. if self._readOnly:
  402. raise PyRectException("Rect object is read-only")
  403. _checkForTwoIntOrFloatTuple(value)
  404. newLeft, newBottom = value
  405. if (newLeft != self._left) or (
  406. newBottom != self._top + self._height
  407. ): # Only run this code if the size/position has changed.
  408. originalLeft = self._left
  409. originalTop = self._top
  410. if self._enableFloat:
  411. self._left = newLeft
  412. self._top = newBottom - self._height
  413. else:
  414. self._left = int(newLeft)
  415. self._top = int(newBottom) - self._height
  416. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  417. # TOP RIGHT CORNER PROPERTY
  418. @property
  419. def topright(self):
  420. """
  421. The x and y coordinates for the top right corner of the rectangle, as a tuple.
  422. >>> r = Rect(0, 0, 10, 20)
  423. >>> r.topright
  424. (10, 0)
  425. >>> r.topright = (30, 30)
  426. >>> r
  427. Rect(left=20, top=30, width=10, height=20)
  428. """
  429. if self.onRead is not None:
  430. self.onRead(TOPRIGHT)
  431. return Point(x=self._left + self._width, y=self._top)
  432. @topright.setter
  433. def topright(self, value):
  434. if self._readOnly:
  435. raise PyRectException("Rect object is read-only")
  436. _checkForTwoIntOrFloatTuple(value)
  437. newRight, newTop = value
  438. if (newRight != self._left + self._width) or (
  439. newTop != self._top
  440. ): # Only run this code if the size/position has changed.
  441. originalLeft = self._left
  442. originalTop = self._top
  443. if self._enableFloat:
  444. self._left = newRight - self._width
  445. self._top = newTop
  446. else:
  447. self._left = int(newRight) - self._width
  448. self._top = int(newTop)
  449. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  450. # BOTTOM RIGHT CORNER PROPERTY
  451. @property
  452. def bottomright(self):
  453. """
  454. The x and y coordinates for the bottom right corner of the rectangle, as a tuple.
  455. >>> r = Rect(0, 0, 10, 20)
  456. >>> r.bottomright
  457. (10, 20)
  458. >>> r.bottomright = (30, 30)
  459. >>> r
  460. Rect(left=20, top=10, width=10, height=20)
  461. """
  462. if self.onRead is not None:
  463. self.onRead(BOTTOMRIGHT)
  464. return Point(x=self._left + self._width, y=self._top + self._height)
  465. @bottomright.setter
  466. def bottomright(self, value):
  467. if self._readOnly:
  468. raise PyRectException("Rect object is read-only")
  469. _checkForTwoIntOrFloatTuple(value)
  470. newRight, newBottom = value
  471. if (newBottom != self._top + self._height) or (
  472. newRight != self._left + self._width
  473. ): # Only run this code if the size/position has changed.
  474. originalLeft = self._left
  475. originalTop = self._top
  476. if self._enableFloat:
  477. self._left = newRight - self._width
  478. self._top = newBottom - self._height
  479. else:
  480. self._left = int(newRight) - self._width
  481. self._top = int(newBottom) - self._height
  482. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  483. # MIDDLE OF TOP SIDE PROPERTY
  484. @property
  485. def midtop(self):
  486. """
  487. The x and y coordinates for the midpoint of the top edge of the rectangle, as a tuple.
  488. >>> r = Rect(0, 0, 10, 20)
  489. >>> r.midtop
  490. (5, 0)
  491. >>> r.midtop = (40, 50)
  492. >>> r
  493. Rect(left=35, top=50, width=10, height=20)
  494. """
  495. if self.onRead is not None:
  496. self.onRead(MIDTOP)
  497. if self._enableFloat:
  498. return Point(x=self._left + (self._width / 2.0), y=self._top)
  499. else:
  500. return Point(x=self._left + (self._width // 2), y=self._top)
  501. @midtop.setter
  502. def midtop(self, value):
  503. if self._readOnly:
  504. raise PyRectException("Rect object is read-only")
  505. _checkForTwoIntOrFloatTuple(value)
  506. newMidTop, newTop = value
  507. originalLeft = self._left
  508. originalTop = self._top
  509. if self._enableFloat:
  510. if (newMidTop != self._left + self._width / 2.0) or (
  511. newTop != self._top
  512. ): # Only run this code if the size/position has changed.
  513. self._left = newMidTop - (self._width / 2.0)
  514. self._top = newTop
  515. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  516. else:
  517. if (newMidTop != self._left + self._width // 2) or (
  518. newTop != self._top
  519. ): # Only run this code if the size/position has changed.
  520. self._left = int(newMidTop) - (self._width // 2)
  521. self._top = int(newTop)
  522. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  523. # MIDDLE OF BOTTOM SIDE PROPERTY
  524. @property
  525. def midbottom(self):
  526. """
  527. The x and y coordinates for the midpoint of the bottom edge of the rectangle, as a tuple.
  528. >>> r = Rect(0, 0, 10, 20)
  529. >>> r.midbottom
  530. (5, 20)
  531. >>> r.midbottom = (40, 50)
  532. >>> r
  533. Rect(left=35, top=30, width=10, height=20)
  534. """
  535. if self.onRead is not None:
  536. self.onRead(MIDBOTTOM)
  537. if self._enableFloat:
  538. return Point(x=self._left + (self._width / 2.0), y=self._top + self._height)
  539. else:
  540. return Point(x=self._left + (self._width // 2), y=self._top + self._height)
  541. @midbottom.setter
  542. def midbottom(self, value):
  543. if self._readOnly:
  544. raise PyRectException("Rect object is read-only")
  545. _checkForTwoIntOrFloatTuple(value)
  546. newMidBottom, newBottom = value
  547. originalLeft = self._left
  548. originalTop = self._top
  549. if self._enableFloat:
  550. if (newMidBottom != self._left + self._width / 2.0) or (
  551. newBottom != self._top + self._height
  552. ): # Only run this code if the size/position has changed.
  553. self._left = newMidBottom - (self._width / 2.0)
  554. self._top = newBottom - self._height
  555. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  556. else:
  557. if (newMidBottom != self._left + self._width // 2) or (
  558. newBottom != self._top + self._height
  559. ): # Only run this code if the size/position has changed.
  560. self._left = int(newMidBottom) - (self._width // 2)
  561. self._top = int(newBottom) - self._height
  562. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  563. # MIDDLE OF LEFT SIDE PROPERTY
  564. @property
  565. def midleft(self):
  566. """
  567. The x and y coordinates for the midpoint of the left edge of the rectangle, as a tuple.
  568. >>> r = Rect(0, 0, 10, 20)
  569. >>> r.midleft
  570. (0, 10)
  571. >>> r.midleft = (40, 50)
  572. >>> r
  573. Rect(left=40, top=40, width=10, height=20)
  574. """
  575. if self.onRead is not None:
  576. self.onRead(MIDLEFT)
  577. if self._enableFloat:
  578. return Point(x=self._left, y=self._top + (self._height / 2.0))
  579. else:
  580. return Point(x=self._left, y=self._top + (self._height // 2))
  581. @midleft.setter
  582. def midleft(self, value):
  583. if self._readOnly:
  584. raise PyRectException("Rect object is read-only")
  585. _checkForTwoIntOrFloatTuple(value)
  586. newLeft, newMidLeft = value
  587. originalLeft = self._left
  588. originalTop = self._top
  589. if self._enableFloat:
  590. if (newLeft != self._left) or (
  591. newMidLeft != self._top + (self._height / 2.0)
  592. ): # Only run this code if the size/position has changed.
  593. self._left = newLeft
  594. self._top = newMidLeft - (self._height / 2.0)
  595. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  596. else:
  597. if (newLeft != self._left) or (
  598. newMidLeft != self._top + (self._height // 2)
  599. ): # Only run this code if the size/position has changed.
  600. self._left = int(newLeft)
  601. self._top = int(newMidLeft) - (self._height // 2)
  602. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  603. # MIDDLE OF RIGHT SIDE PROPERTY
  604. @property
  605. def midright(self):
  606. """
  607. The x and y coordinates for the midpoint of the right edge of the rectangle, as a tuple.
  608. >>> r = Rect(0, 0, 10, 20)
  609. >>> r.midright
  610. (10, 10)
  611. >>> r.midright = (40, 50)
  612. >>> r
  613. Rect(left=30, top=40, width=10, height=20)
  614. """
  615. if self.onRead is not None:
  616. self.onRead(MIDRIGHT)
  617. if self._enableFloat:
  618. return Point(x=self._left + self._width, y=self._top + (self._height / 2.0))
  619. else:
  620. return Point(x=self._left + self._width, y=self._top + (self._height // 2))
  621. @midright.setter
  622. def midright(self, value):
  623. if self._readOnly:
  624. raise PyRectException("Rect object is read-only")
  625. _checkForTwoIntOrFloatTuple(value)
  626. newRight, newMidRight = value
  627. originalLeft = self._left
  628. originalTop = self._top
  629. if self._enableFloat:
  630. if (newRight != self._left + self._width) or (
  631. newMidRight != self._top + self._height / 2.0
  632. ): # Only run this code if the size/position has changed.
  633. self._left = newRight - self._width
  634. self._top = newMidRight - (self._height / 2.0)
  635. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  636. else:
  637. if (newRight != self._left + self._width) or (
  638. newMidRight != self._top + self._height // 2
  639. ): # Only run this code if the size/position has changed.
  640. self._left = int(newRight) - self._width
  641. self._top = int(newMidRight) - (self._height // 2)
  642. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  643. # CENTER POINT PROPERTY
  644. @property
  645. def center(self):
  646. """
  647. The x and y coordinates for the center of the rectangle, as a tuple.
  648. >>> r = Rect(0, 0, 10, 20)
  649. >>> r.center
  650. (5, 10)
  651. >>> r.center = (40, 50)
  652. >>> r
  653. Rect(left=35, top=40, width=10, height=20)
  654. """
  655. if self.onRead is not None:
  656. self.onRead(CENTER)
  657. if self._enableFloat:
  658. return Point(
  659. x=self._left + (self._width / 2.0), y=self._top + (self._height / 2.0)
  660. )
  661. else:
  662. return Point(
  663. x=self._left + (self._width // 2), y=self._top + (self._height // 2)
  664. )
  665. @center.setter
  666. def center(self, value):
  667. if self._readOnly:
  668. raise PyRectException("Rect object is read-only")
  669. _checkForTwoIntOrFloatTuple(value)
  670. newCenterx, newCentery = value
  671. originalLeft = self._left
  672. originalTop = self._top
  673. if self._enableFloat:
  674. if (newCenterx != self._left + self._width / 2.0) or (
  675. newCentery != self._top + self._height / 2.0
  676. ): # Only run this code if the size/position has changed.
  677. self._left = newCenterx - (self._width / 2.0)
  678. self._top = newCentery - (self._height / 2.0)
  679. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  680. else:
  681. if (newCenterx != self._left + self._width // 2) or (
  682. newCentery != self._top + self._height // 2
  683. ): # Only run this code if the size/position has changed.
  684. self._left = int(newCenterx) - (self._width // 2)
  685. self._top = int(newCentery) - (self._height // 2)
  686. self.callOnChange(originalLeft, originalTop, self._width, self._height)
  687. # X COORDINATE OF CENTER POINT PROPERTY
  688. @property
  689. def centerx(self):
  690. """
  691. The x coordinate for the center of the rectangle, as a tuple.
  692. >>> r = Rect(0, 0, 10, 20)
  693. >>> r.centerx
  694. 5
  695. >>> r.centerx = 50
  696. >>> r
  697. Rect(left=45, top=0, width=10, height=20)
  698. """
  699. if self.onRead is not None:
  700. self.onRead(CENTERX)
  701. if self._enableFloat:
  702. return self._left + (self._width / 2.0)
  703. else:
  704. return self._left + (self._width // 2)
  705. @centerx.setter
  706. def centerx(self, newCenterx):
  707. if self._readOnly:
  708. raise PyRectException("Rect object is read-only")
  709. _checkForIntOrFloat(newCenterx)
  710. originalLeft = self._left
  711. if self._enableFloat:
  712. if (
  713. newCenterx != self._left + self._width / 2.0
  714. ): # Only run this code if the size/position has changed.
  715. self._left = newCenterx - (self._width / 2.0)
  716. self.callOnChange(originalLeft, self._top, self._width, self._height)
  717. else:
  718. if (
  719. newCenterx != self._left + self._width // 2
  720. ): # Only run this code if the size/position has changed.
  721. self._left = int(newCenterx) - (self._width // 2)
  722. self.callOnChange(originalLeft, self._top, self._width, self._height)
  723. # Y COORDINATE OF CENTER POINT PROPERTY
  724. @property
  725. def centery(self):
  726. """
  727. The y coordinate for the center of the rectangle, as a tuple.
  728. >>> r = Rect(0, 0, 10, 20)
  729. >>> r.centery
  730. 10
  731. >>> r.centery = 50
  732. >>> r
  733. Rect(left=0, top=40, width=10, height=20)
  734. """
  735. if self.onRead is not None:
  736. self.onRead(CENTERY)
  737. if self._enableFloat:
  738. return self._top + (self._height / 2.0)
  739. else:
  740. return self._top + (self._height // 2)
  741. @centery.setter
  742. def centery(self, newCentery):
  743. if self._readOnly:
  744. raise PyRectException("Rect object is read-only")
  745. _checkForIntOrFloat(newCentery)
  746. originalTop = self._top
  747. if self._enableFloat:
  748. if (
  749. newCentery != self._top + self._height / 2.0
  750. ): # Only run this code if the size/position has changed.
  751. self._top = newCentery - (self._height / 2.0)
  752. self.callOnChange(self._left, originalTop, self._width, self._height)
  753. else:
  754. if (
  755. newCentery != self._top + self._height // 2
  756. ): # Only run this code if the size/position has changed.
  757. self._top = int(newCentery) - (self._height // 2)
  758. self.callOnChange(self._left, originalTop, self._width, self._height)
  759. # SIZE PROPERTY (i.e. (width, height))
  760. @property
  761. def size(self):
  762. """
  763. The width and height of the rectangle, as a tuple.
  764. >>> r = Rect(0, 0, 10, 20)
  765. >>> r.size
  766. (10, 20)
  767. >>> r.size = (40, 50)
  768. >>> r
  769. Rect(left=0, top=0, width=40, height=50)
  770. """
  771. if self.onRead is not None:
  772. self.onRead(SIZE)
  773. return Size(width=self._width, height=self._height)
  774. @size.setter
  775. def size(self, value):
  776. if self._readOnly:
  777. raise PyRectException("Rect object is read-only")
  778. _checkForTwoIntOrFloatTuple(value)
  779. newWidth, newHeight = value
  780. if newWidth != self._width or newHeight != self._height:
  781. originalWidth = self._width
  782. originalHeight = self._height
  783. if self._enableFloat:
  784. self._width = newWidth
  785. self._height = newHeight
  786. else:
  787. self._width = int(newWidth)
  788. self._height = int(newHeight)
  789. self.callOnChange(self._left, self._top, originalWidth, originalHeight)
  790. # WIDTH PROPERTY
  791. @property
  792. def width(self):
  793. """
  794. The width of the rectangle. `w` is an alias for `width`.
  795. >>> r = Rect(0, 0, 10, 20)
  796. >>> r.width
  797. 10
  798. >>> r.width = 50
  799. >>> r
  800. Rect(left=0, top=0, width=50, height=20)
  801. """
  802. if self.onRead is not None:
  803. self.onRead(WIDTH)
  804. return self._width
  805. @width.setter
  806. def width(self, newWidth):
  807. if self._readOnly:
  808. raise PyRectException("Rect object is read-only")
  809. _checkForIntOrFloat(newWidth)
  810. if (
  811. newWidth != self._width
  812. ): # Only run this code if the size/position has changed.
  813. originalWidth = self._width
  814. if self._enableFloat:
  815. self._width = newWidth
  816. else:
  817. self._width = int(newWidth)
  818. self.callOnChange(self._left, self._top, originalWidth, self._height)
  819. w = width
  820. # HEIGHT PROPERTY
  821. @property
  822. def height(self):
  823. """
  824. The height of the rectangle. `h` is an alias for `height`
  825. >>> r = Rect(0, 0, 10, 20)
  826. >>> r.height
  827. 20
  828. >>> r.height = 50
  829. >>> r
  830. Rect(left=0, top=0, width=10, height=50)
  831. """
  832. if self.onRead is not None:
  833. self.onRead(HEIGHT)
  834. return self._height
  835. @height.setter
  836. def height(self, newHeight):
  837. if self._readOnly:
  838. raise PyRectException("Rect object is read-only")
  839. _checkForIntOrFloat(newHeight)
  840. if (
  841. newHeight != self._height
  842. ): # Only run this code if the size/position has changed.
  843. originalHeight = self._height
  844. if self._enableFloat:
  845. self._height = newHeight
  846. else:
  847. self._height = int(newHeight)
  848. self.callOnChange(self._left, self._top, self._width, originalHeight)
  849. h = height
  850. # AREA PROPERTY
  851. @property
  852. def area(self):
  853. """The area of the `Rect`, which is simply the width times the height.
  854. This is a read-only attribute.
  855. >>> r = Rect(0, 0, 10, 20)
  856. >>> r.area
  857. 200
  858. """
  859. if self.onRead is not None:
  860. self.onRead(AREA)
  861. return self._width * self._height
  862. # PERIMETER PROPERTY
  863. @property
  864. def perimeter(self):
  865. """The perimeter of the `Rect`, which is simply the (width + height) * 2.
  866. This is a read-only attribute.
  867. >>> r = Rect(0, 0, 10, 20)
  868. >>> r.area
  869. 200
  870. """
  871. if self.onRead is not None:
  872. self.onRead(AREA)
  873. return (self._width + self._height) * 2
  874. # BOX PROPERTY
  875. @property
  876. def box(self):
  877. """A tuple of four integers: (left, top, width, height).
  878. >>> r = Rect(0, 0, 10, 20)
  879. >>> r.box
  880. (0, 0, 10, 20)
  881. >>> r.box = (5, 15, 100, 200)
  882. >>> r.box
  883. (5, 15, 100, 200)"""
  884. if self.onRead is not None:
  885. self.onRead(BOX)
  886. return Box(
  887. left=self._left, top=self._top, width=self._width, height=self._height
  888. )
  889. @box.setter
  890. def box(self, value):
  891. if self._readOnly:
  892. raise PyRectException("Rect object is read-only")
  893. _checkForFourIntOrFloatTuple(value)
  894. newLeft, newTop, newWidth, newHeight = value
  895. if (
  896. (newLeft != self._left)
  897. or (newTop != self._top)
  898. or (newWidth != self._width)
  899. or (newHeight != self._height)
  900. ):
  901. originalLeft = self._left
  902. originalTop = self._top
  903. originalWidth = self._width
  904. originalHeight = self._height
  905. if self._enableFloat:
  906. self._left = float(newLeft)
  907. self._top = float(newTop)
  908. self._width = float(newWidth)
  909. self._height = float(newHeight)
  910. else:
  911. self._left = int(newLeft)
  912. self._top = int(newTop)
  913. self._width = int(newWidth)
  914. self._height = int(newHeight)
  915. self.callOnChange(originalLeft, originalTop, originalWidth, originalHeight)
  916. def get(self, rectAttrName):
  917. # Access via the properties so that it triggers onRead().
  918. if rectAttrName == TOP:
  919. return self.top
  920. elif rectAttrName == BOTTOM:
  921. return self.bottom
  922. elif rectAttrName == LEFT:
  923. return self.left
  924. elif rectAttrName == RIGHT:
  925. return self.right
  926. elif rectAttrName == TOPLEFT:
  927. return self.topleft
  928. elif rectAttrName == TOPRIGHT:
  929. return self.topright
  930. elif rectAttrName == BOTTOMLEFT:
  931. return self.bottomleft
  932. elif rectAttrName == BOTTOMRIGHT:
  933. return self.bottomright
  934. elif rectAttrName == MIDTOP:
  935. return self.midtop
  936. elif rectAttrName == MIDBOTTOM:
  937. return self.midbottom
  938. elif rectAttrName == MIDLEFT:
  939. return self.midleft
  940. elif rectAttrName == MIDRIGHT:
  941. return self.midright
  942. elif rectAttrName == CENTER:
  943. return self.center
  944. elif rectAttrName == CENTERX:
  945. return self.centerx
  946. elif rectAttrName == CENTERY:
  947. return self.centery
  948. elif rectAttrName == WIDTH:
  949. return self.width
  950. elif rectAttrName == HEIGHT:
  951. return self.height
  952. elif rectAttrName == SIZE:
  953. return self.size
  954. elif rectAttrName == AREA:
  955. return self.area
  956. elif rectAttrName == BOX:
  957. return self.box
  958. else:
  959. raise PyRectException("'%s' is not a valid attribute name" % (rectAttrName))
  960. def set(self, rectAttrName, value):
  961. # Set via the properties so that it triggers onChange().
  962. if rectAttrName == TOP:
  963. self.top = value
  964. elif rectAttrName == BOTTOM:
  965. self.bottom = value
  966. elif rectAttrName == LEFT:
  967. self.left = value
  968. elif rectAttrName == RIGHT:
  969. self.right = value
  970. elif rectAttrName == TOPLEFT:
  971. self.topleft = value
  972. elif rectAttrName == TOPRIGHT:
  973. self.topright = value
  974. elif rectAttrName == BOTTOMLEFT:
  975. self.bottomleft = value
  976. elif rectAttrName == BOTTOMRIGHT:
  977. self.bottomright = value
  978. elif rectAttrName == MIDTOP:
  979. self.midtop = value
  980. elif rectAttrName == MIDBOTTOM:
  981. self.midbottom = value
  982. elif rectAttrName == MIDLEFT:
  983. self.midleft = value
  984. elif rectAttrName == MIDRIGHT:
  985. self.midright = value
  986. elif rectAttrName == CENTER:
  987. self.center = value
  988. elif rectAttrName == CENTERX:
  989. self.centerx = value
  990. elif rectAttrName == CENTERY:
  991. self.centery = value
  992. elif rectAttrName == WIDTH:
  993. self.width = value
  994. elif rectAttrName == HEIGHT:
  995. self.height = value
  996. elif rectAttrName == SIZE:
  997. self.size = value
  998. elif rectAttrName == AREA:
  999. raise PyRectException("area is a read-only attribute")
  1000. elif rectAttrName == BOX:
  1001. self.box = value
  1002. else:
  1003. raise PyRectException("'%s' is not a valid attribute name" % (rectAttrName))
  1004. def move(self, xOffset, yOffset):
  1005. """Moves this Rect object by the given offsets. The xOffset and yOffset
  1006. arguments can be any integer value, positive or negative.
  1007. >>> r = Rect(0, 0, 100, 100)
  1008. >>> r.move(10, 20)
  1009. >>> r
  1010. Rect(left=10, top=20, width=100, height=100)
  1011. """
  1012. if self._readOnly:
  1013. raise PyRectException("Rect object is read-only")
  1014. _checkForIntOrFloat(xOffset)
  1015. _checkForIntOrFloat(yOffset)
  1016. if self._enableFloat:
  1017. self._left += xOffset
  1018. self._top += yOffset
  1019. else:
  1020. self._left += int(xOffset)
  1021. self._top += int(yOffset)
  1022. def copy(self):
  1023. """Return a copied `Rect` object with the same position and size as this
  1024. `Rect` object.
  1025. >>> r1 = Rect(0, 0, 100, 150)
  1026. >>> r2 = r1.copy()
  1027. >>> r1 == r2
  1028. True
  1029. >>> r2
  1030. Rect(left=0, top=0, width=100, height=150)
  1031. """
  1032. return Rect(
  1033. self._left,
  1034. self._top,
  1035. self._width,
  1036. self._height,
  1037. self._enableFloat,
  1038. self._readOnly,
  1039. )
  1040. def inflate(self, widthChange=0, heightChange=0):
  1041. """Increases the size of this Rect object by the given offsets. The
  1042. rectangle's center doesn't move. Negative values will shrink the
  1043. rectangle.
  1044. >>> r = Rect(0, 0, 100, 150)
  1045. >>> r.inflate(20, 40)
  1046. >>> r
  1047. Rect(left=-10, top=-20, width=120, height=190)
  1048. """
  1049. if self._readOnly:
  1050. raise PyRectException("Rect object is read-only")
  1051. originalCenter = self.center
  1052. self.width += widthChange
  1053. self.height += heightChange
  1054. self.center = originalCenter
  1055. def clamp(self, otherRect):
  1056. """Centers this Rect object at the center of otherRect.
  1057. >>> r1 =Rect(0, 0, 100, 100)
  1058. >>> r2 = Rect(-20, -90, 50, 50)
  1059. >>> r2.clamp(r1)
  1060. >>> r2
  1061. Rect(left=25, top=25, width=50, height=50)
  1062. >>> r1.center == r2.center
  1063. True
  1064. """
  1065. if self._readOnly:
  1066. raise PyRectException("Rect object is read-only")
  1067. self.center = otherRect.center
  1068. '''
  1069. def intersection(self, otherRect):
  1070. """Returns a new Rect object of the overlapping area between this
  1071. Rect object and otherRect.
  1072. `clip()` is an alias for `intersection()`.
  1073. """
  1074. pass
  1075. clip = intersection
  1076. '''
  1077. def union(self, otherRect):
  1078. """Adjusts the width and height to also cover the area of `otherRect`.
  1079. >>> r1 = Rect(0, 0, 100, 100)
  1080. >>> r2 = Rect(-10, -10, 100, 100)
  1081. >>> r1.union(r2)
  1082. >>> r1
  1083. Rect(left=-10, top=-10, width=110, height=110)
  1084. """
  1085. # TODO - Change otherRect so that it could be a point as well.
  1086. unionLeft = min(self._left, otherRect._left)
  1087. unionTop = min(self._top, otherRect._top)
  1088. unionRight = max(self.right, otherRect.right)
  1089. unionBottom = max(self.bottom, otherRect.bottom)
  1090. self._left = unionLeft
  1091. self._top = unionTop
  1092. self._width = unionRight - unionLeft
  1093. self._height = unionBottom - unionTop
  1094. def unionAll(self, otherRects):
  1095. """Adjusts the width and height to also cover all the `Rect` objects in
  1096. the `otherRects` sequence.
  1097. >>> r = Rect(0, 0, 100, 100)
  1098. >>> r1 = Rect(0, 0, 150, 100)
  1099. >>> r2 = Rect(-10, -10, 100, 100)
  1100. >>> r.unionAll([r1, r2])
  1101. >>> r
  1102. Rect(left=-10, top=-10, width=160, height=110)
  1103. """
  1104. # TODO - Change otherRect so that it could be a point as well.
  1105. otherRects = list(otherRects)
  1106. otherRects.append(self)
  1107. unionLeft = min([r._left for r in otherRects])
  1108. unionTop = min([r._top for r in otherRects])
  1109. unionRight = max([r.right for r in otherRects])
  1110. unionBottom = max([r.bottom for r in otherRects])
  1111. self._left = unionLeft
  1112. self._top = unionTop
  1113. self._width = unionRight - unionLeft
  1114. self._height = unionBottom - unionTop
  1115. """
  1116. def fit(self, other):
  1117. pass # TODO - needs to be complete
  1118. """
  1119. def normalize(self):
  1120. """Rect objects with a negative width or height cover a region where the
  1121. right/bottom edge is to the left/above of the left/top edge, respectively.
  1122. The `normalize()` method sets the `width` and `height` to positive if they
  1123. were negative.
  1124. The Rect stays in the same place, though with the `top` and `left`
  1125. attributes representing the true top and left side.
  1126. >>> r = Rect(0, 0, -10, -20)
  1127. >>> r.normalize()
  1128. >>> r
  1129. Rect(left=-10, top=-20, width=10, height=20)
  1130. """
  1131. if self._readOnly:
  1132. raise PyRectException("Rect object is read-only")
  1133. if self._width < 0:
  1134. self._width = -self._width
  1135. self._left -= self._width
  1136. if self._height < 0:
  1137. self._height = -self._height
  1138. self._top -= self._height
  1139. # Note: No need to intify here, since the four attributes should already be ints and no multiplication was done.
  1140. def __contains__(
  1141. self, value
  1142. ): # for either points or other Rect objects. For Rects, the *entire* Rect must be in this Rect.
  1143. if isinstance(value, Rect):
  1144. return (
  1145. value.topleft in self
  1146. and value.topright in self
  1147. and value.bottomleft in self
  1148. and value.bottomright in self
  1149. )
  1150. # Check if value is an (x, y) sequence or a (left, top, width, height) sequence.
  1151. try:
  1152. len(value)
  1153. except:
  1154. raise PyRectException(
  1155. "in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s"
  1156. % (value.__class__.__name__)
  1157. )
  1158. if len(value) == 2:
  1159. # Assume that value is an (x, y) sequence.
  1160. _checkForTwoIntOrFloatTuple(value)
  1161. x, y = value
  1162. return (
  1163. self._left < x < self._left + self._width
  1164. and self._top < y < self._top + self._height
  1165. )
  1166. elif len(value) == 4:
  1167. # Assume that value is an (x, y) sequence.
  1168. _checkForFourIntOrFloatTuple(value)
  1169. left, top, width, height = value
  1170. return (
  1171. (left, top) in self
  1172. and (left + width, top) in self
  1173. and (left, top + height) in self
  1174. and (left + width, top + height) in self
  1175. )
  1176. else:
  1177. raise PyRectException(
  1178. "in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s"
  1179. % (value.__class__.__name__)
  1180. )
  1181. def collide(self, value):
  1182. """Returns `True` if value collides with this `Rect` object, where value can
  1183. be an (x, y) tuple, a (left, top, width, height) box tuple, or another `Rect`
  1184. object. If value represents a rectangular area, any part of that area
  1185. can collide with this `Rect` object to make `collide()` return `True`.
  1186. Otherwise, returns `False`."""
  1187. # Note: This code is similar to __contains__(), with some minor changes
  1188. # because __contains__() requires the rectangular are to be COMPELTELY
  1189. # within the Rect object.
  1190. if isinstance(value, Rect):
  1191. return (
  1192. value.topleft in self
  1193. or value.topright in self
  1194. or value.bottomleft in self
  1195. or value.bottomright in self
  1196. )
  1197. # Check if value is an (x, y) sequence or a (left, top, width, height) sequence.
  1198. try:
  1199. len(value)
  1200. except:
  1201. raise PyRectException(
  1202. "in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s"
  1203. % (value.__class__.__name__)
  1204. )
  1205. if len(value) == 2:
  1206. # Assume that value is an (x, y) sequence.
  1207. _checkForTwoIntOrFloatTuple(value)
  1208. x, y = value
  1209. return (
  1210. self._left < x < self._left + self._width
  1211. and self._top < y < self._top + self._height
  1212. )
  1213. elif len(value) == 4:
  1214. # Assume that value is an (x, y) sequence.
  1215. left, top, width, height = value
  1216. return (
  1217. (left, top) in self
  1218. or (left + width, top) in self
  1219. or (left, top + height) in self
  1220. or (left + width, top + height) in self
  1221. )
  1222. else:
  1223. raise PyRectException(
  1224. "in <Rect> requires an (x, y) tuple, a (left, top, width, height) tuple, or a Rect object as left operand, not %s"
  1225. % (value.__class__.__name__)
  1226. )
  1227. '''
  1228. def collideAny(self, rectsOrPoints):
  1229. """Returns True if any of the (x, y) or (left, top, width, height)
  1230. tuples in rectsOrPoints is inside this Rect object.
  1231. >> r = Rect(0, 0, 100, 100)
  1232. >> p1 = (150, 80)
  1233. >> p2 = (100, 100) # This point collides.
  1234. >> r.collideAny([p1, p2])
  1235. True
  1236. >> r1 = Rect(50, 50, 10, 20) # This Rect collides.
  1237. >> r.collideAny([r1])
  1238. True
  1239. >> r.collideAny([p1, p2, r1])
  1240. True
  1241. """
  1242. # TODO - needs to be complete
  1243. pass # returns True or False
  1244. raise NotImplementedError
  1245. '''
  1246. '''
  1247. def collideAll(self, rectsOrPoints):
  1248. """Returns True if all of the (x, y) or (left, top, width, height)
  1249. tuples in rectsOrPoints is inside this Rect object.
  1250. """
  1251. pass # return a list of all rects or points that collide with any other in the argument
  1252. raise NotImplementedError
  1253. '''
  1254. # TODO - Add overloaded operators for + - * / and others once we can determine actual use cases for them.
  1255. """NOTE: All of the comparison magic methods compare the box tuple of Rect
  1256. objects. This is the behavior of the pygame Rect objects. Originally,
  1257. I thought about having the <, <=, >, and >= operators compare the area
  1258. of Rect objects. But at the same time, I wanted to have == and != compare
  1259. not just area, but all four left, top, width, and height attributes.
  1260. But that's weird to have different comparison operators comparing different
  1261. features of a rectangular area. So I just defaulted to what Pygame does
  1262. and compares the box tuple. This means that the == and != operators are
  1263. the only really useful comparison operators, so I decided to ditch the
  1264. other operators altogether and just have Rect only support == and !=.
  1265. """
  1266. def __eq__(self, other):
  1267. if isinstance(other, Rect):
  1268. return other.box == self.box
  1269. else:
  1270. raise PyRectException(
  1271. "Rect objects can only be compared with other Rect objects"
  1272. )
  1273. def __ne__(self, other):
  1274. if isinstance(other, Rect):
  1275. return other.box != self.box
  1276. else:
  1277. raise PyRectException(
  1278. "Rect objects can only be compared with other Rect objects"
  1279. )
  1280. if __name__ == "__main__":
  1281. print(doctest.testmod())