Skip to content

Commit f78fe39

Browse files
committed
Add minimap to tcod samples.
Update changelog. Add context atlas access. Refactor Texture attributes.
1 parent b069532 commit f78fe39

File tree

7 files changed

+108
-60
lines changed

7 files changed

+108
-60
lines changed

.vscode/launch.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
"type": "python",
1010
"request": "launch",
1111
"program": "${file}",
12-
"console": "integratedTerminal",
13-
"preLaunchTask": "develop python-tcod",
12+
"console": "internalConsole",
1413
},
1514
{
1615
// Run the Python samples.
@@ -20,8 +19,7 @@
2019
"request": "launch",
2120
"program": "${workspaceFolder}/examples/samples_tcod.py",
2221
"cwd": "${workspaceFolder}/examples",
23-
"console": "integratedTerminal",
24-
"preLaunchTask": "develop python-tcod",
22+
"console": "internalConsole",
2523
},
2624
{
2725
"name": "Python: Run tests",

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@ Changes relevant to the users of python-tcod are documented here.
44
This project adheres to [Semantic Versioning](https://semver.org/) since version `2.0.0`.
55

66
## [Unreleased]
7+
### Added
8+
- `tcod.sdl.audio`, a new module exposing SDL audio devices. This does not include an audio mixer yet.
9+
- `tcod.sdl.mouse`, for SDL mouse and cursor handing.
10+
- `Context.sdl_atlas`, which provides the relevant `SDLTilesetAtlas` when one is being used by the context.
11+
- Several missing features were added to `tcod.sdl.render`.
12+
- `Window.mouse_rect` added to SDL windows to set the mouse confinement area.
713
### Changed
814
- `Texture.access` and `Texture.blend_mode` properties now return enum instances.
9-
You can still set them with `int` but Mypy will complain.
15+
You can still set `blend_mode` with `int` but Mypy will complain.
1016

1117
## [13.4.0] - 2022-02-04
1218
### Added
13-
- Adds `sdl_window` and `sdl_renderer` to tcod contexts.
19+
- Adds `sdl_window` and `sdl_renderer` properties to tcod contexts.
1420
- Adds `tcod.event.add_watch` and `tcod.event.remove_watch` to handle SDL events via callback.
1521
- Adds the `tcod.sdl.video` module to handle SDL windows.
1622
- Adds the `tcod.sdl.render` module to handle SDL renderers.

examples/samples_tcod.py

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import numpy as np
2020
import tcod
2121
import tcod.render
22+
import tcod.sdl.render
2223
from numpy.typing import NDArray
2324

2425
if not sys.warnoptions:
@@ -50,6 +51,8 @@ def get_data(path: str) -> str:
5051
# Mutable global names.
5152
context: tcod.context.Context
5253
tileset: tcod.tileset.Tileset
54+
console_render: tcod.render.SDLConsoleRender # Optional SDL renderer.
55+
sample_minimap: tcod.sdl.render.Texture # Optional minimap texture.
5356
root_console = tcod.Console(80, 50, order="F")
5457
sample_console = tcod.console.Console(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, order="F")
5558
cur_sample = 0 # Current selected sample.
@@ -68,7 +71,7 @@ def on_draw(self) -> None:
6871
pass
6972

7073
def ev_keydown(self, event: tcod.event.KeyDown) -> None:
71-
global cur_sample, context
74+
global cur_sample
7275
if event.sym == tcod.event.K_DOWN:
7376
cur_sample = (cur_sample + 1) % len(SAMPLES)
7477
SAMPLES[cur_sample].on_enter()
@@ -91,8 +94,7 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> None:
9194
raise SystemExit()
9295
elif event.sym in RENDERER_KEYS:
9396
# Swap the active context for one with a different renderer.
94-
context.close()
95-
context = init_context(RENDERER_KEYS[event.sym])
97+
init_context(RENDERER_KEYS[event.sym])
9698

9799
def ev_quit(self, event: tcod.event.Quit) -> None:
98100
raise SystemExit()
@@ -541,7 +543,7 @@ def __init__(self) -> None:
541543
self.player_y = 10
542544
self.torch = False
543545
self.light_walls = True
544-
self.algo_num = 0
546+
self.algo_num = tcod.FOV_SYMMETRIC_SHADOWCAST
545547
self.noise = tcod.noise.Noise(1) # 1D noise for the torch flickering.
546548

547549
map_shape = (SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT)
@@ -582,7 +584,7 @@ def on_draw(self) -> None:
582584
self.draw_ui()
583585
sample_console.print(self.player_x, self.player_y, "@")
584586
# Draw windows.
585-
sample_console.tiles_rgb["ch"][SAMPLE_MAP == "="] = tcod.CHAR_DHLINE
587+
sample_console.tiles_rgb["ch"][SAMPLE_MAP == "="] = 0x2550 # BOX DRAWINGS DOUBLE HORIZONTAL
586588
sample_console.tiles_rgb["fg"][SAMPLE_MAP == "="] = BLACK
587589

588590
# Get a 2D boolean array of visible cells.
@@ -1394,34 +1396,48 @@ def on_draw(self) -> None:
13941396
)
13951397

13961398

1397-
def init_context(renderer: int) -> tcod.context.Context:
1398-
"""Return a new context with common parameters set.
1399+
def init_context(renderer: int) -> None:
1400+
"""Setup or reset a global context with common parameters set.
13991401
14001402
This function exists to more easily switch between renderers.
14011403
"""
1404+
global context, console_render, sample_minimap
1405+
if "context" in globals():
1406+
context.close()
14021407
libtcod_version = "%i.%i.%i" % (
14031408
tcod.lib.TCOD_MAJOR_VERSION,
14041409
tcod.lib.TCOD_MINOR_VERSION,
14051410
tcod.lib.TCOD_PATCHLEVEL,
14061411
)
1407-
return tcod.context.new(
1412+
context = tcod.context.new(
14081413
columns=root_console.width,
14091414
rows=root_console.height,
14101415
title=f"python-tcod samples" f" (python-tcod {tcod.__version__}, libtcod {libtcod_version})",
14111416
renderer=renderer,
14121417
vsync=False, # VSync turned off since this is for benchmarking.
14131418
tileset=tileset,
14141419
)
1420+
if context.sdl_renderer: # If this context supports SDL rendering.
1421+
# Start by setting the logical size so that window resizing doesn't break anything.
1422+
context.sdl_renderer.logical_size = (
1423+
tileset.tile_width * root_console.width,
1424+
tileset.tile_height * root_console.height,
1425+
)
1426+
assert context.sdl_atlas
1427+
# Generate the console renderer and minimap.
1428+
console_render = tcod.render.SDLConsoleRender(context.sdl_atlas)
1429+
sample_minimap = context.sdl_renderer.new_texture(
1430+
SAMPLE_SCREEN_WIDTH,
1431+
SAMPLE_SCREEN_HEIGHT,
1432+
format=tcod.lib.SDL_PIXELFORMAT_RGB24,
1433+
access=tcod.sdl.render.TextureAccess.STREAMING, # Updated every frame.
1434+
)
14151435

14161436

14171437
def main() -> None:
14181438
global context, tileset
14191439
tileset = tcod.tileset.load_tilesheet(FONT, 32, 8, tcod.tileset.CHARMAP_TCOD)
1420-
context = init_context(tcod.RENDERER_SDL2)
1421-
sdl_renderer = context.sdl_renderer
1422-
assert sdl_renderer
1423-
atlas = tcod.render.SDLTilesetAtlas(sdl_renderer, tileset)
1424-
console_render = tcod.render.SDLConsoleRender(atlas)
1440+
init_context(tcod.RENDERER_SDL2)
14251441
try:
14261442
SAMPLES[cur_sample].on_enter()
14271443

@@ -1434,9 +1450,25 @@ def main() -> None:
14341450
SAMPLES[cur_sample].on_draw()
14351451
sample_console.blit(root_console, SAMPLE_SCREEN_X, SAMPLE_SCREEN_Y)
14361452
draw_stats()
1437-
# context.present(root_console)
1438-
sdl_renderer.copy(console_render.render(root_console))
1439-
sdl_renderer.present()
1453+
if context.sdl_renderer:
1454+
# SDL renderer support, upload the sample console background to a minimap texture.
1455+
sample_minimap.update(sample_console.rgb.T["bg"])
1456+
# Render the root_console normally, this is the drawing step of context.present without presenting.
1457+
context.sdl_renderer.copy(console_render.render(root_console))
1458+
# Render the minimap to the screen.
1459+
context.sdl_renderer.copy(
1460+
sample_minimap,
1461+
dest=(
1462+
tileset.tile_width * 24,
1463+
tileset.tile_height * 36,
1464+
SAMPLE_SCREEN_WIDTH * 3,
1465+
SAMPLE_SCREEN_HEIGHT * 3,
1466+
),
1467+
)
1468+
context.sdl_renderer.present()
1469+
else: # No SDL renderer, just use plain context rendering.
1470+
context.present(root_console)
1471+
14401472
handle_time()
14411473
handle_events()
14421474
finally:

tcod/context.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858

5959
import tcod
6060
import tcod.event
61+
import tcod.render
6162
import tcod.sdl.render
6263
import tcod.sdl.video
6364
import tcod.tileset
@@ -371,6 +372,17 @@ def sdl_renderer(self) -> Optional[tcod.sdl.render.Renderer]:
371372
p = lib.TCOD_context_get_sdl_renderer(self._context_p)
372373
return tcod.sdl.render.Renderer(p) if p else None
373374

375+
@property
376+
def sdl_atlas(self) -> Optional[tcod.render.SDLTilesetAtlas]:
377+
"""Return a :any:`tcod.render.SDLTilesetAtlas` referencing libtcod's SDL texture atlas if it exists.
378+
379+
.. versionadded:: unreleased
380+
"""
381+
if self._context_p.type not in (lib.TCOD_RENDERER_SDL, lib.TCOD_RENDERER_SDL2):
382+
return None
383+
context_data = ffi.cast("struct TCOD_RendererSDL2*", self._context_p.contextdata_)
384+
return tcod.render.SDLTilesetAtlas._from_ref(context_data.renderer, context_data.atlas)
385+
374386
def __reduce__(self) -> NoReturn:
375387
"""Contexts can not be pickled, so this class will raise
376388
:class:`pickle.PicklingError`.

tcod/render.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from __future__ import annotations
3131

32-
from typing import Optional
32+
from typing import Any, Optional
3333

3434
import tcod.console
3535
import tcod.sdl.render
@@ -46,6 +46,14 @@ def __init__(self, renderer: tcod.sdl.render.Renderer, tileset: tcod.tileset.Til
4646
self.tileset = tileset
4747
self.p = ffi.gc(_check_p(lib.TCOD_sdl2_atlas_new(renderer.p, tileset._tileset_p)), lib.TCOD_sdl2_atlas_delete)
4848

49+
@classmethod
50+
def _from_ref(cls, renderer_p: Any, atlas_p: Any) -> SDLTilesetAtlas:
51+
self = object.__new__(cls)
52+
self._renderer = tcod.sdl.render.Renderer(renderer_p)
53+
self.tileset = tcod.tileset.Tileset._from_ref(atlas_p.tileset)
54+
self.p = atlas_p
55+
return self
56+
4957

5058
class SDLConsoleRender:
5159
"""Holds an internal cache console and texture which are used to optimized console rendering."""

tcod/sdl/render.py

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import numpy as np
1111
from numpy.typing import NDArray
12+
from typing_extensions import Final
1213

1314
import tcod.sdl.video
1415
from tcod.loader import ffi, lib
@@ -142,11 +143,27 @@ def compose_blend_mode(
142143

143144

144145
class Texture:
145-
"""SDL hardware textures."""
146+
"""SDL hardware textures.
147+
148+
Create a new texture using :any:`Renderer.new_texture` or :any:`Renderer.upload_texture`.
149+
"""
146150

147151
def __init__(self, sdl_texture_p: Any, sdl_renderer_p: Any = None) -> None:
148152
self.p = sdl_texture_p
149153
self._sdl_renderer_p = sdl_renderer_p # Keep alive.
154+
query = self._query()
155+
self.format: Final[int] = query[0]
156+
"""Texture format, read only."""
157+
self.access: Final[TextureAccess] = TextureAccess(query[1])
158+
"""Texture access mode, read only.
159+
160+
.. versionchanged:: unreleased
161+
Attribute is now a :any:`TextureAccess` value.
162+
"""
163+
self.width: Final[int] = query[2]
164+
"""Texture pixel width, read only."""
165+
self.height: Final[int] = query[3]
166+
"""Texture pixel height, read only."""
150167

151168
def __eq__(self, other: Any) -> bool:
152169
return bool(self.p == getattr(other, "p", None))
@@ -156,7 +173,7 @@ def _query(self) -> Tuple[int, int, int, int]:
156173
format = ffi.new("uint32_t*")
157174
buffer = ffi.new("int[3]")
158175
lib.SDL_QueryTexture(self.p, format, buffer, buffer + 1, buffer + 2)
159-
return int(format), int(buffer[0]), int(buffer[1]), int(buffer[2])
176+
return int(format[0]), int(buffer[0]), int(buffer[1]), int(buffer[2])
160177

161178
def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]] = None) -> None:
162179
"""Update the pixel data of this texture.
@@ -165,42 +182,11 @@ def update(self, pixels: NDArray[Any], rect: Optional[Tuple[int, int, int, int]]
165182
"""
166183
if rect is None:
167184
rect = (0, 0, self.width, self.height)
168-
assert pixels.shape[:2] == rect[3], rect[2]
169-
assert pixels[0].flags.c_contiguous
185+
assert pixels.shape[:2] == (self.height, self.width)
186+
if not pixels[0].flags.c_contiguous:
187+
pixels = np.ascontiguousarray(pixels)
170188
_check(lib.SDL_UpdateTexture(self.p, (rect,), ffi.cast("void*", pixels.ctypes.data), pixels.strides[0]))
171189

172-
@property
173-
def format(self) -> int:
174-
"""Texture format, read only."""
175-
buffer = ffi.new("uint32_t*")
176-
lib.SDL_QueryTexture(self.p, buffer, ffi.NULL, ffi.NULL, ffi.NULL)
177-
return int(buffer[0])
178-
179-
@property
180-
def access(self) -> TextureAccess:
181-
"""Texture access mode, read only.
182-
183-
.. versionchanged:: unreleased
184-
Property now returns a TextureAccess instance.
185-
"""
186-
buffer = ffi.new("int*")
187-
lib.SDL_QueryTexture(self.p, ffi.NULL, buffer, ffi.NULL, ffi.NULL)
188-
return TextureAccess(buffer[0])
189-
190-
@property
191-
def width(self) -> int:
192-
"""Texture pixel width, read only."""
193-
buffer = ffi.new("int*")
194-
lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, buffer, ffi.NULL)
195-
return int(buffer[0])
196-
197-
@property
198-
def height(self) -> int:
199-
"""Texture pixel height, read only."""
200-
buffer = ffi.new("int*")
201-
lib.SDL_QueryTexture(self.p, ffi.NULL, ffi.NULL, ffi.NULL, buffer)
202-
return int(buffer[0])
203-
204190
@property
205191
def alpha_mod(self) -> int:
206192
"""Texture alpha modulate value, can be set to 0 - 255."""

tcod/tileset.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,18 @@ def __init__(self, tile_width: int, tile_height: int) -> None:
4040
@classmethod
4141
def _claim(cls, cdata: Any) -> Tileset:
4242
"""Return a new Tileset that owns the provided TCOD_Tileset* object."""
43-
self: Tileset = object.__new__(cls)
43+
self = object.__new__(cls)
4444
if cdata == ffi.NULL:
4545
raise RuntimeError("Tileset initialized with nullptr.")
4646
self._tileset_p = ffi.gc(cdata, lib.TCOD_tileset_delete)
4747
return self
4848

49+
@classmethod
50+
def _from_ref(cls, tileset_p: Any) -> Tileset:
51+
self = object.__new__(cls)
52+
self._tileset_p = tileset_p
53+
return self
54+
4955
@property
5056
def tile_width(self) -> int:
5157
"""The width of the tile in pixels."""

0 commit comments

Comments
 (0)