Improve font search/selection from the context bar (#4692)

Improves the way we can write text to search a font, use up/down keys
to navigate through the font list, and use Enter/Esc keys to select
the font and go back to the text box.
This commit is contained in:
David Capello 2024-10-22 14:32:33 -03:00
parent b90807f3e3
commit 4c06fbdaf3
11 changed files with 109 additions and 43 deletions

View File

@ -57,7 +57,7 @@ class PasteTextWindow : public app::gen::PasteText {
public:
PasteTextWindow(const FontInfo& fontInfo,
const app::Color& color) {
fontFace()->setInfo(fontInfo);
fontFace()->setInfo(fontInfo, FontEntry::From::Init);
fontColor()->setColor(color);
}

View File

@ -1877,10 +1877,10 @@ class ContextBar::FontSelector : public FontEntry {
public:
FontSelector(ContextBar* contextBar) {
// Load the font from the preferences
setInfo(FontInfo::getFromPreferences());
setInfo(FontInfo::getFromPreferences(), FontEntry::From::Init);
FontChange.connect([contextBar](){
contextBar->FontChange();
FontChange.connect([contextBar](const FontInfo& fontInfo, From from) {
contextBar->FontChange(fontInfo, from);
});
}

View File

@ -17,6 +17,7 @@
#include "app/tools/tool_loop_modifiers.h"
#include "app/ui/context_bar_observer.h"
#include "app/ui/doc_observer_widget.h"
#include "app/ui/font_entry.h"
#include "doc/brush.h"
#include "obs/connection.h"
#include "obs/observable.h"
@ -103,7 +104,7 @@ namespace app {
// Signals
obs::signal<void()> BrushChange;
obs::signal<void()> FontChange;
obs::signal<void(const FontInfo&, FontEntry::From)> FontChange;
protected:
void onInitTheme(ui::InitThemeEvent& ev) override;

View File

@ -505,9 +505,9 @@ void WritingTextState::onBeforeCommandExecution(CommandExecutionEvent& ev)
}
}
void WritingTextState::onFontChange()
void WritingTextState::onFontChange(const FontInfo& fontInfo,
FontEntry::From fromField)
{
const FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
if (auto font = get_font_from_info(fontInfo)) {
m_entry->setFont(font);
m_entry->invalidate();
@ -517,10 +517,10 @@ void WritingTextState::onFontChange()
// immediately.
auto dummy = m_entry->extraCel();
ui::execute_from_ui_thread([this]{
if (fromField == FontEntry::From::Popup) {
if (m_entry)
m_entry->requestFocus();
});
}
}
}

View File

@ -10,11 +10,13 @@
#include "app/ui/editor/delayed_mouse_move.h"
#include "app/ui/editor/standby_state.h"
#include "app/ui/font_entry.h"
#include <memory>
namespace app {
class CommandExecutionEvent;
class FontInfo;
class WritingTextState : public StandbyState
, DelayedMouseMoveDelegate {
@ -47,7 +49,8 @@ namespace app {
gfx::Rect calcEntryBounds();
void onBeforeCommandExecution(CommandExecutionEvent& ev);
void onFontChange();
void onFontChange(const FontInfo& fontInfo,
FontEntry::From fromField);
void cancel();
void drop();

View File

@ -12,6 +12,7 @@
#include "app/console.h"
#include "app/ui/font_popup.h"
#include "base/scoped_value.h"
#include "fmt/format.h"
#include "ui/display.h"
#include "ui/manager.h"
@ -63,11 +64,14 @@ bool FontEntry::FontFace::onProcessMessage(Message* msg)
case kFocusEnterMessage:
if (!m_popup) {
try {
const FontInfo info = static_cast<FontEntry*>(parent())->info();
const FontInfo info = fontEntry()->info();
m_popup.reset(new FontPopup(info));
m_popup->ChangeFont.connect([this](const FontInfo& fontInfo){
FontChange(fontInfo);
m_popup->FontChange.connect([this](const FontInfo& fontInfo){
FontChange(fontInfo,
m_fromEntryChange ?
FontEntry::From::Face:
FontEntry::From::Popup);
});
// If we press ESC in the popup we focus this FontFace field.
@ -94,21 +98,56 @@ bool FontEntry::FontFace::onProcessMessage(Message* msg)
newFocus->window() != m_popup.get()) {
m_popup->closeWindow(nullptr);
}
// Restore the face name (e.g. when we press Escape key)
const FontInfo info = fontEntry()->info();
setText(info.title());
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()) {
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) {
const auto* keymsg = static_cast<const KeyMessage*>(msg);
switch (keymsg->scancode()) {
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:
case kKeyDown:
m_popup->focusListBox();
return true;
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;
}
break;
}
break;
@ -118,9 +157,18 @@ bool FontEntry::FontFace::onProcessMessage(Message* msg)
void FontEntry::FontFace::onChange()
{
base::ScopedValue lock(m_fromEntryChange, true);
SearchEntry::onChange();
m_popup->setSearchText(text());
// 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);
}
FontEntry::FontSize::FontSize()
@ -167,12 +215,13 @@ FontEntry::FontEntry()
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) {
setInfo(FontInfo(newTypeName,
m_info.size(),
m_info.style(),
m_info.flags()),
From::Face);
from);
invalidate();
});
@ -239,7 +288,8 @@ void FontEntry::setInfo(const FontInfo& info,
{
m_info = info;
m_face.setText(info.title());
if (fromField != From::Face)
m_face.setText(info.title());
if (fromField != From::Size)
m_size.setValue(fmt::format("{}", info.size()));
@ -254,7 +304,7 @@ void FontEntry::setInfo(const FontInfo& info,
m_antialias.setSelected(info.antialias());
}
FontChange(m_info);
FontChange(m_info, fromField);
}
} // namespace app

View File

@ -23,32 +23,36 @@ namespace app {
class FontEntry : public ui::HBox {
public:
enum class From {
User,
Init,
Face,
Size,
Style,
Flags,
Popup,
};
FontEntry();
~FontEntry();
FontInfo info() { return m_info; }
void setInfo(const FontInfo& info, From from = From::User);
void setInfo(const FontInfo& info, From from);
obs::signal<void(const FontInfo&)> FontChange;
obs::signal<void(const FontInfo&, From)> FontChange;
private:
class FontFace : public SearchEntry {
public:
FontFace();
obs::signal<void(const FontInfo&)> FontChange;
obs::signal<void(const FontInfo&, From)> FontChange;
protected:
bool onProcessMessage(ui::Message* msg) override;
void onChange() override;
private:
FontEntry* fontEntry() const { return static_cast<FontEntry*>(parent()); }
std::unique_ptr<FontPopup> m_popup;
bool m_fromEntryChange = false;
};
class FontSize : public ui::ComboBox {
@ -74,6 +78,7 @@ namespace app {
FontStyle m_style;
FontLigatures m_ligatures;
ui::CheckBox m_antialias;
bool m_lockFace = false;
};
} // namespace app

View File

@ -185,6 +185,14 @@ bool FontPopup::FontListBox::onProcessMessage(ui::Message* msg)
return result;
}
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;
}
FontPopup::FontPopup(const FontInfo& fontInfo)
: PopupWindow(std::string(),
ClickBehavior::CloseOnClickInOtherWindow,
@ -299,11 +307,6 @@ FontPopup::~FontPopup()
m_timer.stop();
}
void FontPopup::focusListBox()
{
m_listBox.requestFocus();
}
void FontPopup::setSearchText(const std::string& searchText)
{
FontItem* firstItem = nullptr;
@ -353,7 +356,7 @@ void FontPopup::onFontChange()
{
const FontInfo fontInfo = selectedFont();
if (fontInfo.isValid())
ChangeFont(fontInfo);
FontChange(fontInfo);
}
void FontPopup::onLoadFont()
@ -372,7 +375,7 @@ void FontPopup::onLoadFont()
return;
ASSERT(!face.empty());
ChangeFont(FontInfo(FontInfo::Type::File,
FontChange(FontInfo(FontInfo::Type::File,
face.front()));
}

View File

@ -31,24 +31,24 @@ namespace app {
class FontListBox : public ui::ListBox {
protected:
bool onProcessMessage(ui::Message* msg) override;
bool onAcceptKeyInput() override;
};
FontPopup(const FontInfo& fontInfo);
~FontPopup();
void focusListBox();
void setSearchText(const std::string& searchText);
void showPopup(ui::Display* display,
const gfx::Rect& buttonBounds);
FontListBox* getListBox() { return &m_listBox; }
FontInfo selectedFont();
obs::signal<void(const FontInfo&)> ChangeFont;
obs::signal<void(const FontInfo&)> FontChange;
obs::signal<void()> EscKey;
protected:
FontInfo currentFontInfo();
void onFontChange();
void onLoadFont();
void onThumbnailGenerated();
@ -56,8 +56,6 @@ namespace app {
bool onProcessMessage(ui::Message* msg) override;
private:
FontInfo selectedFont();
gen::FontPopup* m_popup;
FontListBox m_listBox;
ui::Timer m_timer;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -283,7 +283,7 @@ bool ListBox::onProcessMessage(Message* msg)
}
case kKeyDownMessage:
if (hasFocus() && !children().empty()) {
if (onAcceptKeyInput() && !children().empty()) {
int select = getSelectedIndex();
int bottom = std::max(0, int(children().size()-1));
View* view = View::getView(this);
@ -420,6 +420,11 @@ void ListBox::onDoubleClickItem()
DoubleClickItem();
}
bool ListBox::onAcceptKeyInput()
{
return hasFocus();
}
int ListBox::advanceIndexThroughVisibleItems(
int startIndex, int delta, const bool loop)
{

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -48,6 +48,7 @@ namespace ui {
virtual void onSizeHint(SizeHintEvent& ev) override;
virtual void onChange();
virtual void onDoubleClickItem();
virtual bool onAcceptKeyInput();
int advanceIndexThroughVisibleItems(
int startIndex, int delta, const bool loop);