1from cpython cimport PyObject
2from . import error
3from . import error as errorfnc
4from libc.stdlib cimport free, malloc
5
6
7WINDOWPOS_UNDEFINED = _SDL_WINDOWPOS_UNDEFINED
8WINDOWPOS_CENTERED = _SDL_WINDOWPOS_CENTERED
9
10MESSAGEBOX_ERROR = _SDL_MESSAGEBOX_ERROR
11MESSAGEBOX_WARNING = _SDL_MESSAGEBOX_WARNING
12MESSAGEBOX_INFORMATION = _SDL_MESSAGEBOX_INFORMATION
13
14
15cdef extern from "SDL.h" nogil:
16    Uint32 SDL_GetWindowPixelFormat(SDL_Window* window)
17    SDL_bool SDL_IntersectRect(const SDL_Rect* A,
18                               const SDL_Rect* B,
19                               SDL_Rect*       result)
20    void SDL_SetWindowResizable(SDL_Window *window, SDL_bool resizable)
21    int SDL_GetWindowOpacity(SDL_Window *window, float *opacity)
22    int SDL_SetWindowOpacity(SDL_Window *window, float opacity)
23    int SDL_SetWindowModalFor(SDL_Window *modal_window, SDL_Window *parent_window)
24    int SDL_SetWindowInputFocus(SDL_Window *window)
25    int SDL_SetRelativeMouseMode(SDL_bool enabled)
26    SDL_bool SDL_GetRelativeMouseMode()
27    SDL_Renderer* SDL_GetRenderer(SDL_Window* window)
28    SDL_Window* SDL_GetWindowFromID(Uint32 id)
29    SDL_Surface * SDL_CreateRGBSurfaceWithFormat(Uint32 flags, int width, int height, int depth, Uint32 format)
30
31
32cdef extern from "pygame.h" nogil:
33    ctypedef struct pgSurfaceObject:
34        pass
35
36    int pgSurface_Check(object surf)
37    SDL_Surface* pgSurface_AsSurface(object surf)
38    void import_pygame_surface()
39
40    SDL_Window* pg_GetDefaultWindow()
41    void import_pygame_base()
42
43    int pgRect_Check(object rect)
44    SDL_Rect *pgRect_FromObject(object obj, SDL_Rect *temp)
45    object pgRect_New(SDL_Rect *r)
46    object pgRect_New4(int x, int y, int w, int h)
47    SDL_Rect pgRect_AsRect(object rect)
48    void import_pygame_rect()
49
50
51    object pgColor_New(Uint8 rgba[])
52    object pgColor_NewLength(Uint8 rgba[], Uint8 length)
53    void import_pygame_color()
54    pgSurfaceObject *pgSurface_New2(SDL_Surface *info, int owner)
55
56cdef extern from "pgcompat.h" nogil:
57    pass
58
59import_pygame_base()
60import_pygame_color()
61import_pygame_surface()
62import_pygame_rect()
63
64class RendererDriverInfo:
65    def __repr__(self):
66        return "<%s(name: %s, flags: 0x%02x, num_texture_formats: %d, max_texture_width: %d, max_texture_height: %d)>" % (
67            self.__class__.__name__,
68            self.name,
69            self.flags,
70            self.num_texture_formats,
71            self.max_texture_width,
72            self.max_texture_height,
73        )
74
75def get_drivers():
76    cdef int num = SDL_GetNumRenderDrivers()
77    cdef SDL_RendererInfo info
78    cdef int ind
79    for ind from 0 <= ind < num:
80        SDL_GetRenderDriverInfo(ind, &info)
81        ret = RendererDriverInfo()
82        ret.name = info.name
83        ret.flags = info.flags
84        ret.num_texture_formats = info.num_texture_formats
85        ret.max_texture_width = info.max_texture_width
86        ret.max_texture_height = info.max_texture_height
87        yield ret
88
89
90def get_grabbed_window():
91    """return the Window with input grab enabled,
92       or None if input isn't grabbed."""
93    cdef SDL_Window *win = SDL_GetGrabbedWindow()
94    cdef void *ptr
95    if win:
96        ptr = SDL_GetWindowData(win, "pg_window")
97        if not ptr:
98            return None
99        return <object>ptr
100    return None
101
102
103def messagebox(title, message,
104               Window window=None,
105               bint info=False,
106               bint warn=False,
107               bint error=False,
108               buttons=('OK', ),
109               return_button=0,
110               escape_button=0):
111    """ Display a message box.
112
113    :param str title: A title string or None.
114    :param str message: A message string.
115    :param bool info: If ``True``, display an info message.
116    :param bool warn: If ``True``, display a warning message.
117    :param bool error: If ``True``, display an error message.
118    :param tuple buttons: An optional sequence of buttons to show to the user (strings).
119    :param int return_button: Button index to use if the return key is hit (-1 for none).
120    :param int escape_button: Button index to use if the escape key is hit (-1 for none).
121    :return: The index of the button that was pushed.
122    """
123    # TODO: type check
124    # TODO: color scheme
125    cdef SDL_MessageBoxButtonData* c_buttons = NULL
126
127    cdef SDL_MessageBoxData data
128    data.flags = 0
129    if warn:
130        data.flags |= _SDL_MESSAGEBOX_WARNING
131    if error:
132        data.flags |= _SDL_MESSAGEBOX_ERROR
133    if info:
134        data.flags |= _SDL_MESSAGEBOX_INFORMATION
135    if not window:
136        data.window = NULL
137    else:
138        data.window = window._win
139    if title is not None:
140        title = title.encode('utf8')
141        data.title = title
142    else:
143        data.title = NULL
144    message = message.encode('utf8')
145    data.message = message
146    data.colorScheme = NULL
147
148    cdef SDL_MessageBoxButtonData button
149    if not buttons:
150        button.flags = _SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT |\
151                       _SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT
152        button.buttonid = 0
153        button.text = "OK"
154        data.buttons = &button
155        data.numbuttons = 1
156    else:
157        buttons_utf8 = [s.encode('utf8') for s in buttons]
158        data.numbuttons = len(buttons)
159        c_buttons =\
160            <SDL_MessageBoxButtonData*>malloc(data.numbuttons * sizeof(SDL_MessageBoxButtonData))
161        if not c_buttons:
162            raise MemoryError()
163        for i, but in enumerate(reversed(buttons_utf8)):
164            c_buttons[i].flags = 0
165            c_buttons[i].buttonid = data.numbuttons - i - 1
166            if c_buttons[i].buttonid == return_button:
167                c_buttons[i].flags |= _SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT
168            if c_buttons[i].buttonid == escape_button:
169                c_buttons[i].flags |= _SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT
170            c_buttons[i].text = but
171        data.buttons = c_buttons
172
173    cdef int buttonid
174    if SDL_ShowMessageBox(&data, &buttonid):
175        free(c_buttons)
176        raise errorfnc()
177
178    free(c_buttons)
179    return buttonid
180
181
182cdef class Window:
183    DEFAULT_SIZE = 640, 480
184
185    _kwarg_to_flag = {
186        'opengl': _SDL_WINDOW_OPENGL,
187        'vulkan': _SDL_WINDOW_VULKAN,
188        'hidden': _SDL_WINDOW_HIDDEN,
189        'borderless': _SDL_WINDOW_BORDERLESS,
190        'resizable': _SDL_WINDOW_RESIZABLE,
191        'minimized': _SDL_WINDOW_MINIMIZED,
192        'maximized': _SDL_WINDOW_MAXIMIZED,
193        'input_grabbed': _SDL_WINDOW_INPUT_GRABBED,
194        'input_focus': _SDL_WINDOW_INPUT_FOCUS,
195        'mouse_focus': _SDL_WINDOW_MOUSE_FOCUS,
196        'allow_highdpi': _SDL_WINDOW_ALLOW_HIGHDPI,
197        'foreign': _SDL_WINDOW_FOREIGN,
198        'mouse_capture': _SDL_WINDOW_MOUSE_CAPTURE,
199        'always_on_top': _SDL_WINDOW_ALWAYS_ON_TOP,
200        'skip_taskbar': _SDL_WINDOW_SKIP_TASKBAR,
201        'utility': _SDL_WINDOW_UTILITY,
202        'tooltip': _SDL_WINDOW_TOOLTIP,
203        'popup_menu': _SDL_WINDOW_POPUP_MENU,
204    }
205
206    @classmethod
207    def from_display_module(cls):
208        cdef Window self = cls.__new__(cls)
209        cdef SDL_Window* window = pg_GetDefaultWindow()
210        if not window:
211            raise error()
212        self._win=window
213        self._is_borrowed=1
214        SDL_SetWindowData(window, "pg_window", <PyObject*>self)
215        return self
216
217    def __init__(self, title='pygame',
218                 size=DEFAULT_SIZE,
219                 position=WINDOWPOS_UNDEFINED,
220                 bint fullscreen=False,
221                 bint fullscreen_desktop=False, **kwargs):
222        """ Create a window with the specified position, dimensions, and flags.
223
224        :param str title: the title of the window, in UTF-8 encoding
225        :param tuple size: the size of the window, in screen coordinates (width, height)
226        :param position: a tuple specifying the window position, WINDOWPOS_CENTERED, or WINDOWPOS_UNDEFINED.
227        :param bool fullscreen: fullscreen window using the window size as the resolution (videomode change)
228        :param bool fullscreen_desktop: fullscreen window using the current desktop resolution
229        :param bool opengl: Usable with OpenGL context. You will still need to create an OpenGL context.
230        :param bool vulkan: usable with a Vulkan instance
231        :param bool hidden: window is not visible
232        :param bool borderless: no window decoration
233        :param bool resizable: window can be resized
234        :param bool minimized: window is minimized
235        :param bool maximized: window is maximized
236        :param bool input_grabbed: window has grabbed input focus
237        :param bool input_focus: window has input focus
238        :param bool mouse_focus: window has mouse focus
239        :param bool foreign: window not created by SDL
240        :param bool allow_highdpi: window should be created in high-DPI mode if supported (>= SDL 2.0.1)
241        :param bool mouse_capture: window has mouse captured (unrelated to INPUT_GRABBED, >= SDL 2.0.4)
242        :param bool always_on_top: window should always be above others (X11 only, >= SDL 2.0.5)
243        :param bool skip_taskbar: window should not be added to the taskbar (X11 only, >= SDL 2.0.5)
244        :param bool utility: window should be treated as a utility window (X11 only, >= SDL 2.0.5)
245        :param bool tooltip: window should be treated as a tooltip (X11 only, >= SDL 2.0.5)
246        :param bool popup_menu: window should be treated as a popup menu (X11 only, >= SDL 2.0.5)
247        """
248        # https://wiki.libsdl.org/SDL_CreateWindow
249        # https://wiki.libsdl.org/SDL_WindowFlags
250        if position == WINDOWPOS_UNDEFINED:
251            x, y = WINDOWPOS_UNDEFINED, WINDOWPOS_UNDEFINED
252        elif position == WINDOWPOS_CENTERED:
253            x, y = WINDOWPOS_CENTERED, WINDOWPOS_CENTERED
254        else:
255            x, y = position
256
257        flags = 0
258        if fullscreen and fullscreen_desktop:
259            raise ValueError("fullscreen and fullscreen_desktop cannot be used at the same time.")
260        if fullscreen:
261            flags |= _SDL_WINDOW_FULLSCREEN
262        elif fullscreen_desktop:
263            flags |= _SDL_WINDOW_FULLSCREEN_DESKTOP
264
265        _kwarg_to_flag = self._kwarg_to_flag
266        for k, v in kwargs.items():
267            try:
268                flag = _kwarg_to_flag[k]
269                if v:
270                    flags |= flag
271            except KeyError:
272                raise TypeError("unknown parameter: %s" % k)
273
274        self._win = SDL_CreateWindow(title.encode('utf8'), x, y,
275                                     size[0], size[1], flags)
276        self._is_borrowed=0
277        if not self._win:
278            raise error()
279        SDL_SetWindowData(self._win, "pg_window", <PyObject*>self)
280
281        import pygame.pkgdata
282        surf = pygame.image.load(pygame.pkgdata.getResource(
283                                 'pygame_icon.bmp'))
284        surf.set_colorkey(0)
285        self.set_icon(surf)
286
287    @property
288    def grab(self):
289        """ Window's input grab state (``True`` or ``False``).
290
291        Set it to ``True`` to grab, ``False`` to release.
292
293        When input is grabbed the mouse is confined to the window.
294        If the caller enables a grab while another window is currently grabbed,
295        the other window loses its grab in favor of the caller's window.
296
297        :rtype: bool
298        """
299        return SDL_GetWindowGrab(self._win) != 0
300
301    @grab.setter
302    def grab(self, bint grabbed):
303        # https://wiki.libsdl.org/SDL_SetWindowGrab
304        SDL_SetWindowGrab(self._win, 1 if grabbed else 0)
305
306    @property
307    def relative_mouse(self):
308        """ Window's relative mouse motion state (``True`` or ``False``).
309
310        Set it to ``True`` to enable, ``False`` to disable.
311        If mouse.set_visible(True) is set the input will be grabbed,
312        and the mouse will enter endless relative motion mode.
313
314        :rtype: bool
315        """
316        return SDL_GetRelativeMouseMode()
317
318
319    @relative_mouse.setter
320    def relative_mouse(self, bint enable):
321        # https://wiki.libsdl.org/SDL_SetRelativeMouseMode
322        #SDL_SetWindowGrab(self._win, 1 if enable else 0)
323        SDL_SetRelativeMouseMode(1 if enable else 0)
324
325    def set_windowed(self):
326        """ Enable windowed mode
327
328        .. seealso:: :func:`set_fullscreen`
329
330        """
331        # https://wiki.libsdl.org/SDL_SetWindowFullscreen
332        if SDL_SetWindowFullscreen(self._win, 0):
333            raise error()
334
335    #TODO: not sure about this...
336    # Perhaps this is more readable:
337    #     window.fullscreen = True
338    #     window.fullscreen_desktop = True
339    #     window.windowed = True
340    def set_fullscreen(self, bint desktop=False):
341        """ Enable fullscreen for the window
342
343        :param bool desktop: If ``True``: use the current desktop resolution.
344                             If ``False``: change the fullscreen resolution to the window size.
345
346        .. seealso:: :func:`set_windowed`
347        """
348        cdef int flags = 0
349        if desktop:
350            flags = _SDL_WINDOW_FULLSCREEN_DESKTOP
351        else:
352            flags = _SDL_WINDOW_FULLSCREEN
353        if SDL_SetWindowFullscreen(self._win, flags):
354            raise error()
355
356    @property
357    def title(self):
358        """ The title of the window or u"" if there is none.
359        """
360        # https://wiki.libsdl.org/SDL_GetWindowTitle
361        return SDL_GetWindowTitle(self._win).decode('utf8')
362
363    @title.setter
364    def title(self, title):
365        """ Set the window title.
366
367        :param str title: the desired window title in UTF-8.
368        """
369        # https://wiki.libsdl.org/SDL_SetWindowTitle
370        SDL_SetWindowTitle(self._win, title.encode('utf8'))
371
372    def destroy(self):
373        """ Destroys the window.
374        """
375        # https://wiki.libsdl.org/SDL_DestroyWindow
376        if self._win:
377            SDL_DestroyWindow(self._win)
378            self._win = NULL
379
380    def hide(self):
381        """ Hide the window.
382        """
383        # https://wiki.libsdl.org/SDL_HideWindow
384        SDL_HideWindow(self._win)
385
386    def show(self):
387        """ Show the window.
388        """
389        # https://wiki.libsdl.org/SDL_ShowWindow
390        SDL_ShowWindow(self._win)
391
392    def focus(self, input_only=False):
393        """ Raise the window above other windows and set the input focus.
394
395        :param bool input_only: if ``True``, the window will be given input focus
396                                but may be completely obscured by other windows.
397        """
398        # https://wiki.libsdl.org/SDL_RaiseWindow
399        if input_only:
400            if SDL_SetWindowInputFocus(self._win):
401                raise error()
402        else:
403            SDL_RaiseWindow(self._win)
404
405    def restore(self):
406        """ Restore the size and position of a minimized or maximized window.
407        """
408        SDL_RestoreWindow(self._win)
409
410    def maximize(self):
411        """ Maximize the window.
412        """
413        SDL_MaximizeWindow(self._win)
414
415    def minimize(self):
416        """ Minimize the window.
417        """
418        SDL_MinimizeWindow(self._win)
419
420    @property
421    def resizable(self):
422        """ Sets whether the window is resizable.
423        """
424        return SDL_GetWindowFlags(self._win) & _SDL_WINDOW_RESIZABLE != 0
425
426    @resizable.setter
427    def resizable(self, enabled):
428        SDL_SetWindowResizable(self._win, 1 if enabled else 0)
429
430    @property
431    def borderless(self):
432        """ Add or remove the border from the actual window.
433
434        .. note:: You can't change the border state of a fullscreen window.
435        """
436        return SDL_GetWindowFlags(self._win) & _SDL_WINDOW_BORDERLESS != 0
437
438    @borderless.setter
439    def borderless(self, enabled):
440        SDL_SetWindowBordered(self._win, 1 if enabled else 0)
441
442    def set_icon(self, surface):
443        """ Set the icon for the window.
444
445        :param pygame.Surface surface: A Surface to use as the icon.
446        """
447        if not pgSurface_Check(surface):
448            raise TypeError('surface must be a Surface object')
449        SDL_SetWindowIcon(self._win, pgSurface_AsSurface(surface))
450
451    @property
452    def id(self):
453        """ A unique window ID. *Read-only*.
454
455        :rtype: int
456        """
457        return SDL_GetWindowID(self._win)
458
459    @property
460    def size(self):
461        """ The size of the window's client area."""
462        cdef int w, h
463        SDL_GetWindowSize(self._win, &w, &h)
464        return (w, h)
465
466    @size.setter
467    def size(self, size):
468        SDL_SetWindowSize(self._win, size[0], size[1])
469
470    @property
471    def position(self):
472        """ Window's screen coordinates, or WINDOWPOS_CENTERED or WINDOWPOS_UNDEFINED"""
473        cdef int x, y
474        SDL_GetWindowPosition(self._win, &x, &y)
475        return (x, y)
476
477    @position.setter
478    def position(self, position):
479        cdef int x, y
480        if position == WINDOWPOS_UNDEFINED:
481            x, y = WINDOWPOS_UNDEFINED, WINDOWPOS_UNDEFINED
482        elif position == WINDOWPOS_CENTERED:
483            x, y = WINDOWPOS_CENTERED, WINDOWPOS_CENTERED
484        else:
485            x, y = position
486        SDL_SetWindowPosition(self._win, x, y)
487
488    @property
489    def opacity(self):
490        """ Window opacity. It ranges between 0.0 (fully transparent)
491        and 1.0 (fully opaque)."""
492        cdef float opacity
493        if SDL_GetWindowOpacity(self._win, &opacity):
494            raise error()
495        return opacity
496
497    @opacity.setter
498    def opacity(self, opacity):
499        if SDL_SetWindowOpacity(self._win, opacity):
500            raise error()
501
502    @property
503    def brightness(self):
504        """ The brightness (gamma multiplier) for the display that owns a given window.
505        0.0 is completely dark and 1.0 is normal brightness."""
506        return SDL_GetWindowBrightness(self._win)
507
508    @brightness.setter
509    def brightness(self, float value):
510        if SDL_SetWindowBrightness(self._win, value):
511            raise error()
512
513    @property
514    def display_index(self):
515        """ The index of the display associated with the window. *Read-only*.
516
517        :rtype: int
518        """
519        cdef int index = SDL_GetWindowDisplayIndex(self._win)
520        if index < 0:
521            raise error()
522        return index
523
524    def set_modal_for(self, Window parent):
525        """set the window as a modal for a parent window
526        This function is only supported on X11."""
527        if SDL_SetWindowModalFor(self._win, parent._win):
528            raise error()
529
530    def __dealloc__(self):
531        if self._is_borrowed:
532            return
533        self.destroy()
534
535cdef Uint32 format_from_depth(int depth):
536    cdef Uint32 Rmask, Gmask, Bmask, Amask
537    if depth == 16:
538        Rmask = 0xF << 8
539        Gmask = 0xF << 4
540        Bmask = 0xF
541        Amask = 0xF << 12
542    elif depth in (0, 32):
543        Rmask = 0xFF << 16
544        Gmask = 0xFF << 8
545        Bmask = 0xFF
546        Amask = 0xFF << 24
547    else:
548        raise ValueError("no standard masks exist for given bitdepth with alpha")
549    return SDL_MasksToPixelFormatEnum(depth,
550                                      Rmask, Gmask, Bmask, Amask)
551
552
553cdef class Texture:
554    def __cinit__(self):
555        cdef Uint8[4] defaultColor = [255, 255, 255, 255]
556        self._color = pgColor_NewLength(defaultColor, 3)
557
558    def __init__(self,
559                 Renderer renderer,
560                 size, int depth=0,
561                 static=False, streaming=False,
562                 target=False):
563        """ Create an empty texture.
564
565        :param Renderer renderer: Rendering context for the texture.
566        :param tuple size: The width and height of the texture.
567        :param int depth: The pixel format (0 to use the default).
568
569        One of ``static``, ``streaming``, or ``target`` can be set
570        to ``True``. If all are ``False``, then ``static`` is used.
571
572        :param bool static: Changes rarely, not lockable.
573        :param bool streaming: Changes frequently, lockable.
574        :param bool target: Can be used as a render target.
575        """
576        # https://wiki.libsdl.org/SDL_CreateTexture
577        # TODO: masks
578        cdef Uint32 format
579        try:
580            format = format_from_depth(depth)
581        except ValueError as e:
582            raise e
583
584        cdef int width, height
585        if len(size) != 2:
586            raise ValueError('size must have two elements')
587        width, height = size[0], size[1]
588        if width <= 0 or height <= 0:
589            raise ValueError('size must contain two positive values')
590
591        cdef int access
592        if static:
593            if streaming or target:
594                raise ValueError('only one of static, streaming, or target can be true')
595            access = _SDL_TEXTUREACCESS_STATIC
596        elif streaming:
597            if static or target:
598                raise ValueError('only one of static, streaming, or target can be true')
599            access = _SDL_TEXTUREACCESS_STREAMING
600        elif target:
601            if streaming or static:
602                raise ValueError('only one of static, streaming, or target can be true')
603            access = _SDL_TEXTUREACCESS_TARGET
604        else:
605            # Create static texture by default.
606            access = _SDL_TEXTUREACCESS_STATIC
607
608        self.renderer = renderer
609        cdef SDL_Renderer* _renderer = renderer._renderer
610        self._tex = SDL_CreateTexture(_renderer,
611                                      format,
612                                      access,
613                                      width, height)
614        if not self._tex:
615            raise error()
616        self.width, self.height = width, height
617
618    @staticmethod
619    def from_surface(Renderer renderer, surface):
620        """ Create a texture from an existing surface.
621
622        :param Renderer renderer: Rendering context for the texture.
623        :param pygame.Surface surface: The surface to create a texture from.
624        """
625        # https://wiki.libsdl.org/SDL_CreateTextureFromSurface
626        if not pgSurface_Check(surface):
627            raise TypeError('2nd argument must be a surface')
628        cdef Texture self = Texture.__new__(Texture)
629        self.renderer = renderer
630        cdef SDL_Renderer* _renderer = renderer._renderer
631        cdef SDL_Surface *surf_ptr = pgSurface_AsSurface(surface)
632        self._tex = SDL_CreateTextureFromSurface(_renderer,
633                                                 surf_ptr)
634        if not self._tex:
635            raise error()
636        self.width = surface.get_width()
637        self.height = surface.get_height()
638        return self
639
640    def __dealloc__(self):
641        if self._tex:
642            SDL_DestroyTexture(self._tex)
643
644    @property
645    def alpha(self):
646        # https://wiki.libsdl.org/SDL_GetTextureAlphaMod
647        cdef Uint8 alpha
648        res = SDL_GetTextureAlphaMod(self._tex, &alpha)
649        if res < 0:
650            raise error()
651
652        return alpha
653
654    @alpha.setter
655    def alpha(self, Uint8 new_value):
656        # https://wiki.libsdl.org/SDL_SetTextureAlphaMod
657        res = SDL_SetTextureAlphaMod(self._tex, new_value)
658        if res < 0:
659            raise error()
660
661    @property
662    def blend_mode(self):
663        # https://wiki.libsdl.org/SDL_GetTextureBlendMode
664        cdef SDL_BlendMode blendMode
665        res = SDL_GetTextureBlendMode(self._tex, &blendMode)
666        if res < 0:
667            raise error()
668
669        return blendMode
670
671    @blend_mode.setter
672    def blend_mode(self, blendMode):
673        # https://wiki.libsdl.org/SDL_SetTextureBlendMode
674        res = SDL_SetTextureBlendMode(self._tex, blendMode)
675        if res < 0:
676            raise error()
677
678    @property
679    def color(self):
680        # https://wiki.libsdl.org/SDL_GetTextureColorMod
681        res = SDL_GetTextureColorMod(self._tex,
682            &self._color.data[0],
683            &self._color.data[1],
684            &self._color.data[2])
685        if res < 0:
686            raise error()
687
688        return self._color
689
690    @color.setter
691    def color(self, new_value):
692        # https://wiki.libsdl.org/SDL_SetTextureColorMod
693        res = SDL_SetTextureColorMod(self._tex,
694                                     new_value[0],
695                                     new_value[1],
696                                     new_value[2])
697        if res < 0:
698            raise error()
699
700    def get_rect(self, **kwargs):
701        """ Get the rectangular area of the texture.
702        like surface.get_rect(), returns a new rectangle covering the entire surface.
703        This rectangle will always start at 0, 0 with a width. and height the same size as the texture.
704        """
705        rect = pgRect_New4(0, 0, self.width, self.height)
706        for key in kwargs:
707            setattr(rect, key, kwargs[key])
708
709        return rect
710
711    cdef draw_internal(self, SDL_Rect *csrcrect, SDL_Rect *cdstrect, float angle=0, SDL_Point *originptr=NULL,
712                       bint flipX=False, bint flipY=False):
713        cdef int flip = SDL_FLIP_NONE
714        if flipX:
715            flip |= SDL_FLIP_HORIZONTAL
716        if flipY:
717            flip |= SDL_FLIP_VERTICAL
718
719        res = SDL_RenderCopyEx(self.renderer._renderer, self._tex, csrcrect, cdstrect,
720                               angle, originptr, <SDL_RendererFlip>flip)
721        if res < 0:
722            raise error()
723
724    cpdef void draw(self, srcrect=None, dstrect=None, float angle=0, origin=None,
725                    bint flipX=False, bint flipY=False):
726        """ Copy a portion of the texture to the rendering target.
727
728        :param srcrect: source rectangle on the texture, or None for the entire texture.
729        :param dstrect: destination rectangle or position on the render target, or None for entire target.
730                        The texture is stretched to fill dstrect.
731        :param float angle: angle (in degrees) to rotate dstrect around (clockwise).
732        :param origin: point around which dstrect will be rotated.
733                       If None, it will equal the center: (dstrect.w/2, dstrect.h/2).
734        :param bool flipX: flip horizontally.
735        :param bool flipY: flip vertically.
736        """
737        cdef SDL_Rect src, dst
738        cdef SDL_Rect *csrcrect = NULL
739        cdef SDL_Rect *cdstrect = NULL
740        cdef SDL_Point corigin
741        cdef SDL_Point *originptr
742
743        if srcrect is not None:
744            csrcrect = pgRect_FromObject(srcrect, &src)
745            if not csrcrect:
746                raise TypeError("the argument is not a rectangle or None")
747
748        if dstrect is not None:
749            cdstrect = pgRect_FromObject(dstrect, &dst)
750            if cdstrect == NULL:
751                if len(dstrect) == 2:
752                    dst.x = dstrect[0]
753                    dst.y = dstrect[1]
754                    dst.w = self.width
755                    dst.h = self.height
756                    cdstrect = &dst
757                else:
758                    raise TypeError('dstrect must be a position, rect, or None')
759
760        if origin:
761            originptr = &corigin
762            corigin.x = origin[0]
763            corigin.y = origin[1]
764        else:
765            originptr = NULL
766
767        self.draw_internal(csrcrect, cdstrect, angle, originptr,
768                           flipX, flipY)
769
770    def update(self, surface, area=None):
771        # https://wiki.libsdl.org/SDL_UpdateTexture
772        # Should it accept a raw pixel data array too?
773        """ Update the texture with Surface.
774        This is a fairly slow function, intended for use with static textures that do not change often.
775
776        If the texture is intended to be updated often,
777        it is preferred to create the texture as streaming and use the locking functions.
778
779        While this function will work with streaming textures,
780        for optimization reasons you may not get the pixels back if you lock the texture afterward.
781
782        :param surface: source Surface.
783        """
784
785        if not pgSurface_Check(surface):
786            raise TypeError("update source should be a Surface.")
787
788        cdef SDL_Rect rect
789        cdef SDL_Rect *rectptr = pgRect_FromObject(area, &rect)
790        cdef SDL_Surface *surf = pgSurface_AsSurface(surface)
791
792        if rectptr == NULL and area is not None:
793            raise TypeError('area must be a rectangle or None')
794
795        res = SDL_UpdateTexture(self._tex, rectptr, surf.pixels, surf.pitch)
796        if res < 0:
797            raise error()
798
799cdef class Image:
800
801    def __cinit__(self):
802        self.angle = 0
803        self._origin.x = 0
804        self._origin.y = 0
805        self._originptr = NULL
806        self.flipX = False
807        self.flipY = False
808
809        cdef Uint8[4] defaultColor = [255, 255, 255, 255]
810        self._color = pgColor_NewLength(defaultColor, 3)
811        self.alpha = 255
812
813    def __init__(self, textureOrImage, srcrect=None):
814        cdef SDL_Rect temp
815        cdef SDL_Rect *rectptr
816
817        if isinstance(textureOrImage, Image):
818            self.texture = textureOrImage.texture
819            self.srcrect = pgRect_New(&(<Rect>textureOrImage.srcrect).r)
820        else:
821            self.texture = textureOrImage
822            self.srcrect = textureOrImage.get_rect()
823        self.blend_mode = textureOrImage.blend_mode
824
825        if srcrect is not None:
826            rectptr = pgRect_FromObject(srcrect, &temp)
827            if rectptr == NULL:
828                raise TypeError('srcrect must be None or a rectangle')
829            temp.x = rectptr.x
830            temp.y = rectptr.y
831            temp.w = rectptr.w
832            temp.h = rectptr.h
833
834            if temp.x < 0 or temp.y < 0 or \
835                temp.w < 0 or temp.h < 0 or \
836                temp.x + temp.w > self.srcrect.w or \
837                temp.y + temp.h > self.srcrect.h:
838                raise ValueError('rect values are out of range')
839            temp.x += self.srcrect.x
840            temp.y += self.srcrect.y
841            self.srcrect = pgRect_New(&temp)
842
843    @property
844    def color(self):
845        return self._color
846
847    @color.setter
848    def color(self, new_color):
849        self._color[:3] = new_color[:3]
850
851    @property
852    def origin(self):
853        if self._originptr == NULL:
854            return None
855        else:
856            return (self._origin.x, self._origin.y)
857
858    @origin.setter
859    def origin(self, new_origin):
860        if new_origin:
861            self._origin.x = <int>new_origin[0]
862            self._origin.y = <int>new_origin[1]
863            self._originptr = &self._origin
864        else:
865            self._originptr = NULL
866
867    def get_rect(self):
868        return pgRect_New(&self.srcrect.r)
869
870    cpdef void draw(self, srcrect=None, dstrect=None):
871        """ Copy a portion of the image to the rendering target.
872
873        :param srcrect: source rectangle specifying a sub-image, or None for the entire image.
874        :param dstrect: destination rectangle or position on the render target, or None for entire target.
875                        The image is stretched to fill dstrect.
876        """
877        cdef SDL_Rect src
878        cdef SDL_Rect dst
879        cdef SDL_Rect *csrcrect = NULL
880        cdef SDL_Rect *cdstrect = NULL
881        cdef SDL_Rect *rectptr
882
883        if srcrect is None:
884            csrcrect = &self.srcrect.r
885        else:
886            if pgRect_Check(srcrect):
887                src = (<Rect>srcrect).r
888            else:
889
890                rectptr = pgRect_FromObject(srcrect, &src)
891                if rectptr == NULL:
892                    raise TypeError('srcrect must be a rect or None')
893                src.x = rectptr.x
894                src.y = rectptr.y
895                src.w = rectptr.w
896                src.h = rectptr.h
897
898            src.x += self.srcrect.x
899            src.y += self.srcrect.y
900            csrcrect = &src
901
902        if dstrect is not None:
903            cdstrect = pgRect_FromObject(dstrect, &dst)
904            if cdstrect == NULL:
905                if len(dstrect) == 2:
906                    dst.x = dstrect[0]
907                    dst.y = dstrect[1]
908                    dst.w = self.srcrect.w
909                    dst.h = self.srcrect.h
910                    cdstrect = &dst
911                else:
912                    raise TypeError('dstrect must be a position, rect, or None')
913
914        self.texture.color = self._color
915        self.texture.alpha = self.alpha
916        self.texture.blend_mode = self.blend_mode
917
918        self.texture.draw_internal(csrcrect, cdstrect, self.angle,
919                                   self._originptr, self.flipX, self.flipY)
920
921
922cdef class Renderer:
923
924    @classmethod
925    def from_window(cls, Window window):
926        cdef Renderer self = cls.__new__(cls)
927        self._win = window
928        if window._is_borrowed:
929            self._is_borrowed=1
930        else:
931            raise error()
932        if not self._win:
933            raise error()
934
935        self._renderer =  SDL_GetRenderer(self._win._win)
936        if not self._renderer:
937            raise error()
938
939        cdef Uint8[4] defaultColor = [255, 255, 255, 255]
940        self._draw_color = pgColor_NewLength(defaultColor, 4)
941        self._target = None
942        return self
943
944    def __init__(self, Window window, int index=-1,
945                 int accelerated=-1, bint vsync=False,
946                 bint target_texture=False):
947        """ Create a 2D rendering context for a window.
948
949        :param Window window: where rendering is displayed.
950        :param int index: index of rendering driver to initialize,
951                          or -1 to init the first supporting requested options.
952        :param int accelerated: if 1, the renderer uses hardware acceleration.
953                                if 0, the renderer is a software fallback.
954                                -1 gives precedence to renderers using hardware acceleration.
955        :param bool vsync: .present() is synchronized with the refresh rate.
956        :param bool target_texture: the renderer supports rendering to texture.
957        """
958        # https://wiki.libsdl.org/SDL_CreateRenderer
959        # https://wiki.libsdl.org/SDL_RendererFlags
960        flags = 0
961        if accelerated >= 0:
962            flags |= _SDL_RENDERER_ACCELERATED if accelerated else _SDL_RENDERER_SOFTWARE
963        if vsync:
964            flags |= _SDL_RENDERER_PRESENTVSYNC
965        if target_texture:
966            flags |= _SDL_RENDERER_TARGETTEXTURE
967
968        self._renderer = SDL_CreateRenderer(window._win, index, flags)
969        if not self._renderer:
970            raise error()
971
972        cdef Uint8[4] defaultColor = [255, 255, 255, 255]
973        self._draw_color = pgColor_NewLength(defaultColor, 4)
974        self._target = None
975        self._win = window
976        self._is_borrowed=0
977
978    def __dealloc__(self):
979        if self._is_borrowed:
980            return
981        if self._renderer:
982            SDL_DestroyRenderer(self._renderer)
983
984    @property
985    def draw_blend_mode(self):
986        # https://wiki.libsdl.org/SDL_GetRenderDrawBlendMode
987        cdef SDL_BlendMode blendMode
988        res = SDL_GetRenderDrawBlendMode(self._renderer, &blendMode)
989        if res < 0:
990            raise error()
991
992        return blendMode
993
994    @draw_blend_mode.setter
995    def draw_blend_mode(self, blendMode):
996        # https://wiki.libsdl.org/SDL_SetRenderDrawBlendMode
997        res = SDL_SetRenderDrawBlendMode(self._renderer, blendMode)
998        if res < 0:
999            raise error()
1000
1001    @property
1002    def draw_color(self):
1003        """ Color used by the drawing functions.
1004        """
1005        return self._draw_color
1006
1007    @draw_color.setter
1008    def draw_color(self, new_value):
1009        """ color used by the drawing functions.
1010        """
1011        # https://wiki.libsdl.org/SDL_SetRenderDrawColor
1012        self._draw_color[:] = new_value
1013        res = SDL_SetRenderDrawColor(self._renderer,
1014                                     new_value[0],
1015                                     new_value[1],
1016                                     new_value[2],
1017                                     new_value[3])
1018        if res < 0:
1019            raise error()
1020
1021    def clear(self):
1022        """ Clear the current rendering target with the drawing color.
1023        """
1024        # https://wiki.libsdl.org/SDL_RenderClear
1025        res = SDL_RenderClear(self._renderer)
1026        if res < 0:
1027            raise error()
1028
1029    def present(self):
1030        """ Present the composed backbuffer to the screen.
1031
1032        Updates the screen with any rendering performed since previous call.
1033        """
1034        # https://wiki.libsdl.org/SDL_RenderPresent
1035        SDL_RenderPresent(self._renderer)
1036
1037    cpdef get_viewport(self):
1038        """ Returns the drawing area on the target.
1039
1040        :rtype: pygame.Rect
1041        """
1042        # https://wiki.libsdl.org/SDL_RenderGetViewport
1043        cdef SDL_Rect rect
1044        SDL_RenderGetViewport(self._renderer, &rect)
1045        return pgRect_New(&rect)
1046
1047    @property
1048    def logical_size(self):
1049        cdef int w
1050        cdef int h
1051        SDL_RenderGetLogicalSize(self._renderer, &w, &h)
1052        return (w, h)
1053
1054    @logical_size.setter
1055    def logical_size(self, size):
1056        cdef int w = size[0]
1057        cdef int h = size[1]
1058        if (SDL_RenderSetLogicalSize(self._renderer, w, h) != 0):
1059            raise error()
1060
1061    @property
1062    def scale(self):
1063        cdef float x
1064        cdef float y
1065        SDL_RenderGetScale(self._renderer, &x, &y);
1066        return (x, y)
1067
1068    @scale.setter
1069    def scale(self, scale):
1070        cdef float x = scale[0]
1071        cdef float y = scale[1]
1072        if (SDL_RenderSetScale(self._renderer, x, y) != 0):
1073            raise error()
1074
1075    # TODO ifdef
1076    # def is_integer_scale(self):
1077    #     return SDL_RenderGetIntegerScale(self._renderer)
1078
1079    def set_viewport(self, area):
1080        """ Set the drawing area on the target.
1081        If this is set to ``None``, the entire target will be used.
1082
1083        :param area: A ``pygame.Rect`` or tuple representing the
1084                     drawing area on the target, or None.
1085        """
1086        # https://wiki.libsdl.org/SDL_RenderSetViewport
1087        if area is None:
1088            if SDL_RenderSetViewport(self._renderer, NULL) < 0:
1089                raise error()
1090            return
1091
1092        cdef SDL_Rect tmprect
1093        cdef SDL_Rect *rectptr = pgRect_FromObject(area, &tmprect)
1094        if rectptr == NULL:
1095            raise TypeError('expected a rectangle')
1096
1097        if SDL_RenderSetViewport(self._renderer, rectptr) < 0:
1098            raise error()
1099
1100
1101    @property
1102    def target(self):
1103        """ The current render target. Set to ``None`` for the default target.
1104
1105        :rtype: Texture, None
1106        """
1107        # https://wiki.libsdl.org/SDL_GetRenderTarget
1108        return self._target
1109
1110    @target.setter
1111    def target(self, newtarget):
1112        # https://wiki.libsdl.org/SDL_SetRenderTarget
1113        if newtarget is None:
1114            self._target = None
1115            if SDL_SetRenderTarget(self._renderer, NULL) < 0:
1116                raise error()
1117        elif isinstance(newtarget, Texture):
1118            self._target = newtarget
1119            if SDL_SetRenderTarget(self._renderer,
1120                                   self._target._tex) < 0:
1121                raise error()
1122        else:
1123            raise TypeError('target must be a Texture or None')
1124
1125    cpdef object blit(self, object source, Rect dest=None, Rect area=None, int special_flags=0):
1126        """ Only for compatibility.
1127        Textures created by different Renderers cannot shared with each other!
1128        :param source: A Texture or Image to draw.
1129        :param dest: destination on the render target.
1130        :param area: the portion of source texture.
1131        :param special_flags: have no effect at this moment.
1132        """
1133        if isinstance(source, Texture):
1134            (<Texture>source).draw(area, dest)
1135        elif isinstance(source, Image):
1136            (<Image>source).draw(area, dest)
1137        elif not hasattr(source, 'draw'):
1138            raise TypeError('source must be drawable')
1139        else:
1140            source.draw(area, dest)
1141
1142        if not dest:
1143            return self.get_viewport()
1144        return dest
1145
1146    def draw_line(self, p1, p2):
1147        # https://wiki.libsdl.org/SDL_RenderDrawLine
1148        res = SDL_RenderDrawLine(self._renderer,
1149                                 p1[0], p1[1],
1150                                 p2[0], p2[1])
1151        if res < 0:
1152            raise error()
1153
1154    def draw_point(self, point):
1155        # https://wiki.libsdl.org/SDL_RenderDrawPoint
1156        res = SDL_RenderDrawPoint(self._renderer,
1157                                  point[0], point[1])
1158        if res < 0:
1159            raise error()
1160
1161    def draw_rect(self, rect):
1162        # https://wiki.libsdl.org/SDL_RenderDrawRect
1163        cdef SDL_Rect _rect
1164        cdef SDL_Rect *rectptr = pgRect_FromObject(rect, &_rect)
1165        if rectptr == NULL:
1166            raise TypeError('expected a rectangle')
1167        res = SDL_RenderDrawRect(self._renderer, rectptr)
1168        if res < 0:
1169            raise error()
1170
1171    def fill_rect(self, rect):
1172        # https://wiki.libsdl.org/SDL_RenderFillRect
1173        cdef SDL_Rect _rect
1174        cdef SDL_Rect *rectptr = pgRect_FromObject(rect, &_rect)
1175        if rectptr == NULL:
1176            raise TypeError('expected a rectangle')
1177        res = SDL_RenderFillRect(self._renderer, rectptr)
1178
1179        if res < 0:
1180            raise error()
1181
1182    def to_surface(self, surface=None, area=None):
1183        # https://wiki.libsdl.org/SDL_RenderReadPixels
1184        """
1185            Read pixels from the current rendering target and create a pygame.Surface.
1186            WARNING: This is a very slow operation, and should not be used frequently.
1187
1188        :param surface: A surface to read the pixel data into.
1189                        It must be large enough to fit the area, or ``ValueError`` is
1190                        raised.
1191                        If ``None``, a new surface is returned.
1192        :param area: The area of the screen to read pixels from. The area is
1193                     clipped to fit inside the viewport.
1194                     If ``None``, the entire viewport is used.
1195        """
1196        cdef Uint32 format
1197        cdef SDL_Rect rarea
1198        cdef SDL_Rect tempviewport
1199        cdef SDL_Rect *areaparam
1200        cdef SDL_Surface *surf
1201        cdef SDL_Rect *rectptr
1202
1203        # obtain area to use
1204        if area is not None:
1205
1206            rectptr = pgRect_FromObject(area, &rarea)
1207            if rectptr == NULL:
1208                raise TypeError('area must be None or a rect')
1209
1210            # clip area
1211            SDL_RenderGetViewport(self._renderer, &tempviewport)
1212            SDL_IntersectRect(rectptr, &tempviewport, rectptr)
1213
1214            areaparam = rectptr
1215            rarea.x = rectptr.x
1216            rarea.y = rectptr.y
1217            rarea.w = rectptr.w
1218            rarea.h = rectptr.h
1219        else:
1220            SDL_RenderGetViewport(self._renderer, &rarea)
1221            areaparam = NULL
1222
1223        # prepare surface
1224        if surface is None:
1225            # create a new surface
1226            format = SDL_GetWindowPixelFormat(self._win._win)
1227            if format == SDL_PIXELFORMAT_UNKNOWN:
1228                raise error()
1229
1230            surf = SDL_CreateRGBSurfaceWithFormat(
1231                0,
1232                rarea.w, rarea.h,
1233                SDL_BITSPERPIXEL(format),
1234                format)
1235            if surf == NULL:
1236                raise MemoryError("not enough memory for the surface")
1237
1238            surface = <object>pgSurface_New2(surf, 1)
1239        elif pgSurface_Check(surface):
1240            surf = pgSurface_AsSurface(surface)
1241            if surf.w < rarea.w or surf.h < rarea.h:
1242                raise ValueError("the surface is too small")
1243            format = surf.format.format
1244        else:
1245            raise TypeError("'surface' must be a surface or None")
1246
1247        if SDL_RenderReadPixels(self._renderer,
1248                                areaparam,
1249                                format, surf.pixels, surf.pitch) < 0:
1250            raise error()
1251        return surface
1252