// Copyright Epic Games, Inc. All Rights Reserved. #include "Components/ComboBoxString.h" #include "Widgets/SNullWidget.h" #include "UObject/EditorObjectVersion.h" #include "UObject/ConstructorHelpers.h" #include "Engine/Font.h" #include "Styling/UMGCoreStyle.h" #define LOCTEXT_NAMESPACE "UMG" ///////////////////////////////////////////////////// // UComboBoxString static FComboBoxStyle* DefaultComboBoxStyle = nullptr; static FTableRowStyle* DefaultComboBoxRowStyle = nullptr; #if WITH_EDITOR static FComboBoxStyle* EditorComboBoxStyle = nullptr; static FTableRowStyle* EditorComboBoxRowStyle = nullptr; #endif UComboBoxString::UComboBoxString(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { if (DefaultComboBoxStyle == nullptr) { DefaultComboBoxStyle = new FComboBoxStyle(FUMGCoreStyle::Get().GetWidgetStyle("ComboBox")); // Unlink UMG default colors. DefaultComboBoxStyle->UnlinkColors(); } if (DefaultComboBoxRowStyle == nullptr) { DefaultComboBoxRowStyle = new FTableRowStyle(FUMGCoreStyle::Get().GetWidgetStyle("TableView.Row")); // Unlink UMG default colors. DefaultComboBoxRowStyle->UnlinkColors(); } WidgetStyle = *DefaultComboBoxStyle; ItemStyle = *DefaultComboBoxRowStyle; #if WITH_EDITOR if (EditorComboBoxStyle == nullptr) { EditorComboBoxStyle = new FComboBoxStyle(FCoreStyle::Get().GetWidgetStyle("EditorUtilityComboBox")); // Unlink UMG Editor colors from the editor settings colors. EditorComboBoxStyle->UnlinkColors(); } if (EditorComboBoxRowStyle == nullptr) { EditorComboBoxRowStyle = new FTableRowStyle(FCoreStyle::Get().GetWidgetStyle("TableView.Row")); // Unlink UMG Editor colors from the editor settings colors. EditorComboBoxRowStyle->UnlinkColors(); } if (IsEditorWidget()) { WidgetStyle = *EditorComboBoxStyle; ItemStyle = *EditorComboBoxRowStyle; // The CDO isn't an editor widget and thus won't use the editor style, call post edit change to mark difference from CDO PostEditChange(); } #endif // WITH_EDITOR WidgetStyle.UnlinkColors(); ItemStyle.UnlinkColors(); ForegroundColor = ItemStyle.TextColor; bIsFocusable = true; ContentPadding = FMargin(4.0, 2.0); MaxListHeight = 450.0f; HasDownArrow = true; EnableGamepadNavigationMode = true; // We don't want to try and load fonts on the server. if ( !IsRunningDedicatedServer() ) { static ConstructorHelpers::FObjectFinder RobotoFontObj(*UWidget::GetDefaultFontName()); Font = FSlateFontInfo(RobotoFontObj.Object, 16, FName("Bold")); } } void UComboBoxString::PostInitProperties() { Super::PostInitProperties(); // Initialize the set of options from the default set only once. for (const FString& DefaultOption : DefaultOptions) { AddOption(DefaultOption); } } void UComboBoxString::ReleaseSlateResources(bool bReleaseChildren) { Super::ReleaseSlateResources(bReleaseChildren); MyComboBox.Reset(); ComboBoxContent.Reset(); } void UComboBoxString::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FEditorObjectVersion::GUID); } void UComboBoxString::PostLoad() { Super::PostLoad(); // Initialize the set of options from the default set only once. for (const FString& DefaultOption : DefaultOptions) { AddOption(DefaultOption); } if (GetLinkerCustomVersion(FEditorObjectVersion::GUID) < FEditorObjectVersion::ComboBoxControllerSupportUpdate) { EnableGamepadNavigationMode = false; } } TSharedRef UComboBoxString::RebuildWidget() { int32 InitialIndex = FindOptionIndex(SelectedOption); if ( InitialIndex != -1 ) { CurrentOptionPtr = Options[InitialIndex]; } MyComboBox = SNew(SComboBox< TSharedPtr >) .ComboBoxStyle(&WidgetStyle) .ItemStyle(&ItemStyle) .ForegroundColor(ForegroundColor) .OptionsSource(&Options) .InitiallySelectedItem(CurrentOptionPtr) .ContentPadding(ContentPadding) .MaxListHeight(MaxListHeight) .HasDownArrow(HasDownArrow) .EnableGamepadNavigationMode(EnableGamepadNavigationMode) .OnGenerateWidget(BIND_UOBJECT_DELEGATE(SComboBox< TSharedPtr >::FOnGenerateWidget, HandleGenerateWidget)) .OnSelectionChanged(BIND_UOBJECT_DELEGATE(SComboBox< TSharedPtr >::FOnSelectionChanged, HandleSelectionChanged)) .OnComboBoxOpening(BIND_UOBJECT_DELEGATE(FOnComboBoxOpening, HandleOpening)) .IsFocusable(bIsFocusable) [ SAssignNew(ComboBoxContent, SBox) ]; if ( InitialIndex != -1 ) { // Generate the widget for the initially selected widget if needed UpdateOrGenerateWidget(CurrentOptionPtr); } return MyComboBox.ToSharedRef(); } void UComboBoxString::AddOption(const FString& Option) { Options.Add(MakeShareable(new FString(Option))); RefreshOptions(); } bool UComboBoxString::RemoveOption(const FString& Option) { int32 OptionIndex = FindOptionIndex(Option); if ( OptionIndex != -1 ) { if ( Options[OptionIndex] == CurrentOptionPtr ) { ClearSelection(); } Options.RemoveAt(OptionIndex); RefreshOptions(); return true; } return false; } int32 UComboBoxString::FindOptionIndex(const FString& Option) const { for ( int32 OptionIndex = 0; OptionIndex < Options.Num(); OptionIndex++ ) { const TSharedPtr& OptionAtIndex = Options[OptionIndex]; if ( ( *OptionAtIndex ) == Option ) { return OptionIndex; } } return -1; } FString UComboBoxString::GetOptionAtIndex(int32 Index) const { if (Index >= 0 && Index < Options.Num()) { return *(Options[Index]); } return FString(); } void UComboBoxString::ClearOptions() { ClearSelection(); Options.Empty(); if ( MyComboBox.IsValid() ) { MyComboBox->RefreshOptions(); } } void UComboBoxString::ClearSelection() { CurrentOptionPtr.Reset(); if ( MyComboBox.IsValid() ) { MyComboBox->ClearSelection(); } if ( ComboBoxContent.IsValid() ) { ComboBoxContent->SetContent(SNullWidget::NullWidget); } } void UComboBoxString::RefreshOptions() { if ( MyComboBox.IsValid() ) { MyComboBox->RefreshOptions(); } } void UComboBoxString::SetSelectedOption(FString Option) { int32 InitialIndex = FindOptionIndex(Option); SetSelectedIndex(InitialIndex); } void UComboBoxString::SetSelectedIndex(const int32 Index) { if (Options.IsValidIndex(Index)) { CurrentOptionPtr = Options[Index]; // Don't select item if its already selected if (SelectedOption != *CurrentOptionPtr) { SelectedOption = *CurrentOptionPtr; if (ComboBoxContent.IsValid()) { MyComboBox->SetSelectedItem(CurrentOptionPtr); UpdateOrGenerateWidget(CurrentOptionPtr); } else { HandleSelectionChanged(CurrentOptionPtr, ESelectInfo::Direct); } } } } FString UComboBoxString::GetSelectedOption() const { if (CurrentOptionPtr.IsValid()) { return *CurrentOptionPtr; } return FString(); } int32 UComboBoxString::GetSelectedIndex() const { if (CurrentOptionPtr.IsValid()) { for (int32 OptionIndex = 0; OptionIndex < Options.Num(); ++OptionIndex) { if (Options[OptionIndex] == CurrentOptionPtr) { return OptionIndex; } } } return -1; } int32 UComboBoxString::GetOptionCount() const { return Options.Num(); } bool UComboBoxString::IsOpen() const { return MyComboBox.IsValid() && MyComboBox->IsOpen(); } void UComboBoxString::UpdateOrGenerateWidget(TSharedPtr Item) { // If no custom widget was supplied and the default STextBlock already exists, // just update its text instead of rebuilding the widget. if (DefaultComboBoxContent.IsValid() && (IsDesignTime() || OnGenerateWidgetEvent.IsBound())) { const FString StringItem = Item.IsValid() ? *Item : FString(); DefaultComboBoxContent.Pin()->SetText(FText::FromString(StringItem)); } else { DefaultComboBoxContent.Reset(); ComboBoxContent->SetContent(HandleGenerateWidget(Item)); } } TSharedRef UComboBoxString::HandleGenerateWidget(TSharedPtr Item) const { FString StringItem = Item.IsValid() ? *Item : FString(); // Call the user's delegate to see if they want to generate a custom widget bound to the data source. if ( !IsDesignTime() && OnGenerateWidgetEvent.IsBound() ) { UWidget* Widget = OnGenerateWidgetEvent.Execute(StringItem); if ( Widget != NULL ) { return Widget->TakeWidget(); } } // If a row wasn't generated just create the default one, a simple text block of the item's name. return SNew(STextBlock) .Text(FText::FromString(StringItem)) .Font(Font); } void UComboBoxString::HandleSelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectionType) { CurrentOptionPtr = Item; SelectedOption = CurrentOptionPtr.IsValid() ? CurrentOptionPtr.ToSharedRef().Get() : FString(); // When the selection changes we always generate another widget to represent the content area of the combobox. if ( ComboBoxContent.IsValid() ) { UpdateOrGenerateWidget(CurrentOptionPtr); } if ( !IsDesignTime() ) { OnSelectionChanged.Broadcast(Item.IsValid() ? *Item : FString(), SelectionType); } } void UComboBoxString::HandleOpening() { OnOpening.Broadcast(); } #if WITH_EDITOR const FText UComboBoxString::GetPaletteCategory() { return LOCTEXT("Input", "Input"); } #endif ///////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE