Source code for tcod.sdl.mouse

"""SDL mouse and cursor functions.

You can use this module to move or capture the cursor.

You can also set the cursor icon to an OS-defined or custom icon.

.. versionadded:: 13.5

from __future__ import annotations

import enum
from typing import Any

import numpy as np
from numpy.typing import ArrayLike, NDArray

import tcod.event
from tcod.cffi import ffi, lib
from tcod.sdl._internal import _check, _check_p

[docs] class Cursor: """A cursor icon for use with :any:`set_cursor`.""" def __init__(self, sdl_cursor_p: Any) -> None: # noqa: ANN401 if ffi.typeof(sdl_cursor_p) is not ffi.typeof("struct SDL_Cursor*"): msg = f"Expected a {ffi.typeof('struct SDL_Cursor*')} type (was {ffi.typeof(sdl_cursor_p)})." raise TypeError(msg) if not sdl_cursor_p: msg = "C pointer must not be null." raise TypeError(msg) self.p = sdl_cursor_p def __eq__(self, other: Any) -> bool: return bool(self.p == getattr(other, "p", None)) @classmethod def _claim(cls, sdl_cursor_p: Any) -> Cursor: """Verify and wrap this pointer in a garbage collector before returning a Cursor.""" return cls(ffi.gc(_check_p(sdl_cursor_p), lib.SDL_FreeCursor))
[docs] class SystemCursor(enum.IntEnum): """An enumerator of system cursor icons.""" ARROW = 0 """""" IBEAM = """""" WAIT = """""" CROSSHAIR = """""" WAITARROW = """""" SIZENWSE = """""" SIZENESW = """""" SIZEWE = """""" SIZENS = """""" SIZEALL = """""" NO = """""" HAND = """"""
[docs] def new_cursor(data: NDArray[np.bool_], mask: NDArray[np.bool_], hot_xy: tuple[int, int] = (0, 0)) -> Cursor: """Return a new non-color Cursor from the provided parameters. Args: data: A row-major boolean array for the data parameters. See the SDL docs for more info. mask: A row-major boolean array for the mask parameters. See the SDL docs for more info. hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0). .. seealso:: :any:`set_cursor` """ if len(data.shape) != 2: # noqa: PLR2004 msg = "Data and mask arrays must be 2D." raise TypeError(msg) if data.shape != mask.shape: msg = "Data and mask arrays must have the same shape." raise TypeError(msg) height, width = data.shape data_packed = np.packbits(data, axis=0, bitorder="big") mask_packed = np.packbits(mask, axis=0, bitorder="big") return Cursor._claim( lib.SDL_CreateCursor( ffi.from_buffer("uint8_t*", data_packed), ffi.from_buffer("uint8_t*", mask_packed), width, height, *hot_xy ) )
[docs] def new_color_cursor(pixels: ArrayLike, hot_xy: tuple[int, int]) -> Cursor: """Create a new color cursor. Args: pixels: A row-major array of RGB or RGBA pixels. hot_xy: The position of the pointer relative to the mouse sprite, starting from the upper-left at (0, 0). .. seealso:: :any:`set_cursor` """ surface = return Cursor._claim(lib.SDL_CreateColorCursor(surface.p, *hot_xy))
[docs] def new_system_cursor(cursor: SystemCursor) -> Cursor: """Return a new Cursor from one of the system cursors labeled by SystemCursor. .. seealso:: :any:`set_cursor` """ return Cursor._claim(lib.SDL_CreateSystemCursor(cursor))
[docs] def set_cursor(cursor: Cursor | SystemCursor | None) -> None: """Change the active cursor to the one provided. Args: cursor: A cursor created from :any:`new_cursor`, :any:`new_color_cursor`, or :any:`new_system_cursor`. Can also take values of :any:`SystemCursor` directly. None will force the current cursor to be redrawn. """ if isinstance(cursor, SystemCursor): cursor = new_system_cursor(cursor) lib.SDL_SetCursor(cursor.p if cursor is not None else ffi.NULL)
[docs] def get_default_cursor() -> Cursor: """Return the default cursor.""" return Cursor(_check_p(lib.SDL_GetDefaultCursor()))
[docs] def get_cursor() -> Cursor | None: """Return the active cursor, or None if these is no mouse.""" cursor_p = lib.SDL_GetCursor() return Cursor(cursor_p) if cursor_p else None
[docs] def capture(enable: bool) -> None: """Enable or disable mouse capture to track the mouse outside of a window. It is highly recommended to read the related remarks section in the SDL docs before using this. Example:: # Make mouse button presses capture the mouse until all buttons are released. # This means that dragging the mouse outside of the window will not cause an interruption in motion events. for event in tcod.event.get(): match event: case tcod.event.MouseButtonDown(button=button, pixel=pixel): # Clicking the window captures the mouse. tcod.sdl.mouse.capture(True) case tcod.event.MouseButtonUp(): # When all buttons are released then the mouse is released. if tcod.event.mouse.get_global_state().state == 0: tcod.sdl.mouse.capture(False) case tcod.event.MouseMotion(pixel=pixel, pixel_motion=pixel_motion, state=state): pass # While a button is held this event is still captured outside of the window. .. seealso:: :any:`tcod.sdl.mouse.set_relative_mode` """ _check(lib.SDL_CaptureMouse(enable))
[docs] def set_relative_mode(enable: bool) -> None: """Enable or disable relative mouse mode which will lock and hide the mouse and only report mouse motion. .. seealso:: :any:`tcod.sdl.mouse.capture` """ _check(lib.SDL_SetRelativeMouseMode(enable))
[docs] def get_relative_mode() -> bool: """Return True if relative mouse mode is enabled.""" return bool(lib.SDL_GetRelativeMouseMode())
[docs] def get_global_state() -> tcod.event.MouseState: """Return the mouse state relative to the desktop. .. seealso:: """ xy ="int[2]") state = lib.SDL_GetGlobalMouseState(xy, xy + 1) return tcod.event.MouseState((xy[0], xy[1]), state=state)
[docs] def get_relative_state() -> tcod.event.MouseState: """Return the mouse state, the coordinates are relative to the last time this function was called. .. seealso:: """ xy ="int[2]") state = lib.SDL_GetRelativeMouseState(xy, xy + 1) return tcod.event.MouseState((xy[0], xy[1]), state=state)
[docs] def get_state() -> tcod.event.MouseState: """Return the mouse state relative to the window with mouse focus. .. seealso:: """ xy ="int[2]") state = lib.SDL_GetMouseState(xy, xy + 1) return tcod.event.MouseState((xy[0], xy[1]), state=state)
[docs] def get_focus() -> | None: """Return the window which currently has mouse focus.""" window_p = lib.SDL_GetMouseFocus() return if window_p else None
[docs] def warp_global(x: int, y: int) -> None: """Move the mouse cursor to a position on the desktop.""" _check(lib.SDL_WarpMouseGlobal(x, y))
[docs] def warp_in_window(window:, x: int, y: int) -> None: """Move the mouse cursor to a position within a window.""" lib.SDL_WarpMouseInWindow(window.p, x, y)
[docs] def show(visible: bool | None = None) -> bool: """Optionally show or hide the mouse cursor then return the state of the cursor. Args: visible: If None then only return the current state. Otherwise set the mouse visibility. Returns: True if the cursor is visible. .. versionadded:: 16.0 """ _OPTIONS = {None: lib.SDL_QUERY, False: lib.SDL_DISABLE, True: lib.SDL_ENABLE} return _check(lib.SDL_ShowCursor(_OPTIONS[visible])) == int(lib.SDL_ENABLE)