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

491 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SlateFontDlgWindow.h"
#include "Fonts/CompositeFont.h"
#include "Framework/Docking/TabManager.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFileManager.h"
#include "SlateOptMacros.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Colors/SColorPicker.h"
#include "Widgets/Colors/SColorBlock.h"
#include "Widgets/Layout/SGridPanel.h"
#include <fontconfig.h>
#define LOCTEXT_NAMESPACE "SlateFontDialogNamespace"
DEFINE_LOG_CATEGORY_STATIC(LogSlateFontDialog, Log, All);
namespace
{
FcPattern* GetFontMatch(TSharedPtr<FString> SelectedFont, TSharedPtr<FString> SelectedTypeface)
{
FcPattern* Query = FcPatternCreate();
FcPatternAddString(Query, FC_FAMILY, reinterpret_cast<const FcChar8*>(TCHAR_TO_UTF8(**SelectedFont)));
FcPatternAddString(Query, FC_STYLE, reinterpret_cast<const FcChar8*>(TCHAR_TO_UTF8(**SelectedTypeface)));
// Increase the scope of possible matches
FcConfigSubstitute(nullptr, Query, FcMatchPattern);
FcDefaultSubstitute(Query);
FcResult Result = FcResultNoMatch;
FcPattern* Match = FcFontMatch(nullptr, Query, &Result);
if (Result != FcResultMatch)
{
UE_LOG(LogSlateFontDialog, Warning, TEXT("FontConfig failed to match a font, error number: %i"), static_cast<int>(Result));
Match = nullptr;
}
FcPatternDestroy(Query);
return Match;
}
FString GetFontFilename(TSharedPtr<FString> SelectedFont, TSharedPtr<FString> SelectedTypeface)
{
FcPattern* Match = GetFontMatch(SelectedFont, SelectedTypeface);
FcChar8* FilenameRaw = nullptr;
FcResult Result = FcPatternGetString(Match, FC_FILE, 0, &FilenameRaw);
if (Result != FcResultMatch)
{
UE_LOG(LogSlateFontDialog, Warning, TEXT("FontConfig failed to match a font, error number: %i"), static_cast<int>(Result));
}
FString Filename(UTF8_TO_TCHAR(FilenameRaw));
FcPatternDestroy(Match);
return Filename;
}
}
FSlateFontInfo FSlateFontDlgWindow::GetSampleFont() const
{
TSharedPtr<FCompositeFont> SampleFont = MakeShareable(new FCompositeFont(**SelectedFont, GetFontFilename(SelectedFont, SelectedTypeface), EFontHinting::Default, EFontLoadingPolicy::LazyLoad));
return FSlateFontInfo(SampleFont, SampleTextSize);
}
void FSlateFontDlgWindow::LoadFonts()
{
for (int i = 0; i < FontSet->nfont; i++)
{
FcPattern* Pattern = FontSet->fonts[i];
// Grab the font name
FcChar8* NameRaw = nullptr;
FcResult Result = FcPatternGetString(Pattern, FC_FAMILY, 0, &NameRaw);
if (Result != FcResultMatch)
{
UE_LOG(LogSlateFontDialog, Warning, TEXT("FontConfig failed to load a font, error number: %i"), static_cast<int>(Result));
continue;
}
FString Name(UTF8_TO_TCHAR(NameRaw));
// Grab the style/typeface name
FcChar8* StyleRaw = nullptr;
Result = FcPatternGetString(Pattern, FC_STYLE, 0, &StyleRaw);
if (Result != FcResultMatch)
{
UE_LOG(LogSlateFontDialog, Warning, TEXT("FontConfig failed to load a font, error number: %i"), static_cast<int>(Result));
continue;
}
FString Style(UTF8_TO_TCHAR(StyleRaw));
TArray<TSharedPtr<FString>>& Typefaces = TypefaceList.FindOrAdd(Name);
if (!Typefaces.ContainsByPredicate([&Style] (const TSharedPtr<FString>& Element) { return *Element == Style; }))
{
Typefaces.Add(MakeShareable(new FString(Style)));
}
}
// Can't use TMap::GenerateKeyArray because we need TSharedPtr<FString>, not FString
for (const TPair<FString, TArray<TSharedPtr<FString>>>& Typeface : TypefaceList)
{
FontList.AddUnique(MakeShareable(new FString(Typeface.Key)));
}
FontList.Sort([] (const TSharedPtr<FString>& First, const TSharedPtr<FString>& Second) { return *First < *Second; });
SelectedFont = FontList[0];
SelectedTypefaceList = *TypefaceList.Find(*SelectedFont);
SelectedTypeface = SelectedTypefaceList[0];
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
FReply FSlateFontDlgWindow::OpenColorPicker()
{
TSharedPtr<SWindow> ColorWindow;
SAssignNew(ColorWindow, SWindow)
.Title(LOCTEXT("ColorPickerTest-WindowTitle-StandardColor", "Standard Color"))
.ClientSize(SColorPicker::DEFAULT_WINDOW_SIZE)
.IsPopupWindow(true);
TSharedPtr<SBox> ColorPicker = SNew(SBox)
.Padding(10.0f)
[
SNew(SColorPicker)
.ParentWindow(ColorWindow)
.UseAlpha(false)
.OnColorCommitted_Lambda([this] (const FLinearColor& NewColor)
{
FontColor = NewColor;
FontColor.A = 1.0f;
SampleTextBlock->SetColorAndOpacity(FontColor);
})
];
ColorWindow->SetContent(ColorPicker.ToSharedRef());
FSlateApplication::Get().AddModalWindow(ColorWindow.ToSharedRef(), FGlobalTabmanager::Get()->GetRootWindow());
return FReply::Handled();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
FSlateFontDlgWindow::FSlateFontDlgWindow(bool& OutSuccess)
{
FontSet = FcConfigGetFonts(nullptr, FcSetSystem);
if (FontSet->nfont < 1)
{
OutSuccess = false;
UE_LOG(LogSlateFontDialog, Warning, TEXT("FontConfig could not find any fonts"));
return;
}
OutSuccess = true;
LoadFonts();
SampleTextStyle = FTextBlockStyle()
.SetFont(GetSampleFont())
.SetColorAndOpacity(FontColor);
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
// OutFontName actually gets set to the filepath of the chosen font to reduce dependencies in UTrueTypeFontFactory::LoadFontFace within TTFontImport.cpp
void FSlateFontDlgWindow::OpenFontWindow(FString& OutFontName, float& OutHeight, EFontImportFlags& OutFlags, bool& OutSuccess)
{
OutFontName = GetFontFilename(SelectedFont, SelectedTypeface);
OutHeight = (float) FontSize;
OutFlags = EFontImportFlags::None;
EnumAddFlags(OutFlags, EFontImportFlags::EnableAntialiasing);
EnumAddFlags(OutFlags, EFontImportFlags::AlphaOnly);
// Create the font dialogue window
SAssignNew(Window, SWindow)
.Title(FText::FromString(TEXT("Fonts")))
.SizingRule(ESizingRule::Autosized)
.AutoCenter(EAutoCenter::PreferredWorkArea)
.SupportsMaximize(false)
.SupportsMinimize(false)
.HasCloseButton(false)
[
SNew(SGridPanel)
// Font
+SGridPanel::Slot(0, 0)
.Padding(8.0f)
[
SNew(SVerticalBox)
// Name
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock).Text(FText::FromString(TEXT("Font")))
]
// Dropdown
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
.WidthOverride(180.0f)
[
SNew(STextComboBox)
.OptionsSource(&FontList)
.InitiallySelectedItem(FontList[0])
.OnSelectionChanged_Lambda([this, &OutFontName] (TSharedPtr<FString> InSelection, ESelectInfo::Type InSelectInfo)
{
if (InSelection.IsValid())
{
SelectedFont = InSelection;
SelectedTypefaceList = *TypefaceList.Find(*SelectedFont);
SelectedTypeface = SelectedTypefaceList[0];
OutFontName = GetFontFilename(SelectedFont, SelectedTypeface);
TypefaceDropdown->RefreshOptions();
TypefaceDropdown->SetSelectedItem(SelectedTypefaceList[0]);
SampleTextBlock->SetFont(GetSampleFont());
}
} )
]
]
]
// Typeface
+SGridPanel::Slot(1, 0)
.Padding(8.0f)
[
SNew(SVerticalBox)
// Name
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock).Text(FText::FromString(TEXT("Typeface")))
]
// Dropdown
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
.WidthOverride(100.0f)
[
SAssignNew(TypefaceDropdown, STextComboBox)
.OptionsSource(&SelectedTypefaceList)
.InitiallySelectedItem(SelectedTypefaceList[0])
.OnSelectionChanged_Lambda([this] (TSharedPtr<FString> InSelection, ESelectInfo::Type InSelectInfo)
{
if (InSelection.IsValid())
{
SelectedTypeface = InSelection;
SampleTextBlock->SetFont(GetSampleFont());
}
} )
]
]
]
// Size
+SGridPanel::Slot(2, 0)
.Padding(8.0f)
[
SNew(SVerticalBox)
// Name
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock).Text(FText::FromString(TEXT("Size")))
]
// Text box
+SVerticalBox::Slot()
.HAlign(HAlign_Left)
.AutoHeight()
[
SNew(SNumericEntryBox<uint8>)
.Value_Lambda( [this] { return FontSize; } )
.OnValueCommitted_Lambda([this, &OutHeight] (float InValue, ETextCommit::Type CommitInfo)
{
FontSize = InValue;
OutHeight = static_cast<float>(FontSize);
} )
]
]
// Effects
+SGridPanel::Slot(0, 1)
.Padding(8.0f)
[
SNew(SVerticalBox)
// Section name
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Effects")))
]
// Strikethrough
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
// Checkbox
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SCheckBox)
.Style(&FAppStyle::Get().GetWidgetStyle<FCheckBoxStyle>("SimplifiedCheckbox"))
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState)
{
if (NewState == ECheckBoxState::Checked)
{
SampleTextStyle.SetStrikeBrush(*FAppStyle::Get().GetBrush("DefaultTextUnderline"));
}
else
{
SampleTextStyle.SetStrikeBrush(*FAppStyle::Get().GetBrush("NoBrush"));
}
SampleTextBlock->SetTextStyle(&SampleTextStyle);
})
]
// Name
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(STextBlock).Text(FText::FromString(TEXT("Strikethrough")))
]
]
// Underline
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
// Checkbox
+SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SCheckBox)
.Style(&FAppStyle::Get().GetWidgetStyle<FCheckBoxStyle>("SimplifiedCheckbox"))
.OnCheckStateChanged_Lambda([this, &OutFlags](ECheckBoxState NewState)
{
if (NewState == ECheckBoxState::Checked)
{
SampleTextStyle.SetUnderlineBrush(*FAppStyle::Get().GetBrush("DefaultTextUnderline"));
EnumAddFlags(OutFlags, EFontImportFlags::EnableUnderline);
}
else
{
SampleTextStyle.SetUnderlineBrush(*FAppStyle::Get().GetBrush("NoBrush"));
EnumRemoveFlags(OutFlags, EFontImportFlags::EnableUnderline);
}
SampleTextBlock->SetTextStyle(&SampleTextStyle);
})
]
// Name
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(STextBlock).Text(FText::FromString(TEXT("Underline")))
]
]
]
// Color
+SGridPanel::Slot(1, 1)
.Padding(8.0f)
[
SNew(SVerticalBox)
// Name
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock).Text(FText::FromString(TEXT("Color")))
]
// Color picker button
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Left)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "Menu.Button")
.OnClicked_Raw(this, &FSlateFontDlgWindow::OpenColorPicker)
[
SNew(SOverlay)
+SOverlay::Slot()
.Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f))
.HAlign(HAlign_Center)
.VAlign(VAlign_Bottom)
[
SAssignNew(ColorIconText, STextBlock)
.Text(LOCTEXT("ColorLabel", "A"))
]
+SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Bottom)
[
SNew(SColorBlock)
.Color_Lambda([this] () { return FontColor; })
.Size(FVector2D(20.0f, 6.0f))
]
]
]
]
// Sample Text
+SGridPanel::Slot(2, 1)
.Padding(8.0f)
[
SNew(SBox)
.MaxDesiredHeight(200)
.MaxDesiredWidth(600)
[
SAssignNew(SampleTextBlock, STextBlock)
.TextStyle(&SampleTextStyle)
.Text(FText::FromString(TEXT("Sample Text")))
]
]
// OK / Cancel buttons
+SGridPanel::Slot(2, 2)
.Padding(8.0f)
.HAlign(EHorizontalAlignment::HAlign_Right)
[
SNew(SHorizontalBox)
// OK Button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(SButton)
.Text(FText::FromString(TEXT("OK")))
.OnClicked_Lambda([this, &OutSuccess] () -> FReply
{
OutSuccess = true;
Window->RequestDestroyWindow();
return FReply::Handled();
})
]
// Cancel Button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(SButton)
.Text(FText::FromString(TEXT("Cancel")))
.OnClicked_Lambda([this, &OutSuccess] () -> FReply
{
OutSuccess = false;
Window->RequestDestroyWindow();
return FReply::Handled();
})
]
]
];
FSlateApplication::Get().AddModalWindow(Window.ToSharedRef(), FGlobalTabmanager::Get()->GetRootWindow());
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
#undef LOCTEXT_NAMESPACE