// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "FontEditorModule.h" #include "SCompositeFontEditor.h" #include "SFilePathPicker.h" #include "SInlineEditableTextBlock.h" #include "SNumericEntryBox.h" #include "ScopedTransaction.h" #include "DesktopPlatformModule.h" #include "Engine/Font.h" #define LOCTEXT_NAMESPACE "FontEditor" /** Entry used to weakly reference a particular typeface entry in the SListView */ struct FTypefaceListViewEntry { TAttribute Typeface; int32 TypefaceEntryIndex; FTypefaceListViewEntry() : Typeface() , TypefaceEntryIndex(INDEX_NONE) { } FTypefaceListViewEntry(TAttribute& InTypeface, const int32 InTypefaceEntryIndex) : Typeface(InTypeface) , TypefaceEntryIndex(InTypefaceEntryIndex) { } void Reset() { *this = FTypefaceListViewEntry(); } FTypefaceEntry* GetTypefaceEntry() const { FTypeface* const TypefacePtr = Typeface.Get(nullptr); return (TypefacePtr && TypefaceEntryIndex < TypefacePtr->Fonts.Num()) ? &TypefacePtr->Fonts[TypefaceEntryIndex] : nullptr; } }; /** Entry used to weakly reference a particular sub-typeface entry in the SListView */ struct FSubTypefaceListViewEntry { FCompositeFont* CompositeFont; int32 SubTypefaceEntryIndex; FSubTypefaceListViewEntry() : CompositeFont(nullptr) , SubTypefaceEntryIndex(INDEX_NONE) { } FSubTypefaceListViewEntry(FCompositeFont* const InCompositeFont, const int32 InSubTypefaceEntryIndex) : CompositeFont(InCompositeFont) , SubTypefaceEntryIndex(InSubTypefaceEntryIndex) { } void Reset() { *this = FSubTypefaceListViewEntry(); } FCompositeSubFont* GetSubTypefaceEntry() const { return (CompositeFont && SubTypefaceEntryIndex < CompositeFont->SubTypefaces.Num()) ? &CompositeFont->SubTypefaces[SubTypefaceEntryIndex] : nullptr; } }; /** Entry used to weakly reference a particular character range entry in the STileView */ struct FCharacterRangeTileViewEntry { FSubTypefaceListViewEntryPtr SubTypefaceEntry; int32 RangeEntryIndex; FCharacterRangeTileViewEntry() : SubTypefaceEntry() , RangeEntryIndex(INDEX_NONE) { } FCharacterRangeTileViewEntry(FSubTypefaceListViewEntryPtr InSubTypefaceEntry, const int32 InRangeEntryIndex) : SubTypefaceEntry(InSubTypefaceEntry) , RangeEntryIndex(InRangeEntryIndex) { } void Reset() { *this = FCharacterRangeTileViewEntry(); } FInt32Range* GetRange() const { FCompositeSubFont* const SubTypefaceEntryPtr = (SubTypefaceEntry.IsValid()) ? SubTypefaceEntry->GetSubTypefaceEntry() : nullptr; return (SubTypefaceEntryPtr && RangeEntryIndex < SubTypefaceEntryPtr->CharacterRanges.Num()) ? &SubTypefaceEntryPtr->CharacterRanges[RangeEntryIndex] : nullptr; } }; SCompositeFontEditor::~SCompositeFontEditor() { } void SCompositeFontEditor::Construct(const FArguments& InArgs) { FontEditorPtr = InArgs._FontEditor; ChildSlot [ SNew(SScrollBox) +SScrollBox::Slot() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SAssignNew(DefaultTypefaceEditor, STypefaceEditor) .CompositeFontEditor(this) .Typeface(this, &SCompositeFontEditor::GetDefaultTypeface) .TypefaceDisplayName(LOCTEXT("DefaultFontFamilyName", "Default Font Family")) ] +SVerticalBox::Slot() .AutoHeight() [ SAssignNew(SubTypefaceEntriesListView, SListView) .ListItemsSource(&SubTypefaceEntries) .SelectionMode(ESelectionMode::None) .OnGenerateRow(this, &SCompositeFontEditor::MakeSubTypefaceEntryWidget) ] ] ]; UpdateSubTypefaceList(); } void SCompositeFontEditor::Refresh() { FlushCachedFont(); DefaultTypefaceEditor->Refresh(); UpdateSubTypefaceList(); } void SCompositeFontEditor::FlushCachedFont() { FCompositeFont* const CompositeFont = GetCompositeFont(); if(CompositeFont) { CompositeFont->MakeDirty(); } TSharedPtr FontEditor = FontEditorPtr.Pin(); if(FontEditor.IsValid()) { FontEditor->RefreshPreview(); } } UFont* SCompositeFontEditor::GetFontObject() const { TSharedPtr FontEditor = FontEditorPtr.Pin(); return (FontEditor.IsValid()) ? FontEditor->GetFont() : nullptr; } FCompositeFont* SCompositeFontEditor::GetCompositeFont() const { UFont* const FontObject = GetFontObject(); return (FontObject) ? &FontObject->CompositeFont : nullptr; } FTypeface* SCompositeFontEditor::GetDefaultTypeface() const { UFont* const FontObject = GetFontObject(); return (FontObject) ? &FontObject->CompositeFont.DefaultTypeface : nullptr; } const FTypeface* SCompositeFontEditor::GetConstDefaultTypeface() const { return GetDefaultTypeface(); } void SCompositeFontEditor::UpdateSubTypefaceList() { for(FSubTypefaceListViewEntryPtr& SubTypefaceListViewEntry : SubTypefaceEntries) { SubTypefaceListViewEntry->Reset(); } FCompositeFont* const CompositeFontPtr = GetCompositeFont(); if(CompositeFontPtr) { SubTypefaceEntries.Empty(CompositeFontPtr->SubTypefaces.Num()); for(int32 SubTypefaceEntryIndex = 0; SubTypefaceEntryIndex < CompositeFontPtr->SubTypefaces.Num(); ++SubTypefaceEntryIndex) { SubTypefaceEntries.Add(MakeShareable(new FSubTypefaceListViewEntry(CompositeFontPtr, SubTypefaceEntryIndex))); } } // Add a dummy entry for the "Add" button slot SubTypefaceEntries.Add(MakeShareable(new FSubTypefaceListViewEntry())); SubTypefaceEntriesListView->RequestListRefresh(); } TSharedRef SCompositeFontEditor::MakeSubTypefaceEntryWidget(FSubTypefaceListViewEntryPtr InSubTypefaceEntry, const TSharedRef& OwnerTable) { TSharedPtr EntryWidget; if(InSubTypefaceEntry->SubTypefaceEntryIndex == INDEX_NONE) { // Dummy entry for the "Add" button EntryWidget = SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.DarkGroupBorder")) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ForegroundColor(FSlateColor::UseForeground()) .ToolTipText(LOCTEXT("AddSubFontFamilyTooltip", "Add a sub-font family to this composite font")) .OnClicked(this, &SCompositeFontEditor::OnAddSubFontFamily) .VAlign(VAlign_Center) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(8.0f) .VAlign(VAlign_Center) [ SNew(SImage) .Image(FEditorStyle::Get().GetBrush("FontEditor.Button_Add")) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .AutoWrapText(true) .Text(LOCTEXT("AddSubFontFamily", "Add Sub-Font Family")) .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) .Justification(ETextJustify::Center) ] ] ]; } else { EntryWidget = SNew(SSubTypefaceEditor) .CompositeFontEditor(this) .SubTypeface(InSubTypefaceEntry) .ParentTypeface(this, &SCompositeFontEditor::GetConstDefaultTypeface) .OnDeleteSubFontFamily(this, &SCompositeFontEditor::OnDeleteSubFontFamily); } return SNew(STableRow, OwnerTable) [ EntryWidget.ToSharedRef() ]; } FReply SCompositeFontEditor::OnAddSubFontFamily() { const FScopedTransaction Transaction(LOCTEXT("AddSubFontFamily", "Add Sub-Font Family")); GetFontObject()->Modify(); FCompositeFont* const CompositeFontPtr = GetCompositeFont(); if(CompositeFontPtr) { CompositeFontPtr->SubTypefaces.Add(FCompositeSubFont()); UpdateSubTypefaceList(); FlushCachedFont(); } return FReply::Handled(); } void SCompositeFontEditor::OnDeleteSubFontFamily(const FSubTypefaceListViewEntryPtr& SubTypefaceEntryToRemove) { const FScopedTransaction Transaction(LOCTEXT("DeleteSubFontFamily", "Delete Sub-Font Family")); GetFontObject()->Modify(); FCompositeFont* const CompositeFontPtr = GetCompositeFont(); if(CompositeFontPtr) { CompositeFontPtr->SubTypefaces.RemoveAt(SubTypefaceEntryToRemove->SubTypefaceEntryIndex); UpdateSubTypefaceList(); FlushCachedFont(); } } STypefaceEditor::~STypefaceEditor() { } void STypefaceEditor::Construct(const FArguments& InArgs) { CompositeFontEditorPtr = InArgs._CompositeFontEditor; Typeface = InArgs._Typeface; ChildSlot .Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f)) [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.DarkGroupBorder")) .Padding(0.0f) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(8.0f, 8.0f, 16.0f, 8.0f)) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() [ SNew(SInlineEditableTextBlock) .Text(InArgs._TypefaceDisplayName) .ToolTipText((InArgs._OnDisplayNameCommitted.IsBound()) ? LOCTEXT("FontFamilyNameTooltip", "The name of this font family (click to edit)") : FText::GetEmpty()) .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) .OnTextCommitted(InArgs._OnDisplayNameCommitted) .IsReadOnly(!InArgs._OnDisplayNameCommitted.IsBound()) ] +SHorizontalBox::Slot() .AutoWidth() [ InArgs._HeaderContent.Widget ] ] +SVerticalBox::Slot() .AutoHeight() [ InArgs._BodyContent.Widget ] +SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(8.0f, 0.0f, 8.0f, 0.0f)) [ SAssignNew(TypefaceEntriesTileView, STileView) .ListItemsSource(&TypefaceEntries) .SelectionMode(ESelectionMode::None) .ItemWidth(160) .ItemHeight(120) .ItemAlignment(EListItemAlignment::LeftAligned) .OnGenerateTile(this, &STypefaceEditor::MakeTypefaceEntryWidget) ] ] ]; UpdateFontList(); } void STypefaceEditor::Refresh() { UpdateFontList(); } void STypefaceEditor::UpdateFontList() { FTypeface* const TypefacePtr = Typeface.Get(nullptr); for(FTypefaceListViewEntryPtr& TypefaceListViewEntry : TypefaceEntries) { TypefaceListViewEntry->Reset(); } TypefaceEntries.Empty((TypefacePtr) ? TypefacePtr->Fonts.Num() : 0); if(TypefacePtr) { for(int32 TypefaceEntryIndex = 0; TypefaceEntryIndex < TypefacePtr->Fonts.Num(); ++TypefaceEntryIndex) { TypefaceEntries.Add(MakeShareable(new FTypefaceListViewEntry(Typeface, TypefaceEntryIndex))); } } // Add a dummy entry for the "Add" button slot TypefaceEntries.Add(MakeShareable(new FTypefaceListViewEntry())); TypefaceEntriesTileView->RequestListRefresh(); } TSharedRef STypefaceEditor::MakeTypefaceEntryWidget(FTypefaceListViewEntryPtr InTypefaceEntry, const TSharedRef& OwnerTable) { TSharedPtr EntryWidget; if(InTypefaceEntry->TypefaceEntryIndex == INDEX_NONE) { // Dummy entry for the "Add" button EntryWidget = SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ForegroundColor(FSlateColor::UseForeground()) .ToolTipText(LOCTEXT("AddFontTooltip", "Add a new font to this font family")) .OnClicked(this, &STypefaceEditor::OnAddFont) .VAlign(VAlign_Center) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(16.0f) .HAlign(HAlign_Center) [ SNew(SImage) .Image(FEditorStyle::Get().GetBrush("FontEditor.Button_Add")) ] +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Center) [ SNew(STextBlock) .AutoWrapText(true) .Text(LOCTEXT("AddFont", "Add Font")) .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) .Justification(ETextJustify::Center) ] ] ]; } else { EntryWidget = SNew(STypefaceEntryEditor) .CompositeFontEditor(CompositeFontEditorPtr) .TypefaceEntry(InTypefaceEntry) .OnDeleteFont(this, &STypefaceEditor::OnDeleteFont) .OnVerifyFontName(this, &STypefaceEditor::OnVerifyFontName); } return SNew(STableRow, OwnerTable) [ SNew(SBox) .Padding(FMargin(0.0f, 0.0f, 8.0f, 8.0f)) [ EntryWidget.ToSharedRef() ] ]; } FReply STypefaceEditor::OnAddFont() { FTypeface* const TypefacePtr = Typeface.Get(nullptr); if(TypefacePtr) { const FScopedTransaction Transaction(LOCTEXT("AddFont", "Add Font")); CompositeFontEditorPtr->GetFontObject()->Modify(); TSet ExistingFontNames; for(const FTypefaceEntry& TypefaceEntry : TypefacePtr->Fonts) { ExistingFontNames.Add(TypefaceEntry.Name); } // Get a valid default name for the font static const FName BaseFontName("Font"); FName NewFontName = BaseFontName; while(ExistingFontNames.Contains(NewFontName)) { NewFontName.SetNumber(NewFontName.GetNumber() + 1); } TypefacePtr->Fonts.Add(FTypefaceEntry(NewFontName)); UpdateFontList(); CompositeFontEditorPtr->FlushCachedFont(); } return FReply::Handled(); } void STypefaceEditor::OnDeleteFont(const FTypefaceListViewEntryPtr& TypefaceEntryToRemove) { FTypeface* const TypefacePtr = Typeface.Get(nullptr); if(TypefacePtr && TypefaceEntryToRemove.IsValid() && TypefaceEntryToRemove->GetTypefaceEntry()) { const FScopedTransaction Transaction(LOCTEXT("DeleteFont", "Delete Font")); CompositeFontEditorPtr->GetFontObject()->Modify(); TypefacePtr->Fonts.RemoveAt(TypefaceEntryToRemove->TypefaceEntryIndex); UpdateFontList(); CompositeFontEditorPtr->FlushCachedFont(); } } bool STypefaceEditor::OnVerifyFontName(const FTypefaceListViewEntryPtr& TypefaceEntryBeingRenamed, const FName& NewName, FText& OutFailureReason) const { FTypeface* const TypefacePtr = Typeface.Get(nullptr); FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntryBeingRenamed->GetTypefaceEntry(); // Empty names are invalid if(NewName.IsNone()) { OutFailureReason = LOCTEXT("Error_FontNameEmpty", "The font name cannot be empty or 'None'"); return false; } // If we already have this name, it's valid if(TypefaceEntryPtr && TypefaceEntryPtr->Name == NewName) { return true; } // Duplicate names are invalid if(TypefacePtr) { const bool bNameExists = TypefacePtr->Fonts.ContainsByPredicate([&NewName](const FTypefaceEntry& TypefaceEntry) -> bool { return TypefaceEntry.Name == NewName; }); if(bNameExists) { OutFailureReason = FText::Format(LOCTEXT("Error_DuplicateFontNameFmt", "A font with the name '{0}' already exists"), FText::FromName(NewName)); return false; } } return true; } STypefaceEntryEditor::~STypefaceEntryEditor() { } void STypefaceEntryEditor::Construct(const FArguments& InArgs) { CompositeFontEditorPtr = InArgs._CompositeFontEditor; TypefaceEntry = InArgs._TypefaceEntry; OnDeleteFont = InArgs._OnDeleteFont; OnVerifyFontName = InArgs._OnVerifyFontName; GatherHintingEnumEntries(); ChildSlot [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(8.0f) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f)) [ SNew(SInlineEditableTextBlock) .Text(this, &STypefaceEntryEditor::GetTypefaceEntryName) .ToolTipText(LOCTEXT("FontNameTooltip", "The name of this font within the font family (click to edit)")) .OnTextCommitted(this, &STypefaceEntryEditor::OnTypefaceEntryNameCommitted) .OnVerifyTextChanged(this, &STypefaceEntryEditor::OnTypefaceEntryChanged) ] +SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f)) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &STypefaceEntryEditor::GetTypefaceEntryFontLeafname) .ToolTipText(this, &STypefaceEntryEditor::GetTypefaceEntryFontFilePath) ] +SHorizontalBox::Slot() .AutoWidth() .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ToolTipText(LOCTEXT("FontFilePathPickerToolTip", "Choose a font file from this computer")) .OnClicked(this, &STypefaceEntryEditor::OnBrowseTypefaceEntryFontPath) .ContentPadding(2.0f) .ForegroundColor(FSlateColor::UseForeground()) .IsFocusable(false) [ SNew(SImage) .Image(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ] +SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f)) [ SNew(SBox) .MinDesiredWidth(100.0f) [ SNew(SComboBox>) .ToolTipText(LOCTEXT("HintingTooltip", "The hinting algorithm to use with this font")) .OptionsSource(&HintingComboData) .OnSelectionChanged(this, &STypefaceEntryEditor::OnHintingComboSelectionChanged) .OnGenerateWidget(this, &STypefaceEntryEditor::MakeHintingComboEntryWidget) [ SNew(STextBlock) .Text(this, &STypefaceEntryEditor::GetHintingComboText) ] ] ] +SVerticalBox::Slot() [ SNew(SSpacer) ] +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Center) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ToolTipText(LOCTEXT("DeleteFontTooltip", "Remove this font from the font family")) .OnClicked(this, &STypefaceEntryEditor::OnDeleteFontClicked) [ SNew(SImage) .Image(FEditorStyle::Get().GetBrush("FontEditor.Button_Delete")) ] ] ] ]; } FText STypefaceEntryEditor::GetTypefaceEntryName() const { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); if(TypefaceEntryPtr) { return FText::FromName(TypefaceEntryPtr->Name); } return FText::GetEmpty(); } void STypefaceEntryEditor::OnTypefaceEntryNameCommitted(const FText& InNewName, ETextCommit::Type InCommitType) { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); if(TypefaceEntryPtr) { const FScopedTransaction Transaction(LOCTEXT("RenameFont", "Rename Font")); CompositeFontEditorPtr->GetFontObject()->Modify(); TypefaceEntryPtr->Name = *InNewName.ToString(); CompositeFontEditorPtr->FlushCachedFont(); } } bool STypefaceEntryEditor::OnTypefaceEntryChanged(const FText& InNewName, FText& OutFailureReason) const { return !OnVerifyFontName.IsBound() || OnVerifyFontName.Execute(TypefaceEntry, *InNewName.ToString(), OutFailureReason); } FText STypefaceEntryEditor::GetTypefaceEntryFontFilePath() const { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); if(TypefaceEntryPtr) { return FText::FromString(TypefaceEntryPtr->Font.FontFilename); } return FText::GetEmpty(); } FText STypefaceEntryEditor::GetTypefaceEntryFontLeafname() const { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); if(TypefaceEntryPtr) { return (TypefaceEntryPtr->Font.FontFilename.Len() > 0) ? FText::FromString(FPaths::GetCleanFilename(TypefaceEntryPtr->Font.FontFilename)) : LOCTEXT("NoFontFileSelected", ""); } return FText::GetEmpty(); } FReply STypefaceEntryEditor::OnBrowseTypefaceEntryFontPath() { IDesktopPlatform* const DesktopPlatform = FDesktopPlatformModule::Get(); if(DesktopPlatform) { const FString DefaultPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); const void* const ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr; TArray OutFiles; if(DesktopPlatform->OpenFileDialog( ParentWindowHandle, LOCTEXT("FontPickerTitle", "Choose a font file...").ToString(), DefaultPath, TEXT(""), TEXT("TrueType fonts (*.ttf)|*.ttf|OpenType fonts (*.otf)|*.otf"), EFileDialogFlags::None, OutFiles )) { OnTypefaceEntryFontPathPicked(OutFiles[0]); } } return FReply::Handled(); } void STypefaceEntryEditor::OnTypefaceEntryFontPathPicked(const FString& InNewFontFilename) { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); if(TypefaceEntryPtr) { const FScopedTransaction Transaction(LOCTEXT("SetFontFile", "Set Font File")); CompositeFontEditorPtr->GetFontObject()->Modify(); // We need to allocate the bulk data with the font as its outer UFontBulkData* const BulkData = NewObject(CompositeFontEditorPtr->GetFontObject()); BulkData->Initialize(InNewFontFilename); TypefaceEntryPtr->Font.SetFont(InNewFontFilename, BulkData); CompositeFontEditorPtr->FlushCachedFont(); } FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_OPEN, FPaths::GetPath(InNewFontFilename)); } FReply STypefaceEntryEditor::OnDeleteFontClicked() { OnDeleteFont.ExecuteIfBound(TypefaceEntry); return FReply::Handled(); } FSlateFontInfo STypefaceEntryEditor::GetPreviewFontStyle() const { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); return FSlateFontInfo(CompositeFontEditorPtr->GetFontObject(), 9, (TypefaceEntryPtr) ? TypefaceEntryPtr->Name : NAME_None); } void STypefaceEntryEditor::GatherHintingEnumEntries() { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); const UEnum* const HintingEnum = FindObject(ANY_PACKAGE, TEXT("EFontHinting"), true); check(HintingEnum); for(int32 EnumIndex = 0; EnumIndex < HintingEnum->NumEnums() - 1; ++EnumIndex) { // Ignore hidden enum entries const bool bShouldBeHidden = HintingEnum->HasMetaData(TEXT("Hidden"), EnumIndex) || HintingEnum->HasMetaData(TEXT("Spacer"), EnumIndex); if(bShouldBeHidden) { continue; } TSharedPtr ComboEntry = MakeShareable(new FFontHintingComboEntry()); ComboEntry->EnumValue = static_cast(EnumIndex); // See if we specified an alternate name for this entry using metadata ComboEntry->DisplayName = HintingEnum->GetDisplayNameText(EnumIndex); if(ComboEntry->DisplayName.IsEmpty()) { ComboEntry->DisplayName = HintingEnum->GetEnumText(EnumIndex); } ComboEntry->Tooltip = HintingEnum->GetToolTipText(EnumIndex); if(TypefaceEntryPtr && ComboEntry->EnumValue == TypefaceEntryPtr->Font.Hinting) { ActiveHintingEnumEntryText = ComboEntry->DisplayName; } HintingComboData.Add(ComboEntry); } } void STypefaceEntryEditor::OnHintingComboSelectionChanged(TSharedPtr InNewSelection, ESelectInfo::Type) { FTypefaceEntry* const TypefaceEntryPtr = TypefaceEntry->GetTypefaceEntry(); if(TypefaceEntryPtr && InNewSelection.IsValid() && TypefaceEntryPtr->Font.Hinting != InNewSelection->EnumValue) { const FScopedTransaction Transaction(LOCTEXT("SetFontHinting", "Set Font Hinting")); CompositeFontEditorPtr->GetFontObject()->Modify(); TypefaceEntryPtr->Font.Hinting = InNewSelection->EnumValue; ActiveHintingEnumEntryText = InNewSelection->DisplayName; CompositeFontEditorPtr->FlushCachedFont(); } } TSharedRef STypefaceEntryEditor::MakeHintingComboEntryWidget(TSharedPtr InHintingEntry) { return SNew(STextBlock) .Text(InHintingEntry->DisplayName) .ToolTipText(InHintingEntry->Tooltip); } FText STypefaceEntryEditor::GetHintingComboText() const { return ActiveHintingEnumEntryText; } SSubTypefaceEditor::~SSubTypefaceEditor() { } void SSubTypefaceEditor::Construct(const FArguments& InArgs) { CompositeFontEditorPtr = InArgs._CompositeFontEditor; SubTypeface = InArgs._SubTypeface; ParentTypeface = InArgs._ParentTypeface; OnDeleteSubFontFamily = InArgs._OnDeleteSubFontFamily; ChildSlot [ SAssignNew(TypefaceEditor, STypefaceEditor) .CompositeFontEditor(InArgs._CompositeFontEditor) .Typeface(this, &SSubTypefaceEditor::GetTypeface) .TypefaceDisplayName(this, &SSubTypefaceEditor::GetDisplayName) .OnDisplayNameCommitted(this, &SSubTypefaceEditor::OnDisplayNameCommitted) .HeaderContent() [ SNew(SBox) .VAlign(VAlign_Center) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(4.0f, 0.0f)) [ SNew(SNumericEntryBox) .ToolTipText(LOCTEXT("ScalingFactorTooltip", "The scaling factor will adjust the size of the rendered glyphs so that you can tweak their size to match that of the default font family")) .Value(this, &SSubTypefaceEditor::GetScalingFactorAsOptional) .OnValueCommitted(this, &SSubTypefaceEditor::OnScalingFactorCommittedAsNumeric) .LabelVAlign(VAlign_Center) .Label() [ SNew(STextBlock) .Text(LOCTEXT("ScalingFactorLabel", "Scaling Factor")) ] ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SAssignNew(FontOverrideCombo, SComboBox>) .OptionsSource(&FontOverrideComboData) .ContentPadding(FMargin(4.0, 2.0)) .Visibility(this, &SSubTypefaceEditor::GetAddFontOverrideVisibility) .OnComboBoxOpening(this, &SSubTypefaceEditor::OnAddFontOverrideComboOpening) .OnSelectionChanged(this, &SSubTypefaceEditor::OnAddFontOverrideSelectionChanged) .OnGenerateWidget(this, &SSubTypefaceEditor::MakeAddFontOverrideWidget) [ SNew(STextBlock) .Text(LOCTEXT("AddFontOverride", "Add Font Override")) .ToolTipText(LOCTEXT("AddFontOverrideTooltip", "Override a font from the default font family to ensure it will be used when drawing a glyph in the range of this sub-font family")) ] ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(8.0f, 0.0f, 0.0f, 0.0f)) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ToolTipText(LOCTEXT("DeleteFontFamilyTooltip", "Remove this sub-font family from the composite font")) .OnClicked(this, &SSubTypefaceEditor::OnDeleteSubFontFamilyClicked) [ SNew(SImage) .Image(FEditorStyle::Get().GetBrush("FontEditor.Button_Delete")) ] ] ] ] .BodyContent() [ SNew(SBox) .Padding(FMargin(8.0f, 0.0f, 8.0f, 0.0f)) [ SAssignNew(CharacterRangeEntriesTileView, STileView) .ListItemsSource(&CharacterRangeEntries) .SelectionMode(ESelectionMode::None) .ItemWidth(160) .ItemHeight(120) .ItemAlignment(EListItemAlignment::LeftAligned) .OnGenerateTile(this, &SSubTypefaceEditor::MakeCharacterRangesEntryWidget) ] ] ]; UpdateCharacterRangesList(); } FTypeface* SSubTypefaceEditor::GetTypeface() const { FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); return (SubTypefaceEntryPtr) ? &SubTypefaceEntryPtr->Typeface : nullptr; } FText SSubTypefaceEditor::GetDisplayName() const { const FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); if(SubTypefaceEntryPtr) { return (SubTypefaceEntryPtr->EditorName.IsNone()) ? FText::Format(LOCTEXT("SubFontFamilyNameFmt", "Sub-Font Family #{0}"), FText::AsNumber(SubTypeface->SubTypefaceEntryIndex + 1)) : FText::FromName(SubTypefaceEntryPtr->EditorName); } return FText::GetEmpty(); } void SSubTypefaceEditor::OnDisplayNameCommitted(const FText& InNewName, ETextCommit::Type InCommitType) { FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); if(SubTypefaceEntryPtr) { const FScopedTransaction Transaction(LOCTEXT("SetFontFamilyDisplayName", "Set Font Family Display Name")); CompositeFontEditorPtr->GetFontObject()->Modify(); const FText DefaultText = FText::Format(LOCTEXT("SubFontFamilyNameFmt", "Sub-Font Family #{0}"), FText::AsNumber(SubTypeface->SubTypefaceEntryIndex + 1)); if(InNewName.ToString().Equals(DefaultText.ToString())) { SubTypefaceEntryPtr->EditorName = NAME_None; } else { SubTypefaceEntryPtr->EditorName = *InNewName.ToString(); } } } EVisibility SSubTypefaceEditor::GetAddFontOverrideVisibility() const { return (ParentTypeface.Get(nullptr)) ? EVisibility::Visible : EVisibility::Collapsed; } void SSubTypefaceEditor::OnAddFontOverrideComboOpening() { FontOverrideComboData.Empty(); FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); const FTypeface* const ParentTypefacePtr = ParentTypeface.Get(nullptr); if(SubTypefaceEntryPtr && ParentTypefacePtr) { TSet LocalFontNames; for(const FTypefaceEntry& LocalTypefaceEntry : SubTypefaceEntryPtr->Typeface.Fonts) { LocalFontNames.Add(LocalTypefaceEntry.Name); } // Add every font from our parent font that hasn't already got a local entry for(const FTypefaceEntry& ParentTypefaceEntry : ParentTypefacePtr->Fonts) { if(!LocalFontNames.Contains(ParentTypefaceEntry.Name)) { FontOverrideComboData.Add(MakeShareable(new FName(ParentTypefaceEntry.Name))); } } } FontOverrideCombo->RefreshOptions(); } void SSubTypefaceEditor::OnAddFontOverrideSelectionChanged(TSharedPtr InNewSelection, ESelectInfo::Type) { FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); if(SubTypefaceEntryPtr && InNewSelection.IsValid() && !InNewSelection->IsNone()) { const FScopedTransaction Transaction(LOCTEXT("AddFontOverride", "Add Font Override")); CompositeFontEditorPtr->GetFontObject()->Modify(); SubTypefaceEntryPtr->Typeface.Fonts.Add(FTypefaceEntry(*InNewSelection)); TypefaceEditor->Refresh(); CompositeFontEditorPtr->FlushCachedFont(); } } TSharedRef SSubTypefaceEditor::MakeAddFontOverrideWidget(TSharedPtr InFontEntry) { return SNew(STextBlock) .Text(FText::FromName(*InFontEntry)); } FReply SSubTypefaceEditor::OnDeleteSubFontFamilyClicked() { OnDeleteSubFontFamily.ExecuteIfBound(SubTypeface); return FReply::Handled(); } void SSubTypefaceEditor::UpdateCharacterRangesList() { FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); for(FCharacterRangeTileViewEntryPtr& CharacterRangeTileViewEntry : CharacterRangeEntries) { CharacterRangeTileViewEntry->Reset(); } CharacterRangeEntries.Empty((SubTypefaceEntryPtr) ? SubTypefaceEntryPtr->CharacterRanges.Num() : 0); if(SubTypefaceEntryPtr) { for(int32 CharacterRangeIndex = 0; CharacterRangeIndex < SubTypefaceEntryPtr->CharacterRanges.Num(); ++CharacterRangeIndex) { CharacterRangeEntries.Add(MakeShareable(new FCharacterRangeTileViewEntry(SubTypeface, CharacterRangeIndex))); } } // Add a dummy entry for the "Add" button slot CharacterRangeEntries.Add(MakeShareable(new FCharacterRangeTileViewEntry())); CharacterRangeEntriesTileView->RequestListRefresh(); } TSharedRef SSubTypefaceEditor::MakeCharacterRangesEntryWidget(FCharacterRangeTileViewEntryPtr InCharacterRangeEntry, const TSharedRef& OwnerTable) { TSharedPtr EntryWidget; if(InCharacterRangeEntry->RangeEntryIndex == INDEX_NONE) { // Dummy entry for the "Add" button EntryWidget = SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ForegroundColor(FSlateColor::UseForeground()) .ToolTipText(LOCTEXT("AddCharacterRangeTooltip", "Add a new character range to this sub-font family")) .OnClicked(this, &SSubTypefaceEditor::OnAddCharacterRangeClicked) .VAlign(VAlign_Center) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(16.0f) .HAlign(HAlign_Center) [ SNew(SImage) .Image(FEditorStyle::Get().GetBrush("FontEditor.Button_Add")) ] +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Center) [ SNew(STextBlock) .AutoWrapText(true) .Text(LOCTEXT("AddCharacterRange", "Add Character Range")) .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) .Justification(ETextJustify::Center) ] ] ]; } else { EntryWidget = SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(8.0f) [ SNew(SVerticalBox) +SVerticalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(SCharacterRangeEditor) .CompositeFontEditor(CompositeFontEditorPtr) .CharacterRange(InCharacterRangeEntry) ] +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Center) [ SNew(SButton) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ToolTipText(LOCTEXT("DeleteCharacterRangeTooltip", "Remove this character range from the sub-font family")) .OnClicked(this, &SSubTypefaceEditor::OnDeleteCharacterRangeClicked, InCharacterRangeEntry) [ SNew(SImage) .Image(FEditorStyle::Get().GetBrush("FontEditor.Button_Delete")) ] ] ]; } return SNew(STableRow, OwnerTable) [ SNew(SBox) .Padding(FMargin(0.0f, 0.0f, 8.0f, 8.0f)) [ EntryWidget.ToSharedRef() ] ]; } FReply SSubTypefaceEditor::OnAddCharacterRangeClicked() { FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); if(SubTypefaceEntryPtr) { const FScopedTransaction Transaction(LOCTEXT("AddCharacterRange", "Add Character Range")); CompositeFontEditorPtr->GetFontObject()->Modify(); SubTypefaceEntryPtr->CharacterRanges.Add(FInt32Range::Empty()); UpdateCharacterRangesList(); CompositeFontEditorPtr->FlushCachedFont(); } return FReply::Handled(); } FReply SSubTypefaceEditor::OnDeleteCharacterRangeClicked(FCharacterRangeTileViewEntryPtr InCharacterRangeEntry) { FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); if(SubTypefaceEntryPtr) { const FScopedTransaction Transaction(LOCTEXT("DeleteCharacterRange", "Delete Character Range")); CompositeFontEditorPtr->GetFontObject()->Modify(); SubTypefaceEntryPtr->CharacterRanges.RemoveAt(InCharacterRangeEntry->RangeEntryIndex); UpdateCharacterRangesList(); CompositeFontEditorPtr->FlushCachedFont(); } return FReply::Handled(); } TOptional SSubTypefaceEditor::GetScalingFactorAsOptional() const { const FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); if(SubTypefaceEntryPtr) { return SubTypefaceEntryPtr->ScalingFactor; } return TOptional(); } void SSubTypefaceEditor::OnScalingFactorCommittedAsNumeric(float InNewValue, ETextCommit::Type InCommitType) { FCompositeSubFont* const SubTypefaceEntryPtr = SubTypeface->GetSubTypefaceEntry(); if(SubTypefaceEntryPtr) { const FScopedTransaction Transaction(LOCTEXT("SetScalingFactor", "Set Scaling Factor")); CompositeFontEditorPtr->GetFontObject()->Modify(); SubTypefaceEntryPtr->ScalingFactor = InNewValue; CompositeFontEditorPtr->FlushCachedFont(); } } SCharacterRangeEditor::~SCharacterRangeEditor() { } void SCharacterRangeEditor::Construct(const FArguments& InArgs) { CompositeFontEditorPtr = InArgs._CompositeFontEditor; CharacterRange = InArgs._CharacterRange; ChildSlot [ SNew(SGridPanel) // Minimum column +SGridPanel::Slot(0, 0) .Padding(2.0f) [ SNew(SEditableTextBox) .Text(this, &SCharacterRangeEditor::GetRangeComponentAsTCHAR, 0) .OnTextCommitted(this, &SCharacterRangeEditor::OnRangeComponentCommittedAsTCHAR, 0) ] +SGridPanel::Slot(0, 1) .Padding(2.0f) [ SNew(SEditableTextBox) .Text(this, &SCharacterRangeEditor::GetRangeComponentAsHexString, 0) .OnTextCommitted(this, &SCharacterRangeEditor::OnRangeComponentCommittedAsHexString, 0) ] +SGridPanel::Slot(0, 2) .Padding(2.0f) [ SNew(SNumericEntryBox) .Value(this, &SCharacterRangeEditor::GetRangeComponentAsOptional, 0) .OnValueCommitted(this, &SCharacterRangeEditor::OnRangeComponentCommittedAsNumeric, 0) ] // Separator +SGridPanel::Slot(1, 0) .RowSpan(3) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(FText::FromString(TEXT(" - "))) .Font(FEditorStyle::GetFontStyle("DetailsView.CategoryFontStyle")) ] // Maximum column +SGridPanel::Slot(2, 0) .Padding(2.0f) [ SNew(SEditableTextBox) .Text(this, &SCharacterRangeEditor::GetRangeComponentAsTCHAR, 1) .OnTextCommitted(this, &SCharacterRangeEditor::OnRangeComponentCommittedAsTCHAR, 1) ] +SGridPanel::Slot(2, 1) .Padding(2.0f) [ SNew(SEditableTextBox) .Text(this, &SCharacterRangeEditor::GetRangeComponentAsHexString, 1) .OnTextCommitted(this, &SCharacterRangeEditor::OnRangeComponentCommittedAsHexString, 1) ] +SGridPanel::Slot(2, 2) .Padding(2.0f) [ SNew(SNumericEntryBox) .Value(this, &SCharacterRangeEditor::GetRangeComponentAsOptional, 1) .OnValueCommitted(this, &SCharacterRangeEditor::OnRangeComponentCommittedAsNumeric, 1) ] ]; } FText SCharacterRangeEditor::GetRangeComponentAsTCHAR(int32 InComponentIndex) const { const int32 RangeComponent = GetRangeComponent(InComponentIndex); const TCHAR RangeComponentStr[] = { static_cast(RangeComponent), 0 }; return FText::FromString(RangeComponentStr); } FText SCharacterRangeEditor::GetRangeComponentAsHexString(int32 InComponentIndex) const { const int32 RangeComponent = GetRangeComponent(InComponentIndex); return FText::FromString(FString::Printf(TEXT("0x%04x"), RangeComponent)); } TOptional SCharacterRangeEditor::GetRangeComponentAsOptional(int32 InComponentIndex) const { return GetRangeComponent(InComponentIndex); } int32 SCharacterRangeEditor::GetRangeComponent(const int32 InComponentIndex) const { check(InComponentIndex == 0 || InComponentIndex == 1); const FInt32Range* const CharacterRangePtr = CharacterRange->GetRange(); if(CharacterRangePtr) { return (InComponentIndex == 0) ? CharacterRangePtr->GetLowerBoundValue() : CharacterRangePtr->GetUpperBoundValue(); } return 0; } void SCharacterRangeEditor::OnRangeComponentCommittedAsTCHAR(const FText& InNewValue, ETextCommit::Type InCommitType, int32 InComponentIndex) { const FString NewValueStr = InNewValue.ToString(); if(NewValueStr.Len() == 1) { SetRangeComponent(static_cast(NewValueStr[0]), InComponentIndex); } else if(NewValueStr.Len() == 0) { SetRangeComponent(0, InComponentIndex); } } void SCharacterRangeEditor::OnRangeComponentCommittedAsHexString(const FText& InNewValue, ETextCommit::Type InCommitType, int32 InComponentIndex) { const FString NewValueStr = InNewValue.ToString(); const TCHAR* HexStart = NewValueStr.GetCharArray().GetData(); if(NewValueStr.StartsWith(TEXT("0x"))) { // Skip the "0x" part, as FParse::HexNumber doesn't handle that HexStart += 2; } const int32 NewValue = FParse::HexNumber(HexStart); SetRangeComponent(NewValue, InComponentIndex); } void SCharacterRangeEditor::OnRangeComponentCommittedAsNumeric(int32 InNewValue, ETextCommit::Type InCommitType, int32 InComponentIndex) { SetRangeComponent(InNewValue, InComponentIndex); } void SCharacterRangeEditor::SetRangeComponent(const int32 InNewValue, const int32 InComponentIndex) { check(InComponentIndex == 0 || InComponentIndex == 1); FInt32Range* const CharacterRangePtr = CharacterRange->GetRange(); if(CharacterRangePtr) { const FScopedTransaction Transaction(LOCTEXT("UpdateCharacterRange", "Update Character Range")); CompositeFontEditorPtr->GetFontObject()->Modify(); *CharacterRangePtr = (InComponentIndex == 0) ? FInt32Range(FInt32Range::BoundsType::Inclusive(InNewValue), FInt32Range::BoundsType::Inclusive(CharacterRangePtr->GetUpperBoundValue())) : FInt32Range(FInt32Range::BoundsType::Inclusive(CharacterRangePtr->GetLowerBoundValue()), FInt32Range::BoundsType::Inclusive(InNewValue)); CompositeFontEditorPtr->FlushCachedFont(); } } #undef LOCTEXT_NAMESPACE