New font selector (fix #4363)

This new font selector list installed fonts with its proper name. It
still needs some extra work to select font set styles (regular, bold,
italic, etc.)
This commit is contained in:
David Capello 2024-03-25 11:59:25 -03:00
parent 1d9f14665f
commit cf514f0c53
26 changed files with 1064 additions and 363 deletions

View File

@ -51,7 +51,7 @@ To compile Aseprite you will need:
* The latest version of [CMake](https://cmake.org) (3.16 or greater)
* [Ninja](https://ninja-build.org) build system
* And a compiled version of the `aseprite-m102` branch of
* And a compiled version of the `aseprite-m124` branch of
the [Skia library](https://github.com/aseprite/skia#readme).
There are [pre-built packages available](https://github.com/aseprite/skia/releases).
You can get some extra information in

View File

@ -349,6 +349,7 @@
<option id="font_face" type="std::string" />
<option id="font_size" type="int" default="12" />
<option id="antialias" type="bool" default="false" />
<option id="font_info" type="std::string" />
</section>
<section id="symmetry_mode">
<option id="enabled" type="bool" default="false" />

View File

@ -765,8 +765,10 @@ preview = &Preview
tiled = &Tiled
[font_popup]
title = Fonts
load = Load
theme_fonts = Theme Fonts
system_fonts = System Fonts
load = Load External Font
select_truetype_fonts = Select a Font File
empty_fonts = No system fonts were found
[frame_combo]

View File

@ -1,12 +1,12 @@
<!-- Aseprite -->
<!-- Copyright (C) 2024 by Igara Studio S.A. -->
<!-- Copyright (C) 2015-2017 by David Capello -->
<gui>
<vbox id="font_popup">
<search id="search" magnet="true" />
<view id="view" expansive="true" />
<hbox>
<boxfiller />
<button id="load_font" text="@.load" magnet="true" minwidth="80" />
<button id="load_font" text="@.load" minwidth="80" />
</hbox>
</vbox>
</gui>

View File

@ -1,24 +1,19 @@
<!-- Aseprite -->
<!-- Copyright (C) 2024 by Igara Studio S.A. -->
<!-- Copyright (C) 2015-2018 by David Capello -->
<gui>
<window id="paste_text" text="@.title">
<grid columns="2">
<label text="@.text" />
<entry expansive="true" maxsize="256" id="user_text" magnet="true" cell_align="horizontal" />
<label text="@.font_size" />
<expr id="font_size" text="32" cell_align="horizontal" />
<entry expansive="true" maxsize="256" minwidth="256" id="user_text" magnet="true" cell_align="horizontal" />
<label text="@.font" />
<dropdownbutton minwidth="60" id="font_face" text="@.select_font" cell_align="horizontal" />
<font id="font_face" cell_align="horizontal" />
<label text="@.color" />
<colorpicker id="font_color" cell_align="horizontal" />
<hbox />
<check id="antialias"
text="@.antialias" cell_align="horizontal"
tooltip="@.antialias_tooltip" />
<hbox>
<colorpicker id="font_color" />
</hbox>
<separator horizontal="true" cell_hspan="2" />

View File

@ -413,6 +413,7 @@ if(ENABLE_UI)
ui/file_list_view.cpp
ui/file_selector.cpp
ui/filename_field.cpp
ui/font_entry.cpp
ui/font_popup.cpp
ui/hex_color_entry.cpp
ui/home_view.cpp
@ -661,6 +662,7 @@ target_sources(app-lib PRIVATE
file_system.cpp
filename_formatter.cpp
flatten.cpp
font_info.cpp
font_path.cpp
gui_xml.cpp
i18n/strings.cpp
@ -710,7 +712,6 @@ target_sources(app-lib PRIVATE
util/conversion_to_surface.cpp
util/expand_cel_canvas.cpp
util/filetoks.cpp
util/freetype_utils.cpp
util/layer_boundaries.cpp
util/layer_utils.cpp
util/msk_file.cpp
@ -720,6 +721,7 @@ target_sources(app-lib PRIVATE
util/pixel_ratio.cpp
util/range_utils.cpp
util/readable_time.cpp
util/render_text.cpp
util/resize_image.cpp
util/shader_helpers.cpp
util/tile_flags_utils.cpp
@ -744,7 +746,6 @@ target_link_libraries(app-lib
tga-lib
laf-gfx
render-lib
laf-ft
laf-os
laf-text
ui-lib

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -13,13 +13,12 @@
#include "app/commands/command.h"
#include "app/console.h"
#include "app/context.h"
#include "app/file_selector.h"
#include "app/pref/preferences.h"
#include "app/ui/drop_down_button.h"
#include "app/ui/editor/editor.h"
#include "app/ui/font_popup.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/timeline/timeline.h"
#include "app/util/freetype_utils.h"
#include "app/util/render_text.h"
#include "base/fs.h"
#include "base/string.h"
#include "doc/image.h"
@ -57,84 +56,16 @@ bool PasteTextCommand::onEnabled(Context* ctx)
class PasteTextWindow : public app::gen::PasteText {
public:
PasteTextWindow(const std::string& face, int size,
bool antialias,
const app::Color& color)
: m_face(face) {
ok()->setEnabled(!m_face.empty());
if (!m_face.empty())
updateFontFaceButton();
fontSize()->setTextf("%d", size);
fontFace()->Click.connect([this]{ onSelectFontFile(); });
fontFace()->DropDownClick.connect([this]{ onSelectSystemFont(); });
PasteTextWindow(const FontInfo& fontInfo,
const app::Color& color) {
fontFace()->setInfo(fontInfo);
fontColor()->setColor(color);
this->antialias()->setSelected(antialias);
}
std::string faceValue() const {
return m_face;
FontInfo fontInfo() const {
return fontFace()->info();
}
int sizeValue() const {
int size = fontSize()->textInt();
size = std::clamp(size, 1, 5000);
return size;
}
private:
void updateFontFaceButton() {
fontFace()->mainButton()
->setTextf("Select Font: %s",
base::get_file_title(m_face).c_str());
}
void onSelectFontFile() {
base::paths exts = { "ttf", "ttc", "otf", "dfont" };
base::paths face;
if (!show_file_selector(
"Select a TrueType Font",
m_face, exts,
FileSelectorType::Open, face))
return;
ASSERT(!face.empty());
setFontFace(face.front());
}
void setFontFace(const std::string& face) {
m_face = face;
ok()->setEnabled(true);
updateFontFaceButton();
}
void onSelectSystemFont() {
if (!m_fontPopup) {
try {
m_fontPopup.reset(new FontPopup());
m_fontPopup->Load.connect(&PasteTextWindow::setFontFace, this);
m_fontPopup->Close.connect([this]{ onCloseFontPopup(); });
}
catch (const std::exception& ex) {
Console::showException(ex);
return;
}
}
if (!m_fontPopup->isVisible()) {
m_fontPopup->showPopup(display(), fontFace()->bounds());
}
else {
m_fontPopup->closeWindow(NULL);
}
}
void onCloseFontPopup() {
fontFace()->dropDown()->requestFocus();
}
std::string m_face;
std::unique_ptr<FontPopup> m_fontPopup;
};
void PasteTextCommand::onExecute(Context* ctx)
@ -144,10 +75,21 @@ void PasteTextCommand::onExecute(Context* ctx)
return;
Preferences& pref = Preferences::instance();
PasteTextWindow window(pref.textTool.fontFace(),
pref.textTool.fontSize(),
pref.textTool.antialias(),
pref.colorBar.fgColor());
FontInfo fontInfo;
// Old configuration
if (!pref.textTool.fontFace().empty()) {
fontInfo = FontInfo(FontInfo::Type::File,
pref.textTool.fontFace(),
pref.textTool.fontSize(),
pref.textTool.antialias());
}
// New configuration
if (!pref.textTool.fontInfo().empty()) {
fontInfo = base::convert_to<FontInfo>(pref.textTool.fontInfo());
}
PasteTextWindow window(fontInfo, pref.colorBar.fgColor());
window.userText()->setText(last_text_used);
@ -157,23 +99,25 @@ void PasteTextCommand::onExecute(Context* ctx)
last_text_used = window.userText()->text();
bool antialias = window.antialias()->isSelected();
std::string faceName = window.faceValue();
int size = window.sizeValue();
size = std::clamp(size, 1, 999);
pref.textTool.fontFace(faceName);
pref.textTool.fontSize(size);
pref.textTool.antialias(antialias);
fontInfo = window.fontInfo();
pref.textTool.fontInfo(base::convert_to<std::string>(fontInfo));
if (!pref.textTool.fontFace().empty()) {
pref.textTool.fontFace.clearValue();
pref.textTool.fontSize.clearValue();
pref.textTool.antialias.clearValue();
}
try {
auto* theme = skin::SkinTheme::instance();
std::string text = window.userText()->text();
app::Color appColor = window.fontColor()->getColor();
doc::color_t color = doc::rgba(appColor.getRed(),
appColor.getGreen(),
appColor.getBlue(),
appColor.getAlpha());
app::Color color = window.fontColor()->getColor();
doc::ImageRef image(render_text(faceName, size, text, color, antialias));
doc::ImageRef image = render_text(
theme->fontMgr(), fontInfo, text,
gfx::rgba(color.getRed(),
color.getGreen(),
color.getBlue(),
color.getAlpha()));
if (image) {
Sprite* sprite = editor->sprite();
if (image->pixelFormat() != sprite->pixelFormat()) {

142
src/app/font_info.cpp Normal file
View File

@ -0,0 +1,142 @@
// Aseprite
// Copyright (c) 2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/font_info.h"
#include "base/fs.h"
#include "base/split_string.h"
#include "fmt/format.h"
#include "text/font_mgr.h"
#include "text/font_style_set.h"
#include <cstdlib>
#include <cmath>
#include <vector>
namespace app {
FontInfo::FontInfo(Type type,
const std::string& name,
const float size,
const bool antialias,
const text::TypefaceRef& typeface)
: m_type(type)
, m_name(name)
, m_size(size)
, m_antialias(antialias)
, m_typeface(typeface)
{
}
FontInfo::FontInfo(const FontInfo& other,
const float size,
const bool antialias)
: m_type(other.type())
, m_name(other.name())
, m_size(size)
, m_antialias(antialias)
, m_typeface(other.typeface())
{
}
std::string FontInfo::title() const
{
return m_type == FontInfo::Type::File ?
base::get_file_name(m_name):
m_name;
}
std::string FontInfo::thumbnailId() const
{
switch (m_type) {
case app::FontInfo::Type::Unknown: break;
case app::FontInfo::Type::Name: return m_name;
case app::FontInfo::Type::File: return "file=" + m_name;
case app::FontInfo::Type::System: return "system=" + m_name;
}
return std::string();
}
void FontInfo::findTypeface(const text::FontMgrRef& fontMgr) const
{
if (m_type != Type::System ||
m_typeface != nullptr) {
return;
}
const text::FontStyleSetRef set = fontMgr->matchFamily(m_name);
if (set && set->typeface(0))
m_typeface = set->typeface(0);
}
} // namespace app
namespace base {
template<> app::FontInfo convert_to(const std::string& from)
{
std::vector<std::string> parts;
base::split_string(from, parts, ",");
app::FontInfo::Type type = app::FontInfo::Type::Unknown;
std::string name;
float size = 0.0f;
bool antialias = false;
if (!parts.empty()) {
if (parts[0].compare(0, 5, "file=") == 0) {
type = app::FontInfo::Type::File;
name = parts[0].substr(5);
}
else if (parts[0].compare(0, 7, "system=") == 0) {
type = app::FontInfo::Type::System;
name = parts[0].substr(7);
}
else {
type = app::FontInfo::Type::Name;
name = parts[0];
}
for (int i=1; i<parts.size(); ++i) {
if (parts[i] == "antialias")
antialias = true;
else if (parts[i].compare(0, 5, "size=") == 0) {
size = std::strtof(parts[i].substr(5).c_str(), nullptr);
}
}
}
return app::FontInfo(type, name, size, antialias);
}
template<> std::string convert_to(const app::FontInfo& from)
{
std::string result;
switch (from.type()) {
case app::FontInfo::Type::Unknown:
// Do nothing
break;
case app::FontInfo::Type::Name:
result = from.name();
break;
case app::FontInfo::Type::File:
result = "file=" + from.name();
break;
case app::FontInfo::Type::System:
result = "system=" + from.name();
break;
}
if (!result.empty()) {
if (from.size() > 0.0f)
result += fmt::format(",size={}", from.size());
if (from.antialias())
result += ",antialias";
}
return result;
}
} // namespace base

86
src/app/font_info.h Normal file
View File

@ -0,0 +1,86 @@
// Aseprite
// Copyright (c) 2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_FONT_INFO_H_INCLUDED
#define APP_UI_FONT_INFO_H_INCLUDED
#pragma once
#include "base/convert_to.h"
#include "text/fwd.h"
#include "text/typeface.h"
#include <cmath>
#include <string>
namespace app {
// TODO should we merge this with skin::FontData?
class FontInfo {
public:
enum class Type {
Unknown,
Name,
File,
System,
};
static constexpr const float kDefaultSize = 0.0f;
FontInfo(Type type = Type::Unknown,
const std::string& name = {},
float size = kDefaultSize,
bool antialias = false,
const text::TypefaceRef& typeface = nullptr);
FontInfo(const FontInfo& other,
float size,
bool antialias);
bool isValid() const { return m_type != Type::Unknown; }
bool useDefaultSize() const { return m_size == kDefaultSize; }
Type type() const { return m_type; }
// Depending on the font type this field indicates a "integrated
// font name" (e.g. "Aseprite" or any font from <font>
// definitions/themes), or a TTF filename, or a system font name.
const std::string& name() const { return m_name; }
// Visible label for this font in the UI.
std::string title() const;
std::string thumbnailId() const;
float size() const { return m_size; }
bool antialias() const { return m_antialias; }
void findTypeface(const text::FontMgrRef& fontMgr) const;
text::TypefaceRef typeface() const { return m_typeface; }
bool operator==(const FontInfo& other) const {
return (m_type == other.m_type &&
m_name == other.m_name &&
std::fabs(m_size-other.m_size) < 0.001f &&
m_antialias == other.m_antialias);
}
private:
Type m_type = Type::Unknown;
std::string m_name;
float m_size = kDefaultSize;
bool m_antialias = false;
mutable text::TypefaceRef m_typeface;
};
} // namespace app
namespace base {
template<> app::FontInfo convert_to(const std::string& from);
template<> std::string convert_to(const app::FontInfo& from);
} // namespace base
#endif

View File

@ -0,0 +1,56 @@
// Aseprite
// Copyright (c) 2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#include "tests/app_test.h"
#include "app/font_info.h"
using namespace app;
using namespace std::literals;
namespace app {
std::ostream& operator<<(std::ostream& os, const FontInfo& info) {
return os << base::convert_to<std::string>(info);
}
} // namespace app
TEST(FontInfo, ByName)
{
FontInfo a(FontInfo::Type::Name, "Aseprite");
FontInfo b(FontInfo::Type::Name, "Aseprite", 24);
FontInfo c(FontInfo::Type::Name, "Arial Unicode");
EXPECT_EQ("Aseprite", base::convert_to<std::string>(a));
EXPECT_EQ("Aseprite,size=24", base::convert_to<std::string>(b));
EXPECT_EQ("Arial Unicode", base::convert_to<std::string>(c));
EXPECT_EQ(a, base::convert_to<FontInfo>("Aseprite"s));
EXPECT_EQ(b, base::convert_to<FontInfo>("Aseprite,size=24"s));
EXPECT_EQ(c, base::convert_to<FontInfo>("Arial Unicode"s));
}
TEST(FontInfo, ByFile)
{
FontInfo a(FontInfo::Type::File, "C:/Windows/fonts/Arial.ttf", 12);
FontInfo b(FontInfo::Type::File, "/usr/share/fonts/truetype/noto/NotoSansMath-Regular.ttf");
EXPECT_EQ("file=C:/Windows/fonts/Arial.ttf,size=12", base::convert_to<std::string>(a));
EXPECT_EQ("file=/usr/share/fonts/truetype/noto/NotoSansMath-Regular.ttf", base::convert_to<std::string>(b));
EXPECT_EQ(a, base::convert_to<FontInfo>("file=C:/Windows/fonts/Arial.ttf,size=12"s));
EXPECT_EQ(b, base::convert_to<FontInfo>("file=/usr/share/fonts/truetype/noto/NotoSansMath-Regular.ttf"s));
}
TEST(FontInfo, BySystem)
{
FontInfo a(FontInfo::Type::System, "FreeMono");
FontInfo b(FontInfo::Type::System, "DejaVu Serif", 12, true);
EXPECT_EQ("system=FreeMono", base::convert_to<std::string>(a));
EXPECT_EQ("system=DejaVu Serif,size=12,antialias", base::convert_to<std::string>(b));
EXPECT_EQ(a, base::convert_to<FontInfo>("system=FreeMono"s));
EXPECT_EQ(b, base::convert_to<FontInfo>("system=DejaVu Serif,size=12,antialias"s));
}

188
src/app/ui/font_entry.cpp Normal file
View File

@ -0,0 +1,188 @@
// Aseprite
// Copyright (c) 2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/font_entry.h"
#include "app/console.h"
#include "app/ui/font_popup.h"
#include "fmt/format.h"
#include "ui/display.h"
#include "ui/manager.h"
#include "ui/message.h"
#include <cstdlib>
namespace app {
using namespace ui;
FontEntry::FontFace::FontFace()
{
}
bool FontEntry::FontFace::onProcessMessage(Message* msg)
{
switch (msg->type()) {
// If we press the mouse button in the FontFace widget, and drag
// the mouse (without releasing the mouse button) to the popup, we
// send the mouse message to the popup.
case kMouseMoveMessage:
if (hasCapture() && m_popup) {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
const gfx::Point screenPos =
mouseMsg->display()->nativeWindow()->pointToScreen(mouseMsg->position());
Widget* pick = manager()->pickFromScreenPos(screenPos);
Widget* target = m_popup->getListBox();
if (pick && (pick == target ||
pick->hasAncestor(target))) {
releaseMouse();
MouseMessage mouseMsg2(
kMouseDownMessage,
*mouseMsg,
mouseMsg->positionForDisplay(pick->display()));
mouseMsg2.setRecipient(pick);
mouseMsg2.setDisplay(pick->display());
pick->sendMessage(&mouseMsg2);
return true;
}
}
break;
case kMouseDownMessage:
case kFocusEnterMessage:
if (!m_popup) {
try {
const FontInfo info = static_cast<FontEntry*>(parent())->info();
m_popup.reset(new FontPopup(info));
m_popup->ChangeFont.connect([this](const FontInfo& fontInfo){
FontChange(fontInfo);
});
// If we press ESC in the popup we focus this FontFace field.
m_popup->EscKey.connect([this](){
requestFocus();
});
}
catch (const std::exception& ex) {
Console::showException(ex);
}
}
if (!m_popup->isVisible()) {
m_popup->showPopup(display(), bounds());
requestFocus();
}
break;
case kFocusLeaveMessage: {
// If we lost focus by a widget that is not part of the popup,
// we close the popup.
auto* newFocus = static_cast<FocusMessage*>(msg)->newFocus();
if (m_popup &&
newFocus &&
newFocus->window() != m_popup.get()) {
m_popup->closeWindow(nullptr);
}
break;
}
case kKeyDownMessage:
// If the popup is visible and we press the Down arrow key, we
// start navigating the popup list.
if (hasFocus() &&
m_popup &&
m_popup->isVisible()) {
const auto* keymsg = static_cast<const KeyMessage*>(msg);
switch (keymsg->scancode()) {
case kKeyDown:
m_popup->focusListBox();
return true;
}
}
break;
}
return SearchEntry::onProcessMessage(msg);
}
void FontEntry::FontFace::onChange()
{
SearchEntry::onChange();
m_popup->setSearchText(text());
}
FontEntry::FontSize::FontSize()
{
setEditable(true);
for (int i : { 8, 9, 10, 11, 12, 14, 16, 18, 22, 24, 26, 28, 36, 48, 72 })
addItem(fmt::format("{}", i));
}
void FontEntry::FontSize::onEntryChange()
{
ComboBox::onEntryChange();
Change();
}
FontEntry::FontEntry()
: m_antialias("Antialias")
{
m_face.setExpansive(true);
m_size.setExpansive(false);
m_antialias.setExpansive(false);
addChild(&m_face);
addChild(&m_size);
addChild(&m_antialias);
m_face.FontChange.connect([this](const FontInfo& newTypeName) {
setInfo(FontInfo(newTypeName,
m_info.size(),
m_info.antialias()),
From::Face);
invalidate();
});
m_size.Change.connect([this](){
const float newSize = std::strtof(m_size.getValue().c_str(), nullptr);
setInfo(FontInfo(m_info,
newSize,
m_info.antialias()),
From::Size);
});
m_antialias.Click.connect([this](){
setInfo(FontInfo(m_info,
m_info.size(),
m_antialias.isSelected()),
From::Antialias);
});
}
void FontEntry::setInfo(const FontInfo& info,
const From fromField)
{
m_info = info;
m_face.setText(info.title());
if (fromField != From::Size)
m_size.setValue(fmt::format("{}", info.size()));
if (fromField != From::Antialias)
m_antialias.setSelected(info.antialias());
FontChange(m_info);
}
} // namespace app

66
src/app/ui/font_entry.h Normal file
View File

@ -0,0 +1,66 @@
// Aseprite
// Copyright (c) 2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_FONT_ENTRY_H_INCLUDED
#define APP_UI_FONT_ENTRY_H_INCLUDED
#pragma once
#include "app/font_info.h"
#include "app/ui/search_entry.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/combobox.h"
#include <string>
namespace app {
class FontPopup;
class FontEntry : public ui::HBox {
public:
enum class From {
User,
Face,
Size,
Antialias,
};
FontEntry();
FontInfo info() { return m_info; }
void setInfo(const FontInfo& info, From from = From::User);
obs::signal<void(const FontInfo&)> FontChange;
private:
class FontFace : public SearchEntry {
public:
FontFace();
obs::signal<void(const FontInfo&)> FontChange;
protected:
bool onProcessMessage(ui::Message* msg) override;
void onChange() override;
private:
std::unique_ptr<FontPopup> m_popup;
};
class FontSize : public ui::ComboBox {
public:
FontSize();
protected:
void onEntryChange() override;
};
FontInfo m_info;
FontFace m_face;
FontSize m_size;
ui::CheckBox m_antialias;
};
} // namespace app
#endif

View File

@ -11,28 +11,29 @@
#include "app/ui/font_popup.h"
#include "app/commands/cmd_set_palette.h"
#include "app/commands/commands.h"
#include "app/console.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/ui/search_entry.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/font_data.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui_context.h"
#include "app/util/conversion_to_surface.h"
#include "app/util/freetype_utils.h"
#include "app/util/render_text.h"
#include "base/fs.h"
#include "base/string.h"
#include "doc/image.h"
#include "doc/image_ref.h"
#include "os/surface.h"
#include "os/system.h"
#include "text/text.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/fit_bounds.h"
#include "ui/graphics.h"
#include "ui/listitem.h"
#include "ui/message.h"
#include "ui/paint_event.h"
#include "ui/size_hint_event.h"
#include "ui/theme.h"
@ -53,144 +54,270 @@ namespace app {
using namespace ui;
static std::map<std::string, doc::ImageRef> g_thumbnails;
static std::map<std::string, os::SurfaceRef> g_thumbnails;
class FontItem : public ListItem {
public:
struct ByName { };
FontItem(const std::string& name, ByName)
: ListItem(name)
, m_fontInfo(FontInfo::Type::Name, name,
FontInfo::kDefaultSize, true) {
getCachedThumbnail();
}
FontItem(const std::string& fn)
: ListItem(base::get_file_title(fn))
, m_image(g_thumbnails[fn])
, m_filename(fn) {
, m_fontInfo(FontInfo::Type::File, fn,
FontInfo::kDefaultSize, true) {
getCachedThumbnail();
}
const std::string& filename() const {
return m_filename;
FontItem(const std::string& name,
const text::TypefaceRef& typeface)
: ListItem(name)
, m_fontInfo(FontInfo::Type::System, name,
FontInfo::kDefaultSize, true, typeface) {
getCachedThumbnail();
}
FontInfo fontInfo() const {
return m_fontInfo;
}
obs::signal<void()> ThumbnailGenerated;
private:
void getCachedThumbnail() {
m_thumbnail = g_thumbnails[m_fontInfo.thumbnailId()];
}
void onPaint(PaintEvent& ev) override {
ListItem::onPaint(ev);
if (m_image) {
generateThumbnail();
if (m_thumbnail) {
Graphics* g = ev.graphics();
os::SurfaceRef sur = os::System::instance()
->makeRgbaSurface(m_image->width(),
m_image->height());
convert_image_to_surface(
m_image.get(), nullptr, sur.get(),
0, 0, 0, 0, m_image->width(), m_image->height());
g->drawRgbaSurface(sur.get(), textWidth()+4, 0);
g->drawRgbaSurface(m_thumbnail.get(), textWidth()+4, 0);
}
}
void onSizeHint(SizeHintEvent& ev) override {
ListItem::onSizeHint(ev);
if (m_image) {
if (m_thumbnail) {
gfx::Size sz = ev.sizeHint();
ev.setSizeHint(
sz.w + 4 + m_image->width(),
std::max(sz.h, m_image->height()));
sz.w + 4 + m_thumbnail->width(),
std::max(sz.h, m_thumbnail->height()));
}
}
void onSelect(bool selected) override {
if (!selected || m_image)
void generateThumbnail() {
if (m_thumbnail)
return;
ListBox* listbox = static_cast<ListBox*>(parent());
if (!listbox)
return;
auto theme = app::skin::SkinTheme::get(this);
gfx::Color color = theme->colors.text();
const auto* theme = app::skin::SkinTheme::get(this);
try {
m_image.reset(
render_text(
m_filename, 16,
"ABCDEabcde", // TODO custom text
doc::rgba(gfx::getr(color),
gfx::getg(color),
gfx::getb(color),
gfx::geta(color)),
true)); // antialias
const text::FontMgrRef fontMgr = theme->fontMgr();
const gfx::Color color = theme->colors.text();
doc::ImageRef image =
render_text(fontMgr, m_fontInfo, text(), color);
if (!image)
return;
View* view = View::getView(listbox);
view->updateView();
listbox->makeChildVisible(this);
// Convert the doc::Image into a os::Surface
m_thumbnail = os::System::instance()
->makeRgbaSurface(image->width(),
image->height());
convert_image_to_surface(
image.get(), nullptr, m_thumbnail.get(),
0, 0, 0, 0, image->width(), image->height());
// Save the thumbnail for future FontPopups
g_thumbnails[m_filename] = m_image;
g_thumbnails[m_fontInfo.thumbnailId()] = m_thumbnail;
ThumbnailGenerated();
}
catch (const std::exception&) {
// Ignore errors
}
}
void onSelect(bool selected) override {
if (!selected || m_thumbnail)
return;
ListBox* listbox = static_cast<ListBox*>(parent());
if (!listbox)
return;
generateThumbnail();
listbox->makeChildVisible(this);
}
private:
doc::ImageRef m_image;
std::string m_filename;
os::SurfaceRef m_thumbnail;
FontInfo m_fontInfo;
};
FontPopup::FontPopup()
: PopupWindow(Strings::font_popup_title(),
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;
}
FontPopup::FontPopup(const FontInfo& fontInfo)
: PopupWindow(std::string(),
ClickBehavior::CloseOnClickInOtherWindow,
EnterBehavior::DoNothingOnEnter)
, m_popup(new gen::FontPopup())
, m_timer(100)
{
setAutoRemap(false);
setBorder(gfx::Border(4*guiscale()));
addChild(m_popup);
m_popup->search()->Change.connect([this]{ onSearchChange(); });
m_timer.Tick.connect([this]{ onTickRelayout(); });
m_popup->loadFont()->Click.connect([this]{ onLoadFont(); });
m_listBox.setFocusMagnet(true);
m_listBox.Change.connect([this]{ onChangeFont(); });
m_listBox.DoubleClickItem.connect([this]{ onLoadFont(); });
m_listBox.Change.connect([this]{
if (m_listBox.hasFocus() ||
m_listBox.hasCapture()) {
onFontChange();
}
});
m_listBox.DoubleClickItem.connect([this]{
onFontChange();
});
m_popup->view()->attachToView(&m_listBox);
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)) {
std::string fullpath = base::join_path(fontDir, file);
if (base::is_file(fullpath))
files.push_back(fullpath);
// Default fonts
bool firstThemeFont = true;
for (auto kv : skin::SkinTheme::get(this)->getWellKnownFonts()) {
if (!kv.second->filename().empty()) {
if (firstThemeFont) {
m_listBox.addChild(new SeparatorInView(Strings::font_popup_theme_fonts()));
firstThemeFont = false;
}
m_listBox.addChild(new FontItem(kv.first, FontItem::ByName()));
}
}
// 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;
});
// Create one FontItem for each font
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")
m_listBox.addChild(new FontItem(file));
m_listBox.addChild(new SeparatorInView(Strings::font_popup_system_fonts()));
bool empty = true;
// Get system fonts from laf-text module
const text::FontMgrRef fontMgr = theme()->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) {
auto* item = new FontItem(name, set->typeface(0));
item->ThumbnailGenerated.connect([this]{ onThumbnailGenerated(); });
m_listBox.addChild(item);
empty = false;
}
}
}
// 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)) {
std::string fullpath = base::join_path(fontDir, file);
if (base::is_file(fullpath))
files.push_back(fullpath);
}
}
// 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") {
m_listBox.addChild(new FontItem(file));
empty = false;
}
}
}
if (m_listBox.children().empty())
if (empty)
m_listBox.addChild(new ListItem(Strings::font_popup_empty_fonts()));
for (auto* child : m_listBox.children()) {
if (auto* childItem = dynamic_cast<FontItem*>(child)) {
if (childItem->fontInfo().title() == childItem->text()) {
m_listBox.selectChild(childItem);
break;
}
}
}
}
FontPopup::~FontPopup()
{
m_timer.stop();
}
void FontPopup::focusListBox()
{
m_listBox.requestFocus();
}
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();
}
void FontPopup::showPopup(Display* display,
const gfx::Rect& buttonBounds)
{
m_popup->loadFont()->setEnabled(false);
m_listBox.selectChild(NULL);
m_listBox.selectChild(nullptr);
ui::fit_bounds(display, this,
gfx::Rect(buttonBounds.x, buttonBounds.y2(), 32, 32),
@ -201,45 +328,74 @@ void FontPopup::showPopup(Display* display,
bounds.h = workarea.h / 2;
});
// Setup the hot-region
setHotRegion(gfx::Region(gfx::Rect(boundsOnScreen()).enlarge(32*guiscale()*display->scale())));
openWindow();
}
void FontPopup::onSearchChange()
FontInfo FontPopup::selectedFont()
{
std::string searchText = m_popup->search()->text();
Widget* firstItem = nullptr;
MatchWords match(searchText);
for (auto child : m_listBox.children()) {
bool visible = match(child->text());
if (visible && !firstItem)
firstItem = child;
child->setVisible(visible);
}
m_listBox.selectChild(firstItem);
layout();
const FontItem* child = dynamic_cast<FontItem*>(m_listBox.getSelectedChild());
if (child)
return child->fontInfo();
return FontInfo();
}
void FontPopup::onChangeFont()
void FontPopup::onFontChange()
{
m_popup->loadFont()->setEnabled(true);
const FontInfo fontInfo = selectedFont();
if (fontInfo.isValid())
ChangeFont(fontInfo);
}
void FontPopup::onLoadFont()
{
FontItem* child = dynamic_cast<FontItem*>(m_listBox.getSelectedChild());
if (!child)
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))
return;
std::string filename = child->filename();
if (base::is_file(filename))
Load(filename); // Fire Load signal
ASSERT(!face.empty());
ChangeFont(FontInfo(FontInfo::Type::File,
face.front()));
}
closeWindow(nullptr);
void FontPopup::onThumbnailGenerated()
{
m_timer.start();
}
void FontPopup::onTickRelayout()
{
m_popup->view()->updateView();
m_timer.stop();
}
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);
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2021-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "ui/listbox.h"
#include "ui/popup_window.h"
#include "ui/timer.h"
namespace ui {
class Button;
@ -23,23 +24,43 @@ namespace app {
class FontPopup;
}
class FontInfo;
class FontPopup : public ui::PopupWindow {
public:
FontPopup();
class FontListBox : public ui::ListBox {
protected:
bool onProcessMessage(ui::Message* msg) override;
};
FontPopup(const FontInfo& fontInfo);
~FontPopup();
void focusListBox();
void setSearchText(const std::string& searchText);
void showPopup(ui::Display* display,
const gfx::Rect& buttonBounds);
obs::signal<void(const std::string&)> Load;
FontListBox* getListBox() { return &m_listBox; }
obs::signal<void(const FontInfo&)> ChangeFont;
obs::signal<void()> EscKey;
protected:
void onSearchChange();
void onChangeFont();
FontInfo currentFontInfo();
void onFontChange();
void onLoadFont();
void onThumbnailGenerated();
void onTickRelayout();
bool onProcessMessage(ui::Message* msg) override;
private:
FontInfo selectedFont();
gen::FontPopup* m_popup;
ui::ListBox m_listBox;
FontListBox m_listBox;
ui::Timer m_timer;
};
} // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2024 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -16,12 +17,13 @@ namespace app {
public:
SearchEntry();
private:
protected:
bool onProcessMessage(ui::Message* msg) override;
void onPaint(ui::PaintEvent& ev) override;
void onSizeHint(ui::SizeHintEvent& ev) override;
gfx::Rect onGetEntryTextBounds() const override;
private:
gfx::Rect getCloseIconBounds() const;
};

View File

@ -18,6 +18,7 @@
namespace app {
namespace skin {
// TODO should we merge this with FontInfo?
class FontData {
public:
FontData(text::FontType type);
@ -32,6 +33,8 @@ namespace skin {
text::FontRef getFont(text::FontMgrRef& fontMgr, int size, int uiscale);
text::FontRef getFont(text::FontMgrRef& fontMgr, int size);
const std::string& filename() const { return m_filename; }
private:
text::FontType m_type;
std::string m_filename;

View File

@ -1678,6 +1678,15 @@ void SkinTheme::drawEntryCaret(ui::Graphics* g, Entry* widget, int x, int y)
g->drawVLine(color, u, y+textHeight/2-caretSize.h/2, caretSize.h);
}
text::FontRef SkinTheme::getFontByName(const std::string& name,
const int size)
{
auto it = m_fonts.find(name);
if (it == m_fonts.end())
return nullptr;
return it->second->getFont(m_fontMgr, size);
}
SkinPartPtr SkinTheme::getToolPart(const char* toolId) const
{
return getPartById(std::string("tool_") + toolId);

View File

@ -34,6 +34,7 @@ namespace app {
namespace skin {
class FontData;
using FontDataMap = std::map<std::string, FontData*>;
class ThemeFont {
public:
@ -155,6 +156,9 @@ namespace app {
void drawEntryCaret(ui::Graphics* g, ui::Entry* widget, int x, int y);
const FontDataMap& getWellKnownFonts() const { return m_fonts; }
text::FontRef getFontByName(const std::string& name, int size);
protected:
void onRegenerateTheme() override;
@ -195,7 +199,7 @@ namespace app {
std::map<std::string, ui::Cursor*> m_cursors;
std::array<ui::Cursor*, ui::kCursorTypes> m_standardCursors;
std::map<std::string, ui::Style*> m_styles;
std::map<std::string, FontData*> m_fonts;
FontDataMap m_fonts;
std::map<std::string, ThemeFont> m_themeFonts;
// Stores the unscaled font version of the Font pointer used as a key.
std::map<text::Font*, text::FontRef> m_unscaledFonts;

View File

@ -1,107 +0,0 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/util/freetype_utils.h"
#include "doc/blend_funcs.h"
#include "doc/blend_internals.h"
#include "doc/color.h"
#include "doc/image.h"
#include "doc/primitives.h"
#include "ft/algorithm.h"
#include "ft/face.h"
#include "ft/hb_shaper.h"
#include "ft/lib.h"
#include <memory>
#include <stdexcept>
namespace app {
doc::Image* render_text(const std::string& fontfile, int fontsize,
const std::string& text,
doc::color_t color,
bool antialias)
{
std::unique_ptr<doc::Image> image(nullptr);
ft::Lib ft;
ft::Face face(ft.open(fontfile));
if (face.isValid()) {
// Set font size
face.setSize(fontsize);
face.setAntialias(antialias);
// Calculate text size
gfx::Rect bounds = ft::calc_text_bounds(face, text);
// Render the image and copy it to the clipboard
if (!bounds.isEmpty()) {
image.reset(doc::Image::create(doc::IMAGE_RGB, bounds.w, bounds.h));
doc::clear_image(image.get(), 0);
ft::ForEachGlyph<ft::Face> feg(face, text);
while (feg.next()) {
auto glyph = feg.glyph();
if (!glyph)
continue;
int t, yimg = - bounds.y + int(glyph->y);
for (int v=0; v<int(glyph->bitmap->rows); ++v, ++yimg) {
const uint8_t* p = glyph->bitmap->buffer + v*glyph->bitmap->pitch;
int ximg = - bounds.x + int(glyph->x);
int bit = 0;
for (int u=0; u<int(glyph->bitmap->width); ++u, ++ximg) {
int alpha;
if (antialias) {
alpha = *(p++);
}
else {
alpha = ((*p) & (1 << (7 - (bit++))) ? 255: 0);
if (bit == 8) {
bit = 0;
++p;
}
}
int output_alpha = MUL_UN8(doc::rgba_geta(color), alpha, t);
if (output_alpha) {
doc::color_t output_color =
doc::rgba(doc::rgba_getr(color),
doc::rgba_getg(color),
doc::rgba_getb(color),
output_alpha);
doc::put_pixel(
image.get(), ximg, yimg,
doc::rgba_blender_normal(
doc::get_pixel(image.get(), ximg, yimg),
output_color));
}
}
}
}
}
else {
throw std::runtime_error("There is no text");
}
}
else {
throw std::runtime_error("Error loading font face");
}
return (image ? image.release(): nullptr);
}
} // namespace app

View File

@ -1,28 +0,0 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UTIL_FREETYPE_UTILS_H_INCLUDED
#define APP_UTIL_FREETYPE_UTILS_H_INCLUDED
#pragma once
#include "doc/color.h"
#include <string>
namespace doc {
class Image;
}
namespace app {
doc::Image* render_text(const std::string& fontfile, int fontsize,
const std::string& text,
doc::color_t color,
bool antialias);
} // namespace app
#endif

View File

@ -0,0 +1,105 @@
// Aseprite
// Copyright (C) 2022-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/util/render_text.h"
#include "app/font_info.h"
#include "app/ui/skin/skin_theme.h"
#include "base/fs.h"
#include "doc/blend_funcs.h"
#include "doc/blend_internals.h"
#include "doc/color.h"
#include "doc/image.h"
#include "doc/primitives.h"
#include "text/draw_text.h"
#include "text/font_metrics.h"
#ifdef LAF_SKIA
#include "app/util/shader_helpers.h"
#include "os/skia/skia_surface.h"
#endif
#include <memory>
#include <stdexcept>
namespace app {
doc::ImageRef render_text(
const text::FontMgrRef& fontMgr,
const FontInfo& fontInfo,
const std::string& text,
gfx::Color color)
{
doc::ImageRef image;
auto* theme = skin::SkinTheme::instance();
ASSERT(theme);
if (!theme)
return nullptr;
text::FontRef font;
if (fontInfo.type() == FontInfo::Type::System) {
// Just in case the typeface is not present in the FontInfo
fontInfo.findTypeface(fontMgr);
const text::FontMgrRef fontMgr = theme->fontMgr();
font = fontMgr->makeFont(fontInfo.typeface());
if (!fontInfo.useDefaultSize())
font->setSize(fontInfo.size());
}
else {
const int size = (fontInfo.useDefaultSize() ? 18: fontInfo.size());
font = theme->getFontByName(fontInfo.name(), size);
if (!font && fontInfo.type() == FontInfo::Type::File) {
font = fontMgr->loadTrueTypeFont(fontInfo.name().c_str(), size);
}
}
if (!font)
return nullptr;
font->setAntialias(fontInfo.antialias());
os::Paint paint;
paint.style(os::Paint::StrokeAndFill);
paint.color(color);
gfx::RectF bounds;
bounds.w = font->measureText(text, &bounds, &paint);
text::FontMetrics metrics;
bounds.w += 1;
bounds.h = font->metrics(&metrics);
if (bounds.w < 1) bounds.w = 1;
if (bounds.h < 1) bounds.h = 1;
image.reset(doc::Image::create(doc::IMAGE_RGB, bounds.w, bounds.h));
#ifdef LAF_SKIA
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get());
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
if (fontInfo.type() == FontInfo::Type::System) {
text::draw_text_with_shaper(
surface.get(), fontMgr, font, text, gfx::PointF(0, 0), &paint);
}
else {
text::draw_text(
surface.get(), fontMgr, font, text,
color, gfx::ColorNone, 0, 0, nullptr);
}
#endif // LAF_SKIA
return image;
}
} // namespace app

View File

@ -0,0 +1,31 @@
// Aseprite
// Copyright (C) 2024 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UTIL_RENDER_TEXT_H_INCLUDED
#define APP_UTIL_RENDER_TEXT_H_INCLUDED
#pragma once
#include "doc/image_ref.h"
#include "gfx/color.h"
#include "text/font_mgr.h"
#include <string>
namespace app {
class Color;
class FontInfo;
doc::ImageRef render_text(
const text::FontMgrRef& fontMgr,
const FontInfo& fontInfo,
const std::string& text,
gfx::Color color);
} // namespace app
#endif

View File

@ -8,19 +8,24 @@
#include "config.h"
#endif
#if SK_ENABLE_SKSL
#include "app/util/shader_helpers.h"
#include "base/exception.h"
#include "doc/image.h"
#include "fmt/format.h"
#include "include/effects/SkRuntimeEffect.h"
#include "src/core/SkRuntimeEffectPriv.h"
#if LAF_SKIA
#include "include/core/SkSurface.h"
#if SK_ENABLE_SKSL
#include "include/effects/SkRuntimeEffect.h"
#include "src/core/SkRuntimeEffectPriv.h"
#endif
namespace app {
#if SK_ENABLE_SKSL
sk_sp<SkRuntimeEffect> make_shader(const char* code)
{
SkRuntimeEffect::Options options;
@ -40,6 +45,8 @@ sk_sp<SkRuntimeEffect> make_shader(const char* code)
return result.effect;
}
#endif // SK_ENABLE_SKSL
SkImageInfo get_skimageinfo_for_docimage(const doc::Image* img)
{
switch (img->colorMode()) {
@ -96,6 +103,14 @@ std::unique_ptr<SkCanvas> make_skcanvas_for_docimage(const doc::Image* img)
img->rowBytes());
}
sk_sp<SkSurface> wrap_docimage_in_sksurface(const doc::Image* img)
{
return SkSurfaces::WrapPixels(
get_skimageinfo_for_docimage(img),
(void*)img->getPixelAddress(0, 0),
img->rowBytes());
}
} // namespace app
#endif // SK_ENABLE_SKSL
#endif // LAF_SKIA

View File

@ -93,6 +93,7 @@ sk_sp<SkRuntimeEffect> make_shader(const char* code);
SkImageInfo get_skimageinfo_for_docimage(const doc::Image* img);
sk_sp<SkImage> make_skimage_for_docimage(const doc::Image* img);
std::unique_ptr<SkCanvas> make_skcanvas_for_docimage(const doc::Image* img);
sk_sp<SkSurface> wrap_docimage_in_sksurface(const doc::Image* img);
} // namespace app

View File

@ -22,6 +22,7 @@
#include "app/ui/color_button.h"
#include "app/ui/drop_down_button.h"
#include "app/ui/expr_entry.h"
#include "app/ui/font_entry.h"
#include "app/ui/icon_button.h"
#include "app/ui/mini_help_button.h"
#include "app/ui/search_entry.h"
@ -544,6 +545,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const XMLElement* elem, Widget*
if (!widget)
widget = new SearchEntry;
}
else if (elem_name == "font") {
if (!widget)
widget = new FontEntry;
}
// Was the widget created?
if (widget) {

View File

@ -115,6 +115,9 @@ static Item convert_to_item(XMLElement* elem)
if (name == "expr")
return item.typeIncl("app::ExprEntry",
"app/ui/expr_entry.h");
if (name == "font")
return item.typeIncl("app::FontEntry",
"app/ui/font_entry.h");
if (name == "grid")
return item.typeIncl("ui::Grid",
"ui/grid.h");