Skip to content

Commit c2fb67e

Browse files
committed
Improve docs. Focus more on showing examples in the page headers.
1 parent 8f4d00c commit c2fb67e

File tree

6 files changed

+219
-86
lines changed

6 files changed

+219
-86
lines changed

tcod/context.py

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@ def __enter__(self) -> Context:
179179
return self
180180

181181
def close(self) -> None:
182-
"""Delete the context, closing any windows opened by this context.
182+
"""Close this context, closing any windows opened by this context.
183183
184-
This instance is invalid after this call."""
184+
Afterwards doing anything with this instance other than closing it again is invalid.
185+
"""
185186
if hasattr(self, "_context_p"):
186187
ffi.release(self._context_p)
187188
del self._context_p
@@ -246,7 +247,21 @@ def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]:
246247
return xy[0], xy[1]
247248

248249
def convert_event(self, event: tcod.event.Event) -> None:
249-
"""Fill in the tile coordinates of a mouse event using this context."""
250+
"""Fill in the tile coordinates of a mouse event using this context.
251+
252+
Example::
253+
254+
context: tcod.context.Context
255+
for event in tcod.event.get():
256+
if isinstance(event, tcod.event.MouseMotion):
257+
# Pixel coordinates are always accessible.
258+
print(f"{event.pixel=}, {event.pixel_motion=}")
259+
context.convert_event(event)
260+
if isinstance(event, tcod.event.MouseMotion):
261+
# Now tile coordinate attributes can be accessed.
262+
print(f"{event.tile=}, {event.tile_motion=}")
263+
# A warning will be raised if you try to access these without convert_event.
264+
"""
250265
if isinstance(event, (tcod.event.MouseState, tcod.event.MouseMotion)):
251266
event.tile = tcod.event.Point(*self.pixel_to_tile(*event.pixel))
252267
if isinstance(event, tcod.event.MouseMotion):
@@ -262,7 +277,17 @@ def save_screenshot(self, path: Optional[str] = None) -> None:
262277
_check(lib.TCOD_context_save_screenshot(self._context_p, c_path))
263278

264279
def change_tileset(self, tileset: Optional[tcod.tileset.Tileset]) -> None:
265-
"""Change the active tileset used by this context."""
280+
"""Change the active tileset used by this context.
281+
282+
The new tileset will take effect on the next call to :any:`present`.
283+
Contexts not using a renderer with an emulated terminal will be unaffected by this method.
284+
285+
This does not do anything to resize the window, keep this in mind if the tileset as a differing tile size.
286+
Access the window with :any:`sdl_window` to resize it manually, if needed.
287+
288+
Using this method only one tileset is active per-frame.
289+
See :any:`tcod.render` if you want to renderer with multiple tilesets in a single frame.
290+
"""
266291
_check(lib.TCOD_context_change_tileset(self._context_p, _handle_tileset(tileset)))
267292

268293
def new_console(
@@ -299,6 +324,25 @@ def new_console(
299324
300325
.. seealso::
301326
:any:`tcod.console.Console`
327+
328+
Example::
329+
330+
scale = 1 # Tile size scale. This example uses integers but floating point numbers are also valid.
331+
context = tcod.context.new()
332+
while True:
333+
# Create a cleared, dynamically-sized console for each frame.
334+
console = context.new_console(magnification=scale)
335+
# This printed output will wrap if the window is shrunk.
336+
console.print_box(0, 0, console.width, console.height, "Hello world")
337+
# Use integer_scaling to prevent subpixel distorsion.
338+
# This may add padding around the rendered console.
339+
context.present(console, integer_scaling=True)
340+
for event in tcod.event.wait():
341+
if isinstance(event, tcod.event.Quit):
342+
raise SystemExit()
343+
elif isinstance(event, tcod.event.MouseWheel):
344+
# Use the mouse wheel to change the rendered tile size.
345+
scale = max(1, scale + event.y)
302346
"""
303347
if magnification < 0:
304348
raise ValueError("Magnification must be greater than zero. (Got %f)" % magnification)
@@ -351,15 +395,31 @@ def toggle_fullscreen(context: tcod.context.Context) -> None:
351395
context.sdl_window_p,
352396
0 if fullscreen else tcod.lib.SDL_WINDOW_FULLSCREEN_DESKTOP,
353397
)
398+
354399
''' # noqa: E501
355400
return lib.TCOD_context_get_sdl_window(self._context_p)
356401

357402
@property
358403
def sdl_window(self) -> Optional[tcod.sdl.video.Window]:
359-
"""Return a :any:`tcod.sdl.video.Window` referencing this contexts SDL window if it exists.
404+
'''Return a :any:`tcod.sdl.video.Window` referencing this contexts SDL window if it exists.
405+
406+
Example::
407+
408+
import tcod
409+
improt tcod.sdl.video
410+
411+
def toggle_fullscreen(context: tcod.context.Context) -> None:
412+
"""Toggle a context window between fullscreen and windowed modes."""
413+
window = context.sdl_window
414+
if not window:
415+
return
416+
if window.fullscreen:
417+
window.fullscreen = False
418+
else:
419+
window.fullscreen = tcod.sdl.video.WindowFlags.FULLSCREEN_DESKTOP
360420
361421
.. versionadded:: 13.4
362-
"""
422+
'''
363423
p = self.sdl_window_p
364424
return tcod.sdl.video.Window(p) if p else None
365425

tcod/event.py

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
"""
2-
A light-weight implementation of event handling built on calls to SDL.
1+
"""A light-weight implementation of event handling built on calls to SDL.
32
43
Many event constants are derived directly from SDL.
54
For example: ``tcod.event.K_UP`` and ``tcod.event.SCANCODE_A`` refer to
@@ -8,14 +7,75 @@
87
<https://wiki.libsdl.org/SDL_Keycode>`_
98
109
Printing any event will tell you its attributes in a human readable format.
11-
An events type attribute if omitted is just the classes name with all letters
12-
upper-case.
10+
An events type attribute if omitted is just the classes name with all letters upper-case.
11+
12+
As a general guideline, you should use :any:`KeyboardEvent.sym` for command inputs,
13+
and :any:`TextInput.text` for name entry fields.
14+
15+
Example::
16+
17+
import tcod
18+
19+
KEY_COMMANDS = {
20+
tcod.event.KeySym.UP: "move N",
21+
tcod.event.KeySym.DOWN: "move S",
22+
tcod.event.KeySym.LEFT: "move W",
23+
tcod.event.KeySym.RIGHT: "move E",
24+
}
25+
26+
context = tcod.context.new()
27+
while True:
28+
console = context.new_console()
29+
context.present(console, integer_scaling=True)
30+
for event in tcod.event.wait():
31+
context.convert_event(event) # Adds tile coordinates to mouse events.
32+
if isinstance(event, tcod.event.Quit):
33+
print(event)
34+
raise SystemExit()
35+
elif isinstance(event, tcod.event.KeyDown):
36+
print(event) # Prints the Scancode and KeySym enums for this event.
37+
if event.sym in KEY_COMMANDS:
38+
print(f"Command: {KEY_COMMANDS[event.sym]}")
39+
elif isinstance(event, tcod.event.MouseButtonDown):
40+
print(event) # Prints the mouse button constant names for this event.
41+
elif isinstance(event, tcod.event.MouseMotion):
42+
print(event) # Prints the mouse button mask bits in a readable format.
43+
else:
44+
print(event) # Print any unhandled events.
45+
46+
Python 3.10 introduced `match statements <https://docs.python.org/3/tutorial/controlflow.html#match-statements>`_
47+
which can be used to dispatch events more gracefully:
48+
49+
Example::
1350
14-
As a general guideline, you should use :any:`KeyboardEvent.sym` for command
15-
inputs, and :any:`TextInput.text` for name entry fields.
51+
import tcod
1652
17-
Remember to add the line ``import tcod.event``, as importing this module is not
18-
implied by ``import tcod``.
53+
KEY_COMMANDS = {
54+
tcod.event.KeySym.UP: "move N",
55+
tcod.event.KeySym.DOWN: "move S",
56+
tcod.event.KeySym.LEFT: "move W",
57+
tcod.event.KeySym.RIGHT: "move E",
58+
}
59+
60+
context = tcod.context.new()
61+
while True:
62+
console = context.new_console()
63+
context.present(console, integer_scaling=True)
64+
for event in tcod.event.wait():
65+
context.convert_event(event) # Adds tile coordinates to mouse events.
66+
match event:
67+
case tcod.event.Quit():
68+
raise SystemExit()
69+
case tcod.event.KeyDown(sym) if sym in KEY_COMMANDS:
70+
print(f"Command: {KEY_COMMANDS[sym]}")
71+
case tcod.event.KeyDown(sym, scancode, mod, repeat):
72+
print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}")
73+
case tcod.event.MouseButtonDown(button, pixel, tile):
74+
print(f"MouseButtonDown: {button=}, {pixel=}, {tile=}")
75+
case tcod.event.MouseMotion(pixel, pixel_motion, tile, tile_motion):
76+
print(f"MouseMotion: {pixel=}, {pixel_motion=}, {tile=}, {tile_motion=}")
77+
case tcod.event.Event() as event:
78+
print(event) # Show any unhandled events.
1979
2080
.. versionadded:: 8.4
2181
"""
@@ -762,48 +822,10 @@ def _parse_event(sdl_event: Any) -> Event:
762822
def get() -> Iterator[Any]:
763823
"""Return an iterator for all pending events.
764824
765-
Events are processed as the iterator is consumed. Breaking out of, or
766-
discarding the iterator will leave the remaining events on the event queue.
767-
It is also safe to call this function inside of a loop that is already
768-
handling events (the event iterator is reentrant.)
769-
770-
Example::
771-
772-
context: tcod.context.Context # Context object initialized earlier.
773-
for event in tcod.event.get():
774-
context.convert_event(event) # Add tile coordinates to mouse events.
775-
if isinstance(event, tcod.event.Quit):
776-
print(event)
777-
raise SystemExit()
778-
elif isinstance(event, tcod.event.KeyDown):
779-
print(event) # Prints the Scancode and KeySym enums for this event.
780-
elif isinstance(event, tcod.event.MouseButtonDown):
781-
print(event) # Prints the mouse button constant names for this event.
782-
elif isinstance(event, tcod.event.MouseMotion):
783-
print(event) # Prints the mouse button mask bits in a readable format.
784-
else:
785-
print(event) # Print any unhandled events.
786-
# For loop exits after all current events are processed.
787-
788-
Python 3.10 introduced `match statements <https://docs.python.org/3/tutorial/controlflow.html#match-statements>`_
789-
which can be used to dispatch events more gracefully:
790-
791-
Example::
792-
793-
context: tcod.context.Context # Context object initialized earlier.
794-
for event in tcod.event.get():
795-
context.convert_event(event) # Add tile coordinates to mouse events.
796-
match event:
797-
case tcod.event.Quit():
798-
raise SystemExit()
799-
case tcod.event.KeyDown(sym, scancode, mod, repeat):
800-
print(f"KeyDown: {sym=}, {scancode=}, {mod=}, {repeat=}")
801-
case tcod.event.MouseButtonDown(button, pixel, tile):
802-
print(f"MouseButtonDown: {button=}, {pixel=}, {tile=}")
803-
case tcod.event.MouseMotion(pixel, pixel_motion, tile, tile_motion):
804-
print(f"MouseMotion: {pixel=}, {pixel_motion=}, {tile=}, {tile_motion=}")
805-
case tcod.event.Event() as event:
806-
print(event) # Show any unhandled events.
825+
Events are processed as the iterator is consumed.
826+
Breaking out of, or discarding the iterator will leave the remaining events on the event queue.
827+
It is also safe to call this function inside of a loop that is already handling events
828+
(the event iterator is reentrant.)
807829
"""
808830
sdl_event = ffi.new("SDL_Event*")
809831
while lib.SDL_PollEvent(sdl_event):

tcod/image.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
"""Functionality for handling images.
1+
"""Libtcod functionality for handling images.
22
3-
**Python-tcod is unable to render pixels to the screen directly.**
4-
If your image can't be represented as tiles then you'll need to use
5-
`an alternative library for graphics rendering
6-
<https://wiki.python.org/moin/PythonGameLibraries>`_.
3+
This module is generally seen as outdated.
4+
To load images you should typically use `Pillow <https://pillow.readthedocs.io/en/stable/>`_ or
5+
`imageio <https://imageio.readthedocs.io/en/stable/>`_ unless you need to use a feature exclusive to libtcod.
6+
7+
**Python-tcod is unable to render pixels to consoles.**
8+
The best it can do with consoles is convert an image into semigraphics which can be shown on non-emulated terminals.
9+
For true pixel-based rendering you'll want to access the SDL rendering port at :any:`tcod.sdl.render`.
710
"""
811
from __future__ import annotations
912

tcod/sdl/audio.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
"""SDL2 audio playback and recording tools.
22
3+
This module includes SDL's low-level audio API and a naive implentation of an SDL mixer.
4+
If you have experience with audio mixing then you might be better off writing your own mixer or
5+
modifying the existing one which was written using Python/Numpy.
6+
7+
This module is designed to integrate with the wider Python ecosystem.
8+
It leaves the loading to sound samples to other libaries like
9+
`SoundFile <https://pysoundfile.readthedocs.io/en/latest/>`_.
10+
11+
Example::
12+
13+
# Synchronous audio example using SDL's low-level API.
14+
import soundfile # pip install soundfile
15+
import tcod.sdl.audio
16+
17+
device = tcod.sdl.audio.open() # Open the default output device.
18+
sound, samplerate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile.
19+
converted = device.convert(sound, samplerate) # Convert this sample to the format expected by the device.
20+
device.queue_audio(converted) # Play audio syncroniously by appending it to the device buffer.
21+
22+
Example::
23+
24+
# Asynchronous audio example using BasicMixer.
25+
import time
26+
27+
import soundfile # pip install soundfile
28+
import tcod.sdl.audio
29+
30+
mixer = tcod.sdl.audio.BasicMixer(tcod.sdl.audio.open()) # Setup BasicMixer with the default audio output.
31+
sound, samplerate = soundfile.read("example_sound.wav") # Load an audio sample using SoundFile.
32+
sound = mixer.device.convert(sound, samplerate) # Convert this sample to the format expected by the device.
33+
channel = mixer.play(sound) # Start asynchronous playback, audio is mixed on a separate Python thread.
34+
while channel.busy: # Wait until the sample is done playing.
35+
time.sleep(0.001)
36+
337
.. versionadded:: 13.5
438
"""
539
from __future__ import annotations
@@ -102,16 +136,6 @@ class AudioDevice:
102136
103137
Open new audio devices using :any:`tcod.sdl.audio.open`.
104138
105-
Example::
106-
107-
import soundfile # pip install soundfile
108-
import tcod.sdl.audio
109-
110-
device = tcod.sdl.audio.open()
111-
sound, samplerate = soundfile.read("example_sound.wav")
112-
converted = device.convert(sound, samplerate)
113-
device.queue_audio(converted) # Play the audio syncroniously.
114-
115139
When you use this object directly the audio passed to :any:`queue_audio` is always played syncroniously.
116140
For more typical asynchronous audio you should pass an AudioDevice to :any:`BasicMixer`.
117141
"""
@@ -360,20 +384,6 @@ def stop(self) -> None:
360384
class BasicMixer(threading.Thread):
361385
"""An SDL sound mixer implemented in Python and Numpy.
362386
363-
Example::
364-
365-
import time
366-
367-
import soundfile # pip install soundfile
368-
import tcod.sdl.audio
369-
370-
mixer = tcod.sdl.audio.BasicMixer(tcod.sdl.audio.open())
371-
sound, samplerate = soundfile.read("example_sound.wav")
372-
sound = mixer.device.convert(sound, samplerate) # Needed if dtype or samplerate differs.
373-
channel = mixer.play(sound)
374-
while channel.busy:
375-
time.sleep(0.001)
376-
377387
.. versionadded:: 13.6
378388
"""
379389

tcod/sdl/mouse.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""SDL mouse and cursor functions.
22
3+
You can use this module to move or capture the cursor.
4+
5+
You can also set the cursor icon to an OS-defined or custom icon.
6+
37
.. versionadded:: 13.5
48
"""
59
from __future__ import annotations
@@ -141,6 +145,20 @@ def capture(enable: bool) -> None:
141145
142146
It is highly reccomended to read the related remarks section in the SDL docs before using this.
143147
148+
Example::
149+
150+
# Make mouse button presses capture the mouse until all buttons are released.
151+
# This means that dragging the mouse outside of the window will not cause an interruption in motion events.
152+
for event in tcod.event.get():
153+
match event:
154+
case tcod.event.MouseButtonDown(button, pixel): # Clicking the window captures the mouse.
155+
tcod.sdl.mouse.capture(True)
156+
case tcod.event.MouseButtonUp(): # When all buttons are released then the mouse is released.
157+
if tcod.event.mouse.get_global_state().state == 0:
158+
tcod.sdl.mouse.capture(False)
159+
case tcod.event.MouseMotion(pixel, pixel_motion, state):
160+
pass # While a button is held this event is still captured outside of the window.
161+
144162
.. seealso::
145163
:any:`tcod.sdl.mouse.set_relative_mode`
146164
https://wiki.libsdl.org/SDL_CaptureMouse

0 commit comments

Comments
 (0)