mirror of https://github.com/WerWolv/ImHex
317 lines
11 KiB
C++
317 lines
11 KiB
C++
#include <font_settings.hpp>
|
|
|
|
#include <hex/api/content_registry/settings.hpp>
|
|
#include <hex/helpers/utils.hpp>
|
|
|
|
#include <imgui.h>
|
|
#include <fonts/fonts.hpp>
|
|
#include <hex/api/task_manager.hpp>
|
|
#include <hex/ui/imgui_imhex_extensions.h>
|
|
#include <romfs/romfs.hpp>
|
|
|
|
#include <wolv/utils/guards.hpp>
|
|
#include <wolv/utils/string.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<std::fs::path, ImFont*> s_previewFonts, s_usedFonts;
|
|
static std::map<std::fs::path, std::string> 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<u8 *>(jetbrainsFont.data<u8>()), 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) {
|
|
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"] = 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) {
|
|
m_fontFilePicker.load(data["font_file"]);
|
|
m_fontSize.load(data["font_size"]);
|
|
m_bold = data["bold"];
|
|
m_italic = data["italic"];
|
|
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<FontSelector>("hex.fonts.setting.font", "hex.fonts.setting.font.custom_font", std::move(name));
|
|
}
|
|
|
|
}
|