Source code for logngraph.graph.graph

import contextlib
with contextlib.redirect_stdout(None):
    import pygame
from logngraph.errors import *
from typing import Callable, Iterable, Any
from logngraph.vector import Vec2D

__all__ = [
    "Window",
]

[docs] class Window:
[docs] def __init__(self, title: str = "Window", width: int = 800, height: int = 600, resizable: bool = True) -> None: pygame.init() pygame.font.init() self.screen = pygame.display.set_mode((width, height), pygame.RESIZABLE if resizable else 0) pygame.display.set_caption(title) self.running = True self.keydown_bindings: dict[int, Callable] = {} self.keydown_args: dict[int, Iterable[Any]] = {} self.keyup_bindings: dict[int, Callable] = {} self.keyup_args: dict[int, Iterable[Any]] = {} self.angle = 0 self.translation = Vec2D(0, 0)
@property def width(self) -> int: return self.screen.get_width() @property def height(self) -> int: return self.screen.get_height()
[docs] def rotate(self, angle: float) -> None: self.angle = angle
[docs] def translate(self, x: float, y: float) -> None: self.translation = Vec2D(x, y)
[docs] @staticmethod def set_title(title: str) -> None: pygame.display.set_caption(title)
[docs] def fill(self, color: tuple[int, int, int] | str) -> None: """ Fill the window with given color. :param color: Color to fill the window with. :return: None """ self.screen.fill(color)
[docs] def update(self) -> None: """ Updates the window. :return: None """ if self.running: pygame.display.flip()
[docs] def stop(self) -> None: """ Stops the window. :return: None """ self.running = False
[docs] def handle_events(self) -> None: """ Handles event (quit, keypresses, etc.) :return: None """ for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False elif event.type == pygame.KEYDOWN: if event.key in self.keydown_bindings: self.keydown_bindings[event.key](*self.keydown_args[event.key]) elif event.type == pygame.KEYUP: if event.key in self.keyup_bindings: self.keyup_bindings[event.key](*self.keyup_args[event.key])
[docs] def screenshot(self, filename: str) -> None: """ Save a screenshot of the window to a file. :param filename: Path to the file to save the screenshot to. :return: None """ pygame.image.save(self.screen, filename)
[docs] def rect(self, left_top: tuple[float, float] | Vec2D, width_height: tuple[float, float] | Vec2D, color="#ffffff") -> None: """ Draw a rectangle with given width and height and at given coordinates. :param left_top: Leftmost top coordinates of the rectangle. :param width_height: Width and height of the rectangle. :param color: Fill color of the rectangle. :return: None """ x, y = left_top w, h = width_height # Define all four corners relative to the origin corners = [ Vec2D(x, y), # top-left Vec2D(x + w, y), # top-right Vec2D(x + w, y + h), # bottom-right Vec2D(x, y + h) # bottom-left ] # Apply rotation and translation to each corner transformed_corners = [] for corner in corners: rotated = corner.rotate(self.angle) translated = rotated.translate(self.translation) transformed_corners.append((translated.x, translated.y)) # Draw the polygon using the transformed corners pygame.draw.polygon(self.screen, color, transformed_corners)
[docs] def circle(self, center: tuple[float, float] | Vec2D, radius: float, color="#ffffff") -> None: """ Create circle with center at center and radius=radius. :param center: Center position of the circle. :param radius: Radius of the circle. :param color: Fill color of the circle. :return: None """ center = (center[0] + self.translation.x, center[1] + self.translation.y) pygame.draw.circle(self.screen, color, center, radius)
[docs] def line(self, start_pos: tuple[float, float] | Vec2D, end_pos: tuple[float, float] | Vec2D, color="#ffffff", width: int = 1) -> None: """ Draw a line from start_pos to end_pos with width=width and color=color. :param start_pos: Starting position of the line. :param end_pos: Ending position of the line. :param color: Color of the line. :param width: Width of the line. :return: None """ x1, y1 = start_pos x2, y2 = end_pos # Define all four corners relative to the origin vertices = [ Vec2D(x1, y1), Vec2D(x2, y2), ] # Apply rotation and translation to each corner v = [] for corner in vertices: rotated = corner.rotate(self.angle) translated = rotated.translate(self.translation) v.append((translated.x, translated.y)) pygame.draw.line(self.screen, color, v[0], v[1], width=width)
[docs] def polygon(self, *args: float | tuple[float, float] | Vec2D, color="#ffffff") -> None: """ Draw a polygon with given position arguments. :param args: x, y pairs of coordinates of the vertices of the polygon. E.g. `(0, 0), (15, 60), (123, 624)`, or `0, 0, 15, 60, 123, 624`. :param color: Fill color of the polygon. :return: None """ try: if len(args) % 2 == 0: points = [] for i in range(0, len(args), 2): points.append((args[i], args[i + 1])) else: points = list(args) except Exception as e: raise InvalidArgsError(f"Could not unpack args: {args}") from e v = [] for point in points: p = Vec2D(*point).rotate(self.angle).translate(self.translation) v.append((p.x, p.y)) pygame.draw.polygon(self.screen, color, v)
[docs] def write(self, center: tuple[int, int], text: str = "", color="#ffffff", antialias: bool = True, font: pygame.font.Font | str = "Arial", size: int = 16) -> None: """ Write given text to the screen. :param center: Center position of the text. :param text: Text to write. :param color: Color of the text. :param antialias: Enable antialiasing of the text. :param font: Font of the text. :param size: Font size of the text. :return: None """ if type(font) is str: try: font = pygame.font.Font(font, size) except FileNotFoundError: font = pygame.font.SysFont(font, size) text = font.render(text, antialias, color, None) textRect = text.get_rect() textRect.center = (round(center[0] + self.translation.x), round(center[1] + self.translation.y)) text = pygame.transform.rotate(text, self.angle) self.screen.blit(text, textRect)
[docs] def bind_keydown(self, key: str, function: Callable, args: Iterable[Any] = None) -> None: """ Bind given keypress to given function. Function will be called when the key is pressed. :param key: Key press. Must be a valid pygame key name (e.g., 'a', 'space', 'enter'). :param function: Function to call when the key is pressed. :param args: Arguments to pass to the function. :return: None """ args = args or () try: key_constant = pygame.key.key_code(key) self.keydown_bindings[key_constant] = function self.keydown_args[key_constant] = args except ValueError: raise InvalidKeyError(f"Invalid key name: {key}")
[docs] def bind_keyup(self, key: str, function: Callable, args: Iterable[Any] = None) -> None: """ Bind given keypress to given function. Function will be called when the key is pressed. :param key: Key press. Must be a valid pygame key name (e.g., 'a', 'space', 'enter'). :param function: Function to call when the key is pressed. :param args: Arguments to pass to the function. :return: None """ args = args or () try: key_constant = pygame.key.key_code(key) self.keyup_bindings[key_constant] = function self.keyup_args[key_constant] = args except ValueError: raise InvalidKeyError(f"Invalid key name: {key}")