segross / UnrealImGui

Unreal plug-in that integrates Dear ImGui framework into Unreal Engine 4.
MIT License
666 stars 211 forks source link

Adding Fonts #50

Closed irajsb closed 2 years ago

irajsb commented 3 years ago

after adding a new font like this:

ImGuiIO& io = ImGui::GetIO();
    ImFont* ImFont= io.Fonts->AddFontFromFileTTF(TCHAR_TO_ANSI(*FilePath), 13);

    if(!ImFont)
        UE_LOG(LogTemp,Error,TEXT("Font is null")); 

I tried to open style editor , new fonts exists but its labeled "Unknown " and clicking on it will crash the engine.(font reference is not null ) I also tried font->build() but after doing so every text in UI will turn into squares

irajsb commented 3 years ago
void FImGuiContextManager::BuildFontAtlas()
{
    if (!FontAtlas.IsBuilt())
    {
        ImFontConfig FontConfig = {};
        FontConfig.SizePixels = FMath::RoundFromZero(13.f * DPIScale);
        FontAtlas.AddFontDefault(&FontConfig);
        const FString Font=UBlueprintPathsLibrary::EngineContentDir().Append("Slate/Fonts/Roboto-Regular.ttf").Replace(TEXT("/"),TEXT("\\"));
        FontAtlas.AddFontFromFileTTF(TCHAR_TO_ANSI(*Font),15,&FontConfig);
        unsigned char* Pixels;
        int Width, Height, Bpp;
        FontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);

        OnFontAtlasBuilt.Broadcast();

    }
}

Adding it in here makes it work! but not in runtime! can we somehow make an interface between ue4 fonts and Imgui fonts?

segross commented 3 years ago

Hey @irajsb. Yeah, I guess the crash was due to missing texture resources. You found the right function, though. I admit that I didn't work on that much and just done it only to the point where I have fonts that I need for debugging. I guess that I never felt much need for changing the fonts ;) And I think that before DPI scaling, there was no even that bit of code.

Surely it can be changed to allow update/add/remove fonts on the fly. What would be the ultimate goal here? Having something that can be configured from the settings and then initialized accordingly or something that can be changed anytime during the runtime (if the latter, why)?

I'm not sure what do you mean by the interface between UE4 fonts and ImGui fonts? One thing to keep in mind is that anything that is created on the ImGui side needs to have corresponding resources on the UE4 side (which is probably why the code from your first post didn't work). I would imagine that something like FImGuiModule::RegisterTexture(...) would be necessary.

And actually, after reading your post again, what do you mean by runtime? Does your change work in the editor but not in the cooked or packaged builds? If it doesn't, are you sure that the resource is available there?

irajsb commented 3 years ago

I made it work using this BP callable function :

void UImGuiFunctionLibrary::AddFont(FString FilePath,int Size,bool Activate)
{
    ImGuiIO& io = ImGui::GetIO();

    const char* FileName=TCHAR_TO_ANSI(*FilePath);
    const char* FontName;
    for (FontName = FileName + strlen(FileName); FontName > FileName && FontName[-1] != '/' && FontName[-1] != '\\'; FontName--) {}

    FString FontToAdd= ANSI_TO_TCHAR(FontName);
    FontToAdd.Append(", ").Append(FString::FromInt(Size)).Append("px");

    for (ImFont* Font: io.Fonts->Fonts)
    {
    FString FontToComarpe=ANSI_TO_TCHAR(Font->GetDebugName());
        if(FontToAdd.Equals(FontToComarpe))
        {
            UE_LOG(LogTemp,Log,TEXT("Duplicate Font Added Skipping "))
            return;
        }
    }
    ImFont* ImFont= io.Fonts->AddFontFromFileTTF(FileName, Size);

    if(!ImFont)
    {
        UE_LOG(LogTemp,Error,TEXT("Font is null"))
        return;
    }
    FImGuiModule::Get().GetModuleManager()->GetContextManager().RebuildFontAtlas(true);

    if(Activate){
    io.FontDefault=ImFont;

    }
}

I had to change some functions from private to public in order to access them outside of plugin ! also you'll need to skip adding default font after first rebuild (that's why I added a bool to RebuildFontAtlas(true); because I wanted to skip adding default font) by interface between UE4 fonts I mean we can just use fonts that UE4 saves as Uassets instead of loading ttf fonts from filesystem(if we use pak files there will be no ttf and we cannot read fonts ). also I could not find a way to get a ref to ContextManager so I had to edit alot of things! was there a better way to get context manager?

segross commented 3 years ago

Context manager and module manager are hidden for a reason, as I don't really want to have any dependencies on the plugin internals. But if it works better for your use case then sure. Normally when I need that sort of functionality, I hide it in the plugin and do possibly minimal interface for other modules (for example delegates and textures interface).

also you'll need to skip adding default font after first rebuild (that's why I added a bool to RebuildFontAtlas(true); because I wanted to skip adding default font)

I'm not sure how that will work. I don't fully remember now, but I think that the main reason behind the RebuildFontAtlas was rebuilding fonts after changing DPI settings. I'd expect that the above will break it. Which is also why I suggested making it a setting and handling it from there. I was thinking at some point about a solution allowing multiple fonts (which seems to be the case for you) - something like named fonts - but I didn't see how it would be used and it would require a bit more thought, so I dropped it due to not enough priority at a time. I think that ImGui allows it but I didn't try it. An additional complication would be that most probably it would have to address the DPI settings.

by interface between UE4 fonts I mean we can just use fonts that UE4 saves as Uassets instead of loading ttf fonts from filesystem(if we use pak files there will be no ttf and we cannot read fonts ).

Yeah, that makes sense. I was also thinking that something like this would be necessary or at least preferable when adding a fonts setting. It would need to be an optional though, as at least in the case of debugging I prefer to completely wash out ImGui from the final build. Actually, I misinterpreted it. I didn't look that much in ImGui fonts and didn't look that much at the fonts in the UE, so I'm not sure how much work it would be to use derived fonts in ImGui, and at this moment I don't plan to work on this. But as far as I remember ImGui has a few options on how to add fonts and it should be also possible to deploy raw fonts, so it should be feasible.

irajsb commented 3 years ago

I understand why you mark some of data as private , but I don't really like the concept , like for example NVidia PhysX vehicles inside UE4 has a very accurate and good simulation running but since 90% of data his hided from user , user cannot even detect if vehicle is braking or not! I usually choose to mark those kind of variables as private and make some getters and setters for them and I'll add a tooltip comment on top of that function saying that this function is for more advanced users and if you don't know what you are doing don't call this function (giving more advanced devs freedom and also ensuring that normal users run the program like its supposed to!)

segross commented 3 years ago

Well, while I, of course, can understand the sentiment, I'm completely on the other side of the fence here. There are various reasons to hide implementations. Exposing details increases dependencies and those eventually kill maintainability. So unless it is somehow crucial, exposing internals just for the sake of exposing, is just an unnecessary dependency.

With ImGui being the main goal, the integration itself should be irrelevant. If something is missing, it usually can be done in the private part and individually exposed (if necessary). Some extensions like delegates or interface for textures are necessary but other than that it should be only ImGui that is visible outside. And while I recognize that some customizations like widgets should be easier (and even plan some refactorization), I think that things like fonts can be managed inside. That saying, I still don't know what exactly is your use case.

I want to be also able to drop the plug-in and later remove it or replace it with possibly minimal friction. Exposing private parts would make it much harder. Adding an 'advanced' comment wouldn't change much as you would still have to consider breaking a code depending on those internals (which now would be part of the public interface).

And when it comes to Unreal, I didn't look that closely at vehicles but I'm afraid that again, I'm on the other side. I think that UE exposes way too much data - which makes prototyping easier but at the same time creates scalability issues. And I would really not want to depend on PhysX (or any other lower-level framework) inside of the game code.

iUltimateLP commented 3 years ago

I agree with @segross that hiding private parts makes sense. However, it might make sense to just expose a FImGuiModule::RebuildFontAtlas(), similar to how you did the RegisterTexture - as it's a crucial step for having custom fonts.

WiggleWizard commented 2 years ago

For those who come across this in the future, modifications to UnrealImGui are required to achieve this at time of posting. I've added custom font support into my own fork for those who are interested in an plug and play solution. Here's the diff for those adventurous types (it's a very small change). Guide is in the readme.

sammyfreg commented 2 years ago

Related to @WiggleWizard 's post, some additional fonts might be of interest to people. I recently added support for: Japanese, GameKenney, Google MaterialDesign icon and FontAwsome 5 (version 6, wasn't available at the time) to UnrealNetImgui recently.

To have similar support in UnrealImGui, people only need to follow the previous post and add the font files that can be found in UnrealNetImgui.

Changelist where the font were added: https://github.com/sammyfreg/UnrealNetImgui/commit/93fec3bd895046d86ad652998be2fcfc37eb0318