303 lines
11 KiB
Python
303 lines
11 KiB
Python
"""
|
|
Rendering mixin for GLWidget - handles OpenGL rendering
|
|
"""
|
|
|
|
from OpenGL.GL import *
|
|
from PyQt6.QtGui import QPainter, QFont, QColor, QPen
|
|
from PyQt6.QtCore import Qt, QRectF
|
|
from pyPhotoAlbum.models import TextBoxData
|
|
|
|
|
|
class RenderingMixin:
|
|
"""
|
|
Mixin providing OpenGL rendering functionality.
|
|
|
|
This mixin handles rendering pages, elements, selection handles,
|
|
and text overlays.
|
|
"""
|
|
|
|
def paintGL(self):
|
|
"""Main rendering function - renders all pages vertically"""
|
|
from pyPhotoAlbum.page_renderer import PageRenderer
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
glLoadIdentity()
|
|
|
|
main_window = self.window()
|
|
if not hasattr(main_window, 'project') or not main_window.project or not main_window.project.pages:
|
|
return
|
|
|
|
# Set initial zoom if not done yet
|
|
if not self.initial_zoom_set:
|
|
self.zoom_level = self._calculate_fit_to_screen_zoom()
|
|
self.initial_zoom_set = True
|
|
|
|
dpi = main_window.project.working_dpi
|
|
|
|
# Calculate page positions with ghosts
|
|
page_positions = self._get_page_positions()
|
|
|
|
# Store page renderers for later use
|
|
self._page_renderers = []
|
|
|
|
# Left margin for page rendering
|
|
PAGE_MARGIN = 50
|
|
|
|
# Render all pages
|
|
for page_info in page_positions:
|
|
page_type, page_or_ghost, y_offset = page_info
|
|
|
|
if page_type == 'page':
|
|
page = page_or_ghost
|
|
page_width_mm, page_height_mm = page.layout.size
|
|
|
|
screen_x = PAGE_MARGIN + self.pan_offset[0]
|
|
screen_y = (y_offset * self.zoom_level) + self.pan_offset[1]
|
|
|
|
renderer = PageRenderer(
|
|
page_width_mm=page_width_mm,
|
|
page_height_mm=page_height_mm,
|
|
screen_x=screen_x,
|
|
screen_y=screen_y,
|
|
dpi=dpi,
|
|
zoom=self.zoom_level
|
|
)
|
|
|
|
self._page_renderers.append((renderer, page))
|
|
|
|
renderer.begin_render()
|
|
page.layout.render(dpi=dpi)
|
|
renderer.end_render()
|
|
|
|
elif page_type == 'ghost':
|
|
ghost = page_or_ghost
|
|
ghost_width_mm, ghost_height_mm = ghost.page_size
|
|
|
|
screen_x = PAGE_MARGIN + self.pan_offset[0]
|
|
screen_y = (y_offset * self.zoom_level) + self.pan_offset[1]
|
|
|
|
renderer = PageRenderer(
|
|
page_width_mm=ghost_width_mm,
|
|
page_height_mm=ghost_height_mm,
|
|
screen_x=screen_x,
|
|
screen_y=screen_y,
|
|
dpi=dpi,
|
|
zoom=self.zoom_level
|
|
)
|
|
|
|
self._render_ghost_page(ghost, renderer)
|
|
|
|
# Update PageRenderer references for selected elements
|
|
for element in self.selected_elements:
|
|
if hasattr(element, '_parent_page'):
|
|
for renderer, page in self._page_renderers:
|
|
if page is element._parent_page:
|
|
element._page_renderer = renderer
|
|
break
|
|
|
|
# Draw selection handles
|
|
if self.selected_element:
|
|
self._draw_selection_handles()
|
|
|
|
# Render text overlays
|
|
self._render_text_overlays()
|
|
|
|
def _draw_selection_handles(self):
|
|
"""Draw selection handles around the selected element"""
|
|
if not self.selected_element:
|
|
return
|
|
|
|
main_window = self.window()
|
|
if not hasattr(main_window, 'project') or not main_window.project or not main_window.project.pages:
|
|
return
|
|
|
|
if not hasattr(self.selected_element, '_page_renderer'):
|
|
return
|
|
|
|
renderer = self.selected_element._page_renderer
|
|
|
|
elem_x, elem_y = self.selected_element.position
|
|
elem_w, elem_h = self.selected_element.size
|
|
handle_size = 8
|
|
|
|
x, y = renderer.page_to_screen(elem_x, elem_y)
|
|
w = elem_w * renderer.zoom
|
|
h = elem_h * renderer.zoom
|
|
|
|
center_x = x + w / 2
|
|
center_y = y + h / 2
|
|
|
|
if self.selected_element.rotation != 0:
|
|
glPushMatrix()
|
|
glTranslatef(center_x, center_y, 0)
|
|
glRotatef(self.selected_element.rotation, 0, 0, 1)
|
|
glTranslatef(-w / 2, -h / 2, 0)
|
|
x, y = 0, 0
|
|
|
|
if self.rotation_mode:
|
|
glColor3f(1.0, 0.5, 0.0)
|
|
else:
|
|
glColor3f(0.0, 0.5, 1.0)
|
|
|
|
glLineWidth(2.0)
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex2f(x, y)
|
|
glVertex2f(x + w, y)
|
|
glVertex2f(x + w, y + h)
|
|
glVertex2f(x, y + h)
|
|
glEnd()
|
|
glLineWidth(1.0)
|
|
|
|
if self.rotation_mode:
|
|
import math
|
|
handle_radius = 6
|
|
handles = [(x, y), (x + w, y), (x, y + h), (x + w, y + h)]
|
|
|
|
glColor3f(1.0, 0.5, 0.0)
|
|
glBegin(GL_TRIANGLE_FAN)
|
|
glVertex2f(center_x, center_y)
|
|
for angle in range(0, 361, 10):
|
|
rad = math.radians(angle)
|
|
hx = center_x + 3 * math.cos(rad)
|
|
hy = center_y + 3 * math.sin(rad)
|
|
glVertex2f(hx, hy)
|
|
glEnd()
|
|
|
|
for hx, hy in handles:
|
|
glColor3f(1.0, 1.0, 1.0)
|
|
glBegin(GL_TRIANGLE_FAN)
|
|
glVertex2f(hx, hy)
|
|
for angle in range(0, 361, 30):
|
|
rad = math.radians(angle)
|
|
px = hx + handle_radius * math.cos(rad)
|
|
py = hy + handle_radius * math.sin(rad)
|
|
glVertex2f(px, py)
|
|
glEnd()
|
|
|
|
glColor3f(1.0, 0.5, 0.0)
|
|
glBegin(GL_LINE_LOOP)
|
|
for angle in range(0, 361, 30):
|
|
rad = math.radians(angle)
|
|
px = hx + handle_radius * math.cos(rad)
|
|
py = hy + handle_radius * math.sin(rad)
|
|
glVertex2f(px, py)
|
|
glEnd()
|
|
else:
|
|
handles = [
|
|
(x - handle_size/2, y - handle_size/2),
|
|
(x + w - handle_size/2, y - handle_size/2),
|
|
(x - handle_size/2, y + h - handle_size/2),
|
|
(x + w - handle_size/2, y + h - handle_size/2),
|
|
]
|
|
|
|
glColor3f(1.0, 1.0, 1.0)
|
|
for hx, hy in handles:
|
|
glBegin(GL_QUADS)
|
|
glVertex2f(hx, hy)
|
|
glVertex2f(hx + handle_size, hy)
|
|
glVertex2f(hx + handle_size, hy + handle_size)
|
|
glVertex2f(hx, hy + handle_size)
|
|
glEnd()
|
|
|
|
glColor3f(0.0, 0.5, 1.0)
|
|
for hx, hy in handles:
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex2f(hx, hy)
|
|
glVertex2f(hx + handle_size, hy)
|
|
glVertex2f(hx + handle_size, hy + handle_size)
|
|
glVertex2f(hx, hy + handle_size)
|
|
glEnd()
|
|
|
|
if self.selected_element.rotation != 0:
|
|
glPopMatrix()
|
|
|
|
def _render_text_overlays(self):
|
|
"""Render text content for TextBoxData elements using QPainter overlay"""
|
|
if not hasattr(self, '_page_renderers') or not self._page_renderers:
|
|
return
|
|
|
|
painter = QPainter(self)
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
|
|
|
|
try:
|
|
for renderer, page in self._page_renderers:
|
|
text_elements = [elem for elem in page.layout.elements if isinstance(elem, TextBoxData)]
|
|
|
|
for element in text_elements:
|
|
if not element.text_content:
|
|
continue
|
|
|
|
x, y = element.position
|
|
w, h = element.size
|
|
|
|
screen_x, screen_y = renderer.page_to_screen(x, y)
|
|
screen_w = w * renderer.zoom
|
|
screen_h = h * renderer.zoom
|
|
|
|
font_family = element.font_settings.get('family', 'Arial')
|
|
font_size = int(element.font_settings.get('size', 12) * renderer.zoom)
|
|
font = QFont(font_family, font_size)
|
|
painter.setFont(font)
|
|
|
|
font_color = element.font_settings.get('color', (0, 0, 0))
|
|
if all(isinstance(c, int) and c > 1 for c in font_color):
|
|
color = QColor(*font_color)
|
|
else:
|
|
color = QColor(int(font_color[0] * 255), int(font_color[1] * 255), int(font_color[2] * 255))
|
|
painter.setPen(QPen(color))
|
|
|
|
if element.rotation != 0:
|
|
painter.save()
|
|
center_x = screen_x + screen_w / 2
|
|
center_y = screen_y + screen_h / 2
|
|
painter.translate(center_x, center_y)
|
|
painter.rotate(element.rotation)
|
|
painter.translate(-screen_w / 2, -screen_h / 2)
|
|
rect = QRectF(0, 0, screen_w, screen_h)
|
|
else:
|
|
rect = QRectF(screen_x, screen_y, screen_w, screen_h)
|
|
|
|
alignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
|
if element.alignment == 'center':
|
|
alignment = Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop
|
|
elif element.alignment == 'right':
|
|
alignment = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
|
|
|
|
text_flags = Qt.TextFlag.TextWordWrap
|
|
|
|
painter.drawText(rect, int(alignment | text_flags), element.text_content)
|
|
|
|
if element.rotation != 0:
|
|
painter.restore()
|
|
|
|
finally:
|
|
painter.end()
|
|
|
|
def _render_ghost_page(self, ghost_data, renderer):
|
|
"""Render a ghost page using PageRenderer"""
|
|
renderer.begin_render()
|
|
ghost_data.render()
|
|
renderer.end_render()
|
|
|
|
painter = QPainter(self)
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
|
|
|
|
try:
|
|
px, py, pw, ph = ghost_data.get_page_rect()
|
|
|
|
screen_x, screen_y = renderer.page_to_screen(px, py)
|
|
screen_w = pw * renderer.zoom
|
|
screen_h = ph * renderer.zoom
|
|
|
|
font = QFont("Arial", int(16 * renderer.zoom), QFont.Weight.Bold)
|
|
painter.setFont(font)
|
|
painter.setPen(QColor(120, 120, 120))
|
|
|
|
rect = QRectF(screen_x, screen_y, screen_w, screen_h)
|
|
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "Click to Add Page")
|
|
|
|
finally:
|
|
painter.end()
|