""" 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()