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