2024-03-25 22:59:25 +08:00
|
|
|
// 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"
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
#include "app/app.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
#include "app/console.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/font_popup.h"
|
2024-10-29 20:48:23 +08:00
|
|
|
#include "app/ui/skin/skin_theme.h"
|
2024-10-23 01:32:33 +08:00
|
|
|
#include "base/scoped_value.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
#include "fmt/format.h"
|
|
|
|
#include "ui/display.h"
|
|
|
|
#include "ui/manager.h"
|
|
|
|
#include "ui/message.h"
|
2024-06-12 09:31:13 +08:00
|
|
|
#include "ui/scale.h"
|
2024-03-25 22:59:25 +08:00
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
#include <algorithm>
|
2024-03-25 22:59:25 +08:00
|
|
|
#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 {
|
2024-10-23 01:32:33 +08:00
|
|
|
const FontInfo info = fontEntry()->info();
|
2024-03-25 22:59:25 +08:00
|
|
|
|
|
|
|
m_popup.reset(new FontPopup(info));
|
2024-10-23 01:32:33 +08:00
|
|
|
m_popup->FontChange.connect([this](const FontInfo& fontInfo){
|
|
|
|
FontChange(fontInfo,
|
|
|
|
m_fromEntryChange ?
|
|
|
|
FontEntry::From::Face:
|
|
|
|
FontEntry::From::Popup);
|
2024-03-25 22:59:25 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
// 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()) {
|
2024-10-29 20:48:23 +08:00
|
|
|
// Reset the search filter before opening the popup window.
|
|
|
|
m_popup->setSearchText(std::string());
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
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);
|
|
|
|
}
|
2024-10-23 01:32:33 +08:00
|
|
|
|
|
|
|
// Restore the face name (e.g. when we press Escape key)
|
|
|
|
const FontInfo info = fontEntry()->info();
|
|
|
|
setText(info.title());
|
2024-03-25 22:59:25 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case kKeyDownMessage:
|
2024-10-23 01:32:33 +08:00
|
|
|
case kKeyUpMessage:
|
|
|
|
// If the popup is visible and we press the Up/Down arrow key,
|
|
|
|
// we start navigating the popup list.
|
|
|
|
if (hasFocus() && m_popup) {
|
2024-03-25 22:59:25 +08:00
|
|
|
const auto* keymsg = static_cast<const KeyMessage*>(msg);
|
|
|
|
switch (keymsg->scancode()) {
|
2024-10-23 01:32:33 +08:00
|
|
|
case kKeyEsc:
|
|
|
|
case kKeyEnter:
|
|
|
|
case kKeyEnterPad:
|
|
|
|
if (m_popup && m_popup->isVisible()) {
|
|
|
|
m_popup->closeWindow(nullptr);
|
|
|
|
|
|
|
|
// This final signal will release the focus from this
|
|
|
|
// entry and give the chance to the client to focus
|
|
|
|
// their own text box.
|
|
|
|
FontChange(m_popup->selectedFont(), From::Popup);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kKeyUp:
|
2024-03-25 22:59:25 +08:00
|
|
|
case kKeyDown:
|
2024-10-23 01:32:33 +08:00
|
|
|
if (m_popup->isVisible()) {
|
|
|
|
base::ScopedValue lock(fontEntry()->m_lockFace, true);
|
|
|
|
|
|
|
|
// Redirect key message to the list box
|
|
|
|
if (msg->recipient() == this) {
|
|
|
|
// Redirect the Up/Down arrow key to the popup list
|
|
|
|
// box, so we move through the list items. This will
|
|
|
|
// not generate a FontChange (as it'd modify the
|
|
|
|
// focused widget and other unexpected behaviors).
|
|
|
|
m_popup->getListBox()->sendMessage(msg);
|
|
|
|
|
|
|
|
// We are explicitly firing the FontChange signal so
|
|
|
|
// the client knows the new selected font from the
|
|
|
|
// popup.
|
|
|
|
FontChange(m_popup->selectedFont(), From::Face);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
2024-10-23 01:32:33 +08:00
|
|
|
break;
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
return SearchEntry::onProcessMessage(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FontEntry::FontFace::onChange()
|
|
|
|
{
|
2024-10-23 01:32:33 +08:00
|
|
|
base::ScopedValue lock(m_fromEntryChange, true);
|
2024-03-25 22:59:25 +08:00
|
|
|
SearchEntry::onChange();
|
|
|
|
|
|
|
|
m_popup->setSearchText(text());
|
2024-10-23 01:32:33 +08:00
|
|
|
|
|
|
|
// Changing the search text doesn't generate a FontChange
|
|
|
|
// signal. Here we are forcing a FontChange signal with the first
|
|
|
|
// selected font from the search. Indicating "From::Face" we avoid
|
|
|
|
// changing the FontEntry text with the face font name (as the user
|
|
|
|
// is writing the text to search, we don't want to touch this Entry
|
|
|
|
// field).
|
|
|
|
FontChange(m_popup->selectedFont(), From::Face);
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2024-04-16 05:52:36 +08:00
|
|
|
FontEntry::FontStyle::FontStyle()
|
|
|
|
: ButtonSet(2, true)
|
|
|
|
{
|
|
|
|
addItem("B");
|
|
|
|
addItem("I");
|
|
|
|
setMultiMode(MultiMode::Set);
|
|
|
|
}
|
|
|
|
|
2024-09-26 03:37:41 +08:00
|
|
|
FontEntry::FontLigatures::FontLigatures()
|
|
|
|
: ButtonSet(1, true)
|
|
|
|
{
|
|
|
|
addItem("fi");
|
|
|
|
setMultiMode(MultiMode::Set);
|
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
FontEntry::FontEntry()
|
|
|
|
: m_antialias("Antialias")
|
|
|
|
{
|
|
|
|
m_face.setExpansive(true);
|
|
|
|
m_size.setExpansive(false);
|
2024-04-16 05:52:36 +08:00
|
|
|
m_style.setExpansive(false);
|
2024-09-26 03:37:41 +08:00
|
|
|
m_ligatures.setExpansive(false);
|
2024-03-25 22:59:25 +08:00
|
|
|
m_antialias.setExpansive(false);
|
|
|
|
addChild(&m_face);
|
|
|
|
addChild(&m_size);
|
2024-04-16 05:52:36 +08:00
|
|
|
addChild(&m_style);
|
2024-09-26 03:37:41 +08:00
|
|
|
addChild(&m_ligatures);
|
2024-03-25 22:59:25 +08:00
|
|
|
addChild(&m_antialias);
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
m_face.setMinSize(gfx::Size(128*guiscale(), 0));
|
|
|
|
|
2024-10-29 20:48:23 +08:00
|
|
|
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);
|
|
|
|
}
|
2024-03-25 22:59:25 +08:00
|
|
|
invalidate();
|
|
|
|
});
|
|
|
|
|
|
|
|
m_size.Change.connect([this](){
|
|
|
|
const float newSize = std::strtof(m_size.getValue().c_str(), nullptr);
|
|
|
|
setInfo(FontInfo(m_info,
|
|
|
|
newSize,
|
2024-04-16 05:52:36 +08:00
|
|
|
m_info.style(),
|
2024-09-26 03:37:41 +08:00
|
|
|
m_info.flags()),
|
2024-03-25 22:59:25 +08:00
|
|
|
From::Size);
|
|
|
|
});
|
|
|
|
|
2024-04-16 05:52:36 +08:00
|
|
|
m_style.ItemChange.connect([this](ButtonSet::Item* item){
|
|
|
|
text::FontStyle style = m_info.style();
|
|
|
|
switch (m_style.getItemIndex(item)) {
|
|
|
|
// Bold button changed
|
|
|
|
case 0: {
|
|
|
|
const bool bold = m_style.getItem(0)->isSelected();
|
|
|
|
style = text::FontStyle(bold ? text::FontStyle::Weight::Bold:
|
|
|
|
text::FontStyle::Weight::Normal,
|
|
|
|
style.width(),
|
|
|
|
style.slant());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Italic button changed
|
|
|
|
case 1: {
|
|
|
|
const bool italic = m_style.getItem(1)->isSelected();
|
|
|
|
style = text::FontStyle(style.weight(),
|
|
|
|
style.width(),
|
|
|
|
italic ? text::FontStyle::Slant::Italic:
|
|
|
|
text::FontStyle::Slant::Upright);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setInfo(FontInfo(m_info,
|
|
|
|
m_info.size(),
|
|
|
|
style,
|
2024-09-26 03:37:41 +08:00
|
|
|
m_info.flags()),
|
2024-04-16 05:52:36 +08:00
|
|
|
From::Style);
|
|
|
|
});
|
|
|
|
|
2024-09-26 03:37:41 +08:00
|
|
|
auto flagsChange = [this]() {
|
|
|
|
FontInfo::Flags flags = FontInfo::Flags::None;
|
|
|
|
if (m_antialias.isSelected())
|
|
|
|
flags |= FontInfo::Flags::Antialias;
|
|
|
|
if (m_ligatures.getItem(0)->isSelected())
|
|
|
|
flags |= FontInfo::Flags::Ligatures;
|
|
|
|
setInfo(FontInfo(m_info, m_info.size(), m_info.style(), flags),
|
|
|
|
From::Flags);
|
|
|
|
};
|
|
|
|
m_ligatures.ItemChange.connect(flagsChange);
|
|
|
|
m_antialias.Click.connect(flagsChange);
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
|
2024-06-12 09:31:13 +08:00
|
|
|
// Defined here as FontPopup type is not fully defined in the header
|
|
|
|
// file (and we have a std::unique_ptr<FontPopup> in FontEntry::FontFace).
|
|
|
|
FontEntry::~FontEntry()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-03-25 22:59:25 +08:00
|
|
|
void FontEntry::setInfo(const FontInfo& info,
|
|
|
|
const From fromField)
|
|
|
|
{
|
|
|
|
m_info = info;
|
|
|
|
|
2024-10-23 01:32:33 +08:00
|
|
|
if (fromField != From::Face)
|
|
|
|
m_face.setText(info.title());
|
2024-03-25 22:59:25 +08:00
|
|
|
|
|
|
|
if (fromField != From::Size)
|
|
|
|
m_size.setValue(fmt::format("{}", info.size()));
|
|
|
|
|
2024-04-16 05:52:36 +08:00
|
|
|
if (fromField != From::Style) {
|
|
|
|
m_style.getItem(0)->setSelected(info.style().weight() >= text::FontStyle::Weight::SemiBold);
|
|
|
|
m_style.getItem(1)->setSelected(info.style().slant() != text::FontStyle::Slant::Upright);
|
|
|
|
}
|
|
|
|
|
2024-09-26 03:37:41 +08:00
|
|
|
if (fromField != From::Flags) {
|
|
|
|
m_ligatures.getItem(0)->setSelected(info.ligatures());
|
2024-03-25 22:59:25 +08:00
|
|
|
m_antialias.setSelected(info.antialias());
|
2024-09-26 03:37:41 +08:00
|
|
|
}
|
2024-03-25 22:59:25 +08:00
|
|
|
|
2024-10-23 01:32:33 +08:00
|
|
|
FontChange(m_info, fromField);
|
2024-03-25 22:59:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace app
|