// Copyright Epic Games, Inc. All Rights Reserved. #include "FontFaceEditor.h" #include "Containers/Array.h" #include "DetailsViewArgs.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "EditorReimportHandler.h" #include "Engine/Engine.h" #include "Engine/EngineTypes.h" #include "Engine/Font.h" #include "Engine/FontFace.h" #include "Engine/UserInterfaceSettings.h" #include "FontEditorModule.h" #include "Framework/Application/SlateApplication.h" #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" #include "Styling/AppStyle.h" #include "Subsystems/ImportSubsystem.h" #include "Textures/SlateIcon.h" #include "Toolkits/AssetEditorToolkit.h" #include "UObject/ObjectPtr.h" #include "Widgets/Docking/SDockTab.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Layout/SGridPanel.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "FontFaceEditor" DEFINE_LOG_CATEGORY_STATIC(LogFontFaceEditor, Log, All); const FName FFontFaceEditor::PreviewTabId( TEXT( "FontFaceEditor_FontFacePreview" ) ); const FName FFontFaceEditor::PropertiesTabId( TEXT( "FontFaceEditor_FontFaceProperties" ) ); void FFontFaceEditor::RegisterTabSpawners(const TSharedRef& InTabManager) { WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_FontFaceEditor", "Font Face Editor")); auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); FAssetEditorToolkit::RegisterTabSpawners(InTabManager); InTabManager->RegisterTabSpawner( PreviewTabId, FOnSpawnTab::CreateSP(this, &FFontFaceEditor::SpawnTab_Preview) ) .SetDisplayName( LOCTEXT("PreviewTab", "Preview") ) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "FontEditor.Tabs.Preview")); InTabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FFontFaceEditor::SpawnTab_Properties) ) .SetDisplayName( LOCTEXT("PropertiesTabId", "Details") ) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details")); } void FFontFaceEditor::UnregisterTabSpawners(const TSharedRef& InTabManager) { FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); InTabManager->UnregisterTabSpawner( PreviewTabId ); InTabManager->UnregisterTabSpawner( PropertiesTabId ); } FFontFaceEditor::FFontFaceEditor() : FontFace(nullptr) { } FFontFaceEditor::~FFontFaceEditor() { FReimportManager::Instance()->OnPostReimport().RemoveAll(this); if (UEditorEngine* Editor = Cast(GEngine)) { Editor->UnregisterForUndo(this); Editor->GetEditorSubsystem()->OnAssetReimport.RemoveAll(this); } } void FFontFaceEditor::InitFontFaceEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit) { FReimportManager::Instance()->OnPostReimport().AddRaw(this, &FFontFaceEditor::OnPostReimport); // Register to be notified when an object is reimported. GEditor->GetEditorSubsystem()->OnAssetReimport.AddSP(this, &FFontFaceEditor::OnObjectReimported); FCoreUObjectDelegates::OnObjectPropertyChanged.AddSP(this, &FFontFaceEditor::OnObjectPropertyChanged); FontFace = CastChecked(ObjectToEdit); // Support undo/redo FontFace->SetFlags(RF_Transactional); if (UEditorEngine* Editor = Cast(GEngine)) { Editor->RegisterForUndo(this); } CreateInternalWidgets(); const TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_FontEditor_Layout_v4") ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation( Orient_Vertical ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Vertical) ->SetSizeCoefficient(0.65f) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.85f) ->AddTab( PropertiesTabId, ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.15f) ->AddTab( PreviewTabId, ETabState::OpenedTab ) ) ) ); const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, FontEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectToEdit); IFontEditorModule* FontEditorModule = &FModuleManager::LoadModuleChecked("FontEditor"); AddMenuExtender(FontEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); } UFontFace* FFontFaceEditor::GetFontFace() const { return FontFace; } FName FFontFaceEditor::GetToolkitFName() const { return FName("FontFaceEditor"); } FText FFontFaceEditor::GetBaseToolkitName() const { return LOCTEXT( "AppLabel", "Font Face Editor" ); } FString FFontFaceEditor::GetWorldCentricTabPrefix() const { return LOCTEXT("WorldCentricTabPrefix", "Font Face ").ToString(); } FLinearColor FFontFaceEditor::GetWorldCentricTabColorScale() const { return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f); } TSharedRef FFontFaceEditor::SpawnTab_Preview( const FSpawnTabArgs& Args ) { check( Args.GetTabId().TabType == PreviewTabId ); TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("FontFacePreviewTitle", "Preview")) [ FontFacePreview.ToSharedRef() ]; AddToSpawnedToolPanels( Args.GetTabId().TabType, SpawnedTab ); return SpawnedTab; } TSharedRef FFontFaceEditor::SpawnTab_Properties( const FSpawnTabArgs& Args ) { check( Args.GetTabId().TabType == PropertiesTabId ); TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("FontFacePropertiesTitle", "Details")) [ FontFaceProperties.ToSharedRef() ]; AddToSpawnedToolPanels( Args.GetTabId().TabType, SpawnedTab ); return SpawnedTab; } void FFontFaceEditor::AddToSpawnedToolPanels( const FName& TabIdentifier, const TSharedRef& SpawnedTab ) { TWeakPtr* TabSpot = SpawnedToolPanels.Find(TabIdentifier); if (!TabSpot) { SpawnedToolPanels.Add(TabIdentifier, SpawnedTab); } else { check(!TabSpot->IsValid()); *TabSpot = SpawnedTab; } } void FFontFaceEditor::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObject(FontFace); Collector.AddReferencedObjects(PreviewFonts); Collector.AddReferencedObjects(PreviewFaces); } void FFontFaceEditor::OnPreviewTextChanged(const FText& Text) { for (TSharedPtr &PreviewTextBlock : PreviewTextBlocks[1]) { PreviewTextBlock->SetText(Text); } } TOptional FFontFaceEditor::GetPreviewFontSize() const { return PreviewFontSize; } void FFontFaceEditor::OnPreviewFontSizeChanged(int32 InNewValue, ETextCommit::Type CommitType) { PreviewFontSize = InNewValue; ApplyPreviewFontSize(); } void FFontFaceEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, class FEditPropertyChain* PropertyThatChanged) { static const FName EnableDistanceFieldRenderingPropertyName = GET_MEMBER_NAME_CHECKED(UFontFace, bEnableDistanceFieldRendering); if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == EnableDistanceFieldRenderingPropertyName) { // Show / hide distance field related properties FontFaceProperties->ForceRefresh(); } RefreshPreview(); } void FFontFaceEditor::CreateInternalWidgets() { const EVerticalAlignment PreviewVAlign = VAlign_Center; const FText DefaultPreviewText = LOCTEXT("DefaultPreviewText", "The quick brown fox jumps over the lazy dog"); FontFacePreview = SNew(SVerticalBox) +SVerticalBox::Slot() .FillHeight(1.0f) .Padding(0.0f, 0.0f, 0.0f, 4.0f) [ SNew(SScrollBox) +SScrollBox::Slot() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SNew(SGridPanel) +SGridPanel::Slot(0, 0) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[0][0], STextBlock) .Text(LOCTEXT("FontFaceReference", "Reference: ")) ] +SGridPanel::Slot(1, 0) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[1][0], STextBlock) .Text(DefaultPreviewText) ] +SGridPanel::Slot(0, 1) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[0][1], STextBlock) .Text(LOCTEXT("FontFaceLowQuality", "Low: ")) ] +SGridPanel::Slot(1, 1) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[1][1], STextBlock) .Text(DefaultPreviewText) ] +SGridPanel::Slot(0, 2) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[0][2], STextBlock) .Text(LOCTEXT("FontFaceMediumQuality", "Medium: ")) ] +SGridPanel::Slot(1, 2) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[1][2], STextBlock) .Text(DefaultPreviewText) ] +SGridPanel::Slot(0, 3) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[0][3], STextBlock) .Text(LOCTEXT("FontFaceHighQuality", "High: ")) ] +SGridPanel::Slot(1, 3) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[1][3], STextBlock) .Text(DefaultPreviewText) ] +SGridPanel::Slot(0, 4) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[0][4], STextBlock) .Text(LOCTEXT("FontFaceMultiLowQuality", "Multi Low: ")) ] +SGridPanel::Slot(1, 4) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[1][4], STextBlock) .Text(DefaultPreviewText) ] +SGridPanel::Slot(0, 5) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[0][5], STextBlock) .Text(LOCTEXT("FontFaceMultiMediumQuality", "Multi Medium: ")) ] +SGridPanel::Slot(1, 5) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[1][5], STextBlock) .Text(DefaultPreviewText) ] +SGridPanel::Slot(0, 6) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[0][6], STextBlock) .Text(LOCTEXT("FontFaceMultiHighQuality", "Multi High: ")) ] +SGridPanel::Slot(1, 6) .VAlign(PreviewVAlign) [ SAssignNew(PreviewTextBlocks[1][6], STextBlock) .Text(DefaultPreviewText) ] ] +SVerticalBox::Slot() .AutoHeight() [ SAssignNew(PreviewNoteTextBlock, STextBlock) .Text(LOCTEXT("FontFaceDistanceFieldProjectSettingNote", "Note: You must also enable Distance Field Font Rasterization in Project Settings / Engine / User Interface.")) .Visibility(EVisibility::Collapsed) ] ] ] +SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() [ SAssignNew(FontFacePreviewText, SEditableTextBox) .Text(DefaultPreviewText) .SelectAllTextWhenFocused(true) .OnTextChanged(this, &FFontFaceEditor::OnPreviewTextChanged) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SNumericEntryBox) .Value(this, &FFontFaceEditor::GetPreviewFontSize) .MinValue(4) .MaxValue(256) .OnValueCommitted(this, &FFontFaceEditor::OnPreviewFontSizeChanged) ] ]; UpdatePreviewFonts(); UpdatePreviewVisibility(); ApplyPreviewFontSize(); FDetailsViewArgs Args; Args.bHideSelectionTip = true; Args.NotifyHook = this; FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); FontFaceProperties = PropertyModule.CreateDetailView(Args); FontFaceProperties->SetIsPropertyVisibleDelegate(FIsPropertyVisible::CreateRaw(this, &FFontFaceEditor::GetIsPropertyVisible)); FontFaceProperties->SetObject( FontFace ); } void FFontFaceEditor::OnPostReimport(UObject* InObject, bool bSuccess) { // Ignore if this is regarding a different object if ( InObject != FontFace ) { return; } if ( bSuccess ) { //FontFaceViewport->RefreshViewport(); RefreshPreview(); } } void FFontFaceEditor::OnObjectPropertyChanged(UObject* InObject, struct FPropertyChangedEvent& InPropertyChangedEvent) { if (Cast(InObject)) { // Force all texts using a font to be refreshed. FSlateApplicationBase::Get().InvalidateAllWidgets(false); GSlateLayoutGeneration++; } } void FFontFaceEditor::OnObjectReimported(UObject* InObject) { // Make sure we are using the object that is being reimported, otherwise a lot of needless work could occur. if(FontFace == InObject) { FontFace = Cast(InObject); TArray< UObject* > ObjectList; ObjectList.Add(InObject); FontFaceProperties->SetObjects(ObjectList); } } bool FFontFaceEditor::GetIsPropertyVisible(const FPropertyAndParent& PropertyAndParent) const { static const FName CategoryFName = "Category"; const FString& CategoryValue = PropertyAndParent.Property.GetMetaData(CategoryFName); return CategoryValue != TEXT("DistanceFieldMode") || IsSlateSdfTextFeatureEnabled(); } bool FFontFaceEditor::ShouldPromptForNewFilesOnReload(const UObject& EditingObject) const { return false; } void FFontFaceEditor::RefreshPreview() { UpdatePreviewFonts(); UpdatePreviewVisibility(); } void FFontFaceEditor::ClonePreviewFontFace(TObjectPtr& TargetFontFace, EFontRasterizationMode RasterizationMode, int32 DistanceFieldPpem) const { TargetFontFace = DuplicateObject(FontFace, GetTransientPackage()); TargetFontFace->MinDistanceFieldPpem = DistanceFieldPpem; TargetFontFace->MidDistanceFieldPpem = DistanceFieldPpem; TargetFontFace->MaxDistanceFieldPpem = DistanceFieldPpem; TargetFontFace->MinMultiDistanceFieldPpem = DistanceFieldPpem; TargetFontFace->MidMultiDistanceFieldPpem = DistanceFieldPpem; TargetFontFace->MaxMultiDistanceFieldPpem = DistanceFieldPpem; TargetFontFace->PlatformRasterizationModeOverrides = FFontFacePlatformRasterizationOverrides(); TargetFontFace->PlatformRasterizationModeOverrides->MsdfOverride = RasterizationMode; TargetFontFace->PlatformRasterizationModeOverrides->SdfOverride = RasterizationMode; TargetFontFace->PlatformRasterizationModeOverrides->SdfApproximationOverride = RasterizationMode; TargetFontFace->PostEditChange(); } void FFontFaceEditor::MakePreviewFont(TObjectPtr& TargetObject, UFontFace* Face) const { if (!TargetObject) { TargetObject = NewObject(); } if (UFont* TargetFont = CastChecked(TargetObject)) { if (TargetFont->CompositeFont.DefaultTypeface.Fonts.IsEmpty()) { FTypefaceEntry FontTypeface; FontTypeface.Name = TEXT("Regular"); FontTypeface.Font = FFontData(Face); TargetFont->FontCacheType = EFontCacheType::Runtime; TargetFont->CompositeFont.DefaultTypeface.Fonts.Add(MoveTemp(FontTypeface)); } else { TargetFont->CompositeFont.DefaultTypeface.Fonts[0].Font = FFontData(Face); } TargetFont->PostEditChange(); } } bool FFontFaceEditor::IsFontFaceDistanceFieldEnabled() const { return FontFace->bEnableDistanceFieldRendering && GetDefault()->bEnableDistanceFieldFontRasterization && IsSlateSdfTextFeatureEnabled(); } void FFontFaceEditor::UpdatePreviewFonts() { if (!FontFace) { return; } const int PreviewFontNum = UE_ARRAY_COUNT(*PreviewTextBlocks); if (IsFontFaceDistanceFieldEnabled()) { PreviewFaces.SetNum(PreviewFontNum, EAllowShrinking::No); PreviewFonts.SetNum(PreviewFontNum, EAllowShrinking::No); ClonePreviewFontFace(PreviewFaces[0], EFontRasterizationMode::Bitmap); ClonePreviewFontFace(PreviewFaces[1], EFontRasterizationMode::Sdf, FontFace->MinDistanceFieldPpem); ClonePreviewFontFace(PreviewFaces[2], EFontRasterizationMode::Sdf, FontFace->MidDistanceFieldPpem); ClonePreviewFontFace(PreviewFaces[3], EFontRasterizationMode::Sdf, FontFace->MaxDistanceFieldPpem); ClonePreviewFontFace(PreviewFaces[4], EFontRasterizationMode::Msdf, FontFace->MinMultiDistanceFieldPpem); ClonePreviewFontFace(PreviewFaces[5], EFontRasterizationMode::Msdf, FontFace->MidMultiDistanceFieldPpem); ClonePreviewFontFace(PreviewFaces[6], EFontRasterizationMode::Msdf, FontFace->MaxMultiDistanceFieldPpem); for (int32 Index = 0; Index < PreviewFontNum; ++Index) { MakePreviewFont(PreviewFonts[Index], PreviewFaces[Index]); } } else { PreviewFaces.SetNum(1, EAllowShrinking::No); PreviewFonts.SetNum(PreviewFontNum, EAllowShrinking::No); ClonePreviewFontFace(PreviewFaces[0], EFontRasterizationMode::Bitmap); for (TObjectPtr& PreviewFont : PreviewFonts) { MakePreviewFont(PreviewFont, PreviewFaces[0]); } } } void FFontFaceEditor::UpdatePreviewVisibility() { if (FontFace) { const EVisibility SecondaryRowsVisibility = IsFontFaceDistanceFieldEnabled() ? EVisibility::Visible : EVisibility::Collapsed; PreviewTextBlocks[0][0]->SetVisibility(SecondaryRowsVisibility); for (int32 Index = 1; Index < UE_ARRAY_COUNT(*PreviewTextBlocks); ++Index) { PreviewTextBlocks[0][Index]->SetVisibility(SecondaryRowsVisibility); PreviewTextBlocks[1][Index]->SetVisibility(SecondaryRowsVisibility); } PreviewNoteTextBlock->SetVisibility( FontFace->bEnableDistanceFieldRendering && IsSlateSdfTextFeatureEnabled() && !GetDefault()->bEnableDistanceFieldFontRasterization ? EVisibility::Visible : EVisibility::Collapsed ); } } void FFontFaceEditor::ApplyPreviewFontSize() { for (int32 Index = 0; Index < UE_ARRAY_COUNT(PreviewTextBlocks[1]) && Index < PreviewFonts.Num(); ++Index) { PreviewTextBlocks[1][Index]->SetFont(FSlateFontInfo(PreviewFonts[Index], PreviewFontSize)); } } #undef LOCTEXT_NAMESPACE