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
[font_popup]
pinned_fonts = Pinned Fonts
theme_fonts = Theme Fonts
system_fonts = System Fonts
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 base {

View File

@ -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 &&

View File

@ -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);
}

View File

@ -17,16 +17,22 @@
namespace app {
class RecentFiles {
enum { kPinnedFiles,
enum {
kPinnedFiles,
kRecentFiles,
kPinnedFolders,
kRecentFolders,
kCollections };
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();

View File

@ -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 <algorithm>
#include <cstdlib>
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<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()
{
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) {
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();
});

View File

@ -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<FontEntry*>(parent()); }

View File

@ -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<FontInfo>(fontInfoStr)));
}
m_pinnedSeparator->setVisible(!pinnedFonts.empty());
}
}
FontInfo FontPopup::selectedFont()
{
const FontItem* child = dynamic_cast<FontItem*>(m_listBox.getSelectedChild());

View File

@ -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

View File

@ -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();

View File

@ -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;
};