// Copyright Epic Games, Inc. All Rights Reserved. #include "SSequencerTrackArea.h" #include "Types/PaintArgs.h" #include "Layout/ArrangedChildren.h" #include "Rendering/DrawElements.h" #include "Layout/LayoutUtils.h" #include "Widgets/SWeakWidget.h" #include "EditorStyleSet.h" #include "SSequencerTrackLane.h" #include "SSequencerTreeView.h" #include "ISequencerHotspot.h" #include "SequencerHotspots.h" #include "Tools/SequencerEditTool_Movement.h" #include "Tools/SequencerEditTool_Selection.h" #include "ISequencerTrackEditor.h" #include "DisplayNodes/SequencerTrackNode.h" #include "DisplayNodes/SequencerObjectBindingNode.h" #include "CommonMovieSceneTools.h" #include "Styling/StyleColors.h" FTrackAreaSlot::FTrackAreaSlot(const TSharedPtr& InSlotContent) : TAlignmentWidgetSlotMixin(HAlign_Fill, VAlign_Top) { TrackLane = InSlotContent; this->AttachWidget( SNew(SWeakWidget) .PossiblyNullContent(InSlotContent) ); } float FTrackAreaSlot::GetVerticalOffset() const { auto PinnedTrackLane = TrackLane.Pin(); return PinnedTrackLane.IsValid() ? PinnedTrackLane->GetPhysicalPosition() : 0.f; } void SSequencerTrackArea::Construct(const FArguments& InArgs, TSharedRef InTimeSliderController, TSharedRef InSequencer) { Sequencer = InSequencer; TimeSliderController = InTimeSliderController; bShowPinnedNodes = false; // Input stack in order or priority // Space for the edit tool InputStack.AddHandler(nullptr); // The time slider controller InputStack.AddHandler(TimeSliderController.Get()); EditTools.Add(MakeShared(*InSequencer, *this)); EditTools.Add(MakeShared(*InSequencer)); } void SSequencerTrackArea::SetTreeView(const TSharedPtr& InTreeView) { TreeView = InTreeView; } void SSequencerTrackArea::Empty() { TrackSlots.Empty(); Children.Empty(); } void SSequencerTrackArea::AddTrackSlot(const TSharedRef& InNode, const TSharedPtr& InSlot) { TrackSlots.Add(InNode, InSlot); Children.AddSlot(FTrackAreaSlot::FSlotArguments(MakeUnique(InSlot))); } TSharedPtr SSequencerTrackArea::FindTrackSlot(const TSharedRef& InNode) { return TrackSlots.FindRef(InNode).Pin(); } void SSequencerTrackArea::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const { for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex) { const FTrackAreaSlot& CurChild = Children[ChildIndex]; const EVisibility ChildVisibility = CurChild.GetWidget()->GetVisibility(); if (!ArrangedChildren.Accepts(ChildVisibility)) { continue; } TSharedPtr PinnedTrackLane = CurChild.TrackLane.Pin(); if (!PinnedTrackLane.IsValid() || PinnedTrackLane->IsPinned() != bShowPinnedNodes) { continue; } const FMargin Padding(0, CurChild.GetVerticalOffset(), 0, 0); AlignmentArrangeResult XResult = AlignChild(AllottedGeometry.GetLocalSize().X, CurChild, Padding, 1.0f, false); AlignmentArrangeResult YResult = AlignChild(AllottedGeometry.GetLocalSize().Y, CurChild, Padding, 1.0f, false); ArrangedChildren.AddWidget(ChildVisibility, AllottedGeometry.MakeChild( CurChild.GetWidget(), FVector2D(XResult.Offset,YResult.Offset), FVector2D(XResult.Size, YResult.Size) ) ); } } FVector2D SSequencerTrackArea::ComputeDesiredSize( float ) const { FVector2D MaxSize(0,0); for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex) { const FTrackAreaSlot& CurChild = Children[ChildIndex]; const EVisibility ChildVisibilty = CurChild.GetWidget()->GetVisibility(); if (ChildVisibilty != EVisibility::Collapsed) { FVector2D ChildDesiredSize = CurChild.GetWidget()->GetDesiredSize(); MaxSize.X = FMath::Max(MaxSize.X, ChildDesiredSize.X); MaxSize.Y = FMath::Max(MaxSize.Y, ChildDesiredSize.Y); } } return MaxSize; } FChildren* SSequencerTrackArea::GetChildren() { return &Children; } int32 SSequencerTrackArea::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { if ( Sequencer.IsValid() ) { // give track editors a chance to paint auto TrackEditors = Sequencer.Pin()->GetTrackEditors(); for (const auto& TrackEditor : TrackEditors) { LayerId = TrackEditor->PaintTrackArea(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId + 1, InWidgetStyle); } // paint the child widgets FArrangedChildren ArrangedChildren(EVisibility::Visible); ArrangeChildren(AllottedGeometry, ArrangedChildren); const FPaintArgs NewArgs = Args.WithNewParent(this); for (int32 ChildIndex = 0; ChildIndex < ArrangedChildren.Num(); ++ChildIndex) { FArrangedWidget& CurWidget = ArrangedChildren[ChildIndex]; FSlateRect ChildClipRect = MyCullingRect.IntersectionWith( CurWidget.Geometry.GetLayoutBoundingRect() ); const int32 ThisWidgetLayerId = CurWidget.Widget->Paint( NewArgs, CurWidget.Geometry, ChildClipRect, OutDrawElements, LayerId + 2, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) ); LayerId = FMath::Max(LayerId, ThisWidgetLayerId); } if (EditTool.IsValid()) { LayerId = EditTool->OnPaint(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId + 1); } TOptional HighlightRegion = TreeView.Pin()->GetHighlightRegion(); if (HighlightRegion.IsSet()) { FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(0.f, HighlightRegion->Top - 4.f), FVector2D(AllottedGeometry.GetLocalSize().X, 4.f)), FEditorStyle::GetBrush("Sequencer.TrackHoverHighlight_Top"), ESlateDrawEffect::None, FLinearColor::Black ); FSlateDrawElement::MakeBox( OutDrawElements, LayerId+1, AllottedGeometry.ToPaintGeometry(FVector2D(0.f, HighlightRegion->Bottom), FVector2D(AllottedGeometry.GetLocalSize().X, 4.f)), FEditorStyle::GetBrush("Sequencer.TrackHoverHighlight_Bottom"), ESlateDrawEffect::None, FLinearColor::Black ); } // Draw drop target if (DroppedNode.IsValid() && TrackSlots.Contains(DroppedNode.Pin())) { TSharedPtr TrackLane = TrackSlots.FindRef(DroppedNode.Pin()).Pin(); FGeometry NodeGeometry = TrackLane.Get()->GetCachedGeometry(); FSlateColor DashColor = bAllowDrop ? FStyleColors::AccentBlue : FStyleColors::Error; const FSlateBrush* HorizontalBrush = FEditorStyle::GetBrush("WideDash.Horizontal"); const FSlateBrush* VerticalBrush = FEditorStyle::GetBrush("WideDash.Vertical"); int32 DashLayer = LayerId + 1; float DropMinX = 0.f; float DropMaxX = NodeGeometry.GetLocalSize().X; if (DropFrameRange.IsSet()) { FTimeToPixel TimeToPixel(NodeGeometry, Sequencer.Pin()->GetViewRange(), Sequencer.Pin()->GetFocusedTickResolution()); DropMinX = TimeToPixel.FrameToPixel(DropFrameRange.GetValue().GetLowerBoundValue()); DropMaxX = TimeToPixel.FrameToPixel(DropFrameRange.GetValue().GetUpperBoundValue()); } // Top FSlateDrawElement::MakeBox( OutDrawElements, DashLayer, AllottedGeometry.ToPaintGeometry(FVector2D(DropMinX, TrackLane.Get()->GetPhysicalPosition()), FVector2D(DropMaxX-DropMinX, HorizontalBrush->ImageSize.Y)), HorizontalBrush, ESlateDrawEffect::None, DashColor.GetSpecifiedColor()); // Bottom FSlateDrawElement::MakeBox( OutDrawElements, DashLayer, AllottedGeometry.ToPaintGeometry(FVector2D(DropMinX, TrackLane.Get()->GetPhysicalPosition() + (TrackLane.Get()->GetCachedGeometry().GetLocalSize().Y - HorizontalBrush->ImageSize.Y)), FVector2D(DropMaxX-DropMinX, HorizontalBrush->ImageSize.Y)), HorizontalBrush, ESlateDrawEffect::None, DashColor.GetSpecifiedColor()); // Left FSlateDrawElement::MakeBox( OutDrawElements, DashLayer, AllottedGeometry.ToPaintGeometry(FVector2D(DropMinX, TrackLane.Get()->GetPhysicalPosition()), FVector2D(VerticalBrush->ImageSize.X, TrackLane.Get()->GetCachedGeometry().GetLocalSize().Y)), VerticalBrush, ESlateDrawEffect::None, DashColor.GetSpecifiedColor()); // Right FSlateDrawElement::MakeBox( OutDrawElements, DashLayer, AllottedGeometry.ToPaintGeometry(FVector2D(DropMaxX - VerticalBrush->ImageSize.X, TrackLane.Get()->GetPhysicalPosition()), FVector2D(VerticalBrush->ImageSize.X, TrackLane.Get()->GetCachedGeometry().GetLocalSize().Y)), VerticalBrush, ESlateDrawEffect::None, DashColor.GetSpecifiedColor()); } } return LayerId; } FReply SSequencerTrackArea::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( Sequencer.IsValid() ) { // Always ensure the edit tool is set up InputStack.SetHandlerAt(0, EditTool.Get()); return InputStack.HandleMouseButtonDown(*this, MyGeometry, MouseEvent); } return FReply::Unhandled(); } FReply SSequencerTrackArea::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( Sequencer.IsValid() ) { FContextMenuSuppressor SuppressContextMenus(TimeSliderController.ToSharedRef()); // Always ensure the edit tool is set up InputStack.SetHandlerAt(0, EditTool.Get()); return InputStack.HandleMouseButtonUp(*this, MyGeometry, MouseEvent); } return FReply::Unhandled(); } bool SSequencerTrackArea::CanActivateEditTool(FName Identifier) const { auto IdentifierIsFound = [=](TSharedPtr InEditTool){ return InEditTool->GetIdentifier() == Identifier; }; if (InputStack.GetCapturedIndex() != INDEX_NONE) { return false; } else if (!EditTool.IsValid()) { return EditTools.ContainsByPredicate(IdentifierIsFound); } // Can't activate a tool that's already active else if (EditTool->GetIdentifier() == Identifier) { return false; } // Can only activate a new tool if the current one will let us else { return EditTool->CanDeactivate() && EditTools.ContainsByPredicate(IdentifierIsFound); } } bool SSequencerTrackArea::AttemptToActivateTool(FName Identifier) { if ( Sequencer.IsValid() && CanActivateEditTool(Identifier) ) { EditTool = *EditTools.FindByPredicate([=](TSharedPtr InEditTool){ return InEditTool->GetIdentifier() == Identifier; }); return true; } return false; } void SSequencerTrackArea::UpdateHoverStates( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { TSharedPtr PinnedTreeView = TreeView.Pin(); // Set the node that we are hovering TSharedPtr NewHoveredNode = PinnedTreeView->HitTestNode(MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).Y); PinnedTreeView->GetNodeTree()->SetHoveredNode(NewHoveredNode); if ( Sequencer.IsValid() ) { TSharedPtr Hotspot = Sequencer.Pin()->GetHotspot(); if (Hotspot.IsValid()) { Hotspot->UpdateOnHover(*this, *Sequencer.Pin()); return; } } // Any other region implies selection mode AttemptToActivateTool(FSequencerEditTool_Selection::Identifier); } FReply SSequencerTrackArea::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( Sequencer.IsValid() ) { UpdateHoverStates(MyGeometry, MouseEvent); // Always ensure the edit tool is set up InputStack.SetHandlerAt(0, EditTool.Get()); FReply Reply = InputStack.HandleMouseMove(*this, MyGeometry, MouseEvent); // Handle right click scrolling on the track area, if the captured index is that of the time slider if (Reply.IsEventHandled() && InputStack.GetCapturedIndex() == 1) { if (MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton) && HasMouseCapture()) { TreeView.Pin()->ScrollByDelta(-MouseEvent.GetCursorDelta().Y); } } return Reply; } return FReply::Unhandled(); } FReply SSequencerTrackArea::OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( Sequencer.IsValid() ) { // Always ensure the edit tool is set up InputStack.SetHandlerAt(0, EditTool.Get()); FReply EditToolHandle = InputStack.HandleMouseWheel(*this, MyGeometry, MouseEvent); if (EditToolHandle.IsEventHandled()) { return EditToolHandle; } const float ScrollAmount = -MouseEvent.GetWheelDelta() * GetGlobalScrollAmount(); Sequencer.Pin()->VerticalScroll(ScrollAmount); return FReply::Handled(); } return FReply::Unhandled(); } void SSequencerTrackArea::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { DroppedNode.Reset(); bAllowDrop = false; DropFrameRange.Reset(); if ( Sequencer.IsValid() ) { if (EditTool.IsValid()) { EditTool->OnMouseEnter(*this, MyGeometry, MouseEvent); } } } void SSequencerTrackArea::OnMouseLeave(const FPointerEvent& MouseEvent) { if ( Sequencer.IsValid() ) { if (EditTool.IsValid()) { EditTool->OnMouseLeave(*this, MouseEvent); } TreeView.Pin()->GetNodeTree()->SetHoveredNode(nullptr); } } void SSequencerTrackArea::OnMouseCaptureLost(const FCaptureLostEvent& CaptureLostEvent) { if ( Sequencer.IsValid() ) { if (EditTool.IsValid()) { EditTool->OnMouseCaptureLost(); } } } FCursorReply SSequencerTrackArea::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const { if ( Sequencer.IsValid() ) { if (CursorEvent.IsMouseButtonDown(EKeys::RightMouseButton) && HasMouseCapture()) { return FCursorReply::Cursor(EMouseCursor::GrabHandClosed); } if (EditTool.IsValid()) { return EditTool->OnCursorQuery(MyGeometry, CursorEvent); } } return FCursorReply::Unhandled(); } void SSequencerTrackArea::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { FVector2D Size = AllottedGeometry.GetLocalSize(); if (!IsSlave() && SizeLastFrame.IsSet() && Size.X != SizeLastFrame->X) { // Zoom by the difference in horizontal size const float Difference = Size.X - SizeLastFrame->X; TRange OldRange = TimeSliderController->GetViewRange().GetAnimationTarget(); double NewRangeMin = OldRange.GetLowerBoundValue(); double NewRangeMax = OldRange.GetUpperBoundValue() + (Difference * OldRange.Size() / SizeLastFrame->X); TRange ClampRange = TimeSliderController->GetClampRange(); if (NewRangeMin < ClampRange.GetLowerBoundValue() || NewRangeMax > ClampRange.GetUpperBoundValue()) { double NewClampRangeMin = NewRangeMin < ClampRange.GetLowerBoundValue() ? NewRangeMin : ClampRange.GetLowerBoundValue(); double NewClampRangeMax = NewRangeMax > ClampRange.GetUpperBoundValue() ? NewRangeMax : ClampRange.GetUpperBoundValue(); TimeSliderController->SetClampRange(NewClampRangeMin, NewClampRangeMax); } TimeSliderController->SetViewRange( NewRangeMin, NewRangeMax, EViewRangeInterpolation::Immediate ); } SizeLastFrame = Size; for (int32 Index = 0; Index < Children.Num();) { if (!StaticCastSharedRef(Children[Index].GetWidget())->ChildWidgetIsValid()) { Children.RemoveAt(Index); } else { ++Index; } } } void SSequencerTrackArea::OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { SPanel::OnDragEnter(MyGeometry, DragDropEvent); } void SSequencerTrackArea::OnDragLeave(const FDragDropEvent& DragDropEvent) { DroppedNode.Reset(); SPanel::OnDragLeave(DragDropEvent); } FReply SSequencerTrackArea::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { TSharedPtr PinnedTreeView = TreeView.Pin(); DroppedNode = PinnedTreeView->HitTestNode(MyGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition()).Y); bAllowDrop = false; DropFrameRange.Reset(); if (DroppedNode.IsValid() && DroppedNode.Pin()->GetType() == ESequencerNode::Track && Sequencer.IsValid()) { TSharedPtr TrackNode = StaticCastSharedPtr(DroppedNode.Pin()); UMovieSceneTrack* Track = TrackNode->GetTrack(); int32 RowIndex = TrackNode->GetSubTrackMode() == FSequencerTrackNode::ESubTrackMode::SubTrack ? TrackNode->GetRowIndex() : 0; FGuid ObjectBinding; TSharedPtr ObjectBindingNode = TrackNode->FindParentObjectBindingNode(); if (ObjectBindingNode.IsValid()) { ObjectBinding = ObjectBindingNode->GetObjectBinding(); } // give track editors a chance to accept the drag event auto TrackEditors = Sequencer.Pin()->GetTrackEditors(); FTimeToPixel TimeToPixel(MyGeometry, Sequencer.Pin()->GetViewRange(), Sequencer.Pin()->GetFocusedTickResolution()); FVector2D LocalPos = MyGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition()); FFrameNumber DropFrameNumber = TimeToPixel.PixelToFrame(LocalPos.X).FrameNumber; if (Sequencer.Pin()->GetSequencerSettings()->GetIsSnapEnabled() && Sequencer.Pin()->GetSequencerSettings()->GetSnapPlayTimeToInterval()) { DropFrameNumber = FFrameRate::Snap(DropFrameNumber, Sequencer.Pin()->GetFocusedTickResolution(), Sequencer.Pin()->GetFocusedDisplayRate()).FrameNumber; } FSequencerDragDropParams DragDropParams(Track, RowIndex, ObjectBinding, DropFrameNumber, TRange()); for (const auto& TrackEditor : TrackEditors) { if (TrackEditor->OnAllowDrop(DragDropEvent, DragDropParams)) { bAllowDrop = true; DropFrameRange = DragDropParams.FrameRange; return FReply::Handled(); } } } return SPanel::OnDragOver(MyGeometry, DragDropEvent); } FReply SSequencerTrackArea::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { TSharedPtr PinnedTreeView = TreeView.Pin(); DroppedNode = PinnedTreeView->HitTestNode(MyGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition()).Y); if (DroppedNode.IsValid() && DroppedNode.Pin()->GetType() == ESequencerNode::Track && Sequencer.IsValid()) { TSharedPtr TrackNode = StaticCastSharedPtr(DroppedNode.Pin()); UMovieSceneTrack* Track = TrackNode->GetTrack(); int32 RowIndex = TrackNode->GetSubTrackMode() == FSequencerTrackNode::ESubTrackMode::SubTrack ? TrackNode->GetRowIndex() : 0; FGuid ObjectBinding; TSharedPtr ObjectBindingNode = TrackNode->FindParentObjectBindingNode(); if (ObjectBindingNode.IsValid()) { ObjectBinding = ObjectBindingNode->GetObjectBinding(); } // give track editors a chance to process the drag event auto TrackEditors = Sequencer.Pin()->GetTrackEditors(); FTimeToPixel TimeToPixel(MyGeometry, Sequencer.Pin()->GetViewRange(), Sequencer.Pin()->GetFocusedTickResolution()); FVector2D LocalPos = MyGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition()); FFrameNumber DropFrameNumber = TimeToPixel.PixelToFrame(LocalPos.X).FrameNumber; if (Sequencer.Pin()->GetSequencerSettings()->GetIsSnapEnabled() && Sequencer.Pin()->GetSequencerSettings()->GetSnapPlayTimeToInterval()) { DropFrameNumber = FFrameRate::Snap(DropFrameNumber, Sequencer.Pin()->GetFocusedTickResolution(), Sequencer.Pin()->GetFocusedDisplayRate()).FrameNumber; } FSequencerDragDropParams DragDropParams(Track, RowIndex, ObjectBinding, DropFrameNumber, TRange()); for (const auto& TrackEditor : TrackEditors) { if (TrackEditor->OnAllowDrop(DragDropEvent, DragDropParams)) { DroppedNode.Reset(); return TrackEditor->OnDrop(DragDropEvent, DragDropParams); } } } DroppedNode.Reset(); return SPanel::OnDrop(MyGeometry, DragDropEvent); }