Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get_surface() for Window class #2350

Merged
merged 19 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions buildconfig/stubs/pygame/_sdl2/video.pyi
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import Any, Generator, Iterable, Optional, Tuple, Union, final
from typing import Any, Generator, Iterable, Optional, Tuple, Union

from pygame.color import Color
from pygame.rect import Rect
from pygame.surface import Surface

from .._common import RectValue, ColorValue
from .._common import ColorValue, RectValue

WINDOWPOS_UNDEFINED: int
WINDOWPOS_CENTERED: int
Expand Down
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/_window.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Window:
def minimize(self) -> None: ...
def set_modal_for(self, parent: Window) -> None: ...
def set_icon(self, icon: Surface) -> None: ...
def get_surface(self) -> Surface: ...
def update_from_surface(self) -> None: ...

grab_mouse: bool
grab_keyboard: bool
Expand Down
37 changes: 37 additions & 0 deletions docs/reST/ref/sdl2_video.rst
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,43 @@
Create a Window object that uses the same window data from the :mod:`pygame.display` module, created upon calling
:func:`pygame.display.set_mode`.

.. method:: get_surface

| :sl:`Get the window surface`
| :sg:`get_surface() -> Surface`

Return a reference to the surface associated with the window.

The size of surface will automatically change to fit the window size.

The window surface become invalid when the window is destroyed.

.. seealso:: :func:`update_from_surface`

.. versionadded:: 2.4.0

.. method:: update_from_surface
MyreMylar marked this conversation as resolved.
Show resolved Hide resolved

| :sl:`Update the window surface to the window.`
| :sg:`update_from_surface() -> None`

Update content from the window surface to the window.

Here is an example of using ``get_surface`` and ``update_from_surface``:

.. code-block:: python

win = video.Window()
surf = win.get_surface() # get the window surface

# draw something on the surface
surf.fill((255,0,0))

win.update_from_surface() # update the surface to the window


.. versionadded:: 2.4.0

.. method:: set_windowed

| :sl:`Enable windowed mode (exit fullscreen)`
Expand Down
3 changes: 3 additions & 0 deletions src_c/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ pg_display_quit(PyObject *self, PyObject *_null)

pg_mod_autoquit(IMPPREFIX "event");
pg_mod_autoquit(IMPPREFIX "time");
pg_mod_autoquit(IMPPREFIX "_window");

if (SDL_WasInit(SDL_INIT_VIDEO)) {
SDL_QuitSubSystem(SDL_INIT_VIDEO);
Expand Down Expand Up @@ -251,6 +252,8 @@ pg_display_init(PyObject *self, PyObject *_null)
return NULL;
if (!pg_mod_autoinit(IMPPREFIX "event"))
return NULL;
if (!pg_mod_autoinit(IMPPREFIX "_window"))
return NULL;

Py_RETURN_NONE;
}
Expand Down
2 changes: 2 additions & 0 deletions src_c/doc/sdl2_video_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#define DOC_SDL2_VIDEO_WINDOW_OPACITY "opacity -> float\nGet or set the window opacity, between 0.0 (fully transparent) and 1.0 (fully opaque)"
#define DOC_SDL2_VIDEO_WINDOW_DISPLAYINDEX "get_display_index -> int\nGet the index of the display that owns the window (**read-only**)"
#define DOC_SDL2_VIDEO_WINDOW_FROMDISPLAYMODULE "from_display_module() -> Window\nCreate a Window object using window data from display module"
#define DOC_SDL2_VIDEO_WINDOW_GETSURFACE "get_surface() -> Surface\nGet the window surface"
#define DOC_SDL2_VIDEO_WINDOW_UPDATEFROMSURFACE "update_from_surface() -> None\nUpdate the window surface to the window."
#define DOC_SDL2_VIDEO_WINDOW_SETWINDOWED "set_windowed() -> None\nEnable windowed mode (exit fullscreen)"
#define DOC_SDL2_VIDEO_WINDOW_SETFULLSCREEN "set_fullscreen(desktop=False) -> None\nEnter fullscreen"
#define DOC_SDL2_VIDEO_WINDOW_DESTROY "destroy() -> None\nDestroy the window"
Expand Down
1 change: 1 addition & 0 deletions src_c/include/_pygame.h
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ typedef struct pgColorObject pgColorObject;
typedef struct {
PyObject_HEAD SDL_Window *_win;
SDL_bool _is_borrowed;
pgSurfaceObject *surf;
} pgWindowObject;

#ifndef PYGAMEAPI_WINDOW_INTERNAL
Expand Down
112 changes: 112 additions & 0 deletions src_c/window.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,89 @@ window_destroy(pgWindowObject *self, PyObject *_null)
SDL_DestroyWindow(self->_win);
self->_win = NULL;
}
if (self->surf) {
// Set the internal surface to NULL to make pygame surface invalid
// since this surface will be deallocated by SDL when the window is
// destroyed.
self->surf->surf = NULL;
yunline marked this conversation as resolved.
Show resolved Hide resolved

Py_DECREF(self->surf);
self->surf = NULL;
}
Py_RETURN_NONE;
}

static PyObject *
window_get_surface(pgWindowObject *self)
{
PyObject *surf = NULL;
SDL_Surface *_surf;

if (self->_is_borrowed) {
surf = (PyObject *)pg_GetDefaultWindowSurface();
if (!surf) {
return RAISE(pgExc_SDLError,
"display.set_mode has not been called yet.");
}
Py_INCREF(surf);
return surf;
}

_surf = SDL_GetWindowSurface(self->_win);
if (!_surf) {
return RAISE(pgExc_SDLError, SDL_GetError());
}
if (self->surf == NULL) {
self->surf = pgSurface_New2(_surf, SDL_FALSE);
if (!self->surf)
return NULL;
}
self->surf->surf = _surf;
Py_INCREF(self->surf);
return (PyObject *)self->surf;
}

static PyObject *
window_update_from_surface(pgWindowObject *self)
{
int result;

Py_BEGIN_ALLOW_THREADS;
result = SDL_UpdateWindowSurface(self->_win);
Py_END_ALLOW_THREADS;
if (result)
return RAISE(pgExc_SDLError, SDL_GetError());
Py_RETURN_NONE;
}

// Callback function for surface auto resize
static int SDLCALL
_resize_event_watch(void *userdata, SDL_Event *event)
{
pgWindowObject *event_window_pg;
SDL_Window *event_window;
if ((event->type != SDL_WINDOWEVENT))
return 0;
if (event->window.event != SDL_WINDOWEVENT_SIZE_CHANGED)
return 0;
event_window = SDL_GetWindowFromID(event->window.windowID);
event_window_pg = SDL_GetWindowData(event_window, "pg_window");

if (!event_window_pg)
return 0;

if (event_window_pg->_is_borrowed) {
// have been handled by event watch in display.c
return 0;
}

if (!event_window_pg->surf)
return 0;

event_window_pg->surf->surf = SDL_GetWindowSurface(event_window);
return 0;
}

static PyObject *
window_set_windowed(pgWindowObject *self, PyObject *_null)
{
Expand Down Expand Up @@ -615,6 +695,15 @@ window_dealloc(pgWindowObject *self, PyObject *_null)
SDL_SetWindowData(self->_win, "pg_window", NULL);
}
}
if (self->surf) {
// Set the internal surface to NULL to make pygame surface invalid
// since this surface will be deallocated by SDL when the window is
// destroyed.
self->surf->surf = NULL;

Py_DECREF(self->surf);
}

Py_TYPE(self)->tp_free(self);
}

Expand Down Expand Up @@ -815,6 +904,7 @@ window_init(pgWindowObject *self, PyObject *args, PyObject *kwargs)
}
self->_win = _win;
self->_is_borrowed = SDL_FALSE;
self->surf = NULL;

SDL_SetWindowData(_win, "pg_window", self);

Expand Down Expand Up @@ -880,6 +970,20 @@ window_repr(pgWindowObject *self)
return PyUnicode_FromFormat("<Window(title='%s', id=%d)>", title, win_id);
}

static PyObject *
_window_internal_mod_init(PyObject *self, PyObject *_null)
{
SDL_AddEventWatch(_resize_event_watch, NULL);
Py_RETURN_NONE;
}

static PyObject *
_window_internal_mod_quit(PyObject *self, PyObject *_null)
{
SDL_DelEventWatch(_resize_event_watch, NULL);
Py_RETURN_NONE;
}

static PyMethodDef window_methods[] = {
{"destroy", (PyCFunction)window_destroy, METH_NOARGS,
DOC_SDL2_VIDEO_WINDOW_DESTROY},
Expand All @@ -903,6 +1007,10 @@ static PyMethodDef window_methods[] = {
DOC_SDL2_VIDEO_WINDOW_SETMODALFOR},
{"set_icon", (PyCFunction)window_set_icon, METH_O,
DOC_SDL2_VIDEO_WINDOW_SETICON},
{"update_from_surface", (PyCFunction)window_update_from_surface,
METH_NOARGS, DOC_SDL2_VIDEO_WINDOW_UPDATEFROMSURFACE},
{"get_surface", (PyCFunction)window_get_surface, METH_NOARGS,
DOC_SDL2_VIDEO_WINDOW_GETSURFACE},
{"from_display_module", (PyCFunction)window_from_display_module,
METH_CLASS | METH_NOARGS, DOC_SDL2_VIDEO_WINDOW_FROMDISPLAYMODULE},
{NULL, NULL, 0, NULL}};
Expand Down Expand Up @@ -959,6 +1067,10 @@ static PyTypeObject pgWindow_Type = {
static PyMethodDef _window_methods[] = {
{"get_grabbed_window", (PyCFunction)get_grabbed_window, METH_NOARGS,
DOC_SDL2_VIDEO_GETGRABBEDWINDOW},
{"_internal_mod_init", (PyCFunction)_window_internal_mod_init, METH_NOARGS,
"auto initialize for _window module"},
{"_internal_mod_quit", (PyCFunction)_window_internal_mod_quit, METH_NOARGS,
"auto quit for _window module"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes these hooks look good, but I think they should be called from the display init/quit hook instead of the base pygame init/quit function (like how event and time are handled right now). This is so that if someone only does pygame.display.init() instead of the full pygame.init() we would still want the window hooks to run, as they are related

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

{NULL, NULL, 0, NULL}};

MODINIT_DEFINE(_window)
Expand Down
41 changes: 41 additions & 0 deletions test/window_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,47 @@ def test_from_display_module(self):
pygame.display.quit()
pygame.init()

def test_window_surface(self):
win = Window(size=(640, 480))
surf = win.get_surface()

self.assertIsInstance(surf, pygame.Surface)

# test auto resize
self.assertTupleEqual(win.size, surf.get_size())
win.size = (100, 100)
MyreMylar marked this conversation as resolved.
Show resolved Hide resolved
self.assertTupleEqual(win.size, surf.get_size())
win.size = (1280, 720)
self.assertTupleEqual(win.size, surf.get_size())

# window surface should be invalid after the window is destroyed
win.destroy()
self.assertRaises(pygame.error, lambda: surf.fill((0, 0, 0)))

def test_window_surface_with_display_module(self):
# get_surface() should raise an error if the set_mode() is not called.
pygame.display.set_mode((640, 480))
win1 = Window.from_display_module()
pygame.display.quit()
pygame.init()
self.assertRaises(pygame.error, lambda: win1.get_surface())

# the surface returned by get_surface() should be
# the surface returned by set_mode()
surf1 = pygame.display.set_mode((640, 480))
win2 = Window.from_display_module()
surf2 = win2.get_surface()
self.assertIs(surf1, surf2)

def test_window_update_from_surface(self):
win = Window(size=(640, 480))
surf = win.get_surface()
surf.fill((255, 0, 0))

self.assertRaises(TypeError, lambda: win.update_from_surface("an argument"))
self.assertIs(win.update_from_surface(), None)
win.destroy()

def tearDown(self):
self.win.destroy()

Expand Down
Loading