diff --git a/dep/imgui/include/IconsEmoji.h b/dep/imgui/include/IconsEmoji.h new file mode 100644 index 000000000..564986757 --- /dev/null +++ b/dep/imgui/include/IconsEmoji.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0) + +#pragma once + +// To generate: "".encode("utf-8") in Python 3. +// List to use: https://www.freecodecamp.org/news/all-emojis-emoji-list-for-copy-and-paste/ + +#define ICON_EMOJI_WARNING "\xe2\x9a\xa0" +#define ICON_EMOJI_OPTICAL_DISK "\xf0\x9f\x92\xbf" +#define ICON_EMOJI_FLOPPY_DISK "\xf0\x9f\x92\xbe" +#define ICON_EMOJI_INFORMATION "\xe2\x84\xb9" +#define ICON_EMOJI_FAST_FORWARD "\xe2\x8f\xa9" +#define ICON_EMOJI_FAST_REVERSE "\xe2\x8f\xaa" +#define ICON_EMOJI_RECORD "\xe2\x8f\xba" +#define ICON_EMOJI_PAUSE "\xe2\x8f\xb8" +#define ICON_EMOJI_CAMERA "\xf0\x9f\x93\xb7" +#define ICON_EMOJI_CAMERA_WITH_FLASH "\xf0\x9f\x93\xb8" +#define ICON_EMOJI_MOVIE_CAMERA "\xf0\x9f\x8e\xa5" +#define ICON_EMOJI_MOUSE "\xf0\x9f\x96\xb1" +#define ICON_EMOJI_MUTED_SPEAKER "\xf0\x9f\x94\x87" +#define ICON_EMOJI_LOW_VOLUME_SPEAKER "\xf0\x9f\x94\x88" +#define ICON_EMOJI_MEDIUM_VOLUME_SPEAKER "\xf0\x9f\x94\x89" +#define ICON_EMOJI_HIGH_VOLUME_SPEAKER "\xf0\x9f\x94\x8a" +#define ICON_EMOJI_FILE_FOLDER "\xf0\x9f\x93\x81" +#define ICON_EMOJI_OPEN_THE_FOLDER "\xf0\x9f\x93\x82" +#define ICON_EMOJI_MAGNIFIYING_GLASS_TILTED_LEFT "\xf0\x9f\x94\x8d" +#define ICON_EMOJI_LOCKED "\xf0\x9f\x94\x92" +#define ICON_EMOJI_UNLOCKED "\xf0\x9f\x94\x93" diff --git a/scripts/generate_update_fa_glyph_ranges.py b/scripts/generate_update_fa_glyph_ranges.py index eee303454..3c6d7c4bf 100755 --- a/scripts/generate_update_fa_glyph_ranges.py +++ b/scripts/generate_update_fa_glyph_ranges.py @@ -10,7 +10,8 @@ import re src_dir = os.path.join(os.path.dirname(__file__), "..", "src") fa_file = os.path.join(os.path.dirname(__file__), "..", "dep", "imgui", "include", "IconsFontAwesome5.h") pf_file = os.path.join(os.path.dirname(__file__), "..", "dep", "imgui", "include", "IconsPromptFont.h") -dst_file = os.path.join(os.path.dirname(__file__), "..", "src", "util", "imgui_manager.cpp") +emoji_file = os.path.join(os.path.dirname(__file__), "..", "dep", "imgui", "include", "IconsEmoji.h") +dst_file = os.path.join(os.path.dirname(__file__), "..", "src", "util", "imgui_glyph_ranges.inl") all_source_files = glob.glob(os.path.join(src_dir, "**", "*.cpp"), recursive=True) + \ glob.glob(os.path.join(src_dir, "**", "*.h"), recursive=True) + \ @@ -18,6 +19,7 @@ all_source_files = glob.glob(os.path.join(src_dir, "**", "*.cpp"), recursive=Tru tokens = set() pf_tokens = set() +emoji_tokens = set() for filename in all_source_files: data = None with open(filename, "r") as f: @@ -28,8 +30,9 @@ for filename in all_source_files: tokens = tokens.union(set(re.findall("(ICON_FA_[a-zA-Z0-9_]+)", data))) pf_tokens = pf_tokens.union(set(re.findall("(ICON_PF_[a-zA-Z0-9_]+)", data))) + emoji_tokens = emoji_tokens.union(set(re.findall("(ICON_EMOJI_[a-zA-Z0-9_]+)", data))) -print("{}/{} tokens found.".format(len(tokens), len(pf_tokens))) +print("{}/{}/{} tokens found.".format(len(tokens), len(pf_tokens), len(emoji_tokens))) if len(tokens) == 0 and len(pf_tokens) == 0: sys.exit(0) @@ -46,9 +49,16 @@ with open(pf_file, "r") as f: if match is None: continue u8_encodings[match[1]] = bytes.fromhex(match[2].replace("\\x", "")) +with open(emoji_file, "r") as f: + for line in f.readlines(): + match = re.match("#define (ICON_EMOJI_[^ ]+) \"([^\"]+)\"", line) + if match is None: + continue + u8_encodings[match[1]] = bytes.fromhex(match[2].replace("\\x", "")) -out_pattern = "(static constexpr ImWchar range_fa\[\] = \{)[0-9A-Z_a-z, \n]+(\};)" -out_pf_pattern = "(static constexpr ImWchar range_pf\[\] = \{)[0-9A-Z_a-z, \n]+(\};)" +out_pattern = "(static constexpr ImWchar FA_ICON_RANGE\[\] = \{)[0-9A-Z_a-z, \n]+(\};)" +out_pf_pattern = "(static constexpr ImWchar PF_ICON_RANGE\[\] = \{)[0-9A-Z_a-z, \n]+(\};)" +out_emoji_pattern = "(static constexpr ImWchar EMOJI_ICON_RANGE\[\] = \{)[0-9A-Z_a-z, \n]+(\};)" def get_pairs(tokens): codepoints = list() @@ -84,6 +94,7 @@ with open(dst_file, "r") as f: original = f.read() updated = re.sub(out_pattern, "\\1 " + get_pairs(tokens) + " \\2", original) updated = re.sub(out_pf_pattern, "\\1 " + get_pairs(pf_tokens) + " \\2", updated) + updated = re.sub(out_emoji_pattern, "\\1 " + get_pairs(emoji_tokens) + " \\2", updated) if original != updated: with open(dst_file, "w") as f: f.write(updated) diff --git a/src/util/imgui_glyph_ranges.inl b/src/util/imgui_glyph_ranges.inl new file mode 100644 index 000000000..2e367253b --- /dev/null +++ b/src/util/imgui_glyph_ranges.inl @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0) + +static constexpr ImWchar FA_ICON_RANGE[] = { 0xe06f,0xe06f,0xe086,0xe086,0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf01c,0xf01c,0xf021,0xf021,0xf023,0xf023,0xf025,0xf025,0xf027,0xf028,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf049,0xf04c,0xf050,0xf050,0xf05e,0xf05e,0xf062,0xf063,0xf067,0xf067,0xf071,0xf071,0xf075,0xf075,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf085,0xf091,0xf091,0xf0a0,0xf0a0,0xf0ac,0xf0ad,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0e7,0xf0e7,0xf0eb,0xf0eb,0xf0f1,0xf0f1,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf119,0xf119,0xf11b,0xf11c,0xf140,0xf140,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf191,0xf192,0xf1ab,0xf1ab,0xf1dd,0xf1de,0xf1e6,0xf1e6,0xf1eb,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf240,0xf240,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f2,0xf2f2,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf466,0xf466,0xf4ce,0xf4ce,0xf500,0xf500,0xf51f,0xf51f,0xf538,0xf538,0xf545,0xf545,0xf547,0xf548,0xf57a,0xf57a,0xf5a2,0xf5a2,0xf5aa,0xf5aa,0xf5e7,0xf5e7,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf6cf,0xf6cf,0xf70c,0xf70c,0xf794,0xf794,0xf7a0,0xf7a0,0xf7c2,0xf7c2,0xf807,0xf807,0xf815,0xf815,0xf818,0xf818,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 }; + +static constexpr ImWchar PF_ICON_RANGE[] = { 0x2196,0x2199,0x219e,0x21a1,0x21b0,0x21b3,0x21ba,0x21c3,0x21c7,0x21ca,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21ed,0x21ee,0x21f7,0x21f8,0x21fa,0x21fb,0x227a,0x227f,0x2284,0x2284,0x235e,0x235e,0x2360,0x2361,0x2364,0x2366,0x23b2,0x23b4,0x23ce,0x23ce,0x23f4,0x23f7,0x2427,0x243a,0x243c,0x243e,0x2460,0x246b,0x24f5,0x24fd,0x24ff,0x24ff,0x2717,0x2717,0x278a,0x278e,0x27fc,0x27fc,0xe001,0xe001,0xff21,0xff3a,0x1f52b,0x1f52b,0x0,0x0 }; + +static constexpr ImWchar EMOJI_ICON_RANGE[] = { 0x0,0x0 }; diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 0decdc6f5..38ca821c1 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -6,6 +6,7 @@ #include "host.h" #include "image.h" #include "imgui_fullscreen.h" +#include "imgui_glyph_ranges.inl" #include "input_manager.h" #include "common/assert.h" @@ -19,6 +20,7 @@ #include "IconsFontAwesome5.h" #include "fmt/format.h" #include "imgui.h" +#include "imgui_freetype.h" #include "imgui_internal.h" #include @@ -64,6 +66,7 @@ static void UpdateScale(); static void SetStyle(); static void SetKeyMap(); static bool LoadFontData(); +static void ReloadFontDataIfActive(); static bool AddImGuiFonts(bool fullscreen_fonts); static ImFont* AddTextFont(float size); static ImFont* AddFixedFont(float size); @@ -80,6 +83,7 @@ static float s_global_scale = 1.0f; static std::string s_font_path; static std::vector s_font_range; +static std::vector s_emoji_range; static ImFont* s_standard_font; static ImFont* s_fixed_font; @@ -90,6 +94,7 @@ static DynamicHeapArray s_standard_font_data; static DynamicHeapArray s_fixed_font_data; static DynamicHeapArray s_icon_fa_font_data; static DynamicHeapArray s_icon_pf_font_data; +static DynamicHeapArray s_emoji_font_data; static float s_window_width; static float s_window_height; @@ -122,22 +127,36 @@ void ImGuiManager::SetFontPathAndRange(std::string path, std::vector s_font_path = std::move(path); s_font_range = std::move(range); s_standard_font_data = {}; + ReloadFontDataIfActive(); +} - if (ImGui::GetCurrentContext()) +void ImGuiManager::SetEmojiFontRange(std::vector range) +{ + static constexpr size_t builtin_size = std::size(EMOJI_ICON_RANGE); + const size_t runtime_size = range.size(); + + if (runtime_size == 0) { - ImGui::EndFrame(); + if (s_emoji_range.empty()) + return; - if (!LoadFontData()) - Panic("Failed to load font data"); - - if (!AddImGuiFonts(HasFullscreenFonts())) - Panic("Failed to create ImGui font text"); - - if (!g_gpu_device->UpdateImGuiFontTexture()) - Panic("Failed to recreate font texture after scale+resize"); - - NewFrame(); + s_emoji_range = {}; } + else + { + if (!s_emoji_range.empty() && (s_emoji_range.size() - builtin_size) == range.size() && + std::memcmp(s_emoji_range.data(), range.data(), range.size() * sizeof(ImWchar)) == 0) + { + // no change + return; + } + + s_emoji_range = std::move(range); + s_emoji_range.resize(s_emoji_range.size() + builtin_size); + std::memcpy(&s_emoji_range[runtime_size], EMOJI_ICON_RANGE, sizeof(EMOJI_ICON_RANGE)); + } + + ReloadFontDataIfActive(); } void ImGuiManager::SetGlobalScale(float global_scale) @@ -518,6 +537,16 @@ bool ImGuiManager::LoadFontData() s_icon_pf_font_data = std::move(font_data.value()); } + if (s_emoji_font_data.empty() && false) + { + std::optional> font_data = + Host::ReadCompressedResourceFile("fonts/TwitterColorEmoji-SVGinOT.ttf.zst", true); + if (!font_data.has_value()) + return false; + + s_emoji_font_data = std::move(font_data.value()); + } + return true; } @@ -539,28 +568,6 @@ ImFont* ImGuiManager::AddFixedFont(float size) bool ImGuiManager::AddIconFonts(float size) { - static constexpr ImWchar range_fa[] = { - 0xe06f, 0xe06f, 0xe086, 0xe086, 0xf002, 0xf002, 0xf005, 0xf005, 0xf007, 0xf007, 0xf00c, 0xf00e, 0xf011, 0xf011, - 0xf013, 0xf013, 0xf017, 0xf017, 0xf019, 0xf019, 0xf01c, 0xf01c, 0xf021, 0xf021, 0xf023, 0xf023, 0xf025, 0xf025, - 0xf027, 0xf028, 0xf02e, 0xf02e, 0xf030, 0xf030, 0xf03a, 0xf03a, 0xf03d, 0xf03d, 0xf049, 0xf04c, 0xf050, 0xf050, - 0xf05e, 0xf05e, 0xf062, 0xf063, 0xf067, 0xf067, 0xf071, 0xf071, 0xf075, 0xf075, 0xf077, 0xf078, 0xf07b, 0xf07c, - 0xf084, 0xf085, 0xf091, 0xf091, 0xf0a0, 0xf0a0, 0xf0ac, 0xf0ad, 0xf0c5, 0xf0c5, 0xf0c7, 0xf0c9, 0xf0cb, 0xf0cb, - 0xf0d0, 0xf0d0, 0xf0dc, 0xf0dc, 0xf0e2, 0xf0e2, 0xf0e7, 0xf0e7, 0xf0eb, 0xf0eb, 0xf0f1, 0xf0f1, 0xf0f3, 0xf0f3, - 0xf0fe, 0xf0fe, 0xf110, 0xf110, 0xf119, 0xf119, 0xf11b, 0xf11c, 0xf140, 0xf140, 0xf14a, 0xf14a, 0xf15b, 0xf15b, - 0xf15d, 0xf15d, 0xf191, 0xf192, 0xf1ab, 0xf1ab, 0xf1dd, 0xf1de, 0xf1e6, 0xf1e6, 0xf1eb, 0xf1eb, 0xf1f8, 0xf1f8, - 0xf1fc, 0xf1fc, 0xf240, 0xf240, 0xf242, 0xf242, 0xf245, 0xf245, 0xf26c, 0xf26c, 0xf279, 0xf279, 0xf2d0, 0xf2d0, - 0xf2db, 0xf2db, 0xf2f2, 0xf2f2, 0xf3c1, 0xf3c1, 0xf3fd, 0xf3fd, 0xf410, 0xf410, 0xf466, 0xf466, 0xf4ce, 0xf4ce, - 0xf500, 0xf500, 0xf51f, 0xf51f, 0xf538, 0xf538, 0xf545, 0xf545, 0xf547, 0xf548, 0xf57a, 0xf57a, 0xf5a2, 0xf5a2, - 0xf5aa, 0xf5aa, 0xf5e7, 0xf5e7, 0xf65d, 0xf65e, 0xf6a9, 0xf6a9, 0xf6cf, 0xf6cf, 0xf70c, 0xf70c, 0xf794, 0xf794, - 0xf7a0, 0xf7a0, 0xf7c2, 0xf7c2, 0xf807, 0xf807, 0xf815, 0xf815, 0xf818, 0xf818, 0xf84c, 0xf84c, 0xf8cc, 0xf8cc, - 0x0, 0x0}; - static constexpr ImWchar range_pf[] = { - 0x2196, 0x2199, 0x219e, 0x21a1, 0x21b0, 0x21b3, 0x21ba, 0x21c3, 0x21c7, 0x21ca, 0x21d0, 0x21d4, 0x21dc, - 0x21dd, 0x21e0, 0x21e3, 0x21ed, 0x21ee, 0x21f7, 0x21f8, 0x21fa, 0x21fb, 0x227a, 0x227f, 0x2284, 0x2284, - 0x235e, 0x235e, 0x2360, 0x2361, 0x2364, 0x2366, 0x23b2, 0x23b4, 0x23ce, 0x23ce, 0x23f4, 0x23f7, 0x2427, - 0x243a, 0x243c, 0x243e, 0x2460, 0x246b, 0x24f5, 0x24fd, 0x24ff, 0x24ff, 0x2717, 0x2717, 0x278a, 0x278e, - 0x27fc, 0x27fc, 0xe001, 0xe001, 0xff21, 0xff3a, 0x1f52b, 0x1f52b, 0x0, 0x0}; - { ImFontConfig cfg; cfg.MergeMode = true; @@ -570,7 +577,8 @@ bool ImGuiManager::AddIconFonts(float size) cfg.FontDataOwnedByAtlas = false; if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF( - s_icon_fa_font_data.data(), static_cast(s_icon_fa_font_data.size()), size * 0.75f, &cfg, range_fa)) + s_icon_fa_font_data.data(), static_cast(s_icon_fa_font_data.size()), size * 0.75f, &cfg, FA_ICON_RANGE)) + [[unlikely]] { return false; } @@ -585,7 +593,26 @@ bool ImGuiManager::AddIconFonts(float size) cfg.FontDataOwnedByAtlas = false; if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF( - s_icon_pf_font_data.data(), static_cast(s_icon_pf_font_data.size()), size * 1.2f, &cfg, range_pf)) + s_icon_pf_font_data.data(), static_cast(s_icon_pf_font_data.size()), size * 1.2f, &cfg, PF_ICON_RANGE)) + [[unlikely]] + { + return false; + } + } + + if constexpr (false) // Not yet used + { + ImFontConfig cfg; + cfg.MergeMode = true; + cfg.PixelSnapH = true; + cfg.GlyphMinAdvanceX = size; + cfg.GlyphMaxAdvanceX = size; + cfg.FontDataOwnedByAtlas = false; + cfg.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor | ImGuiFreeTypeBuilderFlags_Bitmap; + + if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF( + s_emoji_font_data.data(), static_cast(s_emoji_font_data.size()), size * 0.9f, &cfg, + s_emoji_range.empty() ? EMOJI_ICON_RANGE : s_emoji_range.data())) [[unlikely]] { return false; } @@ -632,6 +659,25 @@ bool ImGuiManager::AddImGuiFonts(bool fullscreen_fonts) return io.Fonts->Build(); } +void ImGuiManager::ReloadFontDataIfActive() +{ + if (!ImGui::GetCurrentContext()) + return; + + ImGui::EndFrame(); + + if (!LoadFontData()) + Panic("Failed to load font data"); + + if (!AddImGuiFonts(HasFullscreenFonts())) + Panic("Failed to create ImGui font text"); + + if (!g_gpu_device->UpdateImGuiFontTexture()) + Panic("Failed to recreate font texture after scale+resize"); + + NewFrame(); +} + bool ImGuiManager::AddFullscreenFontsIfMissing() { if (HasFullscreenFonts()) diff --git a/src/util/imgui_manager.h b/src/util/imgui_manager.h index 1422db200..d7de72d1b 100644 --- a/src/util/imgui_manager.h +++ b/src/util/imgui_manager.h @@ -4,6 +4,7 @@ #pragma once #include "common/types.h" +#include #include #include @@ -21,6 +22,10 @@ using WCharType = u32; /// Sets the path to the font to use. Empty string means to use the default. void SetFontPathAndRange(std::string path, std::vector range); +/// Sets the emoji font range to use. Empty means no glyphs will be rasterized. +/// Should NOT be terminated with zeros, unlike the font range above. +void SetEmojiFontRange(std::vector range); + /// Changes the global scale. void SetGlobalScale(float global_scale);