Skip to content
Snippets Groups Projects
Commit 7f9fa646 authored by s224389's avatar s224389
Browse files

Added ability to drag points around. Also implemented point class for better tracking.

parent b69dbdf7
No related branches found
No related tags found
No related merge requests found
......@@ -8,6 +8,42 @@ from PyQt5.QtWidgets import (
)
from PyQt5.QtGui import QPixmap, QPen, QBrush
from PyQt5.QtCore import Qt, QRectF
import math
class PointItem(QGraphicsEllipseItem):
"""
Represents a single draggable point on the scene.
"""
def __init__(self, x, y, radius=4, parent=None):
super().__init__(x - radius, y - radius, 2*radius, 2*radius, parent)
self._x = x
self._y = y
self._r = radius
self.setBrush(QBrush(Qt.red))
self.setPen(QPen(Qt.red))
def get_pos(self):
"""Return the (x, y) of this point in scene coords."""
return (self._x, self._y)
def set_pos(self, x, y):
"""
Move point to (x,y).
This also updates the ellipse rectangle so the visual dot moves.
"""
self._x = x
self._y = y
self.setRect(x - self._r, y - self._r, 2*self._r, 2*self._r)
def distance_to(self, x_other, y_other):
"""Euclidean distance from this point to arbitrary (x_other, y_other)."""
dx = self._x - x_other
dy = self._y - y_other
return math.sqrt(dx*dx + dy*dy)
class ImageGraphicsView(QGraphicsView):
......@@ -30,7 +66,6 @@ class ImageGraphicsView(QGraphicsView):
# Points and dot items
self.points = []
self.point_items = []
self.editor_mode = False
self.dot_radius = 4
......@@ -43,6 +78,8 @@ class ImageGraphicsView(QGraphicsView):
self._press_view_pos = None
self._drag_threshold = 5
self._was_dragging = False
self._dragging_idx = None
self._drag_offset = (0, 0)
def load_image(self, image_path):
"""Load an image and fit it in the view."""
......@@ -56,7 +93,6 @@ class ImageGraphicsView(QGraphicsView):
# Clear existing dots from previous image
self.points.clear()
self._clear_point_items()
# Reset transform then fit image in view
self.resetTransform()
......@@ -72,7 +108,28 @@ class ImageGraphicsView(QGraphicsView):
self._was_dragging = False
self._press_view_pos = event.pos()
# Switch to closed-hand cursor while left mouse is down
if self.editor_mode:
# Check if we're near a point
idx = self._find_point_near(event.pos(), threshold=10)
if idx is not None:
# Start dragging that point
self._dragging_idx = idx
# Compute offset so point doesn't jump if clicked off-center
scene_pos = self.mapToScene(event.pos())
px, py = self.points[idx].get_pos()
self._drag_offset = (scene_pos.x() - px, scene_pos.y() - py)
# Temporarily disable QGraphicsView's panning
self.setDragMode(QGraphicsView.NoDrag)
self.viewport().setCursor(Qt.ClosedHandCursor)
return
else:
# Not near a point, so we do normal panning
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.viewport().setCursor(Qt.ClosedHandCursor)
else:
# Editor mode is off => always do normal panning
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.viewport().setCursor(Qt.ClosedHandCursor)
elif event.button() == Qt.RightButton:
......@@ -82,41 +139,46 @@ class ImageGraphicsView(QGraphicsView):
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
"""
If movement > _drag_threshold: consider it a drag.
The actual panning is handled by QGraphicsView in ScrollHandDrag mode.
"""
# Check if the mouse is being dragged
if self._dragging_idx is not None:
# Move that point to new coords
scene_pos = self.mapToScene(event.pos())
x_new = scene_pos.x() - self._drag_offset[0]
y_new = scene_pos.y() - self._drag_offset[1]
self.points[self._dragging_idx].set_pos(x_new, y_new)
return # Skip QGraphicsView's panning logic
else:
# Old logic: if movement > threshold -> set _was_dragging = True
if self._mouse_pressed and (event.buttons() & Qt.LeftButton):
# If the mouse moved more than the threshold, consider it a drag
dist = (event.pos() - self._press_view_pos).manhattanLength()
if dist > self._drag_threshold:
self._was_dragging = True
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
"""
After releasing the left button, go back to arrow cursor.
If it wasn't a drag, treat as a click (Editor Mode: add dot).
"""
# Let QGraphicsView handle release first
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton and self._mouse_pressed:
self._mouse_pressed = False
# Always go back to arrow cursor AFTER letting QGraphicsView handle release
self.viewport().setCursor(Qt.ArrowCursor)
if not self._was_dragging:
# It's a click: if editor mode is ON add a dot
if self.editor_mode:
# If we were dragging a point, stop.
if self._dragging_idx is not None:
self._dragging_idx = None
self._drag_offset = (0, 0)
self.setDragMode(QGraphicsView.ScrollHandDrag)
else:
# We were NOT dragging a point => check if it was a click to add a new point
if not self._was_dragging and self.editor_mode:
self._add_point(event.pos())
self._was_dragging = False
def wheelEvent(self, event):
"""Mouse wheel = zoom."""
zoom_in_factor = 1.25
......@@ -136,9 +198,8 @@ class ImageGraphicsView(QGraphicsView):
scene_pos = self.mapToScene(view_pos)
x, y = scene_pos.x(), scene_pos.y()
self.points.append((x, y))
dot = self._create_dot_item(x, y)
self.point_items.append(dot)
dot = PointItem(x, y, radius=self.dot_radius)
self.points.append(dot)
self.scene.addItem(dot)
def _remove_point(self, view_pos):
......@@ -146,22 +207,19 @@ class ImageGraphicsView(QGraphicsView):
scene_pos = self.mapToScene(view_pos)
x_click, y_click = scene_pos.x(), scene_pos.y()
# Define threshold for removing a point
threshold = 10
closest_idx = None
min_dist = float('inf')
# Find the closest point to the click
for i, (x, y) in enumerate(self.points):
dist_sq = (x - x_click)**2 + (y - y_click)**2
if dist_sq < min_dist:
min_dist = dist_sq
for i, point_item in enumerate(self.points):
dist = point_item.distance_to(x_click, y_click)
if dist < min_dist:
min_dist = dist
closest_idx = i
# Remove the closest point if it's within the threshold
if closest_idx is not None and min_dist <= threshold**2:
self.scene.removeItem(self.point_items[closest_idx])
del self.point_items[closest_idx]
# Remove if within threshold
if closest_idx is not None and min_dist <= threshold:
self.scene.removeItem(self.points[closest_idx])
del self.points[closest_idx]
def _create_dot_item(self, x, y):
......@@ -174,9 +232,29 @@ class ImageGraphicsView(QGraphicsView):
def _clear_point_items(self):
"""Remove all dot items from the scene."""
for item in self.point_items:
self.scene.removeItem(item)
self.point_items = []
for p in self.points:
self.scene.removeItem(p)
self.points.clear()
def _find_point_near(self, view_pos, threshold=10):
scene_pos = self.mapToScene(view_pos)
x_click, y_click = scene_pos.x(), scene_pos.y()
closest_idx = None
min_dist = float('inf')
for i, p in enumerate(self.points):
dist = p.distance_to(x_click, y_click)
if dist < min_dist:
min_dist = dist
closest_idx = i
if closest_idx is not None and min_dist <= threshold:
return closest_idx
return None
class MainWindow(QMainWindow):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment