// 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& InDrawer, const ESidebarTabLocation InTabLocation) { DrawerWeak = InDrawer; TabLocation = InTabLocation; OnPressed = InArgs._OnPressed; OnPinToggled = InArgs._OnPinToggled; OnDockToggled = InArgs._OnDockToggled; OnGetContextMenuContent = InArgs._OnGetContextMenuContent; DockTabStyle = &FAppStyle::Get().GetWidgetStyle(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 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 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 ButtonContent; const bool bIsVertical = InTabLocation == ESidebarTabLocation::Left || InTabLocation == ESidebarTabLocation::Right; if (bIsVertical) { const TSharedRef 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 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& InLastDrawerOpen) { const TSharedPtr 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(TEXT("Docking.SidebarButton.Opened"))); } else { MainButton->SetButtonStyle(&FAppStyle::Get().GetWidgetStyle(TEXT("Docking.SidebarButton.Closed"))); } } void SSidebarButton::OnTabRenamed(const TWeakPtr& InDrawer) { if (ensure(InDrawer == DrawerWeak) && InDrawer.IsValid()) { if (const TSharedPtr 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 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 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 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 Drawer = DrawerWeak.Pin(); if (Drawer.IsValid() && Drawer->State.bIsPinned) { return ECheckBoxState::Checked; } return ECheckBoxState::Unchecked; } const FSlateBrush* SSidebarButton::GetPinImage() const { const TSharedPtr 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 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 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 Drawer = DrawerWeak.Pin(); if (Drawer.IsValid() && Drawer->State.bIsDocked) { return ECheckBoxState::Checked; } return ECheckBoxState::Unchecked; } const FSlateBrush* SSidebarButton::GetDockImage() const { const TSharedPtr 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