From 6093282ac3c313dd1d9a5296c67e3345bc9eb983 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 29 Oct 2024 09:48:23 -0300 Subject: [PATCH] Add pin/unpin icon to font search field (#4617, #4692) With this we can finally easily save font presets, even references to external .ttf files. --- data/strings/en.ini | 1 + src/app/font_info.cpp | 28 ++++++++++++++++ src/app/font_info.h | 2 ++ src/app/recent_files.cpp | 19 ++++++----- src/app/recent_files.h | 16 ++++++--- src/app/ui/font_entry.cpp | 65 +++++++++++++++++++++++++++++++++---- src/app/ui/font_entry.h | 2 ++ src/app/ui/font_popup.cpp | 51 ++++++++++++++++++++++++++--- src/app/ui/font_popup.h | 3 ++ src/app/ui/search_entry.cpp | 21 ++++++++---- src/app/ui/search_entry.h | 3 ++ 11 files changed, 179 insertions(+), 32 deletions(-) diff --git a/data/strings/en.ini b/data/strings/en.ini index 559cd750e..897d3274e 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -777,6 +777,7 @@ preview = &Preview tiled = &Tiled [font_popup] +pinned_fonts = Pinned Fonts theme_fonts = Theme Fonts system_fonts = System Fonts load = Load External Font diff --git a/src/app/font_info.cpp b/src/app/font_info.cpp index d00b30fc4..33fb53693 100644 --- a/src/app/font_info.cpp +++ b/src/app/font_info.cpp @@ -111,6 +111,34 @@ void FontInfo::updatePreferences() } } +std::string FontInfo::humanString() const +{ + std::string result; + + switch (type()) { + case app::FontInfo::Type::Unknown: + case app::FontInfo::Type::Name: + case app::FontInfo::Type::System: + result = name(); + break; + case app::FontInfo::Type::File: + result = base::get_file_name(name()); + break; + } + result += fmt::format(" {}pt", size()); + if (!result.empty()) { + if (style().weight() >= text::FontStyle::Weight::SemiBold) + result += " Bold"; + if (style().slant() != text::FontStyle::Slant::Upright) + result += " Italic"; + if (antialias()) + result += " Antialias"; + if (ligatures()) + result += " Ligatures"; + } + return result; +} + } // namespace app namespace base { diff --git a/src/app/font_info.h b/src/app/font_info.h index c24d86783..ea7e51274 100644 --- a/src/app/font_info.h +++ b/src/app/font_info.h @@ -73,6 +73,8 @@ namespace app { static FontInfo getFromPreferences(); void updatePreferences(); + std::string humanString() const; + bool operator==(const FontInfo& other) const { return (m_type == other.m_type && m_name == other.m_name && diff --git a/src/app/recent_files.cpp b/src/app/recent_files.cpp index 3744eb4ef..058d9cdf8 100644 --- a/src/app/recent_files.cpp +++ b/src/app/recent_files.cpp @@ -21,13 +21,11 @@ namespace { -enum { kPinnedFiles, kRecentFiles, - kPinnedPaths, kRecentPaths }; - const char* kSectionName[] = { "PinnedFiles", "RecentFiles", "PinnedPaths", - "RecentPaths" }; + "RecentPaths", + "PinnedFonts" }; // Special key used in recent sections (files/paths) to indicate that // the section was already converted at least one time. @@ -183,7 +181,7 @@ void RecentFiles::load() !get_config_bool(section, kConversionKey, false)); const bool processOldPaths = - (i == kRecentPaths && + (i == kRecentFolders && get_config_string(section, "Path00", nullptr) && !get_config_bool(section, kConversionKey, false)); @@ -197,11 +195,14 @@ void RecentFiles::load() const char* fn = get_config_string(section, key.c_str(), nullptr); if (fn && *fn && - ((i < 2 && base::is_file(fn)) || - (i >= 2 && base::is_directory(fn)))) { + (((i == kPinnedFiles || i == kRecentFiles) && base::is_file(fn)) || + ((i == kPinnedFolders || i == kRecentFolders) && base::is_directory(fn)))) { std::string normalFn = normalizePath(fn); m_paths[i].push_back(normalFn); } + else if (i == kPinnedFonts) { + m_paths[i].push_back(fn); + } } } } @@ -215,7 +216,7 @@ void RecentFiles::save() if ((i == kRecentFiles && (std::strncmp(key.c_str(), "Filename", 8) == 0 || key == kConversionKey)) || - (i == kRecentPaths && + (i == kRecentFolders && (std::strncmp(key.c_str(), "Path", 4) == 0 || key == kConversionKey))) { // Ignore old entries if we are going to read the new ones continue; @@ -229,7 +230,7 @@ void RecentFiles::save() m_paths[i][j].c_str()); } // Special entry that indicates that we've already converted - if ((i == kRecentFiles || i == kRecentPaths) && + if ((i == kRecentFiles || i == kRecentFolders) && !get_config_bool(section, kConversionKey, false)) { set_config_bool(section, kConversionKey, true); } diff --git a/src/app/recent_files.h b/src/app/recent_files.h index 9f52bf3d8..45d1ae47f 100644 --- a/src/app/recent_files.h +++ b/src/app/recent_files.h @@ -17,16 +17,22 @@ namespace app { class RecentFiles { - enum { kPinnedFiles, - kRecentFiles, - kPinnedFolders, - kRecentFolders, - kCollections }; + enum { + kPinnedFiles, + kRecentFiles, + kPinnedFolders, + kRecentFolders, + kPinnedFonts, + kCollections + }; + public: const base::paths& pinnedFiles() const { return m_paths[kPinnedFiles]; } const base::paths& recentFiles() const { return m_paths[kRecentFiles]; } const base::paths& pinnedFolders() const { return m_paths[kPinnedFolders]; } const base::paths& recentFolders() const { return m_paths[kRecentFolders]; } + // TODO probably this collection should be in another kind of class. + base::paths& pinnedFonts() { return m_paths[kPinnedFonts]; } RecentFiles(const int limit); ~RecentFiles(); diff --git a/src/app/ui/font_entry.cpp b/src/app/ui/font_entry.cpp index e6d271ba1..fcddb4fc1 100644 --- a/src/app/ui/font_entry.cpp +++ b/src/app/ui/font_entry.cpp @@ -10,8 +10,11 @@ #include "app/ui/font_entry.h" +#include "app/app.h" #include "app/console.h" +#include "app/recent_files.h" #include "app/ui/font_popup.h" +#include "app/ui/skin/skin_theme.h" #include "base/scoped_value.h" #include "fmt/format.h" #include "ui/display.h" @@ -19,6 +22,7 @@ #include "ui/message.h" #include "ui/scale.h" +#include #include namespace app { @@ -84,6 +88,9 @@ bool FontEntry::FontFace::onProcessMessage(Message* msg) } } if (!m_popup->isVisible()) { + // Reset the search filter before opening the popup window. + m_popup->setSearchText(std::string()); + m_popup->showPopup(display(), bounds()); requestFocus(); } @@ -171,6 +178,47 @@ void FontEntry::FontFace::onChange() FontChange(m_popup->selectedFont(), From::Face); } +os::Surface* FontEntry::FontFace::onGetCloseIcon() const +{ + auto& pinnedFonts = App::instance()->recentFiles()->pinnedFonts(); + const FontInfo info = fontEntry()->info(); + const std::string fontInfoStr = base::convert_to(info); + auto it = std::find(pinnedFonts.begin(), + pinnedFonts.end(), + fontInfoStr); + if (it != pinnedFonts.end()) { + return skin::SkinTheme::get(this)->parts.pinned()->bitmap(0); + } + return skin::SkinTheme::get(this)->parts.unpinned()->bitmap(0); +} + +void FontEntry::FontFace::onCloseIconPressed() +{ + const FontInfo info = fontEntry()->info(); + if (info.size() == 0) // Don't save fonts with size=0pt + return; + + auto& pinnedFonts = App::instance()->recentFiles()->pinnedFonts(); + const std::string fontInfoStr = base::convert_to(info); + + auto it = std::find(pinnedFonts.begin(), + pinnedFonts.end(), + fontInfoStr); + if (it != pinnedFonts.end()) { + pinnedFonts.erase(it); + } + else { + pinnedFonts.push_back(fontInfoStr); + std::sort(pinnedFonts.begin(), + pinnedFonts.end()); + } + + // Refill the list with the new pinned/unpinned item + m_popup->recreatePinnedItems(); + + invalidate(); +} + FontEntry::FontSize::FontSize() { setEditable(true); @@ -215,13 +263,16 @@ FontEntry::FontEntry() m_face.setMinSize(gfx::Size(128*guiscale(), 0)); - m_face.FontChange.connect([this](const FontInfo& newTypeName, - const From from) { - setInfo(FontInfo(newTypeName, - m_info.size(), - m_info.style(), - m_info.flags()), - from); + m_face.FontChange.connect([this](const FontInfo& newTypeName, const From from) { + if (newTypeName.size() > 0) + setInfo(newTypeName, from); + else { + setInfo(FontInfo(newTypeName, + m_info.size(), + m_info.style(), + m_info.flags()), + from); + } invalidate(); }); diff --git a/src/app/ui/font_entry.h b/src/app/ui/font_entry.h index c6d794faf..e6912c321 100644 --- a/src/app/ui/font_entry.h +++ b/src/app/ui/font_entry.h @@ -48,6 +48,8 @@ namespace app { protected: bool onProcessMessage(ui::Message* msg) override; void onChange() override; + os::Surface* onGetCloseIcon() const override; + void onCloseIconPressed() override; private: FontEntry* fontEntry() const { return static_cast(parent()); } diff --git a/src/app/ui/font_popup.cpp b/src/app/ui/font_popup.cpp index da6073821..aa689fc6e 100644 --- a/src/app/ui/font_popup.cpp +++ b/src/app/ui/font_popup.cpp @@ -11,11 +11,13 @@ #include "app/ui/font_popup.h" +#include "app/app.h" #include "app/file_selector.h" #include "app/font_info.h" #include "app/font_path.h" #include "app/i18n/strings.h" #include "app/match_words.h" +#include "app/recent_files.h" #include "app/ui/separator_in_view.h" #include "app/ui/skin/font_data.h" #include "app/ui/skin/skin_theme.h" @@ -60,6 +62,12 @@ class FontItem : public ListItem { public: struct ByName { }; + explicit FontItem(const FontInfo& fontInfo) + : ListItem(fontInfo.humanString()) + , m_fontInfo(fontInfo) { + getCachedThumbnail(); + } + FontItem(const std::string& name, ByName) : ListItem(name) , m_fontInfo(FontInfo::Type::Name, name, @@ -69,7 +77,7 @@ public: getCachedThumbnail(); } - FontItem(const std::string& fn) + explicit FontItem(const std::string& fn) : ListItem(base::get_file_title(fn)) , m_fontInfo(FontInfo::Type::File, fn, FontInfo::kDefaultSize, @@ -128,9 +136,14 @@ private: const auto* theme = app::skin::SkinTheme::get(this); try { + const FontInfo fontInfoDefSize(m_fontInfo, + FontInfo::kDefaultSize, + text::FontStyle(), + FontInfo::Flags::Antialias); + const gfx::Color color = theme->colors.text(); doc::ImageRef image = - render_text(m_fontInfo, text(), color); + render_text(fontInfoDefSize, text(), color); if (!image) return; @@ -220,13 +233,17 @@ FontPopup::FontPopup(const FontInfo& fontInfo) m_popup->view()->attachToView(&m_listBox); + // Pinned fonts + m_pinnedSeparator = new SeparatorInView(Strings::font_popup_pinned_fonts()); + m_listBox.addChild(m_pinnedSeparator); + // Default fonts - bool firstThemeFont = true; + bool first = true; for (auto kv : skin::SkinTheme::get(this)->getWellKnownFonts()) { if (!kv.second->filename().empty()) { - if (firstThemeFont) { + if (first) { m_listBox.addChild(new SeparatorInView(Strings::font_popup_theme_fonts())); - firstThemeFont = false; + first = false; } m_listBox.addChild(new FontItem(kv.first, FontItem::ByName())); } @@ -332,6 +349,8 @@ void FontPopup::showPopup(Display* display, { m_listBox.selectChild(nullptr); + recreatePinnedItems(); + ui::fit_bounds(display, this, gfx::Rect(buttonBounds.x, buttonBounds.y2(), buttonBounds.w*2, buttonBounds.h), @@ -344,6 +363,28 @@ void FontPopup::showPopup(Display* display, openWindow(); } +void FontPopup::recreatePinnedItems() +{ + // Update list of pinned fonts + if (m_pinnedSeparator) { + // Delete pinned elements + while (true) { + Widget* next = m_pinnedSeparator->nextSibling(); + if (!next || next->type() == kSeparatorWidget) + break; + delete next; + } + + // Recreate pinned elements + auto& pinnedFonts = App::instance()->recentFiles()->pinnedFonts(); + int i = 1; + for (const auto& fontInfoStr : pinnedFonts) { + m_listBox.insertChild(i++, new FontItem(base::convert_to(fontInfoStr))); + } + m_pinnedSeparator->setVisible(!pinnedFonts.empty()); + } +} + FontInfo FontPopup::selectedFont() { const FontItem* child = dynamic_cast(m_listBox.getSelectedChild()); diff --git a/src/app/ui/font_popup.h b/src/app/ui/font_popup.h index 874284143..9e390a4c7 100644 --- a/src/app/ui/font_popup.h +++ b/src/app/ui/font_popup.h @@ -42,6 +42,8 @@ namespace app { void showPopup(ui::Display* display, const gfx::Rect& buttonBounds); + void recreatePinnedItems(); + FontListBox* getListBox() { return &m_listBox; } FontInfo selectedFont(); @@ -59,6 +61,7 @@ namespace app { gen::FontPopup* m_popup; FontListBox m_listBox; ui::Timer m_timer; + ui::Widget* m_pinnedSeparator = nullptr; }; } // namespace app diff --git a/src/app/ui/search_entry.cpp b/src/app/ui/search_entry.cpp index dab24911d..4a202ca13 100644 --- a/src/app/ui/search_entry.cpp +++ b/src/app/ui/search_entry.cpp @@ -40,8 +40,7 @@ bool SearchEntry::onProcessMessage(ui::Message* msg) - bounds().origin(); if (closeBounds.contains(mousePos)) { - setText(""); - onChange(); + onCloseIconPressed(); return true; } break; @@ -63,7 +62,7 @@ void SearchEntry::onPaint(ui::PaintEvent& ev) bounds.y + bounds.h/2 - icon->height()/2); if (!text().empty()) { - icon = theme->parts.iconClose()->bitmap(0); + icon = onGetCloseIcon(); ev.graphics()->drawColoredRgbaSurface( icon, theme->colors.text(), bounds.x + bounds.w - border().right() - childSpacing() - icon->width(), @@ -88,17 +87,27 @@ Rect SearchEntry::onGetEntryTextBounds() const auto theme = SkinTheme::get(this); Rect bounds = Entry::onGetEntryTextBounds(); auto icon1 = theme->parts.iconSearch()->bitmap(0); - auto icon2 = theme->parts.iconClose()->bitmap(0); + auto icon2 = onGetCloseIcon(); bounds.x += childSpacing() + icon1->width(); bounds.w -= 2*childSpacing() + icon1->width() + icon2->width(); return bounds; } +os::Surface* SearchEntry::onGetCloseIcon() const +{ + return SkinTheme::get(this)->parts.iconClose()->bitmap(0); +} + +void SearchEntry::onCloseIconPressed() +{ + setText(""); + onChange(); +} + Rect SearchEntry::getCloseIconBounds() const { - auto theme = SkinTheme::get(this); Rect bounds = clientBounds(); - auto icon = theme->parts.iconClose()->bitmap(0); + auto icon = onGetCloseIcon(); bounds.x += bounds.w - border().right() - childSpacing() - icon->width(); bounds.y += bounds.h/2 - icon->height()/2; bounds.w = icon->width(); diff --git a/src/app/ui/search_entry.h b/src/app/ui/search_entry.h index 72a1159f9..9f108d6a6 100644 --- a/src/app/ui/search_entry.h +++ b/src/app/ui/search_entry.h @@ -23,6 +23,9 @@ namespace app { void onSizeHint(ui::SizeHintEvent& ev) override; gfx::Rect onGetEntryTextBounds() const override; + virtual os::Surface* onGetCloseIcon() const; + virtual void onCloseIconPressed(); + private: gfx::Rect getCloseIconBounds() const; };