Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions src/core/ui/selection-handles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
detectCornerZone,
detectEdgeZone
} from "@core/interaction/clip-interaction";
import { SELECTION_CONSTANTS, CURSOR_BASE_ANGLES, type CornerName, buildResizeCursor } from "@core/interaction/selection-overlay";
import { SELECTION_CONSTANTS, CURSOR_BASE_ANGLES, type CornerName, buildResizeCursor, buildRotationCursor, calculateHitArea } from "@core/interaction/selection-overlay";
import { type ClipBounds, createClipBounds, createSnapContext, filterContainedClips, snap, snapRotation, visualToLogical } from "@core/interaction/snap-system";
import { updateSvgViewBox, isSimpleRectSvg } from "@core/shared/svg-utils";
import { Pointer } from "@inputs/pointer";
Expand Down Expand Up @@ -127,6 +127,7 @@ export class SelectionHandles implements CanvasOverlayRegistration {
this.app = app;

// Create outline
this.outline.eventMode = "static";
this.container.addChild(this.outline);

// Create corner handles
Expand Down Expand Up @@ -239,6 +240,10 @@ export class SelectionHandles implements CanvasOverlayRegistration {
this.outline.strokeStyle = { width: SELECTION_CONSTANTS.OUTLINE_WIDTH / uiScale, color };
this.outline.rect(0, 0, size.width, size.height);
this.outline.stroke();

// Expand hit area to cover rotation zones outside corners
const hitRect = calculateHitArea(size, uiScale);
this.outline.hitArea = new pixi.Rectangle(hitRect.x, hitRect.y, hitRect.width, hitRect.height);
}

private drawHandles(): void {
Expand Down Expand Up @@ -362,17 +367,18 @@ export class SelectionHandles implements CanvasOverlayRegistration {
}
}

// Check if inside player bounds for drag
if (localPoint.x >= 0 && localPoint.x <= size.width && localPoint.y >= 0 && localPoint.y <= size.height) {
// Check for edge resize first
// Check for edge resize (zone extends outside clip bounds)
if (this.selectedPlayer.supportsEdgeResize()) {
const hitZone = SELECTION_CONSTANTS.EDGE_HIT_ZONE / this.getUIScale();
const edge = detectEdgeZone(localPoint, size, hitZone);
if (edge && this.selectedPlayer.supportsEdgeResize()) {
if (edge) {
this.startEdgeResize(event, edge);
return;
}
}

// Start position drag
// Check if inside player bounds for drag
if (localPoint.x >= 0 && localPoint.x <= size.width && localPoint.y >= 0 && localPoint.y <= size.height) {
this.startDrag(event);
}
}
Expand Down Expand Up @@ -804,23 +810,30 @@ export class SelectionHandles implements CanvasOverlayRegistration {
const playerContainer = this.selectedPlayer.getContainer();
const localPoint = event.getLocalPosition(playerContainer);
const size = this.selectedPlayer.getSize();
const uiScale = this.getUIScale();
const rotation = this.selectedPlayer.getRotation() ?? 0;

this.isHovering = localPoint.x >= 0 && localPoint.x <= size.width && localPoint.y >= 0 && localPoint.y <= size.height;

// Update cursor for edge resize zones
// Priority order mirrors onPointerDown: rotation → edges → default
const rotationCorner = this.getRotationCorner(localPoint, size);
if (rotationCorner) {
const baseAngle = CURSOR_BASE_ANGLES[rotationCorner] ?? 0;
this.outline.cursor = buildRotationCursor(baseAngle + rotation);
return;
}

if (this.selectedPlayer.supportsEdgeResize()) {
const hitZone = SELECTION_CONSTANTS.EDGE_HIT_ZONE / this.getUIScale();
const hitZone = SELECTION_CONSTANTS.EDGE_HIT_ZONE / uiScale;
const edge = detectEdgeZone(localPoint, size, hitZone);

if (edge) {
const rotation = this.selectedPlayer.getRotation() ?? 0;
const baseAngle = CURSOR_BASE_ANGLES[edge] ?? 0;
this.outline.cursor = buildResizeCursor(baseAngle + rotation);
return;
}
}

// Reset cursor when not over an edge
this.outline.cursor = this.isHovering ? "move" : "default";
}

Expand Down
Loading