diff --git a/.gitignore b/.gitignore index e62d64d..dd532b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc *.json -*.txt \ No newline at end of file +*.txt +__pycache__/ \ No newline at end of file diff --git a/README.md b/README.md index e41c1f0..5e8e458 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,48 @@ This project is part of the [Viron](https://github.com/Preponderous-Software/Vir - Interactive toggling of cell states - Modular structure designed for future support of other graphics libraries - Clean interface for testing Viron entity placement and behavior +- **RenderWindow** class for simplified Pygame window management + +## RenderWindow + +Patchwork provides a `RenderWindow` class that encapsulates Pygame initialization and window management. This class is designed for **composition, not inheritance**, making it easy to integrate into projects without subclassing. + +### Key Features + +- Automatic Pygame initialization +- Window and surface management +- Event loop handling with custom event handlers +- Frame rate control +- Clean API for common rendering operations + +### Basic Usage + +```python +from render_window import RenderWindow +import pygame + +# Create window +window = RenderWindow("My Application", 800, 600) +surface = window.get_surface() + +# Register custom event handlers +def handle_input(event): + if event.type == pygame.KEYDOWN: + print(f"Key pressed: {event.key}") + +window.register_event_handler(handle_input) + +# Main loop +while window.should_continue(): + surface.fill((0, 0, 0)) + # ... render your content ... + pygame.display.update() + window.tick(60) # 60 FPS + +pygame.quit() +``` + +See `render_window_example.py` for more usage examples, including integration with the existing `Graphik` class. ## Getting Started diff --git a/render_window.py b/render_window.py new file mode 100644 index 0000000..3739cf2 --- /dev/null +++ b/render_window.py @@ -0,0 +1,78 @@ +import pygame + +# @author Daniel McCoy Stephenson +# @since October 2nd, 2025 +class RenderWindow: + """ + RenderWindow class encapsulates Pygame window initialization and management. + Designed for composition, not inheritance - use as a container for rendering. + + Manages: + - Pygame initialization + - Window creation and display surface + - Event loop processing + - Frame rate control + - Custom event handlers + """ + + def __init__(self, title, width, height): + """ + Initialize the RenderWindow with the specified title and dimensions. + + Args: + title (str): Window title + width (int): Window width in pixels + height (int): Window height in pixels + """ + pygame.init() + self._surface = pygame.display.set_mode((width, height)) + pygame.display.set_caption(title) + self._clock = pygame.time.Clock() + self._running = True + self._event_handlers = [] + + def get_surface(self): + """ + Get the display surface for rendering. + + Returns: + pygame.Surface: The display surface + """ + return self._surface + + def tick(self, fps): + """ + Control the frame rate by limiting the number of frames per second. + + Args: + fps (int): Target frames per second + """ + self._clock.tick(fps) + + def should_continue(self): + """ + Process events and check if the window should continue running. + Handles QUIT events internally and calls registered event handlers. + + Returns: + bool: True if the window should continue running, False otherwise + """ + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self._running = False + else: + # Call all registered event handlers + for handler in self._event_handlers: + handler(event) + + return self._running + + def register_event_handler(self, handler): + """ + Register a custom event handler function. + The handler will be called for each event (except QUIT which is handled internally). + + Args: + handler (callable): A function that takes a pygame.Event as parameter + """ + self._event_handlers.append(handler) diff --git a/render_window_example.py b/render_window_example.py new file mode 100644 index 0000000..eda1543 --- /dev/null +++ b/render_window_example.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +""" +Example demonstrating RenderWindow usage in the Patchwork context. +This shows how to refactor main.py to use RenderWindow instead of raw Pygame calls. +""" + +import random +import pygame +from render_window import RenderWindow +from graphik import Graphik + +# Example 1: Simple usage replacing raw Pygame initialization +def simple_example(): + """Demonstrates basic RenderWindow usage""" + # Instead of: + # pygame.init() + # gameDisplay = pygame.display.set_mode((800, 800)) + # pygame.display.set_caption("Title") + + # Use RenderWindow: + window = RenderWindow("Simple Example", 800, 800) + surface = window.get_surface() + + # Create Graphik instance with the surface + graphik = Graphik(surface) + + # Main loop - instead of manual event handling + while window.should_continue(): + surface.fill((255, 255, 255)) + graphik.drawText("RenderWindow Example", 400, 400, 30, (0, 0, 0)) + pygame.display.update() + window.tick(60) + + pygame.quit() + + +# Example 2: With custom event handlers +def event_handler_example(): + """Demonstrates custom event handler registration""" + window = RenderWindow("Event Handler Example", 800, 800) + surface = window.get_surface() + graphik = Graphik(surface) + + # State + click_count = [0] + + # Custom event handler + def handle_mouse_click(event): + if event.type == pygame.MOUSEBUTTONDOWN: + click_count[0] += 1 + print(f"Mouse clicked! Total clicks: {click_count[0]}") + + # Register the handler + window.register_event_handler(handle_mouse_click) + + # Main loop + while window.should_continue(): + surface.fill((255, 255, 255)) + graphik.drawText(f"Clicks: {click_count[0]}", 400, 400, 30, (0, 0, 0)) + graphik.drawText("Click anywhere or close window", 400, 450, 20, (100, 100, 100)) + pygame.display.update() + window.tick(60) + + pygame.quit() + + +# Example 3: Integration pattern for main.py +def integration_example(): + """ + Shows how main.py could be refactored to use RenderWindow. + + The refactored main() function would replace: + pygame.init() + gameDisplay = pygame.display.set_mode((displayWidth, displayHeight)) + pygame.display.set_caption("Visualizing Environment With Random Colors") + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + quit() + + With: + window = RenderWindow("Visualizing Environment With Random Colors", displayWidth, displayHeight) + surface = window.get_surface() + graphik = Graphik(surface) + + while window.should_continue(): + # ... existing drawing code ... + window.tick(60) # Optional: add frame rate limiting + """ + displayWidth = 800 + displayHeight = 800 + + # Create window using RenderWindow + window = RenderWindow("Visualizing Environment With Random Colors", displayWidth, displayHeight) + surface = window.get_surface() + graphik = Graphik(surface) + + # Example: drawing random rectangles (simulating environment visualization) + white = (255, 255, 255) + + while window.should_continue(): + surface.fill(white) + + # Draw some example content + for i in range(10): + for j in range(10): + red = random.randrange(50, 200) + green = random.randrange(50, 200) + blue = random.randrange(50, 200) + x = i * (displayWidth / 10) + y = j * (displayHeight / 10) + graphik.drawRectangle(x, y, displayWidth / 10, displayHeight / 10, (red, green, blue)) + + pygame.display.update() + window.tick(60) # Limit to 60 FPS + + pygame.quit() + + +if __name__ == "__main__": + print("RenderWindow Usage Examples") + print("=" * 60) + print("1. Simple example") + print("2. Event handler example") + print("3. Integration example (simulates main.py refactoring)") + print() + choice = input("Select example (1-3): ").strip() + + if choice == "1": + simple_example() + elif choice == "2": + event_handler_example() + elif choice == "3": + integration_example() + else: + print("Invalid choice. Running integration example by default.") + integration_example()