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:
246
croppa/main.py
246
croppa/main.py
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user