Files
UnrealEngine/Engine/Source/Developer/ToolWidgets/Private/Sidebar/SSidebarButton.cpp
2025-05-18 13:04:45 +08:00

472 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SSidebarButton.h"
#include "Framework/Application/SlateApplication.h"
#include "Sidebar/SidebarDrawer.h"
#include "Sidebar/SSidebar.h"
#include "Sidebar/SSidebarButtonText.h"
#include "Sidebar/SSidebarDrawer.h"
#include "Styling/AppStyle.h"
#include "Widgets/Colors/SComplexGradient.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SOverlay.h"
#define LOCTEXT_NAMESPACE "SSidebarDrawerButton"
void SSidebarButton::Construct(const FArguments& InArgs, const TSharedRef<FSidebarDrawer>& InDrawer, const ESidebarTabLocation InTabLocation)
{
DrawerWeak = InDrawer;
TabLocation = InTabLocation;
OnPressed = InArgs._OnPressed;
OnPinToggled = InArgs._OnPinToggled;
OnDockToggled = InArgs._OnDockToggled;
OnGetContextMenuContent = InArgs._OnGetContextMenuContent;
DockTabStyle = &FAppStyle::Get().GetWidgetStyle<FDockTabStyle>(TEXT("Docking.Tab"));
static FLinearColor ActiveBorderColor = FAppStyle::Get().GetSlateColor(TEXT("Docking.Tab.ActiveTabIndicatorColor")).GetSpecifiedColor();
static FLinearColor ActiveBorderColorTransparent = FLinearColor(ActiveBorderColor.R, ActiveBorderColor.G, ActiveBorderColor.B, 0.0f);
static TArray<FLinearColor> GradientStops{ ActiveBorderColorTransparent, ActiveBorderColor, ActiveBorderColorTransparent };
const bool bIsHorizontal = TabLocation == ESidebarTabLocation::Top || TabLocation == ESidebarTabLocation::Bottom;
const float MinDesiredWidth = bIsHorizontal ? InArgs._MinButtonSize : InArgs._ButtonThickness;
const float MinDesiredHeight = bIsHorizontal ? InArgs._ButtonThickness : InArgs._MinButtonSize;
const float MaxDesiredWidth = bIsHorizontal ? InArgs._MaxButtonSize : InArgs._ButtonThickness;
const float MaxDesiredHeight = bIsHorizontal ? InArgs._ButtonThickness : InArgs._MaxButtonSize;
TSharedPtr<SImage> IconWidget;
if (InDrawer->Config.Icon.IsSet() || InDrawer->Config.Icon.IsBound())
{
IconWidget = SNew(SImage)
.ColorAndOpacity(FSlateColor::UseForeground())
.Image(InDrawer->Config.Icon)
.DesiredSizeOverride(FVector2D(16, 16));
}
Label.Reset();
if (InDrawer->Config.ButtonText.IsSet() || InDrawer->Config.ButtonText.IsBound())
{
SAssignNew(Label, SSidebarButtonText)
.TextStyle(&DockTabStyle->TabTextStyle)
.Text(InDrawer->Config.ButtonText)
.OverflowPolicy(ETextOverflowPolicy::Ellipsis)
.Clipping(EWidgetClipping::ClipToBounds);
}
SAssignNew(PinCheckBox, SCheckBox)
.Style(FAppStyle::Get(), TEXT("ToggleButtonCheckbox"))
.Visibility(this, &SSidebarButton::GetPinVisibility)
.ToolTipText(this, &SSidebarButton::GetPinToolTipText)
.IsChecked(this, &SSidebarButton::IsPinChecked)
.OnCheckStateChanged(this, &SSidebarButton::OnPinStateChanged)
.Padding(2.0f)
.HAlign(HAlign_Center)
[
SNew(SImage)
.ColorAndOpacity(FSlateColor::UseForeground())
.Image(this, &SSidebarButton::GetPinImage)
];
SAssignNew(DockCheckBox, SCheckBox)
.Style(FAppStyle::Get(), TEXT("ToggleButtonCheckbox"))
.Visibility(this, &SSidebarButton::GetDockVisibility)
.ToolTipText(this, &SSidebarButton::GetDockToolTipText)
.IsChecked(this, &SSidebarButton::IsDockChecked)
.OnCheckStateChanged(this, &SSidebarButton::OnDockStateChanged)
.Padding(2.0f)
.HAlign(HAlign_Center)
[
SNew(SImage)
.ColorAndOpacity(FSlateColor::UseForeground())
.Image(this, &SSidebarButton::GetDockImage)
];
TSharedPtr<SWidget> ButtonContent;
const bool bIsVertical = InTabLocation == ESidebarTabLocation::Left || InTabLocation == ESidebarTabLocation::Right;
if (bIsVertical)
{
const TSharedRef<SVerticalBox> VerticalContent = SNew(SVerticalBox);
if (IconWidget.IsValid())
{
VerticalContent->AddSlot()
.AutoHeight()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(0.f, 2.f, 0.f, 3.f)
[
IconWidget.ToSharedRef()
];
}
if (Label.IsValid())
{
VerticalContent->AddSlot()
.FillHeight(1.f)
.HAlign(HAlign_Center)
.Padding(0.f, 3.f, 0.f, 3.f)
[
Label.ToSharedRef()
];
}
if (!InDrawer->bDisablePin)
{
VerticalContent->AddSlot()
.AutoHeight()
.HAlign(HAlign_Center)
.Padding(0.f, 3.f, 0.f, 1.f)
[
PinCheckBox.ToSharedRef()
];
}
if (!InDrawer->bDisableDock)
{
VerticalContent->AddSlot()
.AutoHeight()
.HAlign(HAlign_Center)
.Padding(0.0f, 1.0f, 0.0f, 3.0f)
[
DockCheckBox.ToSharedRef()
];
}
ButtonContent = VerticalContent;
}
else
{
const TSharedRef<SHorizontalBox> HorizontalContent = SNew(SHorizontalBox);
if (IconWidget.IsValid())
{
HorizontalContent->AddSlot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(2.f, 0.f, 3.f, 0.f)
[
IconWidget.ToSharedRef()
];
}
if (Label.IsValid())
{
HorizontalContent->AddSlot()
.FillWidth(1.f)
.VAlign(VAlign_Center)
.Padding(3.f, 0.f, 3.f, 0.f)
[
Label.ToSharedRef()
];
}
if (!InDrawer->bDisablePin)
{
HorizontalContent->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(3.f, 0.f, 1.f, 0.f)
[
PinCheckBox.ToSharedRef()
];
}
if (!InDrawer->bDisableDock)
{
HorizontalContent->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(1.f, 0.f, 3.f, 0.f)
[
DockCheckBox.ToSharedRef()
];
}
ButtonContent = HorizontalContent;
}
ChildSlot
.Padding(0)
[
SNew(SBox)
.MinDesiredWidth(MinDesiredWidth)
.MaxDesiredWidth(MaxDesiredWidth)
.MinDesiredHeight(MinDesiredHeight)
.MaxDesiredHeight(MaxDesiredHeight)
.Clipping(EWidgetClipping::ClipToBounds)
[
SNew(SOverlay)
+ SOverlay::Slot()
[
SAssignNew(MainButton, SButton)
.ToolTipText(InDrawer->Config.ToolTipText)
.ContentPadding(FMargin(0.f, DockTabStyle->TabPadding.Top, 0.f, DockTabStyle->TabPadding.Bottom))
.OnPressed_Lambda([this]()
{
// Activate tab on mouse down (not mouse down-up) for consistency with non-sidebar tabs
OnPressed.ExecuteIfBound(DrawerWeak.Pin().ToSharedRef());
})
.ForegroundColor(FSlateColor::UseForeground())
[
ButtonContent.ToSharedRef()
]
]
+ SOverlay::Slot()
[
SAssignNew(OpenBorder, SBorder)
.Visibility(EVisibility::HitTestInvisible)
]
+ SOverlay::Slot()
.HAlign(GetHAlignFromTabLocation(TabLocation))
.VAlign(GetVAlignFromTabLocation(TabLocation))
[
SAssignNew(ActiveIndicator, SComplexGradient)
.DesiredSizeOverride(FVector2D(1.f, 1.f))
.GradientColors(GradientStops)
.Orientation(Orient_Horizontal)
.Visibility(this, &SSidebarButton::GetActiveTabIndicatorVisibility)
]
]
];
UpdateAppearance(nullptr);
}
void SSidebarButton::UpdateAppearance(const TSharedPtr<FSidebarDrawer>& InLastDrawerOpen)
{
const TSharedPtr<FSidebarDrawer> ThisDrawer = DrawerWeak.Pin();
if (!ThisDrawer.IsValid())
{
return;
}
float LabelRotation;
FName FocusBorderBrushName;
switch (TabLocation)
{
case ESidebarTabLocation::Left:
LabelRotation = -90.f;
FocusBorderBrushName = TEXT("Docking.Sidebar.Border_SquareRight");
break;
case ESidebarTabLocation::Right:
LabelRotation = 90.f;
FocusBorderBrushName = TEXT("Docking.Sidebar.Border_SquareLeft");
break;
default:
LabelRotation = 0.f;
FocusBorderBrushName = TEXT("None");
break;
}
if (Label.IsValid())
{
Label->SetRotation(LabelRotation);
}
// Border when not docked and open
if (InLastDrawerOpen == ThisDrawer && (!ThisDrawer->State.bIsDocked && ThisDrawer->bIsOpen))
{
OpenBorder->SetVisibility(EVisibility::HitTestInvisible);
OpenBorder->SetBorderImage(FAppStyle::Get().GetBrush(FocusBorderBrushName));
}
else
{
OpenBorder->SetVisibility(EVisibility::Collapsed);
}
// Button style
if (InLastDrawerOpen == ThisDrawer || ThisDrawer->State.bIsDocked)
{
// this button is the one with the tab that is actually opened so show the tab border
MainButton->SetButtonStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>(TEXT("Docking.SidebarButton.Opened")));
}
else
{
MainButton->SetButtonStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>(TEXT("Docking.SidebarButton.Closed")));
}
}
void SSidebarButton::OnTabRenamed(const TWeakPtr<FSidebarDrawer>& InDrawer)
{
if (ensure(InDrawer == DrawerWeak) && InDrawer.IsValid())
{
if (const TSharedPtr<FSidebarDrawer> DrawerConfig = InDrawer.Pin())
{
if (Label.IsValid())
{
Label->SetText(DrawerConfig->Config.ButtonText);
}
MainButton->SetToolTipText(DrawerConfig->Config.ToolTipText);
}
}
}
FReply SSidebarButton::OnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
if (InMouseEvent.GetEffectingButton() == EKeys::RightMouseButton && OnGetContextMenuContent.IsBound())
{
FWidgetPath WidgetPath = InMouseEvent.GetEventPath() != nullptr ? *InMouseEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().PushMenu(AsShared(), WidgetPath, OnGetContextMenuContent.Execute(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect::ContextMenu);
return FReply::Handled();
}
return FReply::Unhandled();
}
FSlateColor SSidebarButton::GetForegroundColor() const
{
if (ActiveIndicator->GetVisibility() != EVisibility::Collapsed)
{
return DockTabStyle->ActiveForegroundColor;
}
else if (IsHovered())
{
return DockTabStyle->HoveredForegroundColor;
}
return FSlateColor::UseStyle();
}
EVisibility SSidebarButton::GetActiveTabIndicatorVisibility() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (Drawer.IsValid()
&& Drawer->bIsOpen
&& Drawer->DrawerWidget.IsValid()
&& Drawer->DrawerWidget->HasAnyUserFocusOrFocusedDescendants())
{
return EVisibility::HitTestInvisible;
}
return EVisibility::Collapsed;
}
EVisibility SSidebarButton::GetPinVisibility() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (!Drawer.IsValid() || Drawer->bDisablePin)
{
return EVisibility::Collapsed;
}
return (Drawer->State.bIsPinned || IsHovered() || Drawer->bIsOpen)
? EVisibility::Visible : EVisibility::Hidden;
}
FText SSidebarButton::GetPinToolTipText() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (Drawer.IsValid() && Drawer->State.bIsPinned)
{
return LOCTEXT("UnpinTabToolTip", "Unpin Tab");
}
return LOCTEXT("PinTabToolTip", "Pin Tab");
}
ECheckBoxState SSidebarButton::IsPinChecked() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (Drawer.IsValid() && Drawer->State.bIsPinned)
{
return ECheckBoxState::Checked;
}
return ECheckBoxState::Unchecked;
}
const FSlateBrush* SSidebarButton::GetPinImage() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (Drawer.IsValid() && Drawer->State.bIsPinned)
{
return FAppStyle::Get().GetBrush(TEXT("Icons.Pinned"));
}
return FAppStyle::Get().GetBrush(TEXT("Icons.Unpinned"));
}
void SSidebarButton::OnPinStateChanged(const ECheckBoxState InNewState)
{
OnPinToggled.ExecuteIfBound(DrawerWeak.Pin().ToSharedRef(), InNewState == ECheckBoxState::Checked);
}
EVisibility SSidebarButton::GetDockVisibility() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (!Drawer.IsValid() || Drawer->bDisableDock)
{
return EVisibility::Collapsed;
}
return (Drawer->State.bIsDocked || IsHovered() || Drawer->bIsOpen) ? EVisibility::Visible : EVisibility::Hidden;
}
FText SSidebarButton::GetDockToolTipText() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (Drawer.IsValid() && Drawer->State.bIsDocked)
{
return LOCTEXT("UndockTabToolTip", "Undock Tab");
}
return LOCTEXT("DockTabToolTip", "Dock Tab");
}
ECheckBoxState SSidebarButton::IsDockChecked() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (Drawer.IsValid() && Drawer->State.bIsDocked)
{
return ECheckBoxState::Checked;
}
return ECheckBoxState::Unchecked;
}
const FSlateBrush* SSidebarButton::GetDockImage() const
{
const TSharedPtr<FSidebarDrawer> Drawer = DrawerWeak.Pin();
if (Drawer.IsValid() && Drawer->State.bIsPinned)
{
return FAppStyle::Get().GetBrush(TEXT("Icons.Layout"));
}
return FAppStyle::Get().GetBrush(TEXT("Icons.Layout"));
}
void SSidebarButton::OnDockStateChanged(const ECheckBoxState InNewState)
{
OnDockToggled.ExecuteIfBound(DrawerWeak.Pin().ToSharedRef(), InNewState == ECheckBoxState::Checked);
}
EHorizontalAlignment SSidebarButton::GetHAlignFromTabLocation(const ESidebarTabLocation InTabLocation)
{
switch (InTabLocation)
{
case ESidebarTabLocation::Left:
return HAlign_Left;
case ESidebarTabLocation::Right:
return HAlign_Right;
case ESidebarTabLocation::Top:
case ESidebarTabLocation::Bottom:
return HAlign_Fill;
}
return HAlign_Fill;
}
EVerticalAlignment SSidebarButton::GetVAlignFromTabLocation(const ESidebarTabLocation InTabLocation)
{
switch (InTabLocation)
{
case ESidebarTabLocation::Left:
case ESidebarTabLocation::Right:
return VAlign_Fill;
case ESidebarTabLocation::Top:
return VAlign_Top;
case ESidebarTabLocation::Bottom:
return VAlign_Bottom;
}
return VAlign_Fill;
}
#undef LOCTEXT_NAMESPACE