#include #include #include #include #include #include #include #include #include #include #include namespace hex::fonts { constexpr static auto PixelPerfectFontName = "Pixel-Perfect Default Font (Proggy Clean)"; constexpr static auto SmoothFontName = "Smooth Default Font (JetBrains Mono)"; constexpr static auto CustomFontName = "Custom Font"; static AutoReset> s_previewFonts, s_usedFonts; static AutoReset> s_filteredFonts; static bool pushPreviewFont(const std::fs::path &fontPath) { if (fontPath.empty()) { pushPreviewFont(SmoothFontName); return true; } ImFontConfig config = {}; config.FontDataOwnedByAtlas = true; config.Flags |= ImFontFlags_NoLoadError; auto atlas = ImGui::GetIO().Fonts; auto it = s_previewFonts->find(fontPath); if (it == s_previewFonts->end()) { ImFont *font = nullptr; if (fontPath == PixelPerfectFontName) { font = atlas->AddFontDefault(&config); } else if (fontPath == SmoothFontName || fontPath.empty()) { static auto jetbrainsFont = romfs::get("fonts/JetBrainsMono.ttf"); config.FontDataOwnedByAtlas = false; font = atlas->AddFontFromMemoryTTF(const_cast(jetbrainsFont.data()), jetbrainsFont.size(), 0.0F, &config); } else { font = atlas->AddFontFromFileTTF(wolv::util::toUTF8String(fontPath).c_str(), 0.0F, &config); } it = s_previewFonts->emplace(fontPath, font).first; } const auto &[path, font] = *it; if (font == nullptr) { return false; } if (!font->IsGlyphInFont(L'A')) { // If the font doesn't contain any of the ASCII characters, it's // probably not of much use to us return false; } ImGui::PushFont(font, 0.0F); s_usedFonts->emplace(path, font); return true; } static void cleanUnusedPreviewFonts() { auto atlas = ImGui::GetIO().Fonts; for (const auto &[path, font] : *s_previewFonts) { if (font == nullptr) continue; if (!s_usedFonts->contains(path)) { atlas->RemoveFont(font); } } s_previewFonts = std::move(s_usedFonts); s_usedFonts->clear(); } bool FontFilePicker::draw(const std::string &name) { bool changed = false; const bool pixelPerfectFont = isPixelPerfectFontSelected(); bool customFont = updateSelectedFontName(); if (ImGui::BeginCombo(name.c_str(), m_selectedFontName.c_str())) { pushPreviewFont(PixelPerfectFontName); if (ImGui::Selectable(PixelPerfectFontName, m_path.empty() && pixelPerfectFont)) { m_path.clear(); m_pixelPerfectFont = true; changed = true; } ImGui::PopFont(); pushPreviewFont(SmoothFontName); if (ImGui::Selectable(SmoothFontName, m_path.empty() && !pixelPerfectFont)) { m_path.clear(); m_pixelPerfectFont = false; changed = true; } ImGui::PopFont(); pushPreviewFont(customFont ? m_path : SmoothFontName); if (ImGui::Selectable(CustomFontName, customFont)) { changed = fs::openFileBrowser(fs::DialogMode::Open, { { "TTF Font", "ttf" }, { "OTF Font", "otf" } }, [this](const std::fs::path &path) { m_path = path; m_pixelPerfectFont = false; }); } ImGui::PopFont(); if (s_filteredFonts->empty()) { for (const auto &[path, fontName] : hex::getFonts()) { if (!pushPreviewFont(path)) continue; ImGui::PopFont(); s_filteredFonts->emplace(path, fontName); } } { u32 index = 0; ImGuiListClipper clipper; clipper.Begin(s_filteredFonts->size(), ImGui::GetTextLineHeightWithSpacing()); while (clipper.Step()) for (const auto &[path, fontName] : *s_filteredFonts | std::views::drop(clipper.DisplayStart) | std::views::take(clipper.DisplayEnd - clipper.DisplayStart)) { if (!pushPreviewFont(path)) continue; ImGui::PushID(index); if (ImGui::Selectable(limitStringLength(fontName, 50).c_str(), m_path == path)) { m_path = path; m_pixelPerfectFont = false; changed = true; } ImGui::PopFont(); ImGui::SetItemTooltip("%s", fontName.c_str()); ImGui::PopID(); index += 1; } clipper.SeekCursorForItem(index); } ImGui::EndCombo(); } TaskManager::doLaterOnce([] { cleanUnusedPreviewFonts(); }); return changed; } bool FontFilePicker::isPixelPerfectFontSelected() const { return m_pixelPerfectFont; } const std::string& FontFilePicker::getSelectedFontName() const { return m_selectedFontName; } void FontFilePicker::load(const nlohmann::json& data) { FilePicker::load(data["path"]); m_pixelPerfectFont = data["pixel_perfect_font"]; updateSelectedFontName(); } nlohmann::json FontFilePicker::store() { nlohmann::json data = nlohmann::json::object(); data["path"] = FilePicker::store(); data["pixel_perfect_font"] = m_pixelPerfectFont; return data; } bool FontFilePicker::updateSelectedFontName() { const auto &fonts = hex::getFonts(); bool customFont = false; const bool pixelPerfectFont = isPixelPerfectFontSelected(); if (m_path.empty() && pixelPerfectFont) { m_selectedFontName = PixelPerfectFontName; } else if (m_path.empty() && !pixelPerfectFont) { m_selectedFontName = SmoothFontName; } else if (fonts.contains(m_path)) { m_selectedFontName = fonts.at(m_path); } else { m_selectedFontName = wolv::util::toUTF8String(m_path.filename()); customFont = true; } return customFont; } bool SliderPoints::draw(const std::string &name) { if (ImGui::SliderFloat(name.c_str(), &m_value, m_min, m_max, "%.0f pt")) m_changed = true; if (m_changed && !ImGui::IsItemActive()) { m_changed = false; return true; } else { return false; } } bool FontSelector::draw(const std::string &name) { ImGui::PushID(name.c_str()); ON_SCOPE_EXIT { ImGui::PopID(); }; bool changed = false; if (ImGui::CollapsingHeader(name.c_str())) { if (ImGuiExt::BeginBox()) { if (m_fontFilePicker.draw("hex.fonts.setting.font.custom_font"_lang)) changed = true; ImGui::BeginDisabled(m_fontFilePicker.isPixelPerfectFontSelected()); { if (m_fontSize.draw("hex.fonts.setting.font.font_size"_lang)) changed = true; const auto buttonHeight = ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y; fonts::Default().pushBold(); if (ImGuiExt::DimmedButtonToggle("hex.fonts.setting.font.button.bold"_lang, &m_bold, ImVec2(buttonHeight, buttonHeight))) changed = true; fonts::Default().pop(); ImGui::SetItemTooltip("%s", "hex.fonts.setting.font.font_bold"_lang.get()); ImGui::SameLine(); fonts::Default().pushItalic(); if (ImGuiExt::DimmedButtonToggle("hex.fonts.setting.font.button.italic"_lang, &m_italic, ImVec2(buttonHeight, buttonHeight))) changed = true; fonts::Default().pop(); ImGui::SetItemTooltip("%s", "hex.fonts.setting.font.font_italic"_lang.get()); if (m_antiAliased.draw("hex.fonts.setting.font.font_antialias"_lang)) changed = true; } ImGui::EndDisabled(); ImGuiExt::EndBox(); } } return changed; } nlohmann::json FontSelector::store() { nlohmann::json json = nlohmann::json::object(); json["font_file"] = m_fontFilePicker.store(); json["font_size_pt"] = m_fontSize.store(); json["bold"] = m_bold; json["italic"] = m_italic; json["antialiased"] = m_antiAliased.store(); return json; } void FontSelector::load(const nlohmann::json& data) { if (data.contains("font_file")) m_fontFilePicker.load(data["font_file"]); if (data.contains("font_size_pt")) m_fontSize.load(data["font_size_pt"]); if (data.contains("bold")) m_bold = data["bold"]; if (data.contains("italic")) m_italic = data["italic"]; if (data.contains("antialiased")) m_antiAliased.load(data["antialiased"]); } [[nodiscard]] const std::fs::path& FontSelector::getFontPath() const { return m_fontFilePicker.getPath(); } [[nodiscard]] bool FontSelector::isPixelPerfectFont() const { return m_fontFilePicker.isPixelPerfectFontSelected(); } [[nodiscard]] float FontSelector::getFontSize() const { return ImHexApi::Fonts::pointsToPixels(m_fontSize.getValue()); } [[nodiscard]] bool FontSelector::isBold() const { return m_bold; } [[nodiscard]] bool FontSelector::isItalic() const { return m_italic; } [[nodiscard]] AntialiasingType FontSelector::getAntialiasingType() const { if (isPixelPerfectFont()) return AntialiasingType::None; auto value = m_antiAliased.getValue(); if (value == "none") return AntialiasingType::None; else if (value == "grayscale") return AntialiasingType::Grayscale; else if (value == "subpixel") return AntialiasingType::Lcd; else return AntialiasingType::Grayscale; } ContentRegistry::Settings::Widgets::Widget::Interface& addFontSettingsWidget(UnlocalizedString name) { return ContentRegistry::Settings::add("hex.fonts.setting.font", "hex.fonts.setting.font.custom_font", std::move(name)); } }