using System; using System.Linq; using UnityEngine; using UnityEngine.Timeline; namespace UnityEditor.Timeline { class ClearSelection : Manipulator { protected override bool MouseDown(Event evt, WindowState state) { // If we hit this point this means no one used the mouse down events. We can safely clear the selection if needed if (evt.button != 0) return false; var window = state.GetWindow(); if (!window.sequenceRect.Contains(evt.mousePosition)) return false; if (ItemSelection.CanClearSelection(evt)) { SelectionManager.Clear(); return true; } return false; } } static class ItemSelection { public static bool CanClearSelection(Event evt) { return !evt.control && !evt.command && !evt.shift; } public static void RangeSelectItems(ITimelineItem lastItemToSelect) { var selectSorted = SelectionManager.SelectedItems().ToList(); var firstSelect = selectSorted.FirstOrDefault(); if (firstSelect == null) { SelectionManager.Add(lastItemToSelect); return; } var allTracks = TimelineEditor.inspectedAsset.flattenedTracks; var allItems = allTracks.SelectMany(ItemsUtils.GetItems).ToList(); TimelineHelpers.RangeSelect(allItems, selectSorted, lastItemToSelect, SelectionManager.Add, SelectionManager.Remove); } public static ISelectable HandleSingleSelection(Event evt) { var item = PickerUtils.TopmostPickedItemOfType(i => i.CanSelect(evt)); if (item != null) { var selected = item.IsSelected(); if (!selected && CanClearSelection(evt)) SelectionManager.Clear(); if (evt.modifiers == EventModifiers.Shift) { if (!selected) RangeSelectItems((item as TimelineItemGUI)?.item); } else { HandleItemSelection(evt, item); } } return item; } public static void HandleItemSelection(Event evt, ISelectable item) { if (evt.modifiers == ManipulatorsUtils.actionModifier) { if (item.IsSelected()) item.Deselect(); else item.Select(); } else { if (!item.IsSelected()) item.Select(); } } } class SelectAndMoveItem : Manipulator { bool m_Dragged; SnapEngine m_SnapEngine; TimeAreaAutoPanner m_TimeAreaAutoPanner; Vector2 m_MouseDownPosition; bool m_HorizontalMovementDone; bool m_VerticalMovementDone; MoveItemHandler m_MoveItemHandler; bool m_CycleMarkersPending; protected override bool MouseDown(Event evt, WindowState state) { if (evt.alt || evt.button != 0) return false; m_Dragged = false; // Cycling markers and selection are mutually exclusive operations if (!HandleMarkerCycle() && !HandleSingleSelection(evt)) return false; m_MouseDownPosition = evt.mousePosition; m_VerticalMovementDone = false; m_HorizontalMovementDone = false; return true; } protected override bool MouseUp(Event evt, WindowState state) { if (!m_Dragged) { var item = PickerUtils.TopmostPickedItem() as ISelectable; if (item == null) return false; if (!item.IsSelected()) return false; // Re-selecting an item part of a multi-selection should only keep this item selected. if (SelectionManager.Count() > 1 && ItemSelection.CanClearSelection(evt)) { SelectionManager.Clear(); item.Select(); return true; } if (m_CycleMarkersPending) { m_CycleMarkersPending = false; TimelineMarkerClusterGUI.CycleMarkers(); return true; } return false; } m_TimeAreaAutoPanner = null; DropItems(); m_SnapEngine = null; m_MoveItemHandler = null; state.Evaluate(); state.RemoveCaptured(this); m_Dragged = false; TimelineCursors.ClearCursor(); return true; } protected override bool DoubleClick(Event evt, WindowState state) { return MouseDown(evt, state) && MouseUp(evt, state); } protected override bool MouseDrag(Event evt, WindowState state) { if (state.editSequence.isReadOnly) return false; // case 1099285 - ctrl-click can cause no clips to be selected var selectedItemsGUI = SelectionManager.SelectedItems(); if (!selectedItemsGUI.Any()) { m_Dragged = false; return false; } const float hDeadZone = 5.0f; const float vDeadZone = 5.0f; bool vDone = m_VerticalMovementDone || Math.Abs(evt.mousePosition.y - m_MouseDownPosition.y) > vDeadZone; bool hDone = m_HorizontalMovementDone || Math.Abs(evt.mousePosition.x - m_MouseDownPosition.x) > hDeadZone; m_CycleMarkersPending = false; if (!m_Dragged) { var canStartMove = vDone || hDone; if (canStartMove) { state.AddCaptured(this); m_Dragged = true; var referenceTrack = GetTrackDropTargetAt(state, m_MouseDownPosition); foreach (var item in selectedItemsGUI) item.gui.StartDrag(); m_MoveItemHandler = new MoveItemHandler(state); m_MoveItemHandler.Grab(selectedItemsGUI, referenceTrack, m_MouseDownPosition); m_SnapEngine = new SnapEngine(m_MoveItemHandler, m_MoveItemHandler, ManipulateEdges.Both, state, m_MouseDownPosition); m_TimeAreaAutoPanner = new TimeAreaAutoPanner(state); } } if (!m_VerticalMovementDone) { m_VerticalMovementDone = vDone; if (m_VerticalMovementDone) m_MoveItemHandler.OnTrackDetach(); } if (!m_HorizontalMovementDone) { m_HorizontalMovementDone = hDone; } if (m_Dragged) { if (m_HorizontalMovementDone) m_SnapEngine.Snap(evt.mousePosition, evt.modifiers); if (m_VerticalMovementDone) { var track = GetTrackDropTargetAt(state, evt.mousePosition); m_MoveItemHandler.UpdateTrackTarget(track); } state.Evaluate(); } return true; } public override void Overlay(Event evt, WindowState state) { if (!m_Dragged) return; if (m_TimeAreaAutoPanner != null) m_TimeAreaAutoPanner.OnGUI(evt); m_MoveItemHandler.OnGUI(evt); if (!m_MoveItemHandler.allowTrackSwitch || m_MoveItemHandler.targetTrack != null) { TimeIndicator.Draw(state, m_MoveItemHandler.start, m_MoveItemHandler.end); m_SnapEngine.OnGUI(); } } bool HandleMarkerCycle() { m_CycleMarkersPending = TimelineMarkerClusterGUI.CanCycleMarkers(); return m_CycleMarkersPending; } static bool HandleSingleSelection(Event evt) { return ItemSelection.HandleSingleSelection(evt) != null; } void DropItems() { // Order matters here: m_MoveItemHandler.movingItems is destroyed during call to Drop() foreach (var movingItem in m_MoveItemHandler.movingItems) { foreach (var item in movingItem.items) item.gui.StopDrag(); } m_MoveItemHandler.Drop(); } static TrackAsset GetTrackDropTargetAt(WindowState state, Vector2 point) { var track = state.spacePartitioner.GetItemsAtPosition(point).FirstOrDefault(); return track != null ? track.asset : null; } } }