1# Copyright 2014 Tom Rothamel <tom@rothamel.us> 2# Copyright 2014 Patrick Dawson <pat@dw.is> 3# 4# This software is provided 'as-is', without any express or implied 5# warranty. In no event will the authors be held liable for any damages 6# arising from the use of this software. 7# 8# Permission is granted to anyone to use this software for any purpose, 9# including commercial applications, and to alter it and redistribute it 10# freely, subject to the following restrictions: 11# 12# 1. The origin of this software must not be misrepresented; you must not 13# claim that you wrote the original software. If you use this software 14# in a product, an acknowledgment in the product documentation would be 15# appreciated but is not required. 16# 2. Altered source versions must be plainly marked as such, and must not be 17# misrepresented as being the original software. 18# 3. This notice may not be removed or altered from any source distribution. 19 20import collections 21 22def flatten(*args): 23 if len(args) == 1: 24 return args[0] 25 else: 26 return args 27 28cdef class Rect: 29 30 def __init__(self, *args): 31 32 cdef int x, y, w, h 33 cdef int len_args 34 cdef Rect rect 35 36 len_args = len(args) 37 38 if len_args == 1 and isinstance(args[0], Rect): 39 rect = args[0] 40 x = rect.x 41 y = rect.y 42 w = rect.w 43 h = rect.h 44 45 elif len_args == 1 and len(args[0]) == 4: 46 x, y, w, h = args[0] 47 48 elif len_args == 1 and len(args[0]) == 2: 49 x, y = args[0] 50 w = 0 51 h = 0 52 53 elif len_args == 2: 54 x, y = args[0] 55 w, h = args[1] 56 57 elif len_args == 4: 58 x, y, w, h = args 59 60 else: 61 raise TypeError("Argument must be a rect style object.") 62 63 self.x = x 64 self.y = y 65 self.w = w 66 self.h = h 67 68 def __reduce__(self): 69 return (Rect, (self.x, self.y, self.w, self.h)) 70 71 def __repr__(self): 72 return "<rect(%d, %d, %d, %d)>" % (self.x, self.y, self.w, self.h) 73 74 def __len__(self): 75 return 4 76 77 def __iter__(self): 78 return iter((self.x, self.y, self.w, self.h)) 79 80 def __richcmp__(Rect a, b, int op): 81 if not isinstance(b, Rect): 82 b = Rect(b) 83 if op == 2: 84 return a.x == b.x and a.y == b.y and a.w == b.w and a.h == b.h 85 86 def __getitem__(self, key): 87 return (self.x, self.y, self.w, self.h)[key] 88 89 def __setitem__(self, key, val): 90 if key == 0: 91 self.x = val 92 elif key == 1: 93 self.y = val 94 elif key == 2: 95 self.w = val 96 elif key == 3: 97 self.h = val 98 else: 99 raise IndexError(key) 100 101 property left: 102 def __get__(self): 103 return self.x 104 def __set__(self, val): 105 self.x = val 106 107 property top: 108 def __get__(self): 109 return self.y 110 def __set__(self, val): 111 self.y = val 112 113 property width: 114 def __get__(self): 115 return self.w 116 def __set__(self, val): 117 self.w = val 118 119 property height: 120 def __get__(self): 121 return self.h 122 def __set__(self, val): 123 self.h = val 124 125 property right: 126 def __get__(self): 127 return self.x + self.width 128 def __set__(self, val): 129 self.x += val - self.right 130 131 property bottom: 132 def __get__(self): 133 return self.y + self.height 134 def __set__(self, val): 135 self.y += val - self.bottom 136 137 property size: 138 def __get__(self): 139 return (self.w, self.h) 140 def __set__(self, val): 141 self.w, self.h = val 142 143 property topleft: 144 def __get__(self): 145 return (self.left, self.top) 146 def __set__(self, val): 147 self.left, self.top = val 148 149 property topright: 150 def __get__(self): 151 return (self.right, self.top) 152 def __set__(self, val): 153 self.right, self.top = val 154 155 property bottomright: 156 def __get__(self): 157 return (self.right, self.bottom) 158 def __set__(self, val): 159 self.right, self.bottom = val 160 161 property bottomleft: 162 def __get__(self): 163 return (self.left, self.bottom) 164 def __set__(self, val): 165 self.left, self.bottom = val 166 167 property centerx: 168 def __get__(self): 169 return self.x + (self.w / 2) 170 def __set__(self, val): 171 self.x += val - self.centerx 172 173 property centery: 174 def __get__(self): 175 return self.y + (self.h / 2) 176 def __set__(self, val): 177 self.y += val - self.centery 178 179 property center: 180 def __get__(self): 181 return (self.centerx, self.centery) 182 def __set__(self, val): 183 self.centerx, self.centery = val 184 185 property midtop: 186 def __get__(self): 187 return (self.centerx, self.top) 188 def __set__(self, val): 189 self.centerx, self.top = val 190 191 property midleft: 192 def __get__(self): 193 return (self.left, self.centery) 194 def __set__(self, val): 195 self.left, self.centery = val 196 197 property midbottom: 198 def __get__(self): 199 return (self.centerx, self.bottom) 200 def __set__(self, val): 201 self.centerx, self.bottom = val 202 203 property midright: 204 def __get__(self): 205 return (self.right, self.centery) 206 def __set__(self, val): 207 self.right, self.centery = val 208 209 def copy(self): 210 return Rect(self) 211 212 def move(self, *args): 213 r = self.copy() 214 r.move_ip(*args) 215 return r 216 217 def move_ip(self, *args): 218 x, y = flatten(args) 219 self.x += x 220 self.y += y 221 222 def inflate(self, *args): 223 r = self.copy() 224 r.inflate_ip(*args) 225 return r 226 227 def inflate_ip(self, *args): 228 x, y = flatten(args) 229 c = self.center 230 self.w += x 231 self.h += y 232 self.center = c 233 234 def clamp(self, other): 235 r = self.copy() 236 r.clamp_ip(other) 237 return r 238 239 def clamp_ip(self, other): 240 if not isinstance(other, Rect): 241 other = Rect(other) 242 243 if self.w > other.w or self.h > other.h: 244 self.center = other.center 245 else: 246 if self.left < other.left: 247 self.left = other.left 248 elif self.right > other.right: 249 self.right = other.right 250 if self.top < other.top: 251 self.top = other.top 252 elif self.bottom > other.bottom: 253 self.bottom = other.bottom 254 255 def clip(self, other, y=None, w=None, h=None): 256 if type(other) == int: 257 other = Rect(other, y, w, h) 258 259 if not isinstance(other, Rect): 260 other = Rect(other) 261 262 if not self.colliderect(other): 263 return Rect(0,0,0,0) 264 265 r = self.copy() 266 267 # Remember that (0,0) is the top left. 268 if r.left < other.left: 269 d = other.left - r.left 270 r.left += d 271 r.width -= d 272 if r.right > other.right: 273 d = r.right - other.right 274 r.width -=d 275 if r.top < other.top: 276 d = other.top - r.top 277 r.top += d 278 r.height -= d 279 if r.bottom > other.bottom: 280 d = r.bottom - other.bottom 281 r.height -= d 282 283 return r 284 285 def union(self, other): 286 r = self.copy() 287 r.union_ip(other) 288 return r 289 290 def union_ip(self, other): 291 if not isinstance(other, Rect): 292 other = Rect(other) 293 294 x = min(self.x, other.x) 295 y = min(self.y, other.y) 296 self.w = max(self.right, other.right) - x 297 self.h = max(self.bottom, other.bottom) - y 298 self.x = x 299 self.y = y 300 301 def unionall(self, other_seq): 302 r = self.copy() 303 r.unionall_ip(other_seq) 304 return r 305 306 def unionall_ip(self, other_seq): 307 for other in other_seq: 308 self.union_ip(other) 309 310 def fit(self, other): 311 if not isinstance(other, Rect): 312 other = Rect(other) 313 314 # Not sure if this is entirely correct. Docs and tests are ambiguous. 315 r = self.copy() 316 r.topleft = other.topleft 317 w_ratio = other.w / float(r.w) 318 h_ratio = other.h / float(r.h) 319 factor = min(w_ratio, h_ratio) 320 r.w *= factor 321 r.h *= factor 322 return r 323 324 def normalize(self): 325 if self.w < 0: 326 self.x += self.w 327 self.w = -self.w 328 if self.h < 0: 329 self.y += self.h 330 self.h = -self.h 331 332 def contains(self, other): 333 if not isinstance(other, Rect): 334 other = Rect(other) 335 336 return other.x >= self.x and other.right <= self.right and \ 337 other.y >= self.y and other.bottom <= self.bottom and \ 338 other.left < self.right and other.top < self.bottom 339 340 def collidepoint(self, x, y=None): 341 if type(x) == tuple: 342 x, y = x 343 return x >= self.x and y >= self.y and \ 344 x < self.right and y < self.bottom 345 346 def colliderect(self, other): 347 if not isinstance(other, Rect): 348 other = Rect(other) 349 350 return self.left < other.right and self.top < other.bottom and \ 351 self.right > other.left and self.bottom > other.top 352 353 def collidelist(self, other_list): 354 for n, other in zip(range(len(other_list)), other_list): 355 if self.colliderect(other): 356 return n 357 return -1 358 359 def collidelistall(self, other_list): 360 ret = [] 361 for n, other in zip(range(len(other_list)), other_list): 362 if self.colliderect(other): 363 ret.append(n) 364 return ret 365 366 def collidedict(self, other_dict, rects_values=0): 367 # What is rects_values supposed to do? Not in docs. 368 for key, val in other_dict.items(): 369 if self.colliderect(val): 370 return key, val 371 return None 372 373 def collidedictall(self, other_dict, rects_values=0): 374 ret = [] 375 for key, val in other_dict.items(): 376 if self.colliderect(val): 377 ret.append((key,val)) 378 return ret 379 380 381cdef int to_sdl_rect(rectlike, SDL_Rect *rect, argname=None) except -1: 382 """ 383 Converts `rectlike` to the SDL_Rect `rect`. 384 385 `rectlike` may be a Rect or a (x, y, w, h) tuple. 386 """ 387 388 cdef int x, y, w, h 389 cdef Rect rl 390 391 try: 392 if isinstance(rectlike, Rect): 393 rl = rectlike 394 395 rect.x = rl.x 396 rect.y = rl.y 397 rect.w = rl.w 398 rect.h = rl.h 399 400 return 0 401 402 elif len(rectlike) == 4: 403 rect.x, rect.y, rect.w, rect.h = rectlike 404 return 0 405 406 elif len(rectlike) == 2: 407 rect.x, rect.y = rectlike 408 rect.w, rect.h = rectlike 409 return 0 410 411 except: 412 pass 413 414 if argname: 415 raise TypeError("Argument {} must be a rect style object.".format(argname)) 416 else: 417 raise TypeError("Argument must be a rect style object.") 418