#include #include #include #include #include #include #include #include #include #include "hex/api/imhex_api.hpp" 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 std::map s_previewFonts, s_usedFonts; static std::map 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 = {}; } 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) { auto scaleFactor = ImHexApi::System::getBackingScaleFactor(); float value = ImHexApi::Fonts::pixelsToPoints(m_value) * scaleFactor; float min = ImHexApi::Fonts::pixelsToPoints(m_min) * scaleFactor; float max = ImHexApi::Fonts::pixelsToPoints(m_max) * scaleFactor; auto changed = ImGui::SliderFloat(name.c_str(), &value, min, max, "%.0f pt"); m_value = ImHexApi::Fonts::pointsToPixels(value / scaleFactor); return changed; } 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; if (m_bold.draw("hex.fonts.setting.font.font_bold"_lang)) changed = true; if (m_italic.draw("hex.fonts.setting.font.font_italic"_lang)) changed = true; 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"] = m_fontSize.store(); json["bold"] = m_bold.store(); json["italic"] = m_italic.store(); json["antialiased"] = m_antiAliased.store(); return json; } void FontSelector::load(const nlohmann::json& data) { m_fontFilePicker.load(data["font_file"]); m_fontSize.load(data["font_size"]); m_bold.load(data["bold"]); m_italic.load(data["italic"]); m_antiAliased.load(data["antialiased"]); } bool FontSelector::drawPopup() { bool changed = false; if (ImGui::BeginPopup("Fonts")) { if (m_fontFilePicker.draw("hex.fonts.setting.font.custom_font"_lang)) m_applyEnabled = true; ImGui::BeginDisabled(m_fontFilePicker.isPixelPerfectFontSelected()); { if (m_fontSize.draw("hex.fonts.setting.font.font_size"_lang)) m_applyEnabled = true; if (m_bold.draw("hex.fonts.setting.font.font_bold"_lang)) m_applyEnabled = true; if (m_italic.draw("hex.fonts.setting.font.font_italic"_lang)) m_applyEnabled = true; if (m_antiAliased.draw("hex.fonts.setting.font.font_antialias"_lang)) m_applyEnabled = true; } ImGui::EndDisabled(); ImGui::NewLine(); ImGui::BeginDisabled(!m_applyEnabled); if (ImGui::Button("hex.ui.common.apply"_lang)) { changed = true; m_applyEnabled = false; } ImGui::EndDisabled(); ImGui::EndPopup(); } return changed; } [[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 m_fontSize.getValue(); } [[nodiscard]] bool FontSelector::isBold() const { return m_bold.isChecked(); } [[nodiscard]] bool FontSelector::isItalic() const { return m_italic.isChecked(); } [[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)); } }