"""Pygame display backend implementation."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import pygame
from .base import BaseDisplay
if TYPE_CHECKING:
from pathlib import Path
[docs]
class PygameDisplay(BaseDisplay):
"""Pygame implementation of display window management.
Manages pygame window creation, event handling, and drawing operations.
Provides both direct access to pygame.Surface and backend-agnostic helpers.
"""
@property
def backend_name(self) -> str:
"""Get backend identifier."""
return "pygame"
def _create_window(self, settings: object) -> pygame.Surface: # noqa: PLR6301
"""Create pygame window.
Args:
settings: Settings object with SCREEN_RES, FULLSCREEN, DISPLAY_INDEX
Returns:
pygame.Surface window object
"""
pygame.init()
flags = pygame.FULLSCREEN if settings.fullscreen else 0
window = pygame.display.set_mode(
settings.screen_res,
flags,
display=settings.display_index,
)
pygame.display.set_caption("PyELink - Pygame Backend")
return window
[docs]
def flip(self) -> None: # noqa: PLR6301
"""Update display."""
pygame.display.flip()
[docs]
def close(self) -> None: # noqa: PLR6301
"""Close pygame window and quit."""
pygame.quit()
[docs]
def get_events(self) -> list[dict[str, Any]]:
"""Get pygame events as unified dicts.
Returns:
List of event dicts with unified keys
"""
events = []
for event in pygame.event.get():
# Check for Ctrl+C (when window has focus, SIGINT won't fire)
if event.type == pygame.KEYDOWN and event.key == pygame.K_c and (event.mod & pygame.KMOD_CTRL):
if self.shutdown_handler:
self.shutdown_handler(None, None) # Call signal handler directly
return events # Return immediately after shutdown initiated
event_dict = {"type": self._event_type_to_string(event.type)}
if event.type in {pygame.KEYDOWN, pygame.KEYUP}:
event_dict["key"] = pygame.key.name(event.key)
event_dict["unicode"] = event.unicode if hasattr(event, "unicode") else ""
event_dict["mod"] = event.mod
elif event.type in {pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP}:
event_dict["pos"] = event.pos
event_dict["button"] = event.button
elif event.type == pygame.MOUSEMOTION:
event_dict["pos"] = event.pos
event_dict["rel"] = event.rel
event_dict["buttons"] = event.buttons
events.append(event_dict)
return events
def _event_type_to_string(self, event_type: int) -> str: # noqa: PLR6301
"""Convert pygame event type constant to string.
Args:
event_type: pygame event type constant
Returns:
Event type as string
"""
event_map = {
pygame.QUIT: "quit",
pygame.KEYDOWN: "keydown",
pygame.KEYUP: "keyup",
pygame.MOUSEBUTTONDOWN: "mousebuttondown",
pygame.MOUSEBUTTONUP: "mousebuttonup",
pygame.MOUSEMOTION: "mousemotion",
}
return event_map.get(event_type, "unknown")
[docs]
def fill(self, color: tuple[int, int, int]) -> None:
"""Fill window with color.
Args:
color: RGB tuple (0-255, 0-255, 0-255)
"""
self._window.fill(color)
[docs]
def clear(self) -> None:
"""Clear window to black."""
self._window.fill((0, 0, 0))
[docs]
def get_size(self) -> tuple[int, int]:
"""Get window dimensions.
Returns:
(width, height) in pixels
"""
return self._window.get_size()
[docs]
def draw_text(
self,
text: str,
pos: tuple[int, int] | None = None,
center: bool = False,
size: int = 24,
color: tuple[int, int, int] = (255, 255, 255),
) -> None:
"""Draw text on window.
Args:
text: Text string to display
pos: (x, y) position in pixels
center: If True, center text on screen
size: Font size in points
color: RGB color tuple
"""
font = pygame.font.Font(None, size)
text_surface = font.render(text, True, color)
if center:
text_rect = text_surface.get_rect(center=(self._window.get_width() // 2, self._window.get_height() // 2))
self._window.blit(text_surface, text_rect)
else:
self._window.blit(text_surface, pos)
[docs]
def draw_image(
self,
image_path: str | Path,
pos: tuple[int, int] | None = None,
center: bool = False,
scale: float | None = None,
) -> None:
"""Draw image on window.
Args:
image_path: Path to image file
pos: (x, y) position in pixels
center: If True, center image on screen
scale: Scale factor for image size
"""
image = pygame.image.load(str(image_path))
if scale is not None:
new_size = (int(image.get_width() * scale), int(image.get_height() * scale))
image = pygame.transform.scale(image, new_size)
if center:
image_rect = image.get_rect(center=(self._window.get_width() // 2, self._window.get_height() // 2))
self._window.blit(image, image_rect)
else:
self._window.blit(image, pos)