Duncan Tourolle 7f32858baf
All checks were successful
Python CI / test (push) Successful in 1m7s
Lint / lint (push) Successful in 1m11s
Tests / test (3.10) (push) Successful in 50s
Tests / test (3.11) (push) Successful in 51s
Tests / test (3.9) (push) Successful in 47s
big refactor to use mixin architecture
2025-11-11 10:35:24 +01:00

83 lines
2.7 KiB
Python

"""
Image pan mixin for GLWidget - handles panning images within frames
"""
from typing import Optional, Tuple
from pyPhotoAlbum.models import ImageData
class ImagePanMixin:
"""
Mixin providing image panning functionality.
This mixin handles Control+drag to pan an image within its frame by
adjusting the crop_info property.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Image pan state (for panning image within frame with Control key)
self.image_pan_mode: bool = False # True when Control+dragging an ImageData element
self.image_pan_start_crop: Optional[Tuple[float, float, float, float]] = None # Starting crop_info
def _handle_image_pan_move(self, x: float, y: float, element: ImageData):
"""
Handle image panning within a frame during mouse move.
Args:
x: Current mouse X position in screen coordinates
y: Current mouse Y position in screen coordinates
element: The ImageData element being panned
"""
if not self.image_pan_mode or not isinstance(element, ImageData):
return
if not self.drag_start_pos:
return
# Calculate mouse movement in screen pixels
screen_dx = x - self.drag_start_pos[0]
screen_dy = y - self.drag_start_pos[1]
# Get element size in page-local coordinates
elem_w, elem_h = element.size
# Convert screen movement to normalized crop coordinates
# Negative because moving mouse right should pan image left (show more of right side)
# Scale by zoom level and element size
crop_dx = -screen_dx / (elem_w * self.zoom_level)
crop_dy = -screen_dy / (elem_h * self.zoom_level)
# Get starting crop info
start_crop = self.image_pan_start_crop
if not start_crop:
start_crop = (0, 0, 1, 1)
# Calculate new crop_info
crop_width = start_crop[2] - start_crop[0]
crop_height = start_crop[3] - start_crop[1]
new_x_min = start_crop[0] + crop_dx
new_y_min = start_crop[1] + crop_dy
new_x_max = new_x_min + crop_width
new_y_max = new_y_min + crop_height
# Clamp to valid range (0-1) to prevent panning beyond image boundaries
if new_x_min < 0:
new_x_min = 0
new_x_max = crop_width
if new_x_max > 1:
new_x_max = 1
new_x_min = 1 - crop_width
if new_y_min < 0:
new_y_min = 0
new_y_max = crop_height
if new_y_max > 1:
new_y_max = 1
new_y_min = 1 - crop_height
# Update element's crop_info
element.crop_info = (new_x_min, new_y_min, new_x_max, new_y_max)