Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b4893f2
fix filename
ben-mkiv Jan 18, 2025
d948257
WIP (working state)
ben-mkiv Jan 18, 2025
8315851
missing files
ben-mkiv Jan 18, 2025
762cb17
enable optimization (temporary) as it could be faster
ben-mkiv Jan 19, 2025
9dad25a
search by tags/character and some UI improvements
ben-mkiv Jan 19, 2025
350b481
few search improvements...
ben-mkiv Jan 19, 2025
c2a8386
use unreal search bar
ben-mkiv Jan 19, 2025
8f3f8d0
fix bug which didn't allow to search for multiple character
ben-mkiv Jan 19, 2025
c53f502
support nested presets
ben-mkiv Jan 19, 2025
8b901ec
move search settings to dropdown
ben-mkiv Jan 20, 2025
d3dc16a
Update README.md
ben-mkiv Jan 20, 2025
84d18c0
Merge remote-tracking branch 'refs/remotes/origin/main'
ben-mkiv Jan 20, 2025
f49e0ba
json importer for glyphs
ben-mkiv Jan 20, 2025
9f5ac7a
WIP rework settings menu
ben-mkiv Jan 20, 2025
97726ae
moved most settings to the settings context menu
ben-mkiv Jan 20, 2025
22d22bc
fix crash/wrong widget ref
ben-mkiv Jan 20, 2025
57b2f55
another approach to fix it...
ben-mkiv Jan 20, 2025
993c1ed
this menu drives me nuts...
ben-mkiv Jan 20, 2025
e57aa70
font/preset as dropdown
ben-mkiv Jan 21, 2025
170fa3b
more reliable font picker...
ben-mkiv Jan 21, 2025
979247b
some small UI improvements
ben-mkiv Jan 21, 2025
f03614b
more UI updates
ben-mkiv Jan 21, 2025
9e9bf81
fix weird overlap when zooming the column count
ben-mkiv Jan 21, 2025
8922d3e
cleanup TextIcon vs. BrushIcon in titlebar (now it looks correct)
ben-mkiv Jan 21, 2025
af0b111
hopefully fix overlapping icons once and for all...
ben-mkiv Jan 21, 2025
b931bba
fix the invalidation also when the scroll is caused by clicking a ran…
ben-mkiv Jan 21, 2025
7f60190
it is what it is... still buggy overdraw but eventually it will be fi…
ben-mkiv Jan 21, 2025
fc9ebb2
first version which uses a TileListView to display the characters
ben-mkiv Jan 22, 2025
c1f1467
fix empty panel when reopening the plugin tab
ben-mkiv Jan 22, 2025
2ea4ccc
fix slow zoom with a large set of characters
ben-mkiv Jan 22, 2025
ccc46ae
an approach at updating the Tile size when the fontsize/padding changes.
ben-mkiv Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"format": "UnicodeBrowserGlyphTags_V1",
"tagFields": [ "TagA", "TagB" ],
"codepointFieldDecimal": "Character",
"codepointFieldHexadecimal": false,
"glyphs": [
{
"TagA": "<control>",
"TagB": "NULL",
"Character": 0
},
{
"TagA": "<control>",
"TagB": "START OF HEADING",
"Character": 1
},
{
"TagB": "DIGIT NINE",
"Character": 57
}
]
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Open Window via `Window -> Unicode Browser`
* Show/hide missing characters
* **Double-click a character to copy it to the system clipboard**.
* Click a block name to scroll to that block.
* Zoom: Change font zize with CTRL + MouseWheel, change column count with CTRL + SHIFT + MouseWheel
* Search: Search in Tags defined in a preset or by characters/letters, supports multiple search terms at once, e.g.: a,b,c,d

> [!WARNING]
> **Disclaimer:** Plugin is currently in its infancy and as such leaves much to be desired regarding the user-experience, but it's usable, and useful!
Expand Down
4 changes: 4 additions & 0 deletions Source/UnicodeBrowser/DataAsset_FontTags.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// all rights reserved


#include "DataAsset_FontTags.h"
245 changes: 245 additions & 0 deletions Source/UnicodeBrowser/DataAsset_FontTags.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
// all rights reserved

#pragma once

#include "CoreMinimal.h"
#include "Engine/Font.h"
#include "UObject/Object.h"
#include "DataAsset_FontTags.generated.h"

/**
*
*/

USTRUCT(BlueprintType)
struct FUnicodeCharacterTags
{
GENERATED_BODY()

UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 Character;

UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<FString> Tags;

bool ContainsNeedle(FString Needle) const
{
return Tags.ContainsByPredicate([Needle](FString const &Tag){ return Tag.Contains(Needle, ESearchCase::IgnoreCase); });
}
};

UCLASS(BlueprintType, Blueprintable)
class UNICODEBROWSER_API UDataAsset_FontTags : public UDataAsset
{
GENERATED_BODY()

public:
// a parent asset, tags will be merged into this preset
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UDataAsset_FontTags> Parent;

// the fonts which should be associated with the Tags
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<TObjectPtr<UFont>> Fonts;

UPROPERTY(EditAnywhere, BlueprintReadOnly)
TArray<FUnicodeCharacterTags> Characters;

// this data is generated at runtime
mutable TMap<int32, int32> CodepointLookup; // Codepoint <> Characters Index
mutable TArray<FUnicodeCharacterTags> CharactersMerged;

// the json file which was used to import the asset
UPROPERTY(VisibleAnywhere)
FString SourceFile;

TArray<FUnicodeCharacterTags>& GetCharactersMerged() const
{
if(CharactersMerged.IsEmpty())
{
CharactersMerged = Characters;
// create the codepoint cache for quicker lookup of existing entries
CacheCodepoints();

if(Parent && Parent != this) // there's a chance of infinite recursion here, this still doesn't catch recursion traps like A => B => A
{
for(FUnicodeCharacterTags &ParentCharacter : Parent->GetCharactersMerged())
{
// character exists, append tags
if(int32 *CharacterIndex = CodepointLookup.Find(ParentCharacter.Character))
{
for(FString &ParentCharacterTag : ParentCharacter.Tags)
{
CharactersMerged[*CharacterIndex].Tags.AddUnique(ParentCharacterTag);
}
}
// character doesn't exist, add it
else
{
CharactersMerged.Add(ParentCharacter);
}
}

// recreate the codepoint cache if the parent has any characters
if(Parent->GetCharactersMerged().Num() > 0){
CacheCodepoints();
}
}
}
return CharactersMerged;
}

TArray<int32> GetCharactersByNeedle(FString NeedleIn) const
{
TArray<int32> Result;
TArray<FString> Needles;

// explode only if the length is >1 since "," is a valid character search term
if(NeedleIn.Len() > 1)
{
NeedleIn.ParseIntoArray(Needles, TEXT(","));
}
else
{
Needles = { NeedleIn };
}

// trim, as the user may type stuff like "Phone, Calculator"
for(FString &Needle : Needles)
{
Needle.TrimStartAndEndInline();
}

Result.Reserve(GetCharactersMerged().Num());
for(FUnicodeCharacterTags const &Entry : GetCharactersMerged())
{
for(FString const &Needle : Needles)
{
if(Entry.ContainsNeedle(Needle))
{
Result.Add(Entry.Character);
break;
}
}
}

return Result;
}

bool SupportsFont(FSlateFontInfo const &FontInfo) const
{
// allow presets to apply to all fonts if they don't contain any specific fonts
return Fonts.IsEmpty() || Fonts.Contains(Cast<UFont>(FontInfo.FontObject));
}

void CacheCodepoints() const
{
CodepointLookup.Reset();
CodepointLookup.Reserve(CharactersMerged.Num());

for(int Idx=0; Idx < CharactersMerged.Num(); Idx++)
{
CodepointLookup.Add(CharactersMerged[Idx].Character, Idx);
}
}

TArray<FString> GetCodepointTags(int32 Codepoint) const
{
if(CodepointLookup.IsEmpty())
{
// this creates the cache
GetCharactersMerged();
}

if(int32 *Index = CodepointLookup.Find(Codepoint))
{
return GetCharactersMerged()[*Index].Tags;
}

return {};
}

bool ImportFromJson(FString Filename)
{
FString JsonString;
if (!FFileHelper::LoadFileToString(JsonString, *Filename))
return false;

SourceFile = Filename;

FString CodePointFieldDecimal = "";
FString CodePointFieldHexadecimal = "";
TArray<FString> TagFields;

TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
const auto Reader = TJsonReaderFactory<>::Create(JsonString);
FJsonSerializer::Deserialize(Reader, JsonObject);

if(!JsonObject->HasTypedField<EJson::Array>(TEXT("tagFields")))
{
return false;
}

JsonObject->TryGetStringField(TEXT("codepointFieldDecimal"), CodePointFieldDecimal);
JsonObject->TryGetStringField(TEXT("codepointFieldHexadecimal"), CodePointFieldHexadecimal);

if(CodePointFieldDecimal.IsEmpty() && CodePointFieldHexadecimal.IsEmpty())
return false;

JsonObject->TryGetStringArrayField(TEXT("tagFields"), TagFields);
if(TagFields.IsEmpty())
return false;

if(!JsonObject->HasTypedField<EJson::Array>(TEXT("glyphs")))
return false;

TArray<TSharedPtr<FJsonValue>> Glyphs = JsonObject->GetArrayField(TEXT("glyphs"));
if(Glyphs.IsEmpty())
return false;

for(TSharedPtr<FJsonValue> &GlyphValue : Glyphs)
{
TSharedPtr<FJsonObject> Glyph = GlyphValue->AsObject();
FUnicodeCharacterTags Data;

if(CodePointFieldDecimal.Len() > 0)
{
if(Glyph->HasTypedField<EJson::Number>(*CodePointFieldDecimal))
{
Data.Character = Glyph->GetNumberField(*CodePointFieldDecimal);
}
else if(Glyph->HasTypedField<EJson::String>(*CodePointFieldDecimal))
{
FString DecimalString = Glyph->GetStringField(*CodePointFieldDecimal);
Data.Character = FCString::Strtoi(*DecimalString, nullptr, 10);
}
}
else if(CodePointFieldHexadecimal.Len() > 0)
{
if(!Glyph->HasTypedField<EJson::String>(*CodePointFieldHexadecimal))
continue;

FString HexString = Glyph->GetStringField(*CodePointFieldHexadecimal);
if(!HexString.StartsWith("0x", ESearchCase::IgnoreCase))
continue;

Data.Character = FCString::Strtoi(*HexString, nullptr, 16);
}

for(FString &TagField : TagFields){
FString Tag = "";
Glyph->TryGetStringField(*TagField, Tag);

if(Tag.Len() > 0)
{
Data.Tags.Add(Tag);
}
}

Characters.Add(Data);
}

return true;
}

};
73 changes: 73 additions & 0 deletions Source/UnicodeBrowser/ImportFactory/GlyphTagsImportFactory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// all rights reserved
#include "GlyphTagsImportFactory.h"
#include "Misc/FileHelper.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "UnicodeBrowser/DataAsset_FontTags.h"

UGlyphTagsImportFactory::UGlyphTagsImportFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
// Factory
SupportedClass = UDataAsset_FontTags::StaticClass();
Formats.Add("json;Unicode Glyph Tags");
bCreateNew = false;
//bEditAfterNew = true;
bEditorImport = true;
bText = true;
}


bool UGlyphTagsImportFactory::CanReimport(UObject* Obj, TArray<FString>& OutFilenames)
{
UDataAsset_FontTags *Object = Cast<UDataAsset_FontTags>(Obj);
return IsValid(Object) && FactoryCanImport(Object->SourceFile);
};

void UGlyphTagsImportFactory::SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths)
{
UDataAsset_FontTags *DataAsset_FontTags = Cast<UDataAsset_FontTags>(Obj);
if(IsValid(DataAsset_FontTags))
{
DataAsset_FontTags->SourceFile = NewReimportPaths[0];
}
}

bool UGlyphTagsImportFactory::FactoryCanImport(const FString& Filename)
{
FString JsonString;
if (!FFileHelper::LoadFileToString(JsonString, *Filename))
return false;


TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
const auto Reader = TJsonReaderFactory<>::Create(JsonString);
FJsonSerializer::Deserialize(Reader, JsonObject);

return JsonObject->HasField(TEXT("format")) && JsonObject->GetStringField(TEXT("format")) == "UnicodeBrowserGlyphTags_V1";
}


UObject* UGlyphTagsImportFactory::ImportObject(UClass* InClass, UObject* InOuter, FName InName, EObjectFlags InFlags, const FString& Filename, const TCHAR* Parms, bool& OutCanceled)
{
UDataAsset_FontTags *Object = Cast<UDataAsset_FontTags>(UFactory::ImportObject(InClass, InOuter, InName, InFlags, Filename, Parms, OutCanceled));
if(!OutCanceled && IsValid(Object))
{
if(Object->ImportFromJson(Filename))
{
return Object;
}
}

return Object;
}

EReimportResult::Type UGlyphTagsImportFactory::Reimport(UObject* Obj)
{
UDataAsset_FontTags *DataAsset_FontTags = Cast<UDataAsset_FontTags>(Obj);
return IsValid(DataAsset_FontTags) && DataAsset_FontTags->ImportFromJson(DataAsset_FontTags->SourceFile) ? EReimportResult::Type::Succeeded : EReimportResult::Type::Failed;
};

UObject* UGlyphTagsImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn)
{
return NewObject<UDataAsset_FontTags>(InParent, InName, Flags);
}
30 changes: 30 additions & 0 deletions Source/UnicodeBrowser/ImportFactory/GlyphTagsImportFactory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// all rights reserved

#pragma once

#include "CoreMinimal.h"
#include "EditorReimportHandler.h"
#include "UObject/Object.h"
#include "GlyphTagsImportFactory.generated.h"

UCLASS()
class UNICODEBROWSER_API UGlyphTagsImportFactory : public UFactory, public FReimportHandler
{
GENERATED_BODY()

public:
UGlyphTagsImportFactory(const FObjectInitializer& ObjectInitializer);

virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn) override;


virtual UObject* ImportObject(UClass* InClass, UObject* InOuter, FName InName, EObjectFlags InFlags, const FString& Filename, const TCHAR* Parms, bool& OutCanceled) override;

virtual bool CanReimport(UObject* Obj, TArray<FString>& OutFilenames) override;
virtual bool FactoryCanImport(const FString& Filename) override;

virtual void SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths) override;

virtual EReimportResult::Type Reimport(UObject* Obj) override;

};
Loading