Merge ae2b34e114 into 238750c025
This commit is contained in:
commit
0965c10aeb
|
|
@ -55,6 +55,10 @@ export interface ElementSelectionApi {
|
|||
getSelected: () => readonly ElementRef[];
|
||||
isSelected: (ref: ElementRef) => boolean;
|
||||
select: (ref: ElementRef) => void;
|
||||
selectRange: (args: {
|
||||
anchor?: ElementRef | null;
|
||||
target: ElementRef;
|
||||
}) => readonly ElementRef[];
|
||||
handleClick: (args: ElementRef & { isMultiKey: boolean }) => void;
|
||||
clearKeyframeSelection: () => void;
|
||||
}
|
||||
|
|
@ -295,6 +299,7 @@ export class ElementInteractionController {
|
|||
// has already returned to idle, so the "was this a drag?" answer must
|
||||
// outlive the session. Reset on the next mousedown.
|
||||
private lastGestureWasDrag = false;
|
||||
private elementSelectionAnchor: ElementRef | null = null;
|
||||
|
||||
private readonly subscribers = new Set<() => void>();
|
||||
private readonly depsRef: ElementInteractionDepsRef;
|
||||
|
|
@ -372,13 +377,29 @@ export class ElementInteractionController {
|
|||
|
||||
const ref = { trackId: track.id, elementId: element.id };
|
||||
|
||||
if (event.metaKey || event.ctrlKey || event.shiftKey) {
|
||||
let rangeSelection: readonly ElementRef[] | null = null;
|
||||
if (event.shiftKey) {
|
||||
const anchor =
|
||||
this.elementSelectionAnchor?.trackId === ref.trackId
|
||||
? this.elementSelectionAnchor
|
||||
: null;
|
||||
rangeSelection = this.deps.selection.selectRange({
|
||||
anchor: anchor ?? ref,
|
||||
target: ref,
|
||||
});
|
||||
|
||||
if (!anchor) {
|
||||
this.elementSelectionAnchor = ref;
|
||||
}
|
||||
} else if (event.metaKey || event.ctrlKey) {
|
||||
this.deps.selection.handleClick({ ...ref, isMultiKey: true });
|
||||
}
|
||||
|
||||
const selectedElements = this.deps.selection.isSelected(ref)
|
||||
? this.deps.selection.getSelected()
|
||||
: [ref];
|
||||
const selectedElements =
|
||||
rangeSelection ??
|
||||
(this.deps.selection.isSelected(ref)
|
||||
? this.deps.selection.getSelected()
|
||||
: [ref]);
|
||||
|
||||
this.session = {
|
||||
kind: "pending",
|
||||
|
|
@ -423,9 +444,11 @@ export class ElementInteractionController {
|
|||
this.deps.selection.getSelected().length > 1
|
||||
) {
|
||||
this.deps.selection.select(ref);
|
||||
this.elementSelectionAnchor = ref;
|
||||
return;
|
||||
}
|
||||
|
||||
this.elementSelectionAnchor = ref;
|
||||
this.deps.selection.clearKeyframeSelection();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export function useElementInteraction({
|
|||
getSelected: () => selection.selectedElements,
|
||||
isSelected: selection.isElementSelected,
|
||||
select: selection.selectElement,
|
||||
selectRange: selection.selectElementRange,
|
||||
handleClick: selection.handleElementClick,
|
||||
clearKeyframeSelection: () => editor.selection.clearKeyframeSelection(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback } from "react";
|
||||
import { useEditor } from "@/editor/use-editor";
|
||||
import { findTrackInSceneTracks } from "@/timeline/track-element-update";
|
||||
import type { ElementRef } from "@/timeline/types";
|
||||
|
||||
export function useElementSelection() {
|
||||
|
|
@ -101,6 +102,60 @@ export function useElementSelection() {
|
|||
[selectedElements, editor],
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles Shift-click range selection. Selects all clips between the anchor
|
||||
* and target when both are on the same track; otherwise selects only target.
|
||||
*/
|
||||
const selectElementRange = useCallback(
|
||||
({
|
||||
anchor,
|
||||
target,
|
||||
}: {
|
||||
anchor?: ElementRef | null;
|
||||
target: ElementRef;
|
||||
}) => {
|
||||
const setSelection = (elements: ElementRef[]) => {
|
||||
editor.selection.setSelectedElements({ elements });
|
||||
return elements;
|
||||
};
|
||||
|
||||
const track = findTrackInSceneTracks({
|
||||
tracks: editor.scenes.getActiveScene().tracks,
|
||||
trackId: target.trackId,
|
||||
});
|
||||
if (!track || anchor?.trackId !== target.trackId) {
|
||||
return setSelection([target]);
|
||||
}
|
||||
|
||||
const orderedElements = track.elements
|
||||
.map((element, index) => ({ element, index }))
|
||||
.sort((a, b) =>
|
||||
a.element.startTime === b.element.startTime
|
||||
? a.index - b.index
|
||||
: a.element.startTime - b.element.startTime,
|
||||
);
|
||||
const anchorIndex = orderedElements.findIndex(
|
||||
({ element }) => element.id === anchor.elementId,
|
||||
);
|
||||
const targetIndex = orderedElements.findIndex(
|
||||
({ element }) => element.id === target.elementId,
|
||||
);
|
||||
|
||||
if (anchorIndex === -1 || targetIndex === -1) {
|
||||
return setSelection([target]);
|
||||
}
|
||||
|
||||
const start = Math.min(anchorIndex, targetIndex);
|
||||
const end = Math.max(anchorIndex, targetIndex);
|
||||
return setSelection(
|
||||
orderedElements.slice(start, end + 1).map(({ element }) => ({
|
||||
trackId: target.trackId,
|
||||
elementId: element.id,
|
||||
})),
|
||||
);
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles click interaction on an element.
|
||||
|
|
@ -128,6 +183,7 @@ export function useElementSelection() {
|
|||
selectElement,
|
||||
setElementSelection,
|
||||
mergeElementsIntoSelection,
|
||||
selectElementRange,
|
||||
addElementToSelection,
|
||||
removeElementFromSelection,
|
||||
toggleElementSelection,
|
||||
|
|
|
|||
Loading…
Reference in New Issue