Files
UnrealEngine/Engine/Source/Editor/CurveEditor/Private/SCurveEditorViewContainer.cpp
2025-05-18 13:04:45 +08:00

490 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SCurveEditorViewContainer.h"
#include "CurveEditor.h"
#include "CurveEditorSelection.h"
#include "Delegates/Delegate.h"
#include "DragOperations/CurveEditorDragOperation_Marquee.h"
#include "DragOperations/CurveEditorDragOperation_Pan.h"
#include "DragOperations/CurveEditorDragOperation_Zoom.h"
#include "DragOperations/CurveEditorDragOperation_ScrubTime.h"
#include "GenericPlatform/ICursor.h"
#include "HAL/PlatformCrt.h"
#include "ICurveEditorBounds.h"
#include "ICurveEditorToolExtension.h"
#include "ITimeSlider.h"
#include "Input/Events.h"
#include "InputCoreTypes.h"
#include "Layout/Children.h"
#include "Layout/Clipping.h"
#include "Layout/Geometry.h"
#include "Layout/Visibility.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/Attribute.h"
#include "Rendering/DrawElements.h"
#include "Rendering/RenderingCommon.h"
#include "SCurveEditorPanel.h"
#include "SCurveEditorView.h"
#include "Slate/SRetainerWidget.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/SlateBrush.h"
#include "Templates/UniquePtr.h"
#include "Types/SlateEnums.h"
#include "Types/SlateStructs.h"
#include "UObject/NameTypes.h"
#include "Views/SInteractiveCurveEditorView.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SWidget.h"
#include "CurveEditorCommands.h"
class FPaintArgs;
class FSlateRect;
class FWidgetStyle;
#define LOCTEXT_NAMESPACE "SCurveEditorViewContainer"
void SCurveEditorViewContainer::Construct(const FArguments& InArgs, TSharedRef<FCurveEditor> InCurveEditor)
{
SetCanTick(true);
CurveEditor = InCurveEditor;
TimeSliderController = InArgs._ExternalTimeSliderController;
MinimumPanelHeight = InArgs._MinimumPanelHeight;
CurveEditor->OnActiveToolChangedDelegate.AddSP(this, &SCurveEditorViewContainer::OnCurveEditorToolChanged);
SetClipping(EWidgetClipping::ClipToBounds);
}
bool SCurveEditorViewContainer::ComputeVolatility() const
{
return true;
}
FVector2D SCurveEditorViewContainer::ComputeDesiredSize(float) const
{
FVector2D MyDesiredSize(0,0);
int32 NumStretchPanels = 0;
for (int32 Index = 0; Index < Children.Num(); ++Index)
{
const SBoxPanel::FSlot& Child = Children[Index];
if (Child.GetWidget()->GetVisibility() != EVisibility::Collapsed)
{
const FVector2D& ChildDesiredSize = Child.GetWidget()->GetDesiredSize();
FMargin SlotPadding = Child.GetPadding();
MyDesiredSize.Y += SlotPadding.GetTotalSpaceAlong<Orient_Vertical>();
// For a vertical panel, we want to find the maximum desired width (including margin).
// That will be the desired width of the whole panel.
MyDesiredSize.X = FMath::Max(MyDesiredSize.X, ChildDesiredSize.X + SlotPadding.GetTotalSpaceAlong<Orient_Horizontal>());
if (Child.GetSizeRule() == FSizeParam::SizeRule_Stretch)
{
++NumStretchPanels;
}
else
{
MyDesiredSize.Y += ChildDesiredSize.Y;
}
}
}
const float PanelHeight = CurveEditor->GetPanel()->GetScrollPanelGeometry().GetLocalSize().Y - 1.f;
if (NumStretchPanels > 0)
{
MyDesiredSize.Y += FMath::Max(MinimumPanelHeight, (PanelHeight - MyDesiredSize.Y) / NumStretchPanels) * NumStretchPanels;
}
MyDesiredSize.Y = FMath::Max(MyDesiredSize.Y, PanelHeight);
return MyDesiredSize;
}
void SCurveEditorViewContainer::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
ExpandInputBounds(AllottedGeometry.GetLocalSize().X);
//we manually check the child views since we can't rely on ::Tick being called since the RetainerWidget will stop them if not actually rendering
for (TSharedPtr<SCurveEditorView>& View : Views)
{
if (View)
{
View->CheckCacheAndInvalidateIfNeeded();
}
}
if (CurveEditor->GetCurrentTool())
{
CurveEditor->GetCurrentTool()->Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
}
}
int32 SCurveEditorViewContainer::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
const ESlateDrawEffect DrawEffects = ShouldBeEnabled(bParentEnabled) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
static const FName BackgroundBrushName("Brushes.Panel");
const FSlateBrush* Background = FAppStyle::GetBrush(BackgroundBrushName);
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Background, DrawEffects, Background->GetTint(InWidgetStyle));
SVerticalBox::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
if (CurveEditor->GetCurrentTool())
{
CurveEditor->GetCurrentTool()->OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId + CurveViewConstants::ELayerOffset::Tools, InWidgetStyle, bParentEnabled);
}
if (DragOperation.IsSet() && DragOperation->IsDragging())
{
// We want this to be relative to the view pane for the curves and not the global editor.
DragOperation->DragImpl->Paint(AllottedGeometry, OutDrawElements, LayerId + CurveViewConstants::ELayerOffset::DragOperations);
}
if (TimeSliderController)
{
FPaintViewAreaArgs PaintArgs;
PaintArgs.bDisplayTickLines = false;
PaintArgs.bDisplayScrubPosition = true;
PaintArgs.bDisplayMarkedFrames = false;
PaintArgs.PlaybackRangeArgs = FPaintPlaybackRangeArgs(
FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_L"),
FAppStyle::GetBrush("Sequencer.Timeline.PlayRange_Bottom_R"),
6.f);
TimeSliderController->OnPaintViewArea(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId + CurveViewConstants::ELayerOffset::GridOverlays, bParentEnabled, PaintArgs);
}
return LayerId + CurveViewConstants::ELayerOffset::Last;
}
bool SCurveEditorViewContainer::IsScrubTimeKeyEvent(const FKeyEvent& InKeyEvent)
{
const FCurveEditorCommands& Commands = FCurveEditorCommands::Get();
// Need to iterate through primary and secondary to make sure they are all pressed.
for (uint32 i = 0; i < static_cast<uint8>(EMultipleKeyBindingIndex::NumChords); ++i)
{
EMultipleKeyBindingIndex ChordIndex = static_cast<EMultipleKeyBindingIndex>(i);
const FInputChord& Chord = *Commands.ScrubTime->GetActiveChord(ChordIndex);
const bool bIsMovingTimeSlider = Chord.IsValidChord() && InKeyEvent.GetKey() == Chord.Key;
if (bIsMovingTimeSlider)
{
return true;
}
}
return false;
}
FReply SCurveEditorViewContainer::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
FReply ToolReply = FReply::Unhandled();
if (CurveEditor->GetCurrentTool())
{
ToolReply = CurveEditor->GetCurrentTool()->OnKeyDown(AsShared(), MyGeometry, InKeyEvent);
}
if (!ToolReply.IsEventHandled() &&
InKeyEvent.GetKey() == EKeys::Escape && DragOperation.IsSet())
{
DragOperation->DragImpl->CancelDrag();
DragOperation.Reset();
return FReply::Handled();
}
if (!ToolReply.IsEventHandled() &&
IsScrubTimeKeyEvent(InKeyEvent))
{
bIsScrubbingTime = true;
return FReply::Handled();
}
return ToolReply;
}
FReply SCurveEditorViewContainer::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
FReply ToolReply = FReply::Unhandled();
if (CurveEditor->GetCurrentTool())
{
ToolReply = CurveEditor->GetCurrentTool()->OnKeyUp(AsShared(), MyGeometry, InKeyEvent);
}
if (!ToolReply.IsEventHandled() &&
IsScrubTimeKeyEvent(InKeyEvent) && bIsScrubbingTime )
{
bIsScrubbingTime = false;
if (DragOperation.IsSet())
{
DragOperation->DragImpl->CancelDrag();
DragOperation.Reset();
return FReply::Handled();
}
}
return ToolReply;
}
FReply SCurveEditorViewContainer::OnPreviewMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
bCaughtMouseDown = true;
if (CurveEditor->GetCurrentTool())
{
return CurveEditor->GetCurrentTool()->OnMouseButtonDown(AsShared(), MyGeometry, MouseEvent);
}
return FReply::Unhandled();
}
FReply SCurveEditorViewContainer::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FVector2D MousePixel = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
// Marquee Selection or time scrub
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (bIsScrubbingTime)
{
DragOperation = FCurveEditorDelayedDrag(MousePixel, MouseEvent.GetEffectingButton());
DragOperation->DragImpl = MakeUnique<FCurveEditorDragOperation_ScrubTime>(CurveEditor.Get());
}
else
{
DragOperation = FCurveEditorDelayedDrag(MousePixel, MouseEvent.GetEffectingButton());
DragOperation->DragImpl = MakeUnique<FCurveEditorDragOperation_Marquee>(CurveEditor.Get());
}
return FReply::Handled();
}
// Middle Click + Alt Pan
else if (MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
{
if (MouseEvent.IsAltDown())
{
DragOperation = FCurveEditorDelayedDrag(MousePixel, MouseEvent.GetEffectingButton());
DragOperation->DragImpl = MakeUnique<FCurveEditorDragOperation_PanInput>(CurveEditor.Get());
return FReply::Handled();
}
}
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
// Zoom Timeline
if (MouseEvent.IsAltDown())
{
DragOperation = FCurveEditorDelayedDrag(MousePixel, MouseEvent.GetEffectingButton());
DragOperation->DragImpl = MakeUnique<FCurveEditorDragOperation_Zoom>(CurveEditor.Get(), nullptr);
return FReply::Handled();
}
// Pan Timeline
else
{
DragOperation = FCurveEditorDelayedDrag(MousePixel, MouseEvent.GetEffectingButton());
DragOperation->DragImpl = MakeUnique<FCurveEditorDragOperation_PanInput>(CurveEditor.Get());
return FReply::Handled();
}
}
return FReply::Unhandled();
}
FReply SCurveEditorViewContainer::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent)
{
if (CurveEditor->GetCurrentTool())
{
return CurveEditor->GetCurrentTool()->OnMouseButtonDoubleClick(AsShared(), InMyGeometry, InMouseEvent);
}
return FReply::Unhandled();
}
FReply SCurveEditorViewContainer::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FVector2D MousePixel = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
FReply ToolReply = FReply::Unhandled();
bHadMouseMovesThisTick = true;
if (CurveEditor->GetCurrentTool())
{
ToolReply = CurveEditor->GetCurrentTool()->OnMouseMove(AsShared(), MyGeometry, MouseEvent);
}
if (!ToolReply.IsEventHandled() && DragOperation.IsSet())
{
FVector2D InitialPosition = DragOperation->GetInitialPosition();
if (!DragOperation->IsDragging() && DragOperation->AttemptDragStart(MouseEvent))
{
DragOperation->DragImpl->BeginDrag(InitialPosition, MousePixel, MouseEvent);
return FReply::Handled().CaptureMouse(AsShared());
}
else if (DragOperation->IsDragging())
{
DragOperation->DragImpl->Drag(InitialPosition, MousePixel, MouseEvent);
return FReply::Handled();
}
}
return ToolReply;
}
void SCurveEditorViewContainer::OnFinishedPointerInput()
{
if (!bHadMouseMovesThisTick)
{
return;
}
// Some tools & operations defer processing Drag calls for performance reasons. Give them a chance to process the accumulated input.
if (CurveEditor->GetCurrentTool())
{
CurveEditor->GetCurrentTool()->OnFinishedPointerInput();
}
if (DragOperation && DragOperation->IsDragging())
{
DragOperation->DragImpl->FinishedPointerInput();
}
bHadMouseMovesThisTick = false;
}
FReply SCurveEditorViewContainer::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FVector2D MousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
FReply Reply = FReply::Unhandled();
if (DragOperation.IsSet() && DragOperation->IsDragging())
{
FVector2D InitialPosition = DragOperation->GetInitialPosition();
DragOperation->DragImpl->EndDrag(InitialPosition, MousePosition, MouseEvent);
Reply = FReply::Handled();
}
else if (CurveEditor->GetCurrentTool())
{
Reply = CurveEditor->GetCurrentTool()->OnMouseButtonUp(AsShared(), MyGeometry, MouseEvent);
}
if (!Reply.IsEventHandled() && MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && bCaughtMouseDown)
{
if (!MouseEvent.IsShiftDown() && !MouseEvent.IsAltDown() && !MouseEvent.IsControlDown())
{
const UE::CurveEditor::FScopedSelectionTransaction Transaction(CurveEditor, LOCTEXT("ClearSelection", "Clear selection"));
CurveEditor->GetSelection().Clear();
Reply = FReply::Handled();
}
}
bCaughtMouseDown = false;
DragOperation.Reset();
return Reply.ReleaseMouseCapture();
}
FCursorReply SCurveEditorViewContainer::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
{
// Override the cursor while the Marquee select is happening. Ideally we'd be able to do a "+" and "-" while holding
// shift and alt, but there's not built in OS cursor to do this.
if (DragOperation.IsSet() && DragOperation->IsDragging())
{
return FCursorReply::Cursor(EMouseCursor::Crosshairs);
}
return FCursorReply::Unhandled();
}
void SCurveEditorViewContainer::OnFocusLost(const FFocusEvent& InFocusEvent)
{
if (DragOperation.IsSet())
{
DragOperation->DragImpl->CancelDrag();
DragOperation.Reset();
}
if (CurveEditor->GetCurrentTool())
{
CurveEditor->GetCurrentTool()->OnFocusLost(InFocusEvent);
}
SVerticalBox::OnFocusLost(InFocusEvent);
}
void SCurveEditorViewContainer::OnCurveEditorToolChanged(FCurveEditorToolID InToolId)
{
// We need to end drag-drop operations if they switch tools. Otherwise they can start
// a marquee select, use the keyboard to switch to a diferent tool, and then the marquee
// select finishes after the tool has had a chance to activate.
if (DragOperation.IsSet())
{
// We have to cancel it instead of ending it because ending it needs mouse position and some other stuff.
DragOperation->DragImpl->CancelDrag();
DragOperation.Reset();
}
}
void SCurveEditorViewContainer::ExpandInputBounds(float NewWidth)
{
const float OldWidth = GetCachedGeometry().GetLocalSize().X;
if (NewWidth != OldWidth && OldWidth > 0)
{
// Retrieve the current bounds and cache them
double InputMin = 0.0, InputMax = 1.0;
CurveEditor->GetBounds().GetInputBounds(InputMin, InputMax);
// Increase the visible input/output ranges based on the new size of the panel
const double PixelToInputRatio = (InputMax - InputMin) / OldWidth;
InputMax += PixelToInputRatio * (NewWidth - OldWidth);
CurveEditor->GetBounds().SetInputBounds(InputMin, InputMax);
}
}
FMargin SCurveEditorViewContainer::GetSlotPadding(int32 SlotIndex) const
{
const bool bIsFirstView = SlotIndex == 0;
const bool bIsLastView = SlotIndex == Views.Num()-1;
return FMargin(0.f, bIsFirstView ? 0.f : 5.f, 0.f, bIsLastView ? 0.f : 5.f);
}
void SCurveEditorViewContainer::AddView(TSharedRef<SCurveEditorView> ViewToAdd)
{
const int32 InsertIndex = Views.Num();
ViewToAdd->RelativeOrder = Views.Num();
Views.Add(ViewToAdd);
SVerticalBox::FSlot* SlotPointer = nullptr;
AddSlot()
.Expose(SlotPointer)
[
SAssignNew(RetainerWidget, SRetainerWidget)
.RenderOnPhase(false)
.RenderOnInvalidation(false)
.bWarnOnInvalidSize(false)
[
SNew(SBox)
.Padding(MakeAttributeSP(this, &SCurveEditorViewContainer::GetSlotPadding, InsertIndex))
.Clipping(EWidgetClipping::ClipToBounds)
[
ViewToAdd
]
]
];
if (ViewToAdd->ShouldAutoSize())
{
SlotPointer->SetAutoHeight();
}
ViewToAdd->SetRetainerWidget(RetainerWidget);
}
void SCurveEditorViewContainer::Clear()
{
Views.Reset();
ClearChildren();
}
#undef LOCTEXT_NAMESPACE