Skip to content

Commit 3654e2f

Browse files
committed
Add public SDL mouse module.
1 parent 9676735 commit 3654e2f

File tree

4 files changed

+223
-0
lines changed

4 files changed

+223
-0
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"howto",
128128
"htbp",
129129
"htmlzip",
130+
"IBEAM",
130131
"ifdef",
131132
"ifndef",
132133
"iinfo",
@@ -267,6 +268,10 @@
267268
"servernum",
268269
"setuptools",
269270
"SHADOWCAST",
271+
"SIZENESW",
272+
"SIZENS",
273+
"SIZENWSE",
274+
"SIZEWE",
270275
"SMILIE",
271276
"snprintf",
272277
"stdeb",

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Contents:
4040
tcod/tileset
4141
libtcodpy
4242
sdl/render
43+
sdl/mouse
4344
sdl/video
4445

4546
Indices and tables

docs/sdl/mouse.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tcod.sdl.mouse - SDL Mouse Functions
2+
====================================
3+
4+
.. automodule:: tcod.sdl.mouse
5+
:members:

tcod/sdl/mouse.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
"""SDL mouse and cursor functions.
2+
3+
.. versionadded:: unreleased
4+
"""
5+
from __future__ import annotations
6+
7+
import enum
8+
from typing import Any, Optional, Tuple, Union
9+
10+
import numpy as np
11+
from numpy.typing import ArrayLike, NDArray
12+
13+
import tcod.event
14+
import tcod.sdl.video
15+
from tcod.loader import ffi, lib
16+
from tcod.sdl import _check, _check_p
17+
18+
19+
class Cursor:
20+
"""A cursor icon for use with :any:`set_cursor`."""
21+
22+
def __init__(self, sdl_cursor_p: Any):
23+
if ffi.typeof(sdl_cursor_p) is not ffi.typeof("struct SDL_Cursor*"):
24+
raise TypeError(f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)}).")
25+
if not sdl_cursor_p:
26+
raise TypeError("C pointer must not be null.")
27+
self.p = sdl_cursor_p
28+
29+
def __eq__(self, other: Any) -> bool:
30+
return bool(self.p == getattr(other, "p", None))
31+
32+
@classmethod
33+
def _claim(cls, sdl_cursor_p: Any) -> Cursor:
34+
"""Verify and wrap this pointer in a garbage collector before returning a Cursor."""
35+
return cls(ffi.gc(_check_p(sdl_cursor_p), lib.SDL_FreeCursor))
36+
37+
38+
class SystemCursor(enum.IntEnum):
39+
"""An enumerator of system cursor icons."""
40+
41+
ARROW = 0
42+
""""""
43+
IBEAM = enum.auto()
44+
""""""
45+
WAIT = enum.auto()
46+
""""""
47+
CROSSHAIR = enum.auto()
48+
""""""
49+
WAITARROW = enum.auto()
50+
""""""
51+
SIZENWSE = enum.auto()
52+
""""""
53+
SIZENESW = enum.auto()
54+
""""""
55+
SIZEWE = enum.auto()
56+
""""""
57+
SIZENS = enum.auto()
58+
""""""
59+
SIZEALL = enum.auto()
60+
""""""
61+
NO = enum.auto()
62+
""""""
63+
HAND = enum.auto()
64+
""""""
65+
66+
67+
def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: Tuple[int, int] = (0, 0)) -> Cursor:
68+
"""Return a new non-color Cursor from the provided parameters.
69+
70+
Args:
71+
data: A row-major boolean array for the data parameters. See the SDL docs for more info.
72+
mask: A row-major boolean array for the mask parameters. See the SDL docs for more info.
73+
hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0).
74+
75+
.. seealso::
76+
:any:`set_cursor`
77+
https://wiki.libsdl.org/SDL_CreateCursor
78+
"""
79+
if len(data.shape) != 2:
80+
raise TypeError("Data and mask arrays must be 2D.")
81+
if data.shape != mask.shape:
82+
raise TypeError("Data and mask arrays must have the same shape.")
83+
height, width = data.shape
84+
data_packed = np.packbits(data, axis=0, bitorder="big")
85+
mask_packed = np.packbits(mask, axis=0, bitorder="big")
86+
return Cursor._claim(
87+
lib.SDL_CreateCursor(
88+
ffi.from_buffer("uint8_t*", data_packed), ffi.from_buffer("uint8_t*", mask_packed), width, height, *hot_xy
89+
)
90+
)
91+
92+
93+
def new_color_cursor(pixels: ArrayLike, hot_xy: Tuple[int, int]) -> Cursor:
94+
"""
95+
Args:
96+
pixels: A row-major array of RGB or RGBA pixels.
97+
hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0).
98+
99+
.. seealso::
100+
:any:`set_cursor`
101+
"""
102+
surface = tcod.sdl.video._TempSurface(pixels)
103+
return Cursor._claim(lib.SDL_CreateColorCursor(surface.p, *hot_xy))
104+
105+
106+
def new_system_cursor(cursor: SystemCursor) -> Cursor:
107+
"""Return a new Cursor from one of the system cursors labeled by SystemCursor.
108+
109+
.. seealso::
110+
:any:`set_cursor`
111+
"""
112+
return Cursor._claim(lib.SDL_CreateSystemCursor(cursor))
113+
114+
115+
def set_cursor(cursor: Optional[Union[Cursor, SystemCursor]]) -> None:
116+
"""Change the active cursor to the one provided.
117+
118+
Args:
119+
cursor: A cursor created from :any:`new_cursor`, :any:`new_color_cursor`, or :any:`new_system_cursor`.
120+
Can also take values of :any:`SystemCursor` directly.
121+
None will force the current cursor to be redrawn.
122+
"""
123+
if isinstance(cursor, SystemCursor):
124+
cursor = new_system_cursor(cursor)
125+
lib.SDL_SetCursor(cursor.p if cursor is not None else ffi.NULL)
126+
127+
128+
def get_default_cursor() -> Cursor:
129+
"""Return the default cursor."""
130+
return Cursor(_check_p(lib.SDL_GetDefaultCursor()))
131+
132+
133+
def get_cursor() -> Optional[Cursor]:
134+
"""Return the active cursor, or None if these is no mouse."""
135+
cursor_p = lib.SDL_GetCursor()
136+
return Cursor(cursor_p) if cursor_p else None
137+
138+
139+
def capture(enable: bool) -> None:
140+
"""Enable or disable mouse capture to track the mouse outside of a window.
141+
142+
It is highly reccomended to read the related remarks section in the SDL docs before using this.
143+
144+
.. seealso::
145+
:any:`tcod.sdl.mouse.set_relative_mode`
146+
https://wiki.libsdl.org/SDL_CaptureMouse
147+
"""
148+
_check(lib.SDL_CaptureMouse(enable))
149+
150+
151+
def set_relative_mode(enable: bool) -> None:
152+
"""Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion.
153+
154+
.. seealso::
155+
:any:`tcod.sdl.mouse.capture`
156+
https://wiki.libsdl.org/SDL_SetRelativeMouseMode
157+
"""
158+
_check(lib.SDL_SetRelativeMouseMode(enable))
159+
160+
161+
def get_relative_mode() -> bool:
162+
"""Return True if relative mouse mode is enabled."""
163+
return bool(lib.SDL_GetRelativeMouseMode())
164+
165+
166+
def get_global_state() -> tcod.event.MouseState:
167+
"""Return the mouse state relative to the desktop.
168+
169+
.. seealso::
170+
https://wiki.libsdl.org/SDL_GetGlobalMouseState
171+
"""
172+
xy = ffi.new("int[2]")
173+
state = lib.SDL_GetGlobalMouseState(xy, xy + 1)
174+
return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state)
175+
176+
177+
def get_relative_state() -> tcod.event.MouseState:
178+
"""Return the mouse state, the coordinates are relative to the last time this function was called.
179+
180+
.. seealso::
181+
https://wiki.libsdl.org/SDL_GetRelativeMouseState
182+
"""
183+
xy = ffi.new("int[2]")
184+
state = lib.SDL_GetRelativeMouseState(xy, xy + 1)
185+
return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state)
186+
187+
188+
def get_state() -> tcod.event.MouseState:
189+
"""Return the mouse state relative to the window with mouse focus.
190+
191+
.. seealso::
192+
https://wiki.libsdl.org/SDL_GetMouseState
193+
"""
194+
xy = ffi.new("int[2]")
195+
state = lib.SDL_GetMouseState(xy, xy + 1)
196+
return tcod.event.MouseState(pixel=(xy[0], xy[1]), state=state)
197+
198+
199+
def get_focus() -> Optional[tcod.sdl.video.Window]:
200+
"""Return the window which currently has mouse focus."""
201+
window_p = lib.SDL_GetMouseFocus()
202+
return tcod.sdl.video.Window(window_p) if window_p else None
203+
204+
205+
def warp_global(x: int, y: int) -> None:
206+
"""Move the mouse cursor to a position on the desktop."""
207+
_check(lib.SDL_WarpMouseGlobal(x, y))
208+
209+
210+
def warp_in_window(window: tcod.sdl.video.Window, x: int, y: int) -> None:
211+
"""Move the mouse cursor to a position within a window."""
212+
_check(lib.SDL_WarpMouseInWindow(window.p, x, y))

0 commit comments

Comments
 (0)