v0.9.16
This commit is contained in:
114
imageviewer.py
114
imageviewer.py
@@ -419,11 +419,22 @@ class FaceCanvas(QLabel):
|
||||
self.edit_handle = None
|
||||
self.edit_start_rect = QRect()
|
||||
self.resize_margin = 8
|
||||
|
||||
# Zoom indicator
|
||||
self.zoom_indicator_point = None
|
||||
self.zoom_indicator_timer = QTimer(self)
|
||||
self.zoom_indicator_timer.setSingleShot(True)
|
||||
self.zoom_indicator_timer.setInterval(500) # Show for 500ms
|
||||
self.zoom_indicator_timer.timeout.connect(self._clear_zoom_indicator)
|
||||
self.crop_rect = QRect()
|
||||
self.crop_handle = None
|
||||
self.crop_start_pos = QPoint()
|
||||
self.crop_start_rect = QRect()
|
||||
|
||||
def _clear_zoom_indicator(self):
|
||||
self.zoom_indicator_point = None
|
||||
self.update()
|
||||
|
||||
def map_from_source(self, face_data):
|
||||
"""Maps original normalized face data to current canvas QRect."""
|
||||
nx = face_data.get('x', 0)
|
||||
@@ -623,6 +634,16 @@ class FaceCanvas(QLabel):
|
||||
painter.drawRect(pt.x() - offset, pt.y() - offset,
|
||||
handle_size, handle_size)
|
||||
|
||||
# Draw zoom indicator
|
||||
if self.zoom_indicator_point:
|
||||
painter.setPen(QPen(QColor(255, 255, 0), 2)) # Yellow crosshair
|
||||
painter.drawLine(self.zoom_indicator_point.x() - 10,
|
||||
self.zoom_indicator_point.y(),
|
||||
self.zoom_indicator_point.x() + 10,
|
||||
self.zoom_indicator_point.y())
|
||||
painter.drawLine(self.zoom_indicator_point.x(), self.zoom_indicator_point.y() - 10,
|
||||
self.zoom_indicator_point.x(), self.zoom_indicator_point.y() + 10)
|
||||
|
||||
def _hit_test(self, pos):
|
||||
"""Determines if the mouse is over a name, handle, or body."""
|
||||
if not self.controller.show_faces:
|
||||
@@ -1122,18 +1143,59 @@ class ZoomManager(QObject):
|
||||
super().__init__(viewer)
|
||||
self.viewer = viewer
|
||||
|
||||
def zoom(self, factor, reset=False):
|
||||
"""Applies zoom to the image."""
|
||||
def zoom(self, factor=1.1, reset=False, focus_point=None, absolute_factor=None):
|
||||
"""Applies zoom to the image, centering on focus_point if provided."""
|
||||
if not self.viewer.controller or self.viewer.controller.pixmap_original.isNull():
|
||||
return
|
||||
|
||||
c_point = None
|
||||
|
||||
if reset:
|
||||
self.viewer.controller.zoom_factor = 1.0
|
||||
self.viewer.update_view(resize_win=True)
|
||||
if self.viewer.canvas:
|
||||
c_point = self.viewer.canvas.rect().center()
|
||||
elif absolute_factor is not None: # New: set absolute zoom factor
|
||||
self.viewer.controller.zoom_factor = absolute_factor
|
||||
self.viewer.update_view(resize_win=False) # Don't resize window for sync zoom
|
||||
if focus_point is not None and self.viewer.canvas:
|
||||
scroll_area = self.viewer.scroll_area
|
||||
viewport = scroll_area.viewport()
|
||||
v_point = viewport.mapFrom(self.viewer, focus_point)
|
||||
c_point = self.viewer.canvas.mapFrom(viewport, v_point)
|
||||
else:
|
||||
# 1. Determinar el punto de enfoque en coordenadas del viewport
|
||||
scroll_area = self.viewer.scroll_area
|
||||
viewport = scroll_area.viewport()
|
||||
|
||||
if focus_point is None:
|
||||
v_point = viewport.rect().center()
|
||||
else:
|
||||
# focus_point es relativo al widget self.viewer (ImageViewer o ImagePane)
|
||||
v_point = viewport.mapFrom(self.viewer, focus_point)
|
||||
|
||||
# 2. Mapear el punto de enfoque a coordenadas del canvas antes del zoom
|
||||
c_point = self.viewer.canvas.mapFrom(viewport, v_point)
|
||||
|
||||
self.viewer.controller.zoom_factor *= factor
|
||||
self.viewer.update_view(resize_win=True)
|
||||
# Aplicar la actualización (esto redimensiona el canvas)
|
||||
self.viewer.update_view(resize_win=(not self.viewer.isFullScreen()))
|
||||
|
||||
# 3. Ajustar las barras de desplazamiento para mantener el píxel bajo el cursor
|
||||
scroll_area.horizontalScrollBar().setValue(
|
||||
int(c_point.x() * factor - v_point.x()))
|
||||
scroll_area.verticalScrollBar().setValue(
|
||||
int(c_point.y() * factor - v_point.y()))
|
||||
|
||||
# Notify the main window that the image (and possibly index) has changed
|
||||
# so it can update its selection.
|
||||
self.viewer.index_changed.emit(self.viewer.controller.index)
|
||||
|
||||
if focus_point is not None and self.viewer.canvas:
|
||||
self.viewer.canvas.zoom_indicator_point = c_point
|
||||
self.viewer.canvas.zoom_indicator_timer.start()
|
||||
self.viewer.canvas.update()
|
||||
|
||||
self.zoomed.emit(self.viewer.controller.zoom_factor)
|
||||
if hasattr(self.viewer, 'sync_filmstrip_selection'):
|
||||
self.viewer.sync_filmstrip_selection(self.viewer.controller.index)
|
||||
@@ -1645,16 +1707,21 @@ class ImageViewer(QWidget):
|
||||
if pane != self.active_pane:
|
||||
pane.controller.zoom_factor = factor
|
||||
pane.update_view(resize_win=False)
|
||||
# Re-apply relative scroll after zoom changes bounds
|
||||
if self.active_pane:
|
||||
h_bar = self.active_pane.scroll_area.horizontalScrollBar()
|
||||
v_bar = self.active_pane.scroll_area.verticalScrollBar()
|
||||
h_max = h_bar.maximum()
|
||||
v_max = v_bar.maximum()
|
||||
if h_max > 0 or v_max > 0:
|
||||
x_pct = h_bar.value() / h_max if h_max > 0 else 0
|
||||
y_pct = v_bar.value() / v_max if v_max > 0 else 0
|
||||
pane.set_scroll_relative(x_pct, y_pct)
|
||||
|
||||
# Re-apply relative scroll after zoom changes bounds
|
||||
# We defer this to the next event loop iteration to ensure
|
||||
# that QScrollArea has updated its scrollbar maximums.
|
||||
if self.active_pane:
|
||||
h_bar = self.active_pane.scroll_area.horizontalScrollBar()
|
||||
v_bar = self.active_pane.scroll_area.verticalScrollBar()
|
||||
h_max = h_bar.maximum()
|
||||
v_max = v_bar.maximum()
|
||||
x_pct = h_bar.value() / h_max if h_max > 0 else 0
|
||||
y_pct = v_bar.value() / v_max if v_max > 0 else 0
|
||||
|
||||
for pane in self.panes:
|
||||
if pane != self.active_pane:
|
||||
QTimer.singleShot(0, lambda p=pane, x=x_pct, y=y_pct: p.set_scroll_relative(x, y))
|
||||
|
||||
def update_grid_layout(self):
|
||||
# Clear layout
|
||||
@@ -1693,6 +1760,8 @@ class ImageViewer(QWidget):
|
||||
for i in range(count - current_panes):
|
||||
new_idx = (start_idx + i + 1) % len(img_list)
|
||||
pane = self.add_pane(img_list, new_idx, None, 0) # Metadata will load
|
||||
if self.panes_linked and self.active_pane:
|
||||
pane.controller.zoom_factor = self.active_pane.controller.zoom_factor
|
||||
pane.load_and_fit_image()
|
||||
else:
|
||||
# Remove panes (keep active if possible, else keep first)
|
||||
@@ -1710,10 +1779,13 @@ class ImageViewer(QWidget):
|
||||
# sizing
|
||||
QTimer.singleShot(
|
||||
0, lambda: self.active_pane.update_view(resize_win=True))
|
||||
self.adjustSize() # Ajustar el tamaño de la ventana después de añadir/eliminar paneles
|
||||
|
||||
def toggle_link_panes(self):
|
||||
"""Toggles the synchronized zoom/scroll for comparison mode."""
|
||||
self.panes_linked = not self.panes_linked
|
||||
if self.panes_linked and self.active_pane:
|
||||
self._sync_zoom(self.active_pane.controller.zoom_factor)
|
||||
self.update_status_bar()
|
||||
|
||||
def update_highlight(self):
|
||||
@@ -1731,6 +1803,9 @@ class ImageViewer(QWidget):
|
||||
|
||||
def reset_inactivity_timer(self):
|
||||
"""Resets the inactivity timer and restores controls visibility."""
|
||||
if self.active_pane and self.active_pane.canvas:
|
||||
self.active_pane.canvas._clear_zoom_indicator()
|
||||
|
||||
if self.isFullScreen():
|
||||
self.unsetCursor()
|
||||
if self.main_win and self.main_win.show_viewer_status_bar:
|
||||
@@ -2110,8 +2185,12 @@ class ImageViewer(QWidget):
|
||||
available_h -= self.status_bar_container.sizeHint().height()
|
||||
should_resize = True
|
||||
|
||||
self.zoom_manager.calculate_initial_zoom(available_w, available_h,
|
||||
self.isFullScreen())
|
||||
if self.panes_linked and self.active_pane and pane != self.active_pane:
|
||||
# Inherit zoom from active pane instead of recalculating
|
||||
pane.controller.zoom_factor = self.active_pane.controller.zoom_factor
|
||||
else:
|
||||
pane.zoom_manager.calculate_initial_zoom(available_w, available_h,
|
||||
self.isFullScreen())
|
||||
|
||||
self.update_view(resize_win=should_resize)
|
||||
else:
|
||||
@@ -3219,10 +3298,11 @@ class ImageViewer(QWidget):
|
||||
self.reset_inactivity_timer()
|
||||
if event.modifiers() & Qt.ControlModifier:
|
||||
# Zoom with Ctrl + Wheel
|
||||
focus_pos = event.position().toPoint()
|
||||
if event.angleDelta().y() > 0:
|
||||
self.zoom_manager.zoom(1.1)
|
||||
self.zoom_manager.zoom(1.1, focus_point=focus_pos)
|
||||
else:
|
||||
self.zoom_manager.zoom(0.9)
|
||||
self.zoom_manager.zoom(0.9, focus_point=focus_pos)
|
||||
else:
|
||||
# Navigate next/previous based on configurable speed
|
||||
speed = APP_CONFIG.get("viewer_wheel_speed", VIEWER_WHEEL_SPEED_DEFAULT)
|
||||
|
||||
Reference in New Issue
Block a user