Skip to content

Commit 05e5405

Browse files
committed
Merge branch 'pr3'
* pr3: (30 commits) an approach at updating the Tile size when the fontsize/padding changes. numColums removed cell padding added (which gives some control about the space between the entries) fix slow zoom with a large set of characters fix empty panel when reopening the plugin tab first version which uses a TileListView to display the characters bugs: num columns doesn't have any influence and the size of the entries is stuck it is what it is... still buggy overdraw but eventually it will be fixed by switching to the list view fix the invalidation also when the scroll is caused by clicking a range in the range selector hopefully fix overlapping icons once and for all... cleanup TextIcon vs. BrushIcon in titlebar (now it looks correct) register to ANY Window menu, moved into TOOLS section in the window menu make docking possible (currently this is limited to one instance, and we would need to rework how/where the current Font stuff is stored, as it's primary bound to the DeveloperSettings) fix weird overlap when zooming the column count more UI updates some small UI improvements more reliable font picker... UDeveloperSettings for persistance \o/ font/preset as dropdown this menu drives me nuts... another approach to fix it... fix crash/wrong widget ref moved most settings to the settings context menu WIP rework settings menu json importer for glyphs Update README.md ...
2 parents d77113f + ccc46ae commit 05e5405

34 files changed

+2089
-830
lines changed

Example.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"format": "UnicodeBrowserGlyphTags_V1",
3+
"tagFields": [ "TagA", "TagB" ],
4+
"codepointFieldDecimal": "Character",
5+
"codepointFieldHexadecimal": false,
6+
"glyphs": [
7+
{
8+
"TagA": "<control>",
9+
"TagB": "NULL",
10+
"Character": 0
11+
},
12+
{
13+
"TagA": "<control>",
14+
"TagB": "START OF HEADING",
15+
"Character": 1
16+
},
17+
{
18+
"TagB": "DIGIT NINE",
19+
"Character": 57
20+
}
21+
]
22+
}

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Unreal Engine Editor Plugin for viewing all supported Unicode Characters.
44

55
----
66
> [!TIP]
7-
> Ever find the *perfect* unicode character for your Unreal editor tooling, only to paste it into Unreal and see a bunch of ��� or  ?
7+
> Ever find the *perfect* Unicode character for your Unreal editor tooling, only to paste it into Unreal and see a bunch of ��� or  ?
88
>
99
> Those times are now over, friend.
1010
>
@@ -33,6 +33,8 @@ Open Window via `Window -> Unicode Browser`
3333
* Show/hide missing characters
3434
* **Double-click a character to copy it to the system clipboard**.
3535
* Click a block name to scroll to that block.
36+
* Zoom: Change font size with CTRL + MouseWheel, change column count with CTRL + SHIFT + MouseWheel
37+
* Search: Search in Tags defined in a preset or by characters/letters, supports multiple search terms at once, e.g.: a,b,c,d
3638

3739
> [!WARNING]
3840
> **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!
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// all rights reserved
2+
3+
#include "DataAsset_FontTags.h"
4+
5+
TArray<FUnicodeCharacterTags>& UDataAsset_FontTags::GetCharactersMerged() const
6+
{
7+
if (CharactersMerged.IsEmpty())
8+
{
9+
CharactersMerged = Characters;
10+
// create the codepoint cache for quicker lookup of existing entries
11+
CacheCodepoints();
12+
13+
if (Parent && Parent != this) // there's a chance of infinite recursion here, this still doesn't catch recursion traps like A => B => A
14+
{
15+
for (FUnicodeCharacterTags& ParentCharacter : Parent->GetCharactersMerged())
16+
{
17+
// character exists, append tags
18+
if (int32 const* CharacterIndex = CodepointLookup.Find(ParentCharacter.Character))
19+
{
20+
for (FString& ParentCharacterTag : ParentCharacter.Tags)
21+
{
22+
CharactersMerged[*CharacterIndex].Tags.AddUnique(ParentCharacterTag);
23+
}
24+
}
25+
// character doesn't exist, add it
26+
else
27+
{
28+
CharactersMerged.Add(ParentCharacter);
29+
}
30+
}
31+
32+
// recreate the codepoint cache if the parent has any characters
33+
if (Parent->GetCharactersMerged().Num() > 0)
34+
{
35+
CacheCodepoints();
36+
}
37+
}
38+
}
39+
return CharactersMerged;
40+
}
41+
42+
TArray<int32> UDataAsset_FontTags::GetCharactersByNeedle(FString NeedleIn) const
43+
{
44+
TArray<int32> Result;
45+
TArray<FString> Needles;
46+
47+
// explode only if the length is >1 since "," is a valid character search term
48+
if (NeedleIn.Len() > 1)
49+
{
50+
NeedleIn.ParseIntoArray(Needles, TEXT(","));
51+
}
52+
else
53+
{
54+
Needles = {NeedleIn};
55+
}
56+
57+
// trim, as the user may type stuff like "Phone, Calculator"
58+
for (FString& Needle : Needles)
59+
{
60+
Needle.TrimStartAndEndInline();
61+
}
62+
63+
Result.Reserve(GetCharactersMerged().Num());
64+
for (FUnicodeCharacterTags const& Entry : GetCharactersMerged())
65+
{
66+
for (FString const& Needle : Needles)
67+
{
68+
if (Entry.ContainsNeedle(Needle))
69+
{
70+
Result.Add(Entry.Character);
71+
break;
72+
}
73+
}
74+
}
75+
76+
return Result;
77+
}
78+
79+
bool UDataAsset_FontTags::SupportsFont(FSlateFontInfo const& FontInfo) const
80+
{
81+
// allow presets to apply to all fonts if they don't contain any specific fonts
82+
return Fonts.IsEmpty() || Fonts.Contains(Cast<UFont>(FontInfo.FontObject));
83+
}
84+
85+
void UDataAsset_FontTags::CacheCodepoints() const
86+
{
87+
CodepointLookup.Reset();
88+
CodepointLookup.Reserve(CharactersMerged.Num());
89+
90+
for (int Idx = 0; Idx < CharactersMerged.Num(); Idx++)
91+
{
92+
CodepointLookup.Add(CharactersMerged[Idx].Character, Idx);
93+
}
94+
}
95+
96+
TArray<FString> UDataAsset_FontTags::GetCodepointTags(int32 Codepoint) const
97+
{
98+
if (CodepointLookup.IsEmpty())
99+
{
100+
// this creates the cache
101+
// ReSharper disable once CppExpressionWithoutSideEffects
102+
GetCharactersMerged();
103+
}
104+
105+
if (int32 const* Index = CodepointLookup.Find(Codepoint))
106+
{
107+
return GetCharactersMerged()[*Index].Tags;
108+
}
109+
110+
return {};
111+
}
112+
113+
bool UDataAsset_FontTags::ImportFromJson(FString Filename)
114+
{
115+
FString JsonString;
116+
if (!FFileHelper::LoadFileToString(JsonString, *Filename))
117+
return false;
118+
119+
SourceFile = Filename;
120+
121+
FString CodePointFieldDecimal = "";
122+
FString CodePointFieldHexadecimal = "";
123+
TArray<FString> TagFields;
124+
125+
TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
126+
const auto Reader = TJsonReaderFactory<>::Create(JsonString);
127+
FJsonSerializer::Deserialize(Reader, JsonObject);
128+
129+
if (!JsonObject->HasTypedField<EJson::Array>(TEXT("tagFields")))
130+
{
131+
return false;
132+
}
133+
134+
JsonObject->TryGetStringField(TEXT("codepointFieldDecimal"), CodePointFieldDecimal);
135+
JsonObject->TryGetStringField(TEXT("codepointFieldHexadecimal"), CodePointFieldHexadecimal);
136+
137+
if (CodePointFieldDecimal.IsEmpty() && CodePointFieldHexadecimal.IsEmpty())
138+
return false;
139+
140+
JsonObject->TryGetStringArrayField(TEXT("tagFields"), TagFields);
141+
if (TagFields.IsEmpty())
142+
return false;
143+
144+
if (!JsonObject->HasTypedField<EJson::Array>(TEXT("glyphs")))
145+
return false;
146+
147+
TArray<TSharedPtr<FJsonValue>> Glyphs = JsonObject->GetArrayField(TEXT("glyphs"));
148+
if (Glyphs.IsEmpty())
149+
return false;
150+
151+
for (TSharedPtr<FJsonValue>& GlyphValue : Glyphs)
152+
{
153+
TSharedPtr<FJsonObject> Glyph = GlyphValue->AsObject();
154+
FUnicodeCharacterTags Data;
155+
156+
if (CodePointFieldDecimal.Len() > 0)
157+
{
158+
if (Glyph->HasTypedField<EJson::Number>(*CodePointFieldDecimal))
159+
{
160+
Data.Character = Glyph->GetNumberField(*CodePointFieldDecimal);
161+
}
162+
else if (Glyph->HasTypedField<EJson::String>(*CodePointFieldDecimal))
163+
{
164+
FString DecimalString = Glyph->GetStringField(*CodePointFieldDecimal);
165+
Data.Character = FCString::Strtoi(*DecimalString, nullptr, 10);
166+
}
167+
}
168+
else if (CodePointFieldHexadecimal.Len() > 0)
169+
{
170+
if (!Glyph->HasTypedField<EJson::String>(*CodePointFieldHexadecimal))
171+
continue;
172+
173+
FString HexString = Glyph->GetStringField(*CodePointFieldHexadecimal);
174+
if (!HexString.StartsWith("0x", ESearchCase::IgnoreCase))
175+
continue;
176+
177+
Data.Character = FCString::Strtoi(*HexString, nullptr, 16);
178+
}
179+
180+
for (FString& TagField : TagFields)
181+
{
182+
FString Tag = "";
183+
Glyph->TryGetStringField(*TagField, Tag);
184+
185+
if (Tag.Len() > 0)
186+
{
187+
Data.Tags.Add(Tag);
188+
}
189+
}
190+
191+
Characters.Add(Data);
192+
}
193+
194+
return true;
195+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// all rights reserved
2+
3+
#pragma once
4+
5+
#include "CoreMinimal.h"
6+
7+
#include "Engine/DataAsset.h"
8+
9+
#include "DataAsset_FontTags.generated.h"
10+
11+
/**
12+
*
13+
*/
14+
15+
struct FSlateFontInfo;
16+
class UFont;
17+
18+
USTRUCT(BlueprintType)
19+
struct FUnicodeCharacterTags
20+
{
21+
GENERATED_BODY()
22+
23+
UPROPERTY(EditAnywhere, BlueprintReadOnly)
24+
int32 Character = 0;
25+
26+
UPROPERTY(EditAnywhere, BlueprintReadOnly)
27+
TArray<FString> Tags;
28+
29+
bool ContainsNeedle(FString Needle) const
30+
{
31+
return Tags.ContainsByPredicate([Needle](FString const& Tag) { return Tag.Contains(Needle, ESearchCase::IgnoreCase); });
32+
}
33+
};
34+
35+
UCLASS(BlueprintType, Blueprintable)
36+
class UNICODEBROWSER_API UDataAsset_FontTags : public UDataAsset
37+
{
38+
GENERATED_BODY()
39+
40+
public:
41+
// a parent asset, tags will be merged into this preset
42+
UPROPERTY(EditAnywhere, BlueprintReadOnly)
43+
TObjectPtr<UDataAsset_FontTags> Parent;
44+
45+
// the fonts which should be associated with the Tags
46+
UPROPERTY(EditAnywhere, BlueprintReadOnly)
47+
TArray<TObjectPtr<UFont>> Fonts;
48+
49+
UPROPERTY(EditAnywhere, BlueprintReadOnly)
50+
TArray<FUnicodeCharacterTags> Characters;
51+
52+
// this data is generated at runtime
53+
mutable TMap<int32, int32> CodepointLookup; // Codepoint <> Characters Index
54+
mutable TArray<FUnicodeCharacterTags> CharactersMerged;
55+
56+
// the json file which was used to import the asset
57+
UPROPERTY(VisibleAnywhere)
58+
FString SourceFile;
59+
60+
TArray<FUnicodeCharacterTags>& GetCharactersMerged() const;
61+
62+
TArray<int32> GetCharactersByNeedle(FString NeedleIn) const;
63+
64+
bool SupportsFont(FSlateFontInfo const& FontInfo) const;
65+
66+
void CacheCodepoints() const;
67+
68+
TArray<FString> GetCodepointTags(int32 Codepoint) const;
69+
70+
bool ImportFromJson(FString Filename);
71+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// all rights reserved
2+
#include "GlyphTagsImportFactory.h"
3+
#include "Misc/FileHelper.h"
4+
#include "Serialization/JsonReader.h"
5+
#include "Serialization/JsonSerializer.h"
6+
#include "UnicodeBrowser/DataAsset_FontTags.h"
7+
8+
UGlyphTagsImportFactory::UGlyphTagsImportFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
9+
{
10+
// Factory
11+
SupportedClass = UDataAsset_FontTags::StaticClass();
12+
Formats.Add("json;Unicode Glyph Tags");
13+
bCreateNew = false;
14+
//bEditAfterNew = true;
15+
bEditorImport = true;
16+
bText = true;
17+
}
18+
19+
20+
bool UGlyphTagsImportFactory::CanReimport(UObject* Obj, TArray<FString>& OutFilenames)
21+
{
22+
UDataAsset_FontTags *Object = Cast<UDataAsset_FontTags>(Obj);
23+
return IsValid(Object) && FactoryCanImport(Object->SourceFile);
24+
};
25+
26+
void UGlyphTagsImportFactory::SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths)
27+
{
28+
UDataAsset_FontTags *DataAsset_FontTags = Cast<UDataAsset_FontTags>(Obj);
29+
if(IsValid(DataAsset_FontTags))
30+
{
31+
DataAsset_FontTags->SourceFile = NewReimportPaths[0];
32+
}
33+
}
34+
35+
bool UGlyphTagsImportFactory::FactoryCanImport(const FString& Filename)
36+
{
37+
FString JsonString;
38+
if (!FFileHelper::LoadFileToString(JsonString, *Filename))
39+
return false;
40+
41+
42+
TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
43+
const auto Reader = TJsonReaderFactory<>::Create(JsonString);
44+
FJsonSerializer::Deserialize(Reader, JsonObject);
45+
46+
return JsonObject->HasField(TEXT("format")) && JsonObject->GetStringField(TEXT("format")) == "UnicodeBrowserGlyphTags_V1";
47+
}
48+
49+
50+
UObject* UGlyphTagsImportFactory::ImportObject(UClass* InClass, UObject* InOuter, FName InName, EObjectFlags InFlags, const FString& Filename, const TCHAR* Parms, bool& OutCanceled)
51+
{
52+
UDataAsset_FontTags *Object = Cast<UDataAsset_FontTags>(UFactory::ImportObject(InClass, InOuter, InName, InFlags, Filename, Parms, OutCanceled));
53+
if(!OutCanceled && IsValid(Object))
54+
{
55+
if(Object->ImportFromJson(Filename))
56+
{
57+
return Object;
58+
}
59+
}
60+
61+
return Object;
62+
}
63+
64+
EReimportResult::Type UGlyphTagsImportFactory::Reimport(UObject* Obj)
65+
{
66+
UDataAsset_FontTags *DataAsset_FontTags = Cast<UDataAsset_FontTags>(Obj);
67+
return IsValid(DataAsset_FontTags) && DataAsset_FontTags->ImportFromJson(DataAsset_FontTags->SourceFile) ? EReimportResult::Type::Succeeded : EReimportResult::Type::Failed;
68+
};
69+
70+
UObject* UGlyphTagsImportFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn)
71+
{
72+
return NewObject<UDataAsset_FontTags>(InParent, InName, Flags);
73+
}

0 commit comments

Comments
 (0)