mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
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:
parent
b90807f3e3
commit
4c06fbdaf3
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user