All checks were successful
Python CI / test (push) Successful in 1m20s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m27s
Tests / test (3.12) (push) Successful in 2m25s
Tests / test (3.13) (push) Successful in 2m52s
Tests / test (3.14) (push) Successful in 1m9s
157 lines
5.2 KiB
Python
157 lines
5.2 KiB
Python
"""
|
|
Page renderer helper for pyPhotoAlbum
|
|
|
|
This module provides a unified coordinate system for rendering pages and their elements.
|
|
All coordinate transformations are centralized here to ensure consistency.
|
|
|
|
Coordinate Systems:
|
|
- Page-local: Coordinates in millimeters relative to the page's top-left corner
|
|
- Pixel: Coordinates in pixels at working DPI
|
|
- Screen: Coordinates on screen after applying zoom and pan
|
|
"""
|
|
|
|
from typing import Tuple, Optional
|
|
from pyPhotoAlbum.gl_imports import glPushMatrix, glPopMatrix, glScalef, glTranslatef
|
|
|
|
|
|
class PageRenderer:
|
|
"""
|
|
Handles rendering and coordinate transformations for a single page.
|
|
|
|
This class encapsulates all coordinate transformations needed to render
|
|
a page and its elements consistently.
|
|
"""
|
|
|
|
def __init__(
|
|
self, page_width_mm: float, page_height_mm: float, screen_x: float, screen_y: float, dpi: int, zoom: float
|
|
):
|
|
"""
|
|
Initialize a page renderer.
|
|
|
|
Args:
|
|
page_width_mm: Page width in millimeters
|
|
page_height_mm: Page height in millimeters
|
|
screen_x: X position on screen where page should be rendered
|
|
screen_y: Y position on screen where page should be rendered
|
|
dpi: Working DPI for converting mm to pixels
|
|
zoom: Current zoom level
|
|
"""
|
|
self.page_width_mm = page_width_mm
|
|
self.page_height_mm = page_height_mm
|
|
self.screen_x = screen_x
|
|
self.screen_y = screen_y
|
|
self.dpi = dpi
|
|
self.zoom = zoom
|
|
|
|
# Calculate page dimensions in pixels
|
|
self.page_width_px = page_width_mm * dpi / 25.4
|
|
self.page_height_px = page_height_mm * dpi / 25.4
|
|
|
|
# Calculate screen dimensions (with zoom applied)
|
|
self.screen_width = self.page_width_px * zoom
|
|
self.screen_height = self.page_height_px * zoom
|
|
|
|
def page_to_screen(self, page_x: float, page_y: float) -> Tuple[float, float]:
|
|
"""
|
|
Convert page-local coordinates (in pixels) to screen coordinates.
|
|
|
|
Args:
|
|
page_x: X coordinate in page-local space (pixels)
|
|
page_y: Y coordinate in page-local space (pixels)
|
|
|
|
Returns:
|
|
Tuple of (screen_x, screen_y)
|
|
"""
|
|
screen_x = self.screen_x + page_x * self.zoom
|
|
screen_y = self.screen_y + page_y * self.zoom
|
|
return (screen_x, screen_y)
|
|
|
|
def screen_to_page(self, screen_x: float, screen_y: float) -> Tuple[float, float]:
|
|
"""
|
|
Convert screen coordinates to page-local coordinates (in pixels).
|
|
|
|
Args:
|
|
screen_x: X coordinate in screen space
|
|
screen_y: Y coordinate in screen space
|
|
|
|
Returns:
|
|
Tuple of (page_x, page_y) in pixels, or None if outside page bounds
|
|
"""
|
|
page_x = (screen_x - self.screen_x) / self.zoom
|
|
page_y = (screen_y - self.screen_y) / self.zoom
|
|
return (page_x, page_y)
|
|
|
|
def is_point_in_page(self, screen_x: float, screen_y: float) -> bool:
|
|
"""
|
|
Check if a screen coordinate is within the page bounds.
|
|
|
|
Args:
|
|
screen_x: X coordinate in screen space
|
|
screen_y: Y coordinate in screen space
|
|
|
|
Returns:
|
|
True if the point is within the page bounds
|
|
"""
|
|
return (
|
|
self.screen_x <= screen_x <= self.screen_x + self.screen_width
|
|
and self.screen_y <= screen_y <= self.screen_y + self.screen_height
|
|
)
|
|
|
|
def get_sub_page_at(self, screen_x: float, is_facing_page: bool) -> Optional[str]:
|
|
"""
|
|
For facing page spreads, determine if mouse is on left or right page.
|
|
|
|
Args:
|
|
screen_x: X coordinate in screen space
|
|
is_facing_page: Whether this is a facing page spread
|
|
|
|
Returns:
|
|
'left' or 'right' for facing pages, None for single pages
|
|
"""
|
|
if not is_facing_page:
|
|
return None
|
|
|
|
# Calculate the center line of the spread
|
|
center_x = self.screen_x + self.screen_width / 2
|
|
|
|
if screen_x < center_x:
|
|
return "left"
|
|
else:
|
|
return "right"
|
|
|
|
def begin_render(self):
|
|
"""
|
|
Set up OpenGL transformations for rendering this page.
|
|
Call this before rendering page content.
|
|
"""
|
|
glPushMatrix()
|
|
# Apply zoom
|
|
glScalef(self.zoom, self.zoom, 1.0)
|
|
# Translate to page position (in zoomed coordinates)
|
|
glTranslatef(self.screen_x / self.zoom, self.screen_y / self.zoom, 0.0)
|
|
|
|
def end_render(self):
|
|
"""
|
|
Clean up OpenGL transformations after rendering this page.
|
|
Call this after rendering page content.
|
|
"""
|
|
glPopMatrix()
|
|
|
|
def get_page_bounds_screen(self) -> Tuple[float, float, float, float]:
|
|
"""
|
|
Get the page bounds in screen coordinates.
|
|
|
|
Returns:
|
|
Tuple of (x, y, width, height) in screen space
|
|
"""
|
|
return (self.screen_x, self.screen_y, self.screen_width, self.screen_height)
|
|
|
|
def get_page_bounds_page(self) -> Tuple[float, float, float, float]:
|
|
"""
|
|
Get the page bounds in page-local coordinates.
|
|
|
|
Returns:
|
|
Tuple of (x, y, width, height) in page-local space (pixels)
|
|
"""
|
|
return (0, 0, self.page_width_px, self.page_height_px)
|