Enhance crop border dragging functionality in VideoEditor class

Refine the crop border dragging logic by introducing mouse button state tracking and adjusting the crop dimensions based on drag direction. The minimum drag distance for crop adjustments has been reduced to improve responsiveness, allowing for more intuitive user interactions when resizing the crop area. This update also clarifies the drag mode for expanding or contracting the crop edges.
This commit is contained in:
2025-12-23 11:11:35 +01:00
parent 88630bbcbc
commit 43feae622e

View File

@@ -72,7 +72,7 @@ class VideoEditor:
# Crop adjustment settings
CROP_SIZE_STEP = 5 # pixels to expand/contract crop
CROP_MIN_SIZE = 10 # minimum crop width/height in pixels
CROP_DRAG_MIN_DISTANCE = 10 # pixels - minimum drag distance before applying crop adjustment
CROP_DRAG_MIN_DISTANCE = 1 # pixels - minimum drag distance before applying crop adjustment
# Motion tracking settings
TRACKING_POINT_THRESHOLD = 10 # pixels for delete/snap radius
@@ -156,7 +156,10 @@ class VideoEditor:
self.crop_border_dragging = False
self.crop_border_drag_start_pos = None # (screen_x, screen_y) when drag started
self.crop_border_drag_start_rect = None # (x, y, w, h) in rotated coords when drag started
self.crop_border_drag_inside = None # True if drag started inside crop area, False if outside
self.crop_drag_edge = None # 'left', 'right', 'top', 'bottom'
self.crop_drag_mode = None # 'expand' or 'contract'
self.mouse_left_down = False
self.mouse_right_down = False
# Zoom settings
self.zoom_factor = 1.0
@@ -3116,125 +3119,126 @@ class VideoEditor:
self.mouse_dragging = False
return
# Handle crop border dragging (only when Shift and Ctrl are NOT pressed)
# Track mouse button state (used for crop drag mode)
if event == cv2.EVENT_LBUTTONDOWN:
self.mouse_left_down = True
elif event == cv2.EVENT_LBUTTONUP:
self.mouse_left_down = False
elif event == cv2.EVENT_RBUTTONDOWN:
self.mouse_right_down = True
elif event == cv2.EVENT_RBUTTONUP:
self.mouse_right_down = False
# Handle crop resizing with drag direction (no Shift/Ctrl)
# Left drag: expand in drag direction
# Left + right drag: contract from opposite edge
if not (flags & cv2.EVENT_FLAG_SHIFTKEY) and not (flags & cv2.EVENT_FLAG_CTRLKEY) and self.crop_rect:
# Get effective crop in rotated coords and map to screen
eff_x, eff_y, eff_w, eff_h = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
sx1, sy1 = self._map_rotated_to_screen(eff_x, eff_y)
sx2, sy2 = self._map_rotated_to_screen(eff_x + eff_w, eff_y + eff_h)
# Check if cursor is inside crop area
inside_crop = sx1 <= x <= sx2 and sy1 <= y <= sy2
if event == cv2.EVENT_LBUTTONDOWN:
# Start dragging - record position and whether we're inside/outside
eff_x, eff_y, eff_w, eff_h = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
self.crop_border_dragging = True
self.crop_border_drag_start_pos = (x, y)
self.crop_border_drag_start_rect = (eff_x, eff_y, eff_w, eff_h)
self.crop_border_drag_inside = inside_crop
elif event == cv2.EVENT_MOUSEMOVE and self.crop_border_dragging:
if self.crop_border_drag_start_pos and self.crop_border_drag_start_rect and self.crop_border_drag_inside is not None:
# Convert mouse movement from screen to rotated coords
start_sx, start_sy = self.crop_border_drag_start_pos
start_rx, start_ry = self._map_screen_to_rotated(start_sx, start_sy)
curr_rx, curr_ry = self._map_screen_to_rotated(x, y)
dx_r = curr_rx - start_rx
dy_r = curr_ry - start_ry
# Check minimum drag distance
drag_distance = (dx_r ** 2 + dy_r ** 2) ** 0.5
if drag_distance < self.CROP_DRAG_MIN_DISTANCE:
return
# Determine primary direction (horizontal vs vertical)
abs_dx = abs(dx_r)
abs_dy = abs(dy_r)
# Adjust the appropriate border based on direction and inside/outside
new_x, new_y, new_w, new_h = self.crop_border_drag_start_rect
# Get rotated frame dimensions
if self.rotation_angle in (90, 270):
rot_w, rot_h = self.frame_height, self.frame_width
else:
rot_w, rot_h = self.frame_width, self.frame_height
# Determine which border to adjust based on movement direction
if abs_dx > abs_dy:
# Horizontal movement
if self.crop_border_drag_inside:
# Inside crop: drag left -> adjust left border, drag right -> adjust right border
if dx_r < 0:
# Dragging left -> move left border left (expand left)
new_x = max(0, new_x + dx_r)
new_w = new_w - dx_r
if new_w < self.CROP_MIN_SIZE:
new_w = self.CROP_MIN_SIZE
new_x = self.crop_border_drag_start_rect[0] + self.crop_border_drag_start_rect[2] - self.CROP_MIN_SIZE
else:
# Dragging right -> move right border right (expand right)
new_w = max(self.CROP_MIN_SIZE, new_w + dx_r)
if new_x + new_w > rot_w:
new_w = rot_w - new_x
self.crop_drag_mode = 'contract' if self.mouse_right_down else 'expand'
# If right button is pressed while dragging, switch to contract mode
if event == cv2.EVENT_RBUTTONDOWN and self.crop_border_dragging:
self.crop_drag_mode = 'contract'
if (
event == cv2.EVENT_MOUSEMOVE
and self.crop_border_dragging
and self.crop_border_drag_start_pos
and self.crop_border_drag_start_rect
and self.crop_drag_mode
):
start_sx, start_sy = self.crop_border_drag_start_pos
start_rx, start_ry = self._map_screen_to_rotated(start_sx, start_sy)
curr_rx, curr_ry = self._map_screen_to_rotated(x, y)
dx_r = curr_rx - start_rx
dy_r = curr_ry - start_ry
# Ignore tiny movements
if abs(dx_r) < self.CROP_DRAG_MIN_DISTANCE and abs(dy_r) < self.CROP_DRAG_MIN_DISTANCE:
return
start_x, start_y, start_w, start_h = self.crop_border_drag_start_rect
if self.rotation_angle in (90, 270):
rot_w, rot_h = self.frame_height, self.frame_width
else:
rot_w, rot_h = self.frame_width, self.frame_height
mode = self.crop_drag_mode # 'expand' or 'contract'
new_x, new_y, new_w, new_h = start_x, start_y, start_w, start_h
abs_dx = abs(dx_r)
abs_dy = abs(dy_r)
# Horizontal drag
if abs_dx >= abs_dy:
if mode == 'expand':
if dx_r < 0:
# Drag left -> extend left edge left
move = min(abs_dx, start_x)
new_x = start_x - move
new_w = start_w + move
else:
# Outside crop: always contract based on drag direction
# Drag left -> contract right (move right border left)
# Drag right -> contract left (move left border right)
if dx_r < 0:
# Dragging left -> move right border left (contract right)
new_w = max(self.CROP_MIN_SIZE, new_w + dx_r)
if new_w < self.CROP_MIN_SIZE:
new_w = self.CROP_MIN_SIZE
else:
# Dragging right -> move left border right (contract left)
new_x = max(0, new_x + dx_r)
new_w = new_w - dx_r
if new_w < self.CROP_MIN_SIZE:
new_w = self.CROP_MIN_SIZE
new_x = self.crop_border_drag_start_rect[0] + self.crop_border_drag_start_rect[2] - self.CROP_MIN_SIZE
# Drag right -> extend right edge right
max_move = max(0, rot_w - (start_x + start_w))
move = min(dx_r, max_move)
new_w = start_w + move
else:
# Vertical movement
if self.crop_border_drag_inside:
# Inside crop: drag up -> adjust top border, drag down -> adjust bottom border
if dy_r < 0:
# Dragging up -> move top border up (expand up)
new_y = max(0, new_y + dy_r)
new_h = new_h - dy_r
if new_h < self.CROP_MIN_SIZE:
new_h = self.CROP_MIN_SIZE
new_y = self.crop_border_drag_start_rect[1] + self.crop_border_drag_start_rect[3] - self.CROP_MIN_SIZE
else:
# Dragging down -> move bottom border down (expand down)
new_h = max(self.CROP_MIN_SIZE, new_h + dy_r)
if new_y + new_h > rot_h:
new_h = rot_h - new_y
max_shrink = max(0, start_w - self.CROP_MIN_SIZE)
if dx_r < 0:
# Left+right drag left -> contract right edge
move = min(abs_dx, max_shrink)
new_w = start_w - move
else:
# Outside crop: always contract based on drag direction
# Drag up -> contract bottom (move bottom border up)
# Drag down -> contract top (move top border down)
if dy_r < 0:
# Dragging up -> move bottom border up (contract bottom)
new_h = max(self.CROP_MIN_SIZE, new_h + dy_r)
if new_h < self.CROP_MIN_SIZE:
new_h = self.CROP_MIN_SIZE
else:
# Dragging down -> move top border down (contract top)
new_y = max(0, new_y + dy_r)
new_h = new_h - dy_r
if new_h < self.CROP_MIN_SIZE:
new_h = self.CROP_MIN_SIZE
new_y = self.crop_border_drag_start_rect[1] + self.crop_border_drag_start_rect[3] - self.CROP_MIN_SIZE
# Convert back from rotated to original frame coords
self._set_crop_from_rotated_rect((new_x, new_y, new_w, new_h))
self.clear_transformation_cache()
self.display_current_frame()
elif event == cv2.EVENT_LBUTTONUP and self.crop_border_dragging:
# Left+right drag right -> contract left edge
move = min(dx_r, max_shrink)
new_x = start_x + move
new_w = start_w - move
# Vertical drag
else:
if mode == 'expand':
if dy_r < 0:
# Drag up -> extend top edge up
move = min(abs_dy, start_y)
new_y = start_y - move
new_h = start_h + move
else:
# Drag down -> extend bottom edge down
max_move = max(0, rot_h - (start_y + start_h))
move = min(dy_r, max_move)
new_h = start_h + move
else:
max_shrink = max(0, start_h - self.CROP_MIN_SIZE)
if dy_r < 0:
# Left+right drag up -> contract bottom edge
move = min(abs_dy, max_shrink)
new_h = start_h - move
else:
# Left+right drag down -> contract top edge
move = min(dy_r, max_shrink)
new_y = start_y + move
new_h = start_h - move
# Clamp to bounds
new_x = max(0, min(new_x, rot_w - self.CROP_MIN_SIZE))
new_y = max(0, min(new_y, rot_h - self.CROP_MIN_SIZE))
new_w = max(self.CROP_MIN_SIZE, min(new_w, rot_w - new_x))
new_h = max(self.CROP_MIN_SIZE, min(new_h, rot_h - new_y))
self._set_crop_from_rotated_rect((new_x, new_y, new_w, new_h))
self.clear_transformation_cache()
self.display_current_frame()
if event == cv2.EVENT_LBUTTONUP and self.crop_border_dragging:
self.crop_border_dragging = False
self.crop_border_drag_start_pos = None
self.crop_border_drag_start_rect = None
self.crop_border_drag_inside = None
self.crop_drag_edge = None
self.crop_drag_mode = None
self.save_state()
# Handle crop selection (Shift + click and drag)
@@ -3267,7 +3271,13 @@ class VideoEditor:
self.zoom_center = (x, y)
# Handle shift+right-click for placing tracking point at previous tracking point position
if event == cv2.EVENT_RBUTTONDOWN and (flags & cv2.EVENT_FLAG_SHIFTKEY) and not (flags & cv2.EVENT_FLAG_CTRLKEY):
# Do not trigger while left button is held (used for crop resizing)
if (
event == cv2.EVENT_RBUTTONDOWN
and (flags & cv2.EVENT_FLAG_SHIFTKEY)
and not (flags & cv2.EVENT_FLAG_CTRLKEY)
and not self.mouse_left_down
):
if not self.is_image_mode:
# Get previous tracking point position
prev_result = self._get_previous_tracking_point()
@@ -3346,7 +3356,12 @@ class VideoEditor:
# Handle right-click for selective feature extraction when mode is active
if event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)):
# Do not trigger while left button is held (used for crop resizing)
if (
event == cv2.EVENT_RBUTTONDOWN
and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY))
and not self.mouse_left_down
):
if not self.is_image_mode and hasattr(self, 'selective_feature_extraction_mode') and self.selective_feature_extraction_mode:
# Start selective feature extraction
self.selective_feature_extraction_start = (x, y)
@@ -3370,7 +3385,12 @@ class VideoEditor:
self.display_needs_update = True
# Handle right-click for tracking points (no modifiers)
if event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)):
# Do not trigger while left button is held (used for crop resizing)
if (
event == cv2.EVENT_RBUTTONDOWN
and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY))
and not self.mouse_left_down
):
if not self.is_image_mode:
# First check for template removal (like motion tracking points)
if self.templates: