""" Hardware Abstraction Layer (HAL) interface for DReader. This module defines the abstract interface that platform-specific display/input implementations must provide. The HAL separates the core e-reader logic from platform-specific hardware details (display, touch input, buttons, etc.). """ from abc import ABC, abstractmethod from typing import AsyncIterator, Optional from PIL import Image from .gesture import TouchEvent class DisplayHAL(ABC): """ Abstract interface for display and input hardware. Platform-specific implementations should subclass this and provide concrete implementations for all abstract methods. The HAL is responsible for: - Displaying images on the screen - Capturing touch/click input and converting to TouchEvent - Hardware-specific features (brightness, sleep, etc.) All methods are async to support non-blocking I/O. """ @abstractmethod async def show_image(self, image: Image.Image): """ Display a PIL Image on the screen. Args: image: PIL Image to display This method should handle: - Converting image format if needed for the display - Scaling/cropping if image size doesn't match display - Updating the physical display hardware """ pass @abstractmethod async def get_touch_event(self) -> Optional[TouchEvent]: """ Wait for and return the next touch event. Returns: TouchEvent if available, None if no event (non-blocking mode) This method should: - Read from touch hardware - Convert raw coordinates to TouchEvent - Detect gesture type (tap, swipe, etc.) - Return None immediately if no event available Note: For blocking behavior, implement a loop that awaits this method in the main event loop. """ pass @abstractmethod async def set_brightness(self, level: int): """ Set display brightness. Args: level: Brightness level (0-10, where 0=dimmest, 10=brightest) Platform implementations should map this to their hardware's actual brightness range. """ pass async def initialize(self): """ Initialize the display hardware. This optional method is called once before the application starts. Override to perform platform-specific initialization. """ pass async def cleanup(self): """ Clean up display hardware resources. This optional method is called during application shutdown. Override to perform platform-specific cleanup. """ pass async def show_message(self, message: str, duration: float = 2.0): """ Display a text message (for loading screens, errors, etc.). Args: message: Text message to display duration: How long to show message (seconds) Default implementation creates a simple text image. Override for platform-specific message display. """ from PIL import ImageDraw, ImageFont # Create simple text image img = Image.new('RGB', (800, 1200), color=(255, 255, 255)) draw = ImageDraw.Draw(img) # Try to use a decent font, fall back to default try: font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32) except: font = ImageFont.load_default() # Draw centered text bbox = draw.textbbox((0, 0), message, font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] x = (800 - text_width) // 2 y = (1200 - text_height) // 2 draw.text((x, y), message, fill=(0, 0, 0), font=font) await self.show_image(img) # Wait for duration if duration > 0: import asyncio await asyncio.sleep(duration) class KeyboardInputHAL(ABC): """ Optional abstract interface for keyboard input. This is separate from DisplayHAL to support platforms that have both touch and keyboard input (e.g., desktop testing). """ @abstractmethod async def get_key_event(self) -> Optional[str]: """ Get the next keyboard event. Returns: Key name as string (e.g., "up", "down", "enter", "q") None if no key event available """ pass class EventLoopHAL(DisplayHAL): """ Extended HAL interface that provides its own event loop. Some platforms (e.g., Pygame, Qt) have their own event loop that must be used. This interface allows the HAL to run the main loop and call back to the application. Usage: hal = MyEventLoopHAL() app = DReaderApplication(AppConfig(display_hal=hal, ...)) await hal.run_event_loop(app) """ @abstractmethod async def run_event_loop(self, app): """ Run the platform's event loop. Args: app: DReaderApplication instance to send events to This method should: 1. Initialize the display 2. Call app.start() 3. Enter event loop 4. Call app.handle_touch(event) for each event 5. Handle quit events and call app.shutdown() """ pass