2015-10-16 05:16:16 +08:00
|
|
|
// Aseprite
|
2025-03-04 09:36:11 +08:00
|
|
|
// Copyright (C) 2020-2025 Igara Studio S.A.
|
2018-02-21 21:39:30 +08:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-10-16 05:16:16 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2015-10-16 05:16:16 +08:00
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "app/ui/font_popup.h"
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
#include "app/app.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
#include "app/file_selector.h"
|
2025-03-04 09:36:11 +08:00
|
|
|
#include "app/fonts/font_data.h"
|
|
|
|
#include "app/fonts/font_info.h"
|
|
|
|
#include "app/fonts/font_path.h"
|
2025-03-05 00:27:39 +08:00
|
|
|
#include "app/fonts/fonts.h"
|
2022-01-07 17:02:38 +08:00
|
|
|
#include "app/i18n/strings.h"
|
2017-03-16 01:24:42 +08:00
|
|
|
#include "app/match_words.h"
|
2024-10-29 20:48:23 +08:00
|
|
|
#include "app/recent_files.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
#include "app/ui/separator_in_view.h"
|
2015-10-16 05:16:16 +08:00
|
|
|
#include "app/ui/skin/skin_theme.h"
|
2020-06-10 06:56:25 +08:00
|
|
|
#include "app/util/conversion_to_surface.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
#include "app/util/render_text.h"
|
2015-10-16 05:16:16 +08:00
|
|
|
#include "base/fs.h"
|
|
|
|
#include "base/string.h"
|
|
|
|
#include "doc/image.h"
|
2015-11-04 23:17:30 +08:00
|
|
|
#include "doc/image_ref.h"
|
2018-08-09 23:58:43 +08:00
|
|
|
#include "os/surface.h"
|
|
|
|
#include "os/system.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
#include "text/text.h"
|
2015-10-16 05:16:16 +08:00
|
|
|
#include "ui/box.h"
|
|
|
|
#include "ui/button.h"
|
2021-03-20 05:57:56 +08:00
|
|
|
#include "ui/fit_bounds.h"
|
2018-06-09 02:52:10 +08:00
|
|
|
#include "ui/graphics.h"
|
|
|
|
#include "ui/listitem.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
#include "ui/message.h"
|
2018-06-09 02:52:10 +08:00
|
|
|
#include "ui/paint_event.h"
|
2015-12-04 08:50:05 +08:00
|
|
|
#include "ui/size_hint_event.h"
|
2025-04-19 06:56:56 +08:00
|
|
|
#include "ui/system.h"
|
2015-10-16 05:16:16 +08:00
|
|
|
#include "ui/theme.h"
|
|
|
|
#include "ui/view.h"
|
|
|
|
|
|
|
|
#include "font_popup.xml.h"
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
2020-04-08 23:03:32 +08:00
|
|
|
#include <shlobj.h>
|
|
|
|
#include <windows.h>
|
|
|
|
#undef max
|
2015-10-16 05:16:16 +08:00
|
|
|
#endif
|
|
|
|
|
2020-04-08 23:03:32 +08:00
|
|
|
#include <algorithm>
|
2015-11-04 23:17:30 +08:00
|
|
|
#include <map>
|
2015-10-20 03:41:14 +08:00
|
|
|
|
2015-10-16 05:16:16 +08:00
|
|
|
namespace app {
|
|
|
|
|
|
|
|
using namespace ui;
|
|
|
|
|
2025-03-25 10:04:50 +08:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
struct ThumbnailInfo {
|
|
|
|
os::SurfaceRef surface;
|
|
|
|
float baseline = 0.0f;
|
|
|
|
float descent = 0.0f;
|
|
|
|
float ascent = 0.0f;
|
|
|
|
};
|
|
|
|
|
|
|
|
static std::map<std::string, ThumbnailInfo> g_thumbnails;
|
|
|
|
|
|
|
|
} // namespace
|
2015-11-04 23:17:30 +08:00
|
|
|
|
2015-10-16 05:16:16 +08:00
|
|
|
class FontItem : public ListItem {
|
|
|
|
public:
|
2024-03-25 22:59:25 +08:00
|
|
|
struct ByName {};
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
explicit FontItem(const FontInfo& fontInfo)
|
|
|
|
: ListItem(fontInfo.humanString())
|
|
|
|
, m_fontInfo(fontInfo)
|
|
|
|
{
|
|
|
|
getCachedThumbnail();
|
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
FontItem(const std::string& name, ByName)
|
|
|
|
: ListItem(name)
|
|
|
|
, m_fontInfo(FontInfo::Type::Name,
|
|
|
|
name,
|
2024-04-16 05:52:36 +08:00
|
|
|
FontInfo::kDefaultSize,
|
2024-09-26 03:37:41 +08:00
|
|
|
text::FontStyle(),
|
|
|
|
FontInfo::Flags::Antialias)
|
|
|
|
{
|
2024-03-25 22:59:25 +08:00
|
|
|
getCachedThumbnail();
|
|
|
|
}
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
explicit FontItem(const std::string& fn)
|
2015-10-17 04:00:10 +08:00
|
|
|
: ListItem(base::get_file_title(fn))
|
2024-03-25 22:59:25 +08:00
|
|
|
, m_fontInfo(FontInfo::Type::File,
|
|
|
|
fn,
|
2024-04-16 05:52:36 +08:00
|
|
|
FontInfo::kDefaultSize,
|
2024-09-26 03:37:41 +08:00
|
|
|
text::FontStyle(),
|
|
|
|
FontInfo::Flags::Antialias)
|
|
|
|
{
|
2024-03-25 22:59:25 +08:00
|
|
|
getCachedThumbnail();
|
|
|
|
}
|
|
|
|
|
|
|
|
FontItem(const std::string& name, const text::FontStyle& style, const text::FontStyleSetRef& set)
|
|
|
|
: ListItem(name)
|
|
|
|
, m_fontInfo(FontInfo::Type::System,
|
|
|
|
name,
|
2024-09-26 03:37:41 +08:00
|
|
|
FontInfo::kDefaultSize,
|
|
|
|
style,
|
|
|
|
FontInfo::Flags::Antialias)
|
2024-04-16 05:52:36 +08:00
|
|
|
, m_set(set)
|
|
|
|
{
|
2024-03-25 22:59:25 +08:00
|
|
|
getCachedThumbnail();
|
2015-10-17 04:00:10 +08:00
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
FontInfo fontInfo() const { return m_fontInfo; }
|
2015-10-16 05:16:16 +08:00
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
obs::signal<void()> ThumbnailGenerated;
|
|
|
|
|
2015-10-16 05:16:16 +08:00
|
|
|
private:
|
2025-03-25 10:04:50 +08:00
|
|
|
void getCachedThumbnail()
|
|
|
|
{
|
|
|
|
auto it = g_thumbnails.find(m_fontInfo.thumbnailId());
|
|
|
|
if (it == g_thumbnails.end())
|
|
|
|
return;
|
|
|
|
m_thumbnail = it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
float onGetTextBaseline() const override
|
|
|
|
{
|
|
|
|
text::FontMetrics metrics;
|
|
|
|
font()->metrics(&metrics);
|
|
|
|
const float descent = std::max<float>(metrics.descent, m_thumbnail.descent);
|
|
|
|
return bounds().h - descent;
|
|
|
|
}
|
2024-03-25 22:59:25 +08:00
|
|
|
|
2015-10-16 05:16:16 +08:00
|
|
|
void onPaint(PaintEvent& ev) override
|
|
|
|
{
|
2015-10-17 05:52:52 +08:00
|
|
|
ListItem::onPaint(ev);
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
generateThumbnail();
|
2025-03-25 10:04:50 +08:00
|
|
|
if (!m_thumbnail.surface)
|
|
|
|
return;
|
2015-10-16 05:16:16 +08:00
|
|
|
|
2025-03-25 10:04:50 +08:00
|
|
|
Graphics* g = ev.graphics();
|
|
|
|
const auto* theme = app::skin::SkinTheme::get(this);
|
|
|
|
const float y = textBaseline() - m_thumbnail.baseline;
|
|
|
|
|
|
|
|
g->drawColoredRgbaSurface(m_thumbnail.surface.get(),
|
|
|
|
theme->colors.text(),
|
|
|
|
textWidth() + 4 * guiscale(),
|
|
|
|
y);
|
2015-10-16 05:16:16 +08:00
|
|
|
}
|
|
|
|
|
2015-12-04 08:50:05 +08:00
|
|
|
void onSizeHint(SizeHintEvent& ev) override
|
|
|
|
{
|
|
|
|
ListItem::onSizeHint(ev);
|
2025-03-25 10:04:50 +08:00
|
|
|
if (!m_thumbnail.surface)
|
|
|
|
return;
|
|
|
|
|
|
|
|
text::FontMetrics metrics;
|
|
|
|
font()->metrics(&metrics);
|
|
|
|
const float lineHeight = std::max<float>(metrics.descent, m_thumbnail.descent) -
|
|
|
|
std::min<float>(metrics.ascent, m_thumbnail.ascent);
|
|
|
|
|
|
|
|
gfx::Size sz = ev.sizeHint();
|
|
|
|
ev.setSizeHint(sz.w + 4 * guiscale() + m_thumbnail.surface->width(),
|
|
|
|
std::max<float>(sz.h, lineHeight));
|
2015-10-16 05:16:16 +08:00
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
void generateThumbnail()
|
|
|
|
{
|
2025-03-25 10:04:50 +08:00
|
|
|
if (m_thumbnail.surface)
|
2015-10-16 05:16:16 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
try {
|
2025-03-25 10:04:50 +08:00
|
|
|
Fonts* fonts = Fonts::instance();
|
2024-10-29 20:48:23 +08:00
|
|
|
const FontInfo fontInfoDefSize(m_fontInfo,
|
|
|
|
FontInfo::kDefaultSize,
|
|
|
|
text::FontStyle(),
|
2025-04-12 04:15:47 +08:00
|
|
|
FontInfo::Flags::Antialias,
|
|
|
|
text::FontHinting::Normal);
|
2025-03-25 10:04:50 +08:00
|
|
|
const text::FontRef font = fonts->fontFromInfo(fontInfoDefSize);
|
|
|
|
if (!font)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (font->type() != text::FontType::SpriteSheet)
|
|
|
|
font->setSize(12.0f);
|
2024-10-29 20:48:23 +08:00
|
|
|
|
2025-03-25 10:04:50 +08:00
|
|
|
text::TextBlobRef blob = text::TextBlob::MakeWithShaper(fonts->fontMgr(), font, text());
|
|
|
|
if (!blob)
|
|
|
|
return;
|
|
|
|
|
|
|
|
doc::ImageRef image = render_text_blob(blob, gfx::rgba(0, 0, 0));
|
2024-03-25 22:59:25 +08:00
|
|
|
if (!image)
|
|
|
|
return;
|
|
|
|
|
2025-03-25 10:04:50 +08:00
|
|
|
// This font metrics
|
|
|
|
text::FontMetrics metrics;
|
|
|
|
font->metrics(&metrics);
|
|
|
|
m_thumbnail.baseline = blob->baseline();
|
|
|
|
m_thumbnail.descent = metrics.descent;
|
|
|
|
m_thumbnail.ascent = metrics.ascent;
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
// Convert the doc::Image into a os::Surface
|
2025-03-25 10:04:50 +08:00
|
|
|
m_thumbnail.surface = os::System::instance()->makeRgbaSurface(image->width(),
|
|
|
|
image->height());
|
2024-03-25 22:59:25 +08:00
|
|
|
convert_image_to_surface(image.get(),
|
|
|
|
nullptr,
|
2025-03-25 10:04:50 +08:00
|
|
|
m_thumbnail.surface.get(),
|
2024-03-25 22:59:25 +08:00
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
image->width(),
|
|
|
|
image->height());
|
2015-11-04 23:17:30 +08:00
|
|
|
|
|
|
|
// Save the thumbnail for future FontPopups
|
2024-03-25 22:59:25 +08:00
|
|
|
g_thumbnails[m_fontInfo.thumbnailId()] = m_thumbnail;
|
|
|
|
|
|
|
|
ThumbnailGenerated();
|
2015-10-16 05:16:16 +08:00
|
|
|
}
|
2015-10-30 03:18:51 +08:00
|
|
|
catch (const std::exception&) {
|
2015-10-17 05:52:52 +08:00
|
|
|
// Ignore errors
|
2015-10-16 05:16:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
void onSelect(bool selected) override
|
|
|
|
{
|
2025-03-25 10:04:50 +08:00
|
|
|
if (!selected || m_thumbnail.surface)
|
2024-03-25 22:59:25 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
ListBox* listbox = static_cast<ListBox*>(parent());
|
|
|
|
if (!listbox)
|
|
|
|
return;
|
|
|
|
|
|
|
|
generateThumbnail();
|
|
|
|
listbox->makeChildVisible(this);
|
|
|
|
}
|
|
|
|
|
2015-10-16 05:16:16 +08:00
|
|
|
private:
|
2025-03-25 10:04:50 +08:00
|
|
|
ThumbnailInfo m_thumbnail;
|
2024-03-25 22:59:25 +08:00
|
|
|
FontInfo m_fontInfo;
|
2024-04-16 05:52:36 +08:00
|
|
|
text::FontStyleSetRef m_set;
|
2015-10-16 05:16:16 +08:00
|
|
|
};
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
bool FontPopup::FontListBox::onProcessMessage(ui::Message* msg)
|
|
|
|
{
|
|
|
|
const bool result = ui::ListBox::onProcessMessage(msg);
|
|
|
|
|
|
|
|
// When we release the mouse button we close the popup, i.e. like
|
|
|
|
// selecting an item from a combo box.
|
|
|
|
if (msg->type() == ui::kMouseUpMessage) {
|
|
|
|
if (auto* win = this->window()) {
|
|
|
|
win->closeWindow(nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-10-23 01:32:33 +08:00
|
|
|
bool FontPopup::FontListBox::onAcceptKeyInput()
|
|
|
|
{
|
|
|
|
// Always accept a kKeyDownMessage so we can get Up/Down keyboard
|
|
|
|
// messages from the FontEntry field (when the user is editing the
|
|
|
|
// font name).
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
FontPopup::FontPopup(const FontInfo& fontInfo)
|
|
|
|
: PopupWindow(std::string(),
|
2015-12-06 02:56:32 +08:00
|
|
|
ClickBehavior::CloseOnClickInOtherWindow,
|
|
|
|
EnterBehavior::DoNothingOnEnter)
|
2015-10-16 05:16:16 +08:00
|
|
|
, m_popup(new gen::FontPopup())
|
2024-03-25 22:59:25 +08:00
|
|
|
, m_timer(100)
|
2015-10-16 05:16:16 +08:00
|
|
|
{
|
|
|
|
setAutoRemap(false);
|
|
|
|
setBorder(gfx::Border(4 * guiscale()));
|
|
|
|
|
|
|
|
addChild(m_popup);
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
m_timer.Tick.connect([this] { onTickRelayout(); });
|
2020-07-04 08:51:46 +08:00
|
|
|
m_popup->loadFont()->Click.connect([this] { onLoadFont(); });
|
2015-10-20 02:32:44 +08:00
|
|
|
m_listBox.setFocusMagnet(true);
|
2024-03-25 22:59:25 +08:00
|
|
|
m_listBox.Change.connect([this] {
|
|
|
|
if (m_listBox.hasFocus() || m_listBox.hasCapture()) {
|
|
|
|
onFontChange();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
m_listBox.DoubleClickItem.connect([this] { onFontChange(); });
|
2015-10-16 05:16:16 +08:00
|
|
|
|
|
|
|
m_popup->view()->attachToView(&m_listBox);
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
// Pinned fonts
|
|
|
|
m_pinnedSeparator = new SeparatorInView(Strings::font_popup_pinned_fonts());
|
|
|
|
m_listBox.addChild(m_pinnedSeparator);
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
// Default fonts
|
2025-03-05 00:27:39 +08:00
|
|
|
Fonts* fonts = Fonts::instance();
|
2024-10-29 20:48:23 +08:00
|
|
|
bool first = true;
|
2025-03-05 00:27:39 +08:00
|
|
|
for (const auto& kv : fonts->definedFonts()) {
|
2024-03-25 22:59:25 +08:00
|
|
|
if (!kv.second->filename().empty()) {
|
2024-10-29 20:48:23 +08:00
|
|
|
if (first) {
|
2024-03-25 22:59:25 +08:00
|
|
|
m_listBox.addChild(new SeparatorInView(Strings::font_popup_theme_fonts()));
|
2024-10-29 20:48:23 +08:00
|
|
|
first = false;
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
m_listBox.addChild(new FontItem(kv.first, FontItem::ByName()));
|
2015-10-20 03:41:14 +08:00
|
|
|
}
|
2015-10-17 04:00:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create one FontItem for each font
|
2025-04-19 06:56:56 +08:00
|
|
|
m_systemFontsSeparator = new SeparatorInView(Strings::font_popup_system_fonts());
|
|
|
|
m_listBox.addChild(m_systemFontsSeparator);
|
2024-03-25 22:59:25 +08:00
|
|
|
|
2025-04-19 06:56:56 +08:00
|
|
|
m_listFontsTask.run([this](base::task_token& token) { listSystemFonts(token); });
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
FontPopup::~FontPopup()
|
|
|
|
{
|
|
|
|
m_timer.stop();
|
2025-04-19 06:56:56 +08:00
|
|
|
m_listFontsTask.cancel();
|
|
|
|
m_listFontsTask.wait();
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FontPopup::setSearchText(const std::string& searchText)
|
|
|
|
{
|
|
|
|
FontItem* firstItem = nullptr;
|
|
|
|
|
|
|
|
const MatchWords match(searchText);
|
|
|
|
for (auto* child : m_listBox.children()) {
|
|
|
|
auto* childItem = dynamic_cast<FontItem*>(child);
|
|
|
|
if (!childItem)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const bool visible = match(childItem->text());
|
|
|
|
if (visible && !firstItem)
|
|
|
|
firstItem = childItem;
|
|
|
|
childItem->setVisible(visible);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_listBox.selectChild(firstItem);
|
|
|
|
layout();
|
2015-10-16 05:16:16 +08:00
|
|
|
}
|
|
|
|
|
2021-03-20 05:57:56 +08:00
|
|
|
void FontPopup::showPopup(Display* display, const gfx::Rect& buttonBounds)
|
2015-10-16 05:16:16 +08:00
|
|
|
{
|
2024-03-25 22:59:25 +08:00
|
|
|
m_listBox.selectChild(nullptr);
|
2015-10-16 05:16:16 +08:00
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
recreatePinnedItems();
|
|
|
|
|
2021-03-20 05:57:56 +08:00
|
|
|
ui::fit_bounds(
|
|
|
|
display,
|
|
|
|
this,
|
2024-06-12 09:31:13 +08:00
|
|
|
gfx::Rect(buttonBounds.x, buttonBounds.y2(), buttonBounds.w * 2, buttonBounds.h),
|
2021-03-20 05:57:56 +08:00
|
|
|
[](const gfx::Rect& workarea,
|
|
|
|
gfx::Rect& bounds,
|
|
|
|
std::function<gfx::Rect(Widget*)> getWidgetBounds) { bounds.h = workarea.y2() - bounds.y; });
|
2015-10-16 05:16:16 +08:00
|
|
|
|
|
|
|
openWindow();
|
|
|
|
}
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
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());
|
2024-11-22 05:11:01 +08:00
|
|
|
layout();
|
2024-10-29 20:48:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
FontInfo FontPopup::selectedFont()
|
2017-03-16 01:24:42 +08:00
|
|
|
{
|
2024-03-25 22:59:25 +08:00
|
|
|
const FontItem* child = dynamic_cast<FontItem*>(m_listBox.getSelectedChild());
|
|
|
|
if (child)
|
|
|
|
return child->fontInfo();
|
|
|
|
return FontInfo();
|
2017-03-16 01:24:42 +08:00
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
void FontPopup::onFontChange()
|
2015-10-16 05:16:16 +08:00
|
|
|
{
|
2024-03-25 22:59:25 +08:00
|
|
|
const FontInfo fontInfo = selectedFont();
|
|
|
|
if (fontInfo.isValid())
|
2024-10-23 01:32:33 +08:00
|
|
|
FontChange(fontInfo);
|
2015-10-16 05:16:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void FontPopup::onLoadFont()
|
|
|
|
{
|
2024-03-25 22:59:25 +08:00
|
|
|
std::string currentFile;
|
|
|
|
const FontInfo fontInfo = selectedFont();
|
|
|
|
if (fontInfo.isValid() && fontInfo.type() == FontInfo::Type::File)
|
|
|
|
currentFile = fontInfo.name();
|
|
|
|
|
|
|
|
base::paths exts = { "ttf", "ttc", "otf", "dfont" };
|
|
|
|
base::paths face;
|
|
|
|
if (!show_file_selector(Strings::font_popup_select_truetype_fonts(),
|
|
|
|
currentFile,
|
|
|
|
exts,
|
|
|
|
FileSelectorType::Open,
|
|
|
|
face))
|
2015-10-16 05:16:16 +08:00
|
|
|
return;
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
ASSERT(!face.empty());
|
2024-10-23 01:32:33 +08:00
|
|
|
FontChange(FontInfo(FontInfo::Type::File, face.front()));
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
2015-10-16 05:16:16 +08:00
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
void FontPopup::onThumbnailGenerated()
|
|
|
|
{
|
|
|
|
m_timer.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FontPopup::onTickRelayout()
|
|
|
|
{
|
|
|
|
m_popup->view()->updateView();
|
2025-04-19 06:56:56 +08:00
|
|
|
if (!m_listFontsTask.running())
|
|
|
|
m_timer.stop();
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool FontPopup::onProcessMessage(ui::Message* msg)
|
|
|
|
{
|
|
|
|
switch (msg->type()) {
|
|
|
|
case kKeyDownMessage: {
|
|
|
|
const auto* keymsg = static_cast<const KeyMessage*>(msg);
|
|
|
|
|
|
|
|
// Pressing Esc or Enter will just close the popup.
|
|
|
|
if (keymsg->scancode() == kKeyEsc || keymsg->scancode() == kKeyEnter ||
|
|
|
|
keymsg->scancode() == kKeyEnterPad) {
|
|
|
|
EscKey();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ui::PopupWindow::onProcessMessage(msg);
|
2015-10-16 05:16:16 +08:00
|
|
|
}
|
|
|
|
|
2025-04-19 06:56:56 +08:00
|
|
|
void FontPopup::listSystemFonts(base::task_token& token)
|
|
|
|
{
|
|
|
|
Fonts* fonts = Fonts::instance();
|
|
|
|
bool empty = true;
|
|
|
|
|
|
|
|
// Get system fonts from laf-text module
|
|
|
|
const text::FontMgrRef fontMgr = fonts->fontMgr();
|
|
|
|
const int n = fontMgr->countFamilies();
|
|
|
|
if (n > 0) {
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
std::string name = fontMgr->familyName(i);
|
|
|
|
text::FontStyleSetRef set = fontMgr->familyStyleSet(i);
|
|
|
|
if (set && set->count() > 0) {
|
|
|
|
// Match the typeface with the default FontStyle (Normal
|
|
|
|
// weight, Upright slant, etc.)
|
|
|
|
auto typeface = set->matchStyle(text::FontStyle());
|
|
|
|
if (typeface) {
|
|
|
|
ui::execute_from_ui_thread([=, &token] {
|
|
|
|
if (token.canceled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto* item = new FontItem(name, typeface->fontStyle(), set);
|
|
|
|
item->ThumbnailGenerated.connect([this] { onThumbnailGenerated(); });
|
|
|
|
|
|
|
|
int j = m_listBox.getChildIndex(m_systemFontsSeparator) + 1;
|
|
|
|
for (; j < m_listBox.getItemsCount(); ++j) {
|
|
|
|
if (name < m_listBox.at(j)->text())
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
m_listBox.insertChild(j, item);
|
|
|
|
layout();
|
|
|
|
});
|
|
|
|
empty = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (token.canceled())
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Get fonts listing .ttf files TODO we should be able to remove
|
|
|
|
// this code in the future (probably after DirectWrite API is always
|
|
|
|
// available).
|
|
|
|
else {
|
|
|
|
base::paths fontDirs;
|
|
|
|
get_font_dirs(fontDirs);
|
|
|
|
|
|
|
|
// Create a list of fullpaths to every font found in all font
|
|
|
|
// directories (fontDirs)
|
|
|
|
base::paths files;
|
|
|
|
for (const auto& fontDir : fontDirs) {
|
|
|
|
for (const auto& file : base::list_files(fontDir, base::ItemType::Files)) {
|
|
|
|
files.push_back(base::join_path(fontDir, file));
|
|
|
|
if (token.canceled())
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort all files by "file title"
|
|
|
|
std::sort(files.begin(), files.end(), [](const std::string& a, const std::string& b) {
|
|
|
|
return base::utf8_icmp(base::get_file_title(a), base::get_file_title(b)) < 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
for (auto& file : files) {
|
|
|
|
std::string ext = base::string_to_lower(base::get_file_extension(file));
|
|
|
|
if (ext == "ttf" || ext == "ttc" || ext == "otf" || ext == "dfont") {
|
|
|
|
ui::execute_from_ui_thread([this, file, &token] {
|
|
|
|
if (token.canceled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_listBox.addChild(new FontItem(file));
|
|
|
|
});
|
|
|
|
empty = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
done:;
|
|
|
|
if (token.canceled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (empty) {
|
|
|
|
ui::execute_from_ui_thread([this, &token] {
|
|
|
|
if (token.canceled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_listBox.addChild(new ListItem(Strings::font_popup_empty_fonts()));
|
|
|
|
layout();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
ui::execute_from_ui_thread([this, &token] {
|
|
|
|
if (token.canceled())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Stop the view relayout
|
|
|
|
onTickRelayout();
|
|
|
|
m_timer.stop();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-10-16 05:16:16 +08:00
|
|
|
} // namespace app
|