Files
UnrealEngine/Engine/Source/Runtime/Slate/Private/Widgets/Layout/SWindowTitleBarArea.cpp
2025-05-18 13:04:45 +08:00

439 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Widgets/Layout/SWindowTitleBarArea.h"
#include "Types/PaintArgs.h"
#include "Layout/ArrangedChildren.h"
#include "Layout/LayoutUtils.h"
#include "Widgets/SWindow.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Brushes/SlateBoxBrush.h"
#include "Brushes/SlateImageBrush.h"
#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush(FPaths::EngineContentDir() / TEXT("Slate") / RelativePath + TEXT(".png"), __VA_ARGS__)
#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush(FPaths::EngineContentDir() / TEXT("Slate") / RelativePath + TEXT(".png"), __VA_ARGS__)
FSimpleDelegate SWindowTitleBarArea::OnCloseButtonClicked;
bool SWindowTitleBarArea::bIsCloseButtonActive = true;
FButtonStyle SWindowTitleBarArea::MinimizeButtonStyle;
FButtonStyle SWindowTitleBarArea::MaximizeButtonStyle;
FButtonStyle SWindowTitleBarArea::RestoreButtonStyle;
FButtonStyle SWindowTitleBarArea::CloseButtonStyle;
SWindowTitleBarArea::SWindowTitleBarArea()
: ChildSlot(this)
, bIsMinimizeButtonEnabled(true)
, bIsMaximizeRestoreButtonEnabled(true)
, bIsCloseButtonEnabled(true)
{
SetCanTick(false);
bCanSupportFocus = false;
static bool bButtonsStyleInitialized = false;
if (!bButtonsStyleInitialized)
{
const FButtonStyle ButtonStyle = FButtonStyle()
.SetNormal(BOX_BRUSH("Common/Button", FVector2D(32, 32), 8.0f / 32.0f))
.SetHovered(BOX_BRUSH("Common/Button_Hovered", FVector2D(32, 32), 8.0f / 32.0f))
.SetPressed(BOX_BRUSH("Common/Button_Pressed", FVector2D(32, 32), 8.0f / 32.0f))
.SetNormalPadding(FMargin(2, 2, 2, 2))
.SetPressedPadding(FMargin(2, 3, 2, 1));
MinimizeButtonStyle = FButtonStyle(ButtonStyle)
.SetNormal(IMAGE_BRUSH("Common/Window/WindowButton_Minimize_Normal", FVector2D(27.0f, 18.0f)))
.SetHovered(IMAGE_BRUSH("Common/Window/WindowButton_Minimize_Hovered", FVector2D(27.0f, 18.0f)))
.SetPressed(IMAGE_BRUSH("Common/Window/WindowButton_Minimize_Pressed", FVector2D(27.0f, 18.0f)))
.SetDisabled(IMAGE_BRUSH("Common/Window/WindowButton_Minimize_Disabled", FVector2D(27.0f, 18.0f)));
MaximizeButtonStyle = FButtonStyle(ButtonStyle)
.SetNormal(IMAGE_BRUSH("Common/Window/WindowButton_Maximize_Normal", FVector2D(23.0f, 18.0f)))
.SetHovered(IMAGE_BRUSH("Common/Window/WindowButton_Maximize_Hovered", FVector2D(23.0f, 18.0f)))
.SetPressed(IMAGE_BRUSH("Common/Window/WindowButton_Maximize_Pressed", FVector2D(23.0f, 18.0f)))
.SetDisabled(IMAGE_BRUSH("Common/Window/WindowButton_Maximize_Disabled", FVector2D(27.0f, 18.0f)));
RestoreButtonStyle = FButtonStyle(ButtonStyle)
.SetNormal(IMAGE_BRUSH("Common/Window/WindowButton_Restore_Normal", FVector2D(23.0f, 18)))
.SetHovered(IMAGE_BRUSH("Common/Window/WindowButton_Restore_Hovered", FVector2D(23.0f, 18)))
.SetPressed(IMAGE_BRUSH("Common/Window/WindowButton_Restore_Pressed", FVector2D(23.0f, 18)))
.SetDisabled(IMAGE_BRUSH("Common/Window/WindowButton_Maximize_Disabled", FVector2D(27.0f, 18.0f)));
CloseButtonStyle = FButtonStyle(ButtonStyle)
.SetNormal(IMAGE_BRUSH("Common/Window/WindowButton_Close_Normal", FVector2D(44.0f, 18.0f)))
.SetHovered(IMAGE_BRUSH("Common/Window/WindowButton_Close_Hovered", FVector2D(44.0f, 18.0f)))
.SetPressed(IMAGE_BRUSH("Common/Window/WindowButton_Close_Pressed", FVector2D(44.0f, 18.0f)));
bButtonsStyleInitialized = true;
}
}
SWindowTitleBarArea::~SWindowTitleBarArea() = default;
void SWindowTitleBarArea::Construct( const FArguments& InArgs )
{
MinimizeButton = SNew(SButton)
.IsFocusable(false)
.IsEnabled(bIsMinimizeButtonEnabled)
.ContentPadding(0.f)
.OnClicked(this, &SWindowTitleBarArea::MinimizeButton_OnClicked)
.Cursor(EMouseCursor::Default)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
[
SNew(SImage)
.Image(this, &SWindowTitleBarArea::GetMinimizeImage)
.AccessibleText(NSLOCTEXT("WindowTitleBar", "Minimize", "Minimize"))
];
MaximizeRestoreButton = SNew(SButton)
.IsFocusable(false)
.IsEnabled(bIsMaximizeRestoreButtonEnabled)
.ContentPadding(0.f)
.OnClicked(this, &SWindowTitleBarArea::MaximizeRestoreButton_OnClicked)
.Cursor(EMouseCursor::Default)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
[
SNew(SImage)
.Image(this, &SWindowTitleBarArea::GetMaximizeRestoreImage)
.AccessibleText(NSLOCTEXT("WindowTitleBar", "Maximize", "Maximize"))
];
CloseButton = SNew(SButton)
.IsFocusable(false)
.IsEnabled(bIsCloseButtonEnabled)
.ContentPadding(0.0f)
.OnClicked(this, &SWindowTitleBarArea::CloseButton_OnClicked)
.Cursor(EMouseCursor::Default)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
[
SNew(SImage)
.Image(this, &SWindowTitleBarArea::GetCloseImage)
.AccessibleText(NSLOCTEXT("WindowTitleBar", "Close", "Close"))
];
ChildSlot
.HAlign( InArgs._HAlign )
.VAlign( InArgs._VAlign )
.Padding( InArgs._Padding )
[
SNew(SOverlay)
+ SOverlay::Slot()
[
InArgs._Content.Widget
]
+ SOverlay::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Right)
[
SAssignNew(WindowButtonsBox, SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Right)
.AutoWidth()
[
SNew(SBox)
.Visibility(EVisibility::SelfHitTestInvisible)
.Padding(FMargin(0))
[
// Minimize
SNew(SHorizontalBox)
.Visibility(EVisibility::SelfHitTestInvisible)
+ SHorizontalBox::Slot()
.AutoWidth()
[
MinimizeButton.ToSharedRef()
]
// Maximize/Restore
+ SHorizontalBox::Slot()
.AutoWidth()
[
MaximizeRestoreButton.ToSharedRef()
]
// Close button
+ SHorizontalBox::Slot()
.AutoWidth()
[
CloseButton.ToSharedRef()
]
]
]
]
]
];
SetWindowButtonsVisibility(false);
RequestToggleFullscreen = InArgs._RequestToggleFullscreen;
}
void SWindowTitleBarArea::SetContent(const TSharedRef< SWidget >& InContent)
{
ChildSlot
[
InContent
];
}
void SWindowTitleBarArea::SetHAlign(EHorizontalAlignment HAlign)
{
ChildSlot.SetHorizontalAlignment(HAlign);
}
void SWindowTitleBarArea::SetVAlign(EVerticalAlignment VAlign)
{
ChildSlot.SetVerticalAlignment(VAlign);
}
void SWindowTitleBarArea::SetPadding(TAttribute<FMargin> InPadding)
{
ChildSlot.SetPadding(MoveTemp(InPadding));
}
FVector2D SWindowTitleBarArea::ComputeDesiredSize( float ) const
{
EVisibility ChildVisibility = ChildSlot.GetWidget()->GetVisibility();
if ( ChildVisibility != EVisibility::Collapsed )
{
return ChildSlot.GetWidget()->GetDesiredSize() + ChildSlot.GetPadding().GetDesiredSize();
}
return FVector2D::ZeroVector;
}
void SWindowTitleBarArea::OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const
{
const EVisibility& MyCurrentVisibility = this->GetVisibility();
if ( ArrangedChildren.Accepts( MyCurrentVisibility ) )
{
const FMargin SlotPadding(ChildSlot.GetPadding());
AlignmentArrangeResult XAlignmentResult = AlignChild<Orient_Horizontal>( AllottedGeometry.GetLocalSize().X, ChildSlot, SlotPadding );
AlignmentArrangeResult YAlignmentResult = AlignChild<Orient_Vertical>( AllottedGeometry.GetLocalSize().Y, ChildSlot, SlotPadding );
ArrangedChildren.AddWidget(
AllottedGeometry.MakeChild(
ChildSlot.GetWidget(),
FVector2D(XAlignmentResult.Offset, YAlignmentResult.Offset),
FVector2D(XAlignmentResult.Size, YAlignmentResult.Size)
)
);
}
}
FChildren* SWindowTitleBarArea::GetChildren()
{
return &ChildSlot;
}
int32 SWindowTitleBarArea::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
// An SWindowTitleBarArea just draws its only child
FArrangedChildren ArrangedChildren(EVisibility::Visible);
this->ArrangeChildren(AllottedGeometry, ArrangedChildren);
// Maybe none of our children are visible
if( ArrangedChildren.Num() > 0 )
{
check( ArrangedChildren.Num() == 1 );
FArrangedWidget& TheChild = ArrangedChildren[0];
return TheChild.Widget->Paint( Args.WithNewParent(this), TheChild.Geometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled( bParentEnabled ) );
}
return LayerId;
}
FReply SWindowTitleBarArea::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
// Handle double click here in fullscreen mode only. In windowed mode double click is handled via window actions.
if (GameWindow.IsValid() && GameWindow->GetWindowMode() != EWindowMode::Windowed)
{
if (RequestToggleFullscreen.IsBound())
{
RequestToggleFullscreen.Execute();
return FReply::Handled();
}
}
return FReply::Unhandled();
}
EWindowZone::Type SWindowTitleBarArea::GetWindowZoneOverride() const
{
EWindowZone::Type Zone = EWindowZone::TitleBar;
if (GameWindow.IsValid() && GameWindow->GetWindowMode() != EWindowMode::Windowed)
{
// In fullscreen, return ClientArea to prevent window from being moved
Zone = EWindowZone::ClientArea;
}
return Zone;
}
FReply SWindowTitleBarArea::MinimizeButton_OnClicked()
{
if (GameWindow.IsValid())
{
TSharedPtr<FGenericWindow> NativeWindow = GameWindow->GetNativeWindow();
if (NativeWindow.IsValid())
{
NativeWindow->Minimize();
}
}
return FReply::Handled();
}
FReply SWindowTitleBarArea::MaximizeRestoreButton_OnClicked()
{
if (GameWindow.IsValid())
{
TSharedPtr<FGenericWindow> NativeWindow = GameWindow->GetNativeWindow();
if (NativeWindow.IsValid())
{
if (NativeWindow->GetWindowMode() != EWindowMode::Windowed && RequestToggleFullscreen.IsBound())
{
RequestToggleFullscreen.Execute();
}
else if (NativeWindow->IsMaximized())
{
NativeWindow->Restore();
}
else
{
NativeWindow->Maximize();
}
}
}
return FReply::Handled();
}
FReply SWindowTitleBarArea::CloseButton_OnClicked()
{
if (bIsCloseButtonActive)
{
if (OnCloseButtonClicked.IsBound())
{
OnCloseButtonClicked.Execute();
}
else if (GameWindow.IsValid())
{
GameWindow->RequestDestroyWindow();
}
}
return FReply::Handled();
}
const FSlateBrush* SWindowTitleBarArea::GetMinimizeImage() const
{
if (!GameWindow.IsValid())
{
return &MinimizeButtonStyle.Normal;
}
TSharedPtr<FGenericWindow> NativeWindow = GameWindow->GetNativeWindow();
if (!bIsMinimizeButtonEnabled || !GameWindow->HasMinimizeBox())
{
return &MinimizeButtonStyle.Disabled;
}
else if (MinimizeButton->IsPressed())
{
return &MinimizeButtonStyle.Pressed;
}
else if (MinimizeButton->IsHovered())
{
return &MinimizeButtonStyle.Hovered;
}
else
{
return &MinimizeButtonStyle.Normal;
}
}
const FSlateBrush* SWindowTitleBarArea::GetMaximizeRestoreImage() const
{
if (!GameWindow.IsValid())
{
return &MaximizeButtonStyle.Normal;
}
TSharedPtr<FGenericWindow> NativeWindow = GameWindow->GetNativeWindow();
if (NativeWindow.IsValid() && (NativeWindow->IsMaximized() || NativeWindow->GetWindowMode() != EWindowMode::Windowed))
{
if (!bIsMaximizeRestoreButtonEnabled || !GameWindow->HasMaximizeBox())
{
return &MaximizeButtonStyle.Disabled;
}
else if (MaximizeRestoreButton->IsPressed())
{
return &RestoreButtonStyle.Pressed;
}
else if (MaximizeRestoreButton->IsHovered())
{
return &RestoreButtonStyle.Hovered;
}
else
{
return &RestoreButtonStyle.Normal;
}
}
else
{
if (!bIsMaximizeRestoreButtonEnabled || !GameWindow->HasMaximizeBox())
{
return &MaximizeButtonStyle.Disabled;
}
else if (MaximizeRestoreButton->IsPressed())
{
return &MaximizeButtonStyle.Pressed;
}
else if (MaximizeRestoreButton->IsHovered())
{
return &MaximizeButtonStyle.Hovered;
}
else
{
return &MaximizeButtonStyle.Normal;
}
}
}
const FSlateBrush* SWindowTitleBarArea::GetCloseImage() const
{
if (!GameWindow.IsValid())
{
return &CloseButtonStyle.Normal;
}
TSharedPtr<FGenericWindow> NativeWindow = GameWindow->GetNativeWindow();
if (!bIsCloseButtonEnabled)
{
return &CloseButtonStyle.Disabled;
}
else if (CloseButton->IsPressed())
{
return &CloseButtonStyle.Pressed;
}
else if (CloseButton->IsHovered())
{
return &CloseButtonStyle.Hovered;
}
else
{
return &CloseButtonStyle.Normal;
}
}