Skip to content

Commit 65d6bf3

Browse files
committed
Port more methods to SDL windows.
1 parent 9073a8e commit 65d6bf3

File tree

4 files changed

+246
-25
lines changed

4 files changed

+246
-25
lines changed

tcod/render.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
"""Handle the rendering of libtcod's tilesets.
2+
3+
Example::
4+
5+
tileset = tcod.tileset.load_tilsheet("dejavu16x16_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD)
6+
console = tcod.Console(20, 8)
7+
console.print(0, 0, "Hello World")
8+
sdl_window = tcod.sdl.video.new_window(
9+
console.width * tileset.tile_width,
10+
console.height * tileset.tile_height,
11+
flags=tcod.lib.SDL_WINDOW_RESIZABLE,
12+
)
13+
sdl_renderer = tcod.sdl.render.new_renderer(sdl_window, target_textures=True)
14+
atlas = tcod.render.SDLTilesetAtlas(sdl_renderer, tileset)
15+
console_render = tcod.render.SDLConsoleRender(atlas)
16+
while True:
17+
sdl_renderer.copy(console_render.render(console))
18+
sdl_renderer.present()
19+
for event in tcod.event.wait():
20+
if isinstance(event, tcod.event.Quit):
21+
raise SystemExit()
22+
"""
23+
124
from __future__ import annotations
225

326
from typing import Optional

tcod/sdl/__init__.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

33
import logging
4-
from typing import Any
4+
from typing import Any, Callable, Tuple, TypeVar
55

66
from tcod.loader import ffi, lib
77

8+
T = TypeVar("T")
9+
810
logger = logging.getLogger(__name__)
911

1012
_LOG_PRIORITY = {
@@ -44,3 +46,27 @@ def _check_p(result: Any) -> Any:
4446

4547
if lib._sdl_log_output_function:
4648
lib.SDL_LogSetOutputFunction(lib._sdl_log_output_function, ffi.NULL)
49+
50+
51+
def _compiled_version() -> Tuple[int, int, int]:
52+
return int(lib.SDL_MAJOR_VERSION), int(lib.SDL_MINOR_VERSION), int(lib.SDL_PATCHLEVEL)
53+
54+
55+
def _linked_version() -> Tuple[int, int, int]:
56+
sdl_version = ffi.new("SDL_version*")
57+
lib.SDL_GetVersion(sdl_version)
58+
return int(sdl_version.major), int(sdl_version.minor), int(sdl_version.patch)
59+
60+
61+
def _required_version(required: Tuple[int, int, int]) -> Callable[[T], T]:
62+
if not lib: # Read the docs mock object.
63+
return lambda x: x
64+
if required <= _compiled_version():
65+
return lambda x: x
66+
67+
def replacement(*_args: Any, **_kwargs: Any) -> Any:
68+
raise RuntimeError(
69+
f"This feature requires SDL version {required}, but tcod was compiled with version {_compiled_version()}"
70+
)
71+
72+
return lambda x: replacement # type: ignore[return-value]

tcod/sdl/sys.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88

99

1010
class Subsystem(enum.IntFlag):
11-
TIMER = getattr(lib, "SDL_INIT_TIMER", 0x00000001)
12-
AUDIO = getattr(lib, "SDL_INIT_AUDIO", 0x00000010)
13-
VIDEO = getattr(lib, "SDL_INIT_VIDEO", 0x00000020)
14-
JOYSTICK = getattr(lib, "SDL_INIT_JOYSTICK", 0x00000200)
15-
HAPTIC = getattr(lib, "SDL_INIT_HAPTIC", 0x00001000)
16-
GAMECONTROLLER = getattr(lib, "SDL_INIT_GAMECONTROLLER", 0x00002000)
17-
EVENTS = getattr(lib, "SDL_INIT_EVENTS", 0x00004000)
18-
SENSOR = getattr(lib, "SDL_INIT_SENSOR", 0x00008000)
19-
EVERYTHING = getattr(lib, "SDL_INIT_EVERYTHING", 0)
11+
TIMER = lib.SDL_INIT_TIMER or 0x00000001
12+
AUDIO = lib.SDL_INIT_AUDIO or 0x00000010
13+
VIDEO = lib.SDL_INIT_VIDEO or 0x00000020
14+
JOYSTICK = lib.SDL_INIT_JOYSTICK or 0x00000200
15+
HAPTIC = lib.SDL_INIT_HAPTIC or 0x00001000
16+
GAMECONTROLLER = lib.SDL_INIT_GAMECONTROLLER or 0x00002000
17+
EVENTS = lib.SDL_INIT_EVENTS or 0x00004000
18+
SENSOR = getattr(lib, "SDL_INIT_SENSOR", None) or 0x00008000 # SDL >= 2.0.9
19+
EVERYTHING = lib.SDL_INIT_EVERYTHING or 0
2020

2121

2222
def init(flags: int = Subsystem.EVERYTHING) -> None:

tcod/sdl/video.py

Lines changed: 187 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,60 @@
55
"""
66
from __future__ import annotations
77

8+
import enum
89
import sys
910
from typing import Any, Optional, Tuple
1011

1112
import numpy as np
1213
from numpy.typing import ArrayLike, NDArray
1314

1415
from tcod.loader import ffi, lib
15-
from tcod.sdl import _check_p
16+
from tcod.sdl import _check, _check_p, _required_version
1617

17-
__all__ = ("Window",)
18+
__all__ = (
19+
"WindowFlags",
20+
"FlashOperation",
21+
"Window",
22+
"new_window",
23+
"get_grabbed_window",
24+
)
25+
26+
27+
class WindowFlags(enum.IntFlag):
28+
"""Bit flags which make up a windows state.
29+
30+
.. seealso::
31+
https://wiki.libsdl.org/SDL_WindowFlags
32+
"""
33+
34+
FULLSCREEN = lib.SDL_WINDOW_FULLSCREEN or 0
35+
FULLSCREEN_DESKTOP = lib.SDL_WINDOW_FULLSCREEN_DESKTOP or 0
36+
OPENGL = lib.SDL_WINDOW_OPENGL or 0
37+
SHOWN = lib.SDL_WINDOW_SHOWN or 0
38+
HIDDEN = lib.SDL_WINDOW_HIDDEN or 0
39+
BORDERLESS = lib.SDL_WINDOW_BORDERLESS or 0
40+
RESIZABLE = lib.SDL_WINDOW_RESIZABLE or 0
41+
MINIMIZED = lib.SDL_WINDOW_MINIMIZED or 0
42+
MAXIMIZED = lib.SDL_WINDOW_MAXIMIZED or 0
43+
MOUSE_GRABBED = lib.SDL_WINDOW_INPUT_GRABBED or 0
44+
INPUT_FOCUS = lib.SDL_WINDOW_INPUT_FOCUS or 0
45+
MOUSE_FOCUS = lib.SDL_WINDOW_MOUSE_FOCUS or 0
46+
FOREIGN = lib.SDL_WINDOW_FOREIGN or 0
47+
ALLOW_HIGHDPI = lib.SDL_WINDOW_ALLOW_HIGHDPI or 0
48+
MOUSE_CAPTURE = lib.SDL_WINDOW_MOUSE_CAPTURE or 0
49+
ALWAYS_ON_TOP = lib.SDL_WINDOW_ALWAYS_ON_TOP or 0
50+
SKIP_TASKBAR = lib.SDL_WINDOW_SKIP_TASKBAR or 0
51+
UTILITY = lib.SDL_WINDOW_UTILITY or 0
52+
TOOLTIP = lib.SDL_WINDOW_TOOLTIP or 0
53+
POPUP_MENU = lib.SDL_WINDOW_POPUP_MENU or 0
54+
VULKAN = lib.SDL_WINDOW_VULKAN or 0
55+
METAL = getattr(lib, "SDL_WINDOW_METAL", None) or 0x20000000 # SDL >= 2.0.14
56+
57+
58+
class FlashOperation(enum.IntEnum):
59+
CANCEL = 0
60+
BRIEFLY = 1
61+
UNTIL_FOCUSED = 2
1862

1963

2064
class _TempSurface:
@@ -67,10 +111,7 @@ def set_icon(self, image: ArrayLike) -> None:
67111

68112
@property
69113
def allow_screen_saver(self) -> bool:
70-
"""If True the operating system is allowed to display a screen saver.
71-
72-
You can set this attribute to enable or disable the screen saver.
73-
"""
114+
"""Get or set if the operating system is allowed to display a screen saver."""
74115
return bool(lib.SDL_IsScreenSaverEnabled(self.p))
75116

76117
@allow_screen_saver.setter
@@ -82,11 +123,10 @@ def allow_screen_saver(self, value: bool) -> None:
82123

83124
@property
84125
def position(self) -> Tuple[int, int]:
85-
"""Return the (x, y) position of the window.
126+
"""Get or set the (x, y) position of the window.
86127
87128
This attribute can be set the move the window.
88-
The constants tcod.lib.SDL_WINDOWPOS_CENTERED or
89-
tcod.lib.SDL_WINDOWPOS_UNDEFINED can be used.
129+
The constants tcod.lib.SDL_WINDOWPOS_CENTERED or tcod.lib.SDL_WINDOWPOS_UNDEFINED may be used.
90130
"""
91131
xy = ffi.new("int[2]")
92132
lib.SDL_GetWindowPosition(self.p, xy, xy + 1)
@@ -99,11 +139,10 @@ def position(self, xy: Tuple[int, int]) -> None:
99139

100140
@property
101141
def size(self) -> Tuple[int, int]:
102-
"""Return the pixel (width, height) of the window.
142+
"""Get or set the pixel (width, height) of the window client area.
103143
104-
This attribute can be set to change the size of the window but the
105-
given size must be greater than (1, 1) or else an exception will be
106-
raised.
144+
This attribute can be set to change the size of the window but the given size must be greater than (1, 1) or
145+
else ValueError will be raised.
107146
"""
108147
xy = ffi.new("int[2]")
109148
lib.SDL_GetWindowSize(self.p, xy, xy + 1)
@@ -112,19 +151,146 @@ def size(self) -> Tuple[int, int]:
112151
@size.setter
113152
def size(self, xy: Tuple[int, int]) -> None:
114153
if any(i <= 0 for i in xy):
115-
raise ValueError("Window size must be greater than zero, not %r" % (xy,))
154+
raise ValueError(f"Window size must be greater than zero, not {xy}")
116155
x, y = xy
117156
lib.SDL_SetWindowSize(self.p, x, y)
118157

158+
@property
159+
def min_size(self) -> Tuple[int, int]:
160+
"""Get or set this windows minimum client area."""
161+
xy = ffi.new("int[2]")
162+
lib.SDL_GetWindowMinimumSize(self.p, xy, xy + 1)
163+
return xy[0], xy[1]
164+
165+
@min_size.setter
166+
def min_size(self, xy: Tuple[int, int]) -> None:
167+
lib.SDL_SetWindowMinimumSize(self.p, xy[0], xy[1])
168+
169+
@property
170+
def max_size(self) -> Tuple[int, int]:
171+
"""Get or set this windows maximum client area."""
172+
xy = ffi.new("int[2]")
173+
lib.SDL_GetWindowMaximumSize(self.p, xy, xy + 1)
174+
return xy[0], xy[1]
175+
176+
@max_size.setter
177+
def max_size(self, xy: Tuple[int, int]) -> None:
178+
lib.SDL_SetWindowMaximumSize(self.p, xy[0], xy[1])
179+
119180
@property
120181
def title(self) -> str:
121-
"""The title of the window. You may set this attribute to change it."""
182+
"""Get or set the title of the window."""
122183
return str(ffi.string(lib.SDL_GetWindowtitle(self.p)), encoding="utf-8")
123184

124185
@title.setter
125186
def title(self, value: str) -> None:
126187
lib.SDL_SetWindowtitle(self.p, value.encode("utf-8"))
127188

189+
@property
190+
def flags(self) -> WindowFlags:
191+
"""The current flags of this window, read-only."""
192+
return WindowFlags(lib.SDL_GetWindowFlags(self.p))
193+
194+
@property
195+
def fullscreen(self) -> int:
196+
"""Get or set the fullscreen status of this window.
197+
198+
Can be set to :any:`WindowFlags.FULLSCREEN` or :any:`WindowFlags.FULLSCREEN_DESKTOP` flags
199+
200+
Example::
201+
202+
# Toggle fullscreen.
203+
window: tcod.sdl.video.Window
204+
if window.fullscreen:
205+
window.fullscreen = False # Set windowed mode.
206+
else:
207+
window.fullscreen = tcod.sdl.video.WindowFlags.FULLSCREEN_DESKTOP
208+
"""
209+
return self.flags & (WindowFlags.FULLSCREEN | WindowFlags.FULLSCREEN_DESKTOP)
210+
211+
@fullscreen.setter
212+
def fullscreen(self, value: int) -> None:
213+
_check(lib.SDL_SetWindowFullscreen(self.p, value))
214+
215+
@property
216+
def resizable(self) -> bool:
217+
"""Get or set if this window can be resized."""
218+
return bool(self.flags & WindowFlags.RESIZABLE)
219+
220+
@resizable.setter
221+
def resizable(self, value: bool) -> None:
222+
lib.SDL_SetWindowResizable(self.p, value)
223+
224+
@property
225+
def border_size(self) -> Tuple[int, int, int, int]:
226+
"""Get the (top, left, bottom, right) size of the window decorations around the client area.
227+
228+
If this fails or the window doesn't have decorations yet then the value will be (0, 0, 0, 0).
229+
230+
.. seealso::
231+
https://wiki.libsdl.org/SDL_GetWindowBordersSize
232+
"""
233+
borders = ffi.new("int[4]")
234+
# The return code is ignored.
235+
_ = lib.SDL_GetWindowBordersSize(self.p, borders, borders + 1, borders + 2, borders + 3)
236+
return borders[0], borders[1], borders[2], borders[3]
237+
238+
@property
239+
def opacity(self) -> float:
240+
"""Get or set this windows opacity. 0.0 is fully transarpent and 1.0 is fully opaque.
241+
242+
Will error if you try to set this and opacity isn't supported.
243+
"""
244+
out = ffi.new("float*")
245+
_check(lib.SDL_GetWindowOpacity(self.p, out))
246+
return float(out[0])
247+
248+
@opacity.setter
249+
def opacity(self, value: float) -> None:
250+
_check(lib.SDL_SetWindowOpacity(self.p, value))
251+
252+
@property
253+
def grab(self) -> bool:
254+
"""Get or set this windows input grab mode.
255+
256+
.. seealso::
257+
https://wiki.libsdl.org/SDL_SetWindowGrab
258+
"""
259+
return bool(lib.SDL_GetWindowGrab(self.p))
260+
261+
@grab.setter
262+
def grab(self, value: bool) -> None:
263+
lib.SDL_SetWindowGrab(self.p, value)
264+
265+
@_required_version((2, 0, 16))
266+
def flash(self, operation: FlashOperation = FlashOperation.UNTIL_FOCUSED) -> None:
267+
"""Get the users attention."""
268+
_check(lib.SDL_FlashWindow(self.p, operation))
269+
270+
def raise_window(self) -> None:
271+
"""Raise the window and set input focus."""
272+
lib.SDL_RaiseWindow(self.p)
273+
274+
def restore(self) -> None:
275+
"""Restore a minimized or maximized window to its original size and position."""
276+
lib.SDL_RestoreWindow(self.p)
277+
278+
def maximize(self) -> None:
279+
"""Make the window as big as possible."""
280+
lib.SDL_MaximizeWindow(self.p)
281+
282+
def minimize(self) -> None:
283+
"""Minimize the window to an iconic state."""
284+
lib.SDL_MinimizeWindow(self.p)
285+
286+
def show(self) -> None:
287+
"""Show this window."""
288+
lib.SDL_ShowWindow(self.p)
289+
290+
def hide(self) -> None:
291+
"""Hide this window."""
292+
lib.SDL_HideWindow(self.p)
293+
128294

129295
def new_window(
130296
width: int,
@@ -153,6 +319,12 @@ def new_window(
153319
return Window(_check_p(window_p))
154320

155321

322+
def get_grabbed_window() -> Optional[Window]:
323+
"""Return the window which has input grab enabled, if any."""
324+
sdl_window_p = lib.SDL_GetGrabbedWindow()
325+
return Window(sdl_window_p) if sdl_window_p else None
326+
327+
156328
def _get_active_window() -> Window:
157329
"""Return the SDL2 window current managed by libtcod.
158330

0 commit comments

Comments
 (0)