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.
This commit is contained in:
David Capello 2024-10-29 09:48:23 -03:00
parent 77dd92c679
commit 6093282ac3
11 changed files with 179 additions and 32 deletions

View File

@ -777,6 +777,7 @@ preview = &Preview
tiled = &Tiled tiled = &Tiled
[font_popup] [font_popup]
pinned_fonts = Pinned Fonts
theme_fonts = Theme Fonts theme_fonts = Theme Fonts
system_fonts = System Fonts system_fonts = System Fonts
load = Load External Font load = Load External Font

View File

@ -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 app
namespace base { namespace base {

View File

@ -73,6 +73,8 @@ namespace app {
static FontInfo getFromPreferences(); static FontInfo getFromPreferences();
void updatePreferences(); void updatePreferences();
std::string humanString() const;
bool operator==(const FontInfo& other) const { bool operator==(const FontInfo& other) const {
return (m_type == other.m_type && return (m_type == other.m_type &&
m_name == other.m_name && m_name == other.m_name &&

View File

@ -21,13 +21,11 @@
namespace { namespace {
enum { kPinnedFiles, kRecentFiles,
kPinnedPaths, kRecentPaths };
const char* kSectionName[] = { "PinnedFiles", const char* kSectionName[] = { "PinnedFiles",
"RecentFiles", "RecentFiles",
"PinnedPaths", "PinnedPaths",
"RecentPaths" }; "RecentPaths",
"PinnedFonts" };
// Special key used in recent sections (files/paths) to indicate that // Special key used in recent sections (files/paths) to indicate that
// the section was already converted at least one time. // the section was already converted at least one time.
@ -183,7 +181,7 @@ void RecentFiles::load()
!get_config_bool(section, kConversionKey, false)); !get_config_bool(section, kConversionKey, false));
const bool processOldPaths = const bool processOldPaths =
(i == kRecentPaths && (i == kRecentFolders &&
get_config_string(section, "Path00", nullptr) && get_config_string(section, "Path00", nullptr) &&
!get_config_bool(section, kConversionKey, false)); !get_config_bool(section, kConversionKey, false));
@ -197,11 +195,14 @@ void RecentFiles::load()
const char* fn = get_config_string(section, key.c_str(), nullptr); const char* fn = get_config_string(section, key.c_str(), nullptr);
if (fn && *fn && if (fn && *fn &&
((i < 2 && base::is_file(fn)) || (((i == kPinnedFiles || i == kRecentFiles) && base::is_file(fn)) ||
(i >= 2 && base::is_directory(fn)))) { ((i == kPinnedFolders || i == kRecentFolders) && base::is_directory(fn)))) {
std::string normalFn = normalizePath(fn); std::string normalFn = normalizePath(fn);
m_paths[i].push_back(normalFn); 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 && if ((i == kRecentFiles &&
(std::strncmp(key.c_str(), "Filename", 8) == 0 || key == kConversionKey)) (std::strncmp(key.c_str(), "Filename", 8) == 0 || key == kConversionKey))
|| ||
(i == kRecentPaths && (i == kRecentFolders &&
(std::strncmp(key.c_str(), "Path", 4) == 0 || key == kConversionKey))) { (std::strncmp(key.c_str(), "Path", 4) == 0 || key == kConversionKey))) {
// Ignore old entries if we are going to read the new ones // Ignore old entries if we are going to read the new ones
continue; continue;
@ -229,7 +230,7 @@ void RecentFiles::save()
m_paths[i][j].c_str()); m_paths[i][j].c_str());
} }
// Special entry that indicates that we've already converted // 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)) { !get_config_bool(section, kConversionKey, false)) {
set_config_bool(section, kConversionKey, true); set_config_bool(section, kConversionKey, true);
} }

View File

@ -17,16 +17,22 @@
namespace app { namespace app {
class RecentFiles { class RecentFiles {
enum { kPinnedFiles, enum {
kRecentFiles, kPinnedFiles,
kPinnedFolders, kRecentFiles,
kRecentFolders, kPinnedFolders,
kCollections }; kRecentFolders,
kPinnedFonts,
kCollections
};
public: public:
const base::paths& pinnedFiles() const { return m_paths[kPinnedFiles]; } const base::paths& pinnedFiles() const { return m_paths[kPinnedFiles]; }
const base::paths& recentFiles() const { return m_paths[kRecentFiles]; } const base::paths& recentFiles() const { return m_paths[kRecentFiles]; }
const base::paths& pinnedFolders() const { return m_paths[kPinnedFolders]; } const base::paths& pinnedFolders() const { return m_paths[kPinnedFolders]; }
const base::paths& recentFolders() const { return m_paths[kRecentFolders]; } 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(const int limit);
~RecentFiles(); ~RecentFiles();

View File

@ -10,8 +10,11 @@
#include "app/ui/font_entry.h" #include "app/ui/font_entry.h"
#include "app/app.h"
#include "app/console.h" #include "app/console.h"
#include "app/recent_files.h"
#include "app/ui/font_popup.h" #include "app/ui/font_popup.h"
#include "app/ui/skin/skin_theme.h"
#include "base/scoped_value.h" #include "base/scoped_value.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "ui/display.h" #include "ui/display.h"
@ -19,6 +22,7 @@
#include "ui/message.h" #include "ui/message.h"
#include "ui/scale.h" #include "ui/scale.h"
#include <algorithm>
#include <cstdlib> #include <cstdlib>
namespace app { namespace app {
@ -84,6 +88,9 @@ bool FontEntry::FontFace::onProcessMessage(Message* msg)
} }
} }
if (!m_popup->isVisible()) { if (!m_popup->isVisible()) {
// Reset the search filter before opening the popup window.
m_popup->setSearchText(std::string());
m_popup->showPopup(display(), bounds()); m_popup->showPopup(display(), bounds());
requestFocus(); requestFocus();
} }
@ -171,6 +178,47 @@ void FontEntry::FontFace::onChange()
FontChange(m_popup->selectedFont(), From::Face); 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<std::string>(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<std::string>(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() FontEntry::FontSize::FontSize()
{ {
setEditable(true); setEditable(true);
@ -215,13 +263,16 @@ FontEntry::FontEntry()
m_face.setMinSize(gfx::Size(128*guiscale(), 0)); m_face.setMinSize(gfx::Size(128*guiscale(), 0));
m_face.FontChange.connect([this](const FontInfo& newTypeName, m_face.FontChange.connect([this](const FontInfo& newTypeName, const From from) {
const From from) { if (newTypeName.size() > 0)
setInfo(FontInfo(newTypeName, setInfo(newTypeName, from);
m_info.size(), else {
m_info.style(), setInfo(FontInfo(newTypeName,
m_info.flags()), m_info.size(),
from); m_info.style(),
m_info.flags()),
from);
}
invalidate(); invalidate();
}); });

View File

@ -48,6 +48,8 @@ namespace app {
protected: protected:
bool onProcessMessage(ui::Message* msg) override; bool onProcessMessage(ui::Message* msg) override;
void onChange() override; void onChange() override;
os::Surface* onGetCloseIcon() const override;
void onCloseIconPressed() override;
private: private:
FontEntry* fontEntry() const { return static_cast<FontEntry*>(parent()); } FontEntry* fontEntry() const { return static_cast<FontEntry*>(parent()); }

View File

@ -11,11 +11,13 @@
#include "app/ui/font_popup.h" #include "app/ui/font_popup.h"
#include "app/app.h"
#include "app/file_selector.h" #include "app/file_selector.h"
#include "app/font_info.h" #include "app/font_info.h"
#include "app/font_path.h" #include "app/font_path.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/match_words.h" #include "app/match_words.h"
#include "app/recent_files.h"
#include "app/ui/separator_in_view.h" #include "app/ui/separator_in_view.h"
#include "app/ui/skin/font_data.h" #include "app/ui/skin/font_data.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
@ -60,6 +62,12 @@ class FontItem : public ListItem {
public: public:
struct ByName { }; struct ByName { };
explicit FontItem(const FontInfo& fontInfo)
: ListItem(fontInfo.humanString())
, m_fontInfo(fontInfo) {
getCachedThumbnail();
}
FontItem(const std::string& name, ByName) FontItem(const std::string& name, ByName)
: ListItem(name) : ListItem(name)
, m_fontInfo(FontInfo::Type::Name, name, , m_fontInfo(FontInfo::Type::Name, name,
@ -69,7 +77,7 @@ public:
getCachedThumbnail(); getCachedThumbnail();
} }
FontItem(const std::string& fn) explicit FontItem(const std::string& fn)
: ListItem(base::get_file_title(fn)) : ListItem(base::get_file_title(fn))
, m_fontInfo(FontInfo::Type::File, fn, , m_fontInfo(FontInfo::Type::File, fn,
FontInfo::kDefaultSize, FontInfo::kDefaultSize,
@ -128,9 +136,14 @@ private:
const auto* theme = app::skin::SkinTheme::get(this); const auto* theme = app::skin::SkinTheme::get(this);
try { try {
const FontInfo fontInfoDefSize(m_fontInfo,
FontInfo::kDefaultSize,
text::FontStyle(),
FontInfo::Flags::Antialias);
const gfx::Color color = theme->colors.text(); const gfx::Color color = theme->colors.text();
doc::ImageRef image = doc::ImageRef image =
render_text(m_fontInfo, text(), color); render_text(fontInfoDefSize, text(), color);
if (!image) if (!image)
return; return;
@ -220,13 +233,17 @@ FontPopup::FontPopup(const FontInfo& fontInfo)
m_popup->view()->attachToView(&m_listBox); m_popup->view()->attachToView(&m_listBox);
// Pinned fonts
m_pinnedSeparator = new SeparatorInView(Strings::font_popup_pinned_fonts());
m_listBox.addChild(m_pinnedSeparator);
// Default fonts // Default fonts
bool firstThemeFont = true; bool first = true;
for (auto kv : skin::SkinTheme::get(this)->getWellKnownFonts()) { for (auto kv : skin::SkinTheme::get(this)->getWellKnownFonts()) {
if (!kv.second->filename().empty()) { if (!kv.second->filename().empty()) {
if (firstThemeFont) { if (first) {
m_listBox.addChild(new SeparatorInView(Strings::font_popup_theme_fonts())); m_listBox.addChild(new SeparatorInView(Strings::font_popup_theme_fonts()));
firstThemeFont = false; first = false;
} }
m_listBox.addChild(new FontItem(kv.first, FontItem::ByName())); m_listBox.addChild(new FontItem(kv.first, FontItem::ByName()));
} }
@ -332,6 +349,8 @@ void FontPopup::showPopup(Display* display,
{ {
m_listBox.selectChild(nullptr); m_listBox.selectChild(nullptr);
recreatePinnedItems();
ui::fit_bounds(display, this, ui::fit_bounds(display, this,
gfx::Rect(buttonBounds.x, buttonBounds.y2(), gfx::Rect(buttonBounds.x, buttonBounds.y2(),
buttonBounds.w*2, buttonBounds.h), buttonBounds.w*2, buttonBounds.h),
@ -344,6 +363,28 @@ void FontPopup::showPopup(Display* display,
openWindow(); 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<FontInfo>(fontInfoStr)));
}
m_pinnedSeparator->setVisible(!pinnedFonts.empty());
}
}
FontInfo FontPopup::selectedFont() FontInfo FontPopup::selectedFont()
{ {
const FontItem* child = dynamic_cast<FontItem*>(m_listBox.getSelectedChild()); const FontItem* child = dynamic_cast<FontItem*>(m_listBox.getSelectedChild());

View File

@ -42,6 +42,8 @@ namespace app {
void showPopup(ui::Display* display, void showPopup(ui::Display* display,
const gfx::Rect& buttonBounds); const gfx::Rect& buttonBounds);
void recreatePinnedItems();
FontListBox* getListBox() { return &m_listBox; } FontListBox* getListBox() { return &m_listBox; }
FontInfo selectedFont(); FontInfo selectedFont();
@ -59,6 +61,7 @@ namespace app {
gen::FontPopup* m_popup; gen::FontPopup* m_popup;
FontListBox m_listBox; FontListBox m_listBox;
ui::Timer m_timer; ui::Timer m_timer;
ui::Widget* m_pinnedSeparator = nullptr;
}; };
} // namespace app } // namespace app

View File

@ -40,8 +40,7 @@ bool SearchEntry::onProcessMessage(ui::Message* msg)
- bounds().origin(); - bounds().origin();
if (closeBounds.contains(mousePos)) { if (closeBounds.contains(mousePos)) {
setText(""); onCloseIconPressed();
onChange();
return true; return true;
} }
break; break;
@ -63,7 +62,7 @@ void SearchEntry::onPaint(ui::PaintEvent& ev)
bounds.y + bounds.h/2 - icon->height()/2); bounds.y + bounds.h/2 - icon->height()/2);
if (!text().empty()) { if (!text().empty()) {
icon = theme->parts.iconClose()->bitmap(0); icon = onGetCloseIcon();
ev.graphics()->drawColoredRgbaSurface( ev.graphics()->drawColoredRgbaSurface(
icon, theme->colors.text(), icon, theme->colors.text(),
bounds.x + bounds.w - border().right() - childSpacing() - icon->width(), bounds.x + bounds.w - border().right() - childSpacing() - icon->width(),
@ -88,17 +87,27 @@ Rect SearchEntry::onGetEntryTextBounds() const
auto theme = SkinTheme::get(this); auto theme = SkinTheme::get(this);
Rect bounds = Entry::onGetEntryTextBounds(); Rect bounds = Entry::onGetEntryTextBounds();
auto icon1 = theme->parts.iconSearch()->bitmap(0); auto icon1 = theme->parts.iconSearch()->bitmap(0);
auto icon2 = theme->parts.iconClose()->bitmap(0); auto icon2 = onGetCloseIcon();
bounds.x += childSpacing() + icon1->width(); bounds.x += childSpacing() + icon1->width();
bounds.w -= 2*childSpacing() + icon1->width() + icon2->width(); bounds.w -= 2*childSpacing() + icon1->width() + icon2->width();
return bounds; return bounds;
} }
os::Surface* SearchEntry::onGetCloseIcon() const
{
return SkinTheme::get(this)->parts.iconClose()->bitmap(0);
}
void SearchEntry::onCloseIconPressed()
{
setText("");
onChange();
}
Rect SearchEntry::getCloseIconBounds() const Rect SearchEntry::getCloseIconBounds() const
{ {
auto theme = SkinTheme::get(this);
Rect bounds = clientBounds(); Rect bounds = clientBounds();
auto icon = theme->parts.iconClose()->bitmap(0); auto icon = onGetCloseIcon();
bounds.x += bounds.w - border().right() - childSpacing() - icon->width(); bounds.x += bounds.w - border().right() - childSpacing() - icon->width();
bounds.y += bounds.h/2 - icon->height()/2; bounds.y += bounds.h/2 - icon->height()/2;
bounds.w = icon->width(); bounds.w = icon->width();

View File

@ -23,6 +23,9 @@ namespace app {
void onSizeHint(ui::SizeHintEvent& ev) override; void onSizeHint(ui::SizeHintEvent& ev) override;
gfx::Rect onGetEntryTextBounds() const override; gfx::Rect onGetEntryTextBounds() const override;
virtual os::Surface* onGetCloseIcon() const;
virtual void onCloseIconPressed();
private: private:
gfx::Rect getCloseIconBounds() const; gfx::Rect getCloseIconBounds() const;
}; };