Custom font support

This commit is contained in:
WiggleWizard 2022-04-01 01:33:38 +01:00
parent 48b1abd1e1
commit b346fa894d
8 changed files with 127 additions and 21 deletions

View File

@ -16,12 +16,13 @@ Also note that the NetImGui branch is not up to date with any of this fork's cha
Fork Additions/Fixes Fork Additions/Fixes
-------------------- --------------------
- Updated core source files for Unreal Engine 5, new ImGui and ImPlot - Updated core source files for Unreal Engine 5.
- Updated Dear ImGui to 1.87 - Updated Dear ImGui to 1.87.
- Added ImPlot v0.13 WIP - Added ImPlot v0.13 WIP.
- `ImGui::IsKey*` now functional with all known ImGui keys. - `ImGui::IsKey*` now functional with all known ImGui keys.
- Updated input handling flow to be [standard compliant](https://github.com/ocornut/imgui/issues/4921) with Dear ImGui 1.87 which makes ImGui react better at low FPS. Will add `IMGUI_DISABLE_OBSOLETE_KEYIO` preprocessor once I've ripped out old style input. - Updated input handling flow to be [standard compliant](https://github.com/ocornut/imgui/issues/4921) with Dear ImGui 1.87 which makes ImGui react better at low FPS. Will add `IMGUI_DISABLE_OBSOLETE_KEYIO` preprocessor once I've ripped out old style input.
- Allowed `UTexture` for Texture Manager so render targets can also be rendered to quads rather than just being limited to using `UTexture2D` instances. - Allowed `UTexture` for Texture Manager so render targets can also be rendered to quads rather than just being limited to using `UTexture2D` instances.
- Added the ability to instruct ImGui context to build custom fonts (like FontAwesome).
Status Status
------ ------
@ -46,14 +47,14 @@ PrivateDefinitions.Add(string.Format("IMPLOT_API=DLLIMPORT"));
# Additional Knowledge # Additional Knowledge
Using ImPlot ## Using ImPlot
------------
It's pretty easy to use ImPlot, it's pretty much the same drill as using Dear ImGui with the UnrealImGui plugin. You can see documentation on how to use ImPlot here: [ImPlot](https://github.com/epezent/implot). It's pretty easy to use ImPlot, it's pretty much the same drill as using Dear ImGui with the UnrealImGui plugin. You can see documentation on how to use ImPlot here: [ImPlot](https://github.com/epezent/implot).
The only thing you won't need to do is call the `ImPlot::CreateContext()` and `ImPlot::DestroyContext` routines as they're already called when ImGui's context is created within UnrealImGui's guts. The only thing you won't need to do is call the `ImPlot::CreateContext()` and `ImPlot::DestroyContext` routines as they're already called when ImGui's context is created within UnrealImGui's guts.
Drawing a UTextureRenderTarget2D ## Drawing a UTextureRenderTarget2D
----
One might want to render viewports into the world in an ImGui window. You can do this pretty simply by generating a `UTextureRenderTarget2D` then assigning that to a `ASceneCapture2D` actor in your world. Here's some sample code for generating an correctly managing the `UTextureRenderTarget2D`: One might want to render viewports into the world in an ImGui window. You can do this pretty simply by generating a `UTextureRenderTarget2D` then assigning that to a `ASceneCapture2D` actor in your world. Here's some sample code for generating an correctly managing the `UTextureRenderTarget2D`:
```cpp ```cpp
void Init() void Init()
@ -68,13 +69,13 @@ void Init()
// ... Generate a unique TextureName here // ... Generate a unique TextureName here
// Register this render target as an ImGui interop handled texture // Register this render target as an ImGui interop handled texture
ImGuiTextureHandle = FImGuiModule::Get().FindTextureHandle(TextureName); ImGuiTextureHandle = FImGuiModule::Get().FindTextureHandle(TextureName);
if(!ImGuiTextureHandle.IsValid()) if(!ImGuiTextureHandle.IsValid())
{ {
if(IsValid(TextureRenderTarget)) if(IsValid(TextureRenderTarget))
{ {
ImGuiTextureHandle = FImGuiModule::Get().RegisterTexture(TextureName, TextureRenderTarget, true); ImGuiTextureHandle = FImGuiModule::Get().RegisterTexture(TextureName, TextureRenderTarget, true);
} }
} }
} }
~Class() ~Class()
@ -87,9 +88,9 @@ void Render()
{ {
// Actually submit the draw command to ImGui to render the quad with the texture // Actually submit the draw command to ImGui to render the quad with the texture
if(ImGuiTextureHandle.IsValid()) if(ImGuiTextureHandle.IsValid())
{ {
ImGui::Image(ImGuiTextureHandle.GetTextureId(), {512.f, 512.f}); ImGui::Image(ImGuiTextureHandle.GetTextureId(), {512.f, 512.f});
} }
} }
``` ```
@ -112,6 +113,65 @@ If you're using a scene capture and your quad is not drawing at all, make sure y
If you're getting crashes or seg faults during rendering, make sure you're using `UPROPERTY()` on your class variables! If you're getting crashes or seg faults during rendering, make sure you're using `UPROPERTY()` on your class variables!
## Adding custom fonts
### FontAwesome
Adding custom fonts is fairly simple. As a more complex and more commonly done, we're going to embed and build FontAwesome 6 into the font atlas. First thing you'll need is a binary C version of the FontAwesome font along with the necessary [companion descriptors](https://github.com/juliettef/IconFontCppHeaders/blob/main/IconsFontAwesome6.h). The descriptors are pre-generated, however if you have a new version of FA you wish to use, then use the Python script in that repository. As for the binary C, you'll need to compile Dear ImGui's [binary_to_compressed_c.cpp](https://github.com/ocornut/imgui/blob/master/misc/fonts/binary_to_compressed_c.cpp).
Once you have the necessary files, you'll need to encode your FontAwesome font using the command:
```
binary_to_compressed_c.exe -nocompress fa-solid-900.ttf FontAwesomeFont > FontAwesomeFont.h
```
The only mandatory field here is `-nocompress` as this instructs the encoder to create an uncompressed binary file (1:1) since currently there's no immediate support for compressed fonts.
Move over your descriptors and your binary C font file to an appropriate location for inclusion in your Unreal Engine project. The following code is how to instruct ImGui to build the font atlas with your FontAwesome font:
```cpp
#include "FontAwesomeFont.h"
#include "IconsFontAwesome6.h"
// Add FontAwesome font glyphs from memory
if(TSharedPtr<ImFontConfig> FAFontConfig = MakeShareable(new ImFontConfig()))
{
static const ImWchar IconRange[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
FAFontConfig->FontDataOwnedByAtlas = false; // Global font data lifetime
FAFontConfig->FontData = (void*)FontAwesomeData; // Declared in binary C .h file
FAFontConfig->FontDataSize = FontAwesomeSize; // Declared in binary C .h file
FAFontConfig->SizePixels = 16;
FAFontConfig->MergeMode = true; // Forces ImGui to place this font into the same atlas as the previous font
FAFontConfig->GlyphRanges = IconRange; // Required; instructs ImGui to use these glyphs
FAFontConfig->GlyphMinAdvanceX = 16.f; // Use for monospaced icons
FAFontConfig->PixelSnapH = true; // Better rendering (align to pixel grid)
FAFontConfig->GlyphOffset = {0, 3}; // Moves icons around, for alignment with general typesets
FImGuiModule::Get().GetProperties().AddCustomFont("FontAwesome", FAFontConfig);
FImGuiModule::Get().RebuildFontAtlas();
}
```
That's it. Make sure you execute the above code once at the beginning of the first ImGui frame (or at any point of your framework where the ImGui context has been initialized correctly) and it should build the main atlas with FontAwesome inside it. ImFontConfig lifetime is currently managed via reference counting (`TSharedPtr`).
### Using the icons
```cpp
#include "IconsFontAwesome6.h"
void OnPaint()
{
ImGui::Text(ICON_FA_AWARD);
}
```
Pretty simple. Building FStrings that incorporate FontAwesome icons however gets a little trickier:
```cpp
FString Str = "Hello " + FString(UTF8_TO_TCHAR(ICON_FA_WAVE_SQUARE)) " World";
ImGui::TextUnformatted(TCHAR_TO_UTF8(*Str));
```
### More info
- [Dear ImGui: Using Fonts](https://github.com/ocornut/imgui/blob/master/docs/FONTS.md)
- [IconFontCppHeaders](https://github.com/juliettef/IconFontCppHeaders)
- [FontAwesome with general Dear ImGui](https://pixtur.github.io/mkdocs-for-imgui/site/FONTS/)
# Misc # Misc
See also See also

View File

@ -5,6 +5,7 @@
#include "ImGuiDelegatesContainer.h" #include "ImGuiDelegatesContainer.h"
#include "ImGuiImplementation.h" #include "ImGuiImplementation.h"
#include "ImGuiModuleSettings.h" #include "ImGuiModuleSettings.h"
#include "ImGuiModule.h"
#include "Utilities/WorldContext.h" #include "Utilities/WorldContext.h"
#include "Utilities/WorldContextIndex.h" #include "Utilities/WorldContextIndex.h"
@ -254,7 +255,7 @@ void FImGuiContextManager::SetDPIScale(const FImGuiDPIScaleInfo& ScaleInfo)
} }
} }
void FImGuiContextManager::BuildFontAtlas() void FImGuiContextManager::BuildFontAtlas(const TMap<FName, TSharedPtr<ImFontConfig>>& CustomFontConfigs)
{ {
if (!FontAtlas.IsBuilt()) if (!FontAtlas.IsBuilt())
{ {
@ -262,6 +263,21 @@ void FImGuiContextManager::BuildFontAtlas()
FontConfig.SizePixels = FMath::RoundFromZero(13.f * DPIScale); FontConfig.SizePixels = FMath::RoundFromZero(13.f * DPIScale);
FontAtlas.AddFontDefault(&FontConfig); FontAtlas.AddFontDefault(&FontConfig);
// Build custom fonts
for (const TPair<FName, TSharedPtr<ImFontConfig>>& CustomFontPair : CustomFontConfigs)
{
FName CustomFontName = CustomFontPair.Key;
TSharedPtr<ImFontConfig> CustomFontConfig = CustomFontPair.Value;
// Set font name for debugging
if (CustomFontConfig.IsValid())
{
strcpy_s(CustomFontConfig->Name, 40, TCHAR_TO_ANSI(*CustomFontName.ToString()));
}
FontAtlas.AddFont(CustomFontConfig.Get());
}
unsigned char* Pixels; unsigned char* Pixels;
int Width, Height, Bpp; int Width, Height, Bpp;
FontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp); FontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);
@ -283,5 +299,5 @@ void FImGuiContextManager::RebuildFontAtlas()
FontResourcesReleaseCountdown = 3; FontResourcesReleaseCountdown = 3;
} }
BuildFontAtlas(); BuildFontAtlas(FImGuiModule::Get().GetProperties().GetCustomFonts());
} }

View File

@ -66,6 +66,8 @@ public:
void Tick(float DeltaSeconds); void Tick(float DeltaSeconds);
void RebuildFontAtlas();
private: private:
struct FContextData struct FContextData
@ -102,8 +104,7 @@ private:
FContextData& GetWorldContextData(const UWorld& World, int32* OutContextIndex = nullptr); FContextData& GetWorldContextData(const UWorld& World, int32* OutContextIndex = nullptr);
void SetDPIScale(const FImGuiDPIScaleInfo& ScaleInfo); void SetDPIScale(const FImGuiDPIScaleInfo& ScaleInfo);
void BuildFontAtlas(); void BuildFontAtlas(const TMap<FName, TSharedPtr<ImFontConfig>>& CustomFontConfigs = {});
void RebuildFontAtlas();
TMap<int32, FContextData> Contexts; TMap<int32, FContextData> Contexts;

View File

@ -104,6 +104,14 @@ void FImGuiModule::ReleaseTexture(const FImGuiTextureHandle& Handle)
} }
} }
void FImGuiModule::RebuildFontAtlas()
{
if (ImGuiModuleManager)
{
ImGuiModuleManager->RebuildFontAtlas();
}
}
void FImGuiModule::StartupModule() void FImGuiModule::StartupModule()
{ {
// Initialize handles to allow cross-module redirections. Other handles will always look for parents in the active // Initialize handles to allow cross-module redirections. Other handles will always look for parents in the active

View File

@ -74,6 +74,11 @@ FImGuiModuleManager::~FImGuiModuleManager()
UnregisterTick(); UnregisterTick();
} }
void FImGuiModuleManager::RebuildFontAtlas()
{
ContextManager.RebuildFontAtlas();
}
void FImGuiModuleManager::LoadTextures() void FImGuiModuleManager::LoadTextures()
{ {
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can create textures.")); checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can create textures."));

View File

@ -34,6 +34,8 @@ public:
// Event called right after ImGui is updated, to give other subsystems chance to react. // Event called right after ImGui is updated, to give other subsystems chance to react.
FSimpleMulticastDelegate& OnPostImGuiUpdate() { return PostImGuiUpdateEvent; } FSimpleMulticastDelegate& OnPostImGuiUpdate() { return PostImGuiUpdateEvent; }
void RebuildFontAtlas();
private: private:
FImGuiModuleManager(); FImGuiModuleManager();

View File

@ -118,6 +118,8 @@ public:
*/ */
virtual void ReleaseTexture(const FImGuiTextureHandle& Handle); virtual void ReleaseTexture(const FImGuiTextureHandle& Handle);
virtual void RebuildFontAtlas();
/** /**
* Get ImGui module properties. * Get ImGui module properties.
* *

View File

@ -2,6 +2,7 @@
#pragma once #pragma once
struct ImFontConfig;
/** Properties that define state of the ImGui module. */ /** Properties that define state of the ImGui module. */
class IMGUI_API FImGuiModuleProperties class IMGUI_API FImGuiModuleProperties
@ -71,6 +72,15 @@ public:
/** Toggle ImGui demo. */ /** Toggle ImGui demo. */
void ToggleDemo() { SetShowDemo(!ShowDemo()); } void ToggleDemo() { SetShowDemo(!ShowDemo()); }
/** Adds a new font to initialize */
void AddCustomFont(FName FontName, TSharedPtr<ImFontConfig> Font) { CustomFonts.Emplace(FontName, Font); }
/** Removes a font from the custom font list */
void RemoveCustomFont(FName FontName) { CustomFonts.Remove(FontName); }
/** Gets the map of registered custom fonts */
TMap<FName, TSharedPtr<ImFontConfig>>& GetCustomFonts() { return CustomFonts; }
private: private:
bool bInputEnabled = false; bool bInputEnabled = false;
@ -83,4 +93,6 @@ private:
bool bMouseInputShared = false; bool bMouseInputShared = false;
bool bShowDemo = false; bool bShowDemo = false;
TMap<FName, TSharedPtr<ImFontConfig>> CustomFonts;
}; };