diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index 149798b8f..41b164d42 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -13,6 +13,8 @@ class IOStream; namespace EE { namespace Graphics { +struct FontDesc; + class EE_API FontTrueType : public Font { public: static FontTrueType* New( const std::string& FontName ); @@ -35,6 +37,8 @@ class EE_API FontTrueType : public Font { const Font::Info& getInfo() const; + bool getFontDesc( FontDesc& desc ) const; + const Uint32& getFaceIndex() const { return mFaceIndex; } Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic, diff --git a/include/eepp/graphics/systemfontresolver.hpp b/include/eepp/graphics/systemfontresolver.hpp index 5b3577d9a..358f1e9d1 100644 --- a/include/eepp/graphics/systemfontresolver.hpp +++ b/include/eepp/graphics/systemfontresolver.hpp @@ -62,6 +62,32 @@ struct FontDesc { FontStretch stretch{ FontStretch::Normal }; bool italic{ false }; bool monospace{ false }; + + std::string getFileKey() const { return path + "#" + String::toString( faceIndex ); } + + std::string getStyleKey() const { + return String::toLower( family ) + "#" + String::toString( static_cast( weight ) ) + + "#" + String::toString( static_cast( stretch ) ) + "#" + + String::toString( italic ? 1 : 0 ) + "#" + String::toString( faceIndex ); + } + + bool sameFile( const FontDesc& other ) const { + return path == other.path && faceIndex == other.faceIndex; + } + + bool sameFile( const std::string& otherPath, Uint32 otherFaceIndex = 0 ) const { + return path == otherPath && faceIndex == otherFaceIndex; + } + + bool sameStyle( const FontDesc& other ) const { return getStyleKey() == other.getStyleKey(); } + + bool operator==( const FontDesc& other ) const { + return family == other.family && path == other.path && faceIndex == other.faceIndex && + weight == other.weight && stretch == other.stretch && italic == other.italic && + monospace == other.monospace; + } + + bool operator!=( const FontDesc& other ) const { return !( *this == other ); } }; class EE_API SystemFontResolver { diff --git a/include/eepp/ui/tools/uifontpickerdialog.hpp b/include/eepp/ui/tools/uifontpickerdialog.hpp index a8cce3b35..58d1858ed 100644 --- a/include/eepp/ui/tools/uifontpickerdialog.hpp +++ b/include/eepp/ui/tools/uifontpickerdialog.hpp @@ -33,11 +33,20 @@ struct UIFontSelection { bool strikeThrough{ false }; bool antialiasing{ true }; Color color{ Color::White }; + + bool operator==( const UIFontSelection& other ) const { + return font == other.font && size == other.size && underline == other.underline && + strikeThrough == other.strikeThrough && antialiasing == other.antialiasing && + color == other.color; + } + + bool operator!=( const UIFontSelection& other ) const { return !( *this == other ); } }; class EE_API UIFontPickerDialog : public UIWindow { public: using FontPickedCb = std::function; + using FontSelectionChangedCb = std::function; enum Flags { MonospaceOnly = 1 << 0, @@ -73,6 +82,8 @@ class EE_API UIFontPickerDialog : public UIWindow { void setOnFontPicked( FontPickedCb cb ); + void setOnFontSelectionChanged( FontSelectionChangedCb cb ); + UIPushButton* getButtonOK() const; UIPushButton* getButtonCancel() const; @@ -100,10 +111,13 @@ class EE_API UIFontPickerDialog : public UIWindow { KeyBindings::Shortcut mCloseShortcut{ KEY_UNKNOWN }; UIFontSelection mSelection; FontPickedCb mFontPickedCb; + FontSelectionChangedCb mFontSelectionChangedCb; std::vector mFonts; std::vector mFamilies; std::vector mStyles; std::vector mSizes; + UnorderedSet mLoadedFontKeys; + UnorderedMap mFontTags; std::shared_ptr mFamilyModel; std::shared_ptr mStyleModel; std::shared_ptr mSizeModel; @@ -125,13 +139,14 @@ class EE_API UIFontPickerDialog : public UIWindow { UICheckBox* mAntialiasing{ nullptr }; UICheckBox* mUnderline{ nullptr }; UICheckBox* mStrikeThrough{ nullptr }; - UIPushButton* mColorButton{ nullptr }; + UIWidget* mColorButton{ nullptr }; UIPushButton* mButtonOK{ nullptr }; UIPushButton* mButtonCancel{ nullptr }; UIPushButton* mButtonApply{ nullptr }; UIPushButton* mButtonBrowse{ nullptr }; bool mUpdating{ false }; bool mLoadingFonts{ false }; + bool mSelectionColorExplicit{ false }; Uint64 mLoadFontsTaskId{ 0 }; struct LoadFontsRequest { @@ -156,13 +171,21 @@ class EE_API UIFontPickerDialog : public UIWindow { void sortFonts(); + void mergeFontManagerFonts( std::vector& fonts ); + + void updateFontTags(); + void setFontsLoading( bool loading ); + void updateDefaultSelectionColor(); + void updateFamilies(); void updateStyles(); - void updateSelectionFromLists(); + void updateSelectionFromLists( bool notify = true ); + + void emitSelectionChanged(); void updatePreview(); @@ -170,6 +193,8 @@ class EE_API UIFontPickerDialog : public UIWindow { void selectFamily( const std::string& family ); + void selectRegularStyle(); + void selectStyle( const FontDesc& desc ); void selectSize( Uint32 size ); diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index c8ed8d345..b4b37b92c 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -545,6 +545,20 @@ const FontTrueType::Info& FontTrueType::getInfo() const { return mInfo; } +bool FontTrueType::getFontDesc( FontDesc& desc ) const { + if ( !loaded() || mInfo.filename.empty() || mInfo.fontpath.empty() ) + return false; + + desc = FontDesc{}; + desc.family = mInfo.family.empty() ? getName() : mInfo.family; + desc.path = mInfo.fontpath + mInfo.filename; + desc.faceIndex = getFaceIndex(); + desc.weight = isBold() || isBoldItalic() ? FontWeight::Bold : FontWeight::Normal; + desc.italic = isItalic() || isBoldItalic(); + desc.monospace = isIdentifiedAsMonospace(); + return !desc.family.empty() && !desc.path.empty(); +} + void FontTrueType::updateFontInternalId() { auto fontInternalId = fontsInternalIds.find( mInfo.family ); if ( fontsInternalIds.end() == fontInternalId ) { diff --git a/src/eepp/ui/tools/uifontpickerdialog.cpp b/src/eepp/ui/tools/uifontpickerdialog.cpp index 7fc3794d6..64d594144 100644 --- a/src/eepp/ui/tools/uifontpickerdialog.cpp +++ b/src/eepp/ui/tools/uifontpickerdialog.cpp @@ -23,6 +23,7 @@ #include #include #include +#include using namespace EE::UI::Abstract; using namespace EE::UI::Models; @@ -60,6 +61,9 @@ static const char* FONT_PICKER_STYLE = R"css( } #font_picker .color_button { min-width: 48dp; + border-color: var(--button-border); + border-radius: var(--button-radius); + border-width: var(--border-width); } #font_picker .options_panel { background-color: var(--list-back); @@ -108,7 +112,7 @@ static const char* FONT_PICKER_LAYOUT = R"xml( - + @@ -151,23 +155,57 @@ static Uint64 loadFontsTaskTag( const UIFontPickerDialog* dialog ) { return reinterpret_cast( dialog ); } -static std::string styleLabel( const FontDesc& desc ) { - std::string label; - if ( desc.weight == FontWeight::Normal ) { - label = desc.italic ? "Italic" : "Regular"; - } else { - label = Text::fontWeightToString( desc.weight ); - std::replace( label.begin(), label.end(), '-', ' ' ); - if ( !label.empty() ) { - label[0] = static_cast( std::toupper( label[0] ) ); - for ( size_t i = 1; i < label.size(); i++ ) { - if ( label[i - 1] == ' ' ) - label[i] = static_cast( std::toupper( label[i] ) ); - } - } - if ( desc.italic ) - label += " Italic"; +static std::string titleCaseStyleName( std::string label ) { + std::replace( label.begin(), label.end(), '-', ' ' ); + for ( size_t i = 0; i < label.size(); i++ ) { + if ( i == 0 || label[i - 1] == ' ' ) + label[i] = static_cast( std::toupper( label[i] ) ); } + return label; +} + +static std::string stretchLabel( FontStretch stretch ) { + switch ( stretch ) { + case FontStretch::UltraCondensed: + return "Ultra Condensed"; + case FontStretch::ExtraCondensed: + return "Extra Condensed"; + case FontStretch::Condensed: + return "Condensed"; + case FontStretch::SemiCondensed: + return "Semi Condensed"; + case FontStretch::Normal: + return ""; + case FontStretch::SemiExpanded: + return "Semi Expanded"; + case FontStretch::Expanded: + return "Expanded"; + case FontStretch::ExtraExpanded: + return "Extra Expanded"; + case FontStretch::UltraExpanded: + return "Ultra Expanded"; + } + return ""; +} + +static std::string styleLabel( const FontDesc& desc ) { + std::string label( stretchLabel( desc.stretch ) ); + + if ( desc.weight != FontWeight::Normal ) { + if ( !label.empty() ) + label += " "; + label += titleCaseStyleName( Text::fontWeightToString( desc.weight ) ); + } + + if ( desc.italic ) { + if ( !label.empty() ) + label += " "; + label += "Italic"; + } + + if ( label.empty() ) + label = "Regular"; + if ( desc.faceIndex != 0 ) label += " #" + String::toString( desc.faceIndex ); return label; @@ -209,6 +247,8 @@ UIFontPickerDialog::UIFontPickerDialog( Uint32 flags ) : UIWindow(), mFlags( fla loadWidgets(); if ( getUISceneNode()->getUIThemeManager()->getDefaultTheme() ) setTheme( getUISceneNode()->getUIThemeManager()->getDefaultTheme() ); + else + updateDefaultSelectionColor(); selectInitialRows(); loadFonts(); } @@ -259,6 +299,7 @@ void UIFontPickerDialog::setTheme( UITheme* theme ) { } onThemeLoaded(); + updateDefaultSelectionColor(); } void UIFontPickerDialog::loadWidgets() { @@ -397,16 +438,17 @@ void UIFontPickerDialog::loadFonts() { void UIFontPickerDialog::setFonts( std::vector fonts ) { FontDesc selectedFont = mSelection.font; + mergeFontManagerFonts( fonts ); for ( const auto& font : mFonts ) { - auto found = std::find_if( fonts.begin(), fonts.end(), [&]( const auto& desc ) { - return desc.path == font.path && desc.faceIndex == font.faceIndex; - } ); - if ( found == fonts.end() ) + if ( std::find_if( fonts.begin(), fonts.end(), [&]( const FontDesc& desc ) { + return desc.sameFile( font ); + } ) == fonts.end() ) fonts.push_back( font ); } mFonts = std::move( fonts ); sortFonts(); + updateFontTags(); setFontsLoading( false ); updateFamilies(); if ( !selectedFont.path.empty() || !selectedFont.family.empty() ) @@ -429,6 +471,42 @@ void UIFontPickerDialog::sortFonts() { } ); } +void UIFontPickerDialog::mergeFontManagerFonts( std::vector& fonts ) { + FontManager::instance()->each( [&]( const auto& res ) { + if ( res.second == nullptr || res.second->getType() != FontType::TTF ) + return; + + FontDesc desc; + if ( !static_cast( res.second )->getFontDesc( desc ) ) + return; + + mLoadedFontKeys.insert( desc.getFileKey() ); + if ( std::find_if( fonts.begin(), fonts.end(), [&]( const FontDesc& font ) { + return font.sameFile( desc ); + } ) == fonts.end() ) + fonts.push_back( desc ); + } ); +} + +void UIFontPickerDialog::updateFontTags() { + mFontTags.clear(); + + std::unordered_map styleCounts; + for ( const auto& font : mFonts ) + styleCounts[font.getStyleKey()]++; + + for ( const auto& font : mFonts ) { + const auto styleCountIt = styleCounts.find( font.getStyleKey() ); + if ( styleCountIt == styleCounts.end() || styleCountIt->second < 2 ) + continue; + if ( mLoadedFontKeys.find( font.getFileKey() ) == mLoadedFontKeys.end() ) + continue; + + const std::string fileName( FileSystem::fileNameFromPath( font.path ) ); + mFontTags[font.getFileKey()] = fileName.empty() ? font.path : fileName; + } +} + void UIFontPickerDialog::setFontsLoading( bool loading ) { mLoadingFonts = loading; if ( mFontContent ) @@ -445,6 +523,11 @@ void UIFontPickerDialog::setFontsLoading( bool loading ) { mButtonApply->setEnabled( !loading && ( mFlags & ShowApplyButton ) != 0 ); } +void UIFontPickerDialog::updateDefaultSelectionColor() { + if ( !mSelectionColorExplicit && mPreviewText ) + mSelection.color = mPreviewText->getFontColor(); +} + bool UIFontPickerDialog::wantsMonospaceOnly() const { return mMonospaceOnly && mMonospaceOnly->isChecked(); } @@ -488,8 +571,13 @@ void UIFontPickerDialog::updateStyles() { if ( row >= 0 && row < static_cast( mFamilies.size() ) ) { const std::string& family = mFamilies[row]; for ( const auto& font : mFonts ) { - if ( font.family == family && ( !wantsMonospaceOnly() || font.monospace ) ) - mStyles.push_back( { styleLabel( font ), font } ); + if ( font.family == family && ( !wantsMonospaceOnly() || font.monospace ) ) { + std::string label( styleLabel( font ) ); + auto tagIt = mFontTags.find( font.getFileKey() ); + if ( tagIt != mFontTags.end() ) + label += " [" + tagIt->second + "]"; + mStyles.push_back( { label, font } ); + } } } } @@ -500,13 +588,18 @@ void UIFontPickerDialog::updateStyles() { mUpdating = false; if ( !mStyles.empty() ) { - selectStyle( mSelection.font ); + const std::string selectedFamily( mStyles.front().desc.family ); + if ( selectedFamily == mSelection.font.family ) + selectStyle( mSelection.font ); + else + selectRegularStyle(); if ( mStyleList->getSelection().isEmpty() ) mStyleList->setSelection( mStyleModel->index( 0 ) ); } } -void UIFontPickerDialog::updateSelectionFromLists() { +void UIFontPickerDialog::updateSelectionFromLists( bool notify ) { + UIFontSelection previousSelection = mSelection; if ( !mStyleList->getSelection().isEmpty() ) { const Int64 row = mStyleList->getSelection().first().row(); if ( row >= 0 && row < static_cast( mStyles.size() ) ) @@ -520,6 +613,15 @@ void UIFontPickerDialog::updateSelectionFromLists() { mSelection.antialiasing = mAntialiasing->isChecked(); mSelection.underline = mUnderline->isChecked(); mSelection.strikeThrough = mStrikeThrough->isChecked(); + + if ( notify && previousSelection != mSelection ) + emitSelectionChanged(); +} + +void UIFontPickerDialog::emitSelectionChanged() { + if ( mFontSelectionChangedCb ) + mFontSelectionChangedCb( mSelection ); + sendCommonEvent( Event::OnValueChange ); } void UIFontPickerDialog::updatePreview() { @@ -574,13 +676,25 @@ void UIFontPickerDialog::selectFamily( const std::string& family ) { } } +void UIFontPickerDialog::selectRegularStyle() { + if ( !mStyleModel ) + return; + + for ( size_t i = 0; i < mStyles.size(); i++ ) { + const auto& style = mStyles[i].desc; + if ( style.weight == FontWeight::Normal && !style.italic ) { + mStyleList->setSelection( mStyleModel->index( i ) ); + return; + } + } +} + void UIFontPickerDialog::selectStyle( const FontDesc& desc ) { if ( desc.family.empty() || !mStyleModel ) return; for ( size_t i = 0; i < mStyles.size(); i++ ) { const auto& style = mStyles[i].desc; - if ( style.path == desc.path && style.faceIndex == desc.faceIndex && - style.weight == desc.weight && style.italic == desc.italic ) { + if ( style.sameFile( desc ) && style.sameStyle( desc ) ) { mStyleList->setSelection( mStyleModel->index( i ) ); return; } @@ -612,9 +726,11 @@ void UIFontPickerDialog::pickColor() { mColorPicker = UIColorPicker::NewWindow( [this]( Color color ) { mSelection.color = color; + mSelectionColorExplicit = true; updatePreview(); } ); mColorPicker->setColor( mSelection.color ); + mColorPicker->getUIWindow()->center(); mColorPickerCloseCb = mColorPicker->getUIWindow()->on( Event::OnWindowClose, [this]( const Event* ) { mColorPicker = nullptr; @@ -654,7 +770,7 @@ bool UIFontPickerDialog::addExternalFont( const std::string& path, Uint32 faceIn return false; auto found = std::find_if( mFonts.begin(), mFonts.end(), [&]( const auto& desc ) { - return desc.path == path && desc.faceIndex == faceIndex; + return desc.sameFile( path, faceIndex ); } ); if ( found != mFonts.end() ) { @@ -673,16 +789,16 @@ bool UIFontPickerDialog::addExternalFont( const std::string& path, Uint32 faceIn } FontDesc desc; - desc.family = font->getInfo().family.empty() ? fontName : font->getInfo().family; - desc.path = path; - desc.faceIndex = font->getFaceIndex(); - desc.weight = font->isBold() || font->isBoldItalic() ? FontWeight::Bold : FontWeight::Normal; - desc.italic = font->isItalic() || font->isBoldItalic(); - desc.monospace = font->isIdentifiedAsMonospace(); + if ( !font->getFontDesc( desc ) ) { + eeSAFE_DELETE( font ); + return false; + } eeSAFE_DELETE( font ); + mLoadedFontKeys.insert( desc.getFileKey() ); mFonts.push_back( desc ); sortFonts(); + updateFontTags(); if ( !mSearchInput->getText().empty() ) mSearchInput->setText( "" ); @@ -706,11 +822,11 @@ void UIFontPickerDialog::clearBrowseDialog() { } void UIFontPickerDialog::setSelectedFont( const FontDesc& desc ) { + UIFontSelection previousSelection = mSelection; FontDesc selection = desc; if ( !desc.path.empty() ) { - auto found = std::find_if( mFonts.begin(), mFonts.end(), [&]( const auto& font ) { - return font.path == desc.path && font.faceIndex == desc.faceIndex; - } ); + auto found = std::find_if( mFonts.begin(), mFonts.end(), + [&]( const auto& font ) { return font.sameFile( desc ); } ); if ( found == mFonts.end() && addExternalFont( desc.path, desc.faceIndex ) ) return; if ( found != mFonts.end() ) @@ -723,12 +839,15 @@ void UIFontPickerDialog::setSelectedFont( const FontDesc& desc ) { selectFamily( selection.family ); updateStyles(); selectStyle( selection ); + updateSelectionFromLists( false ); + if ( previousSelection != mSelection ) + emitSelectionChanged(); updatePreview(); } void UIFontPickerDialog::setSelectedFont( const std::string& path, Uint32 faceIndex ) { auto found = std::find_if( mFonts.begin(), mFonts.end(), [&]( const auto& desc ) { - return desc.path == path && desc.faceIndex == faceIndex; + return desc.sameFile( path, faceIndex ); } ); if ( found != mFonts.end() ) { setSelectedFont( *found ); @@ -744,6 +863,7 @@ const UIFontSelection& UIFontPickerDialog::getSelection() const { void UIFontPickerDialog::setSelection( const UIFontSelection& selection ) { mSelection = selection; + mSelectionColorExplicit = true; if ( mAntialiasing ) mAntialiasing->setChecked( selection.antialiasing ); if ( mUnderline ) @@ -758,6 +878,10 @@ void UIFontPickerDialog::setOnFontPicked( FontPickedCb cb ) { mFontPickedCb = std::move( cb ); } +void UIFontPickerDialog::setOnFontSelectionChanged( FontSelectionChangedCb cb ) { + mFontSelectionChangedCb = std::move( cb ); +} + UIPushButton* UIFontPickerDialog::getButtonOK() const { return mButtonOK; } diff --git a/src/tests/unit_tests/uifontpickerdialog_tests.cpp b/src/tests/unit_tests/uifontpickerdialog_tests.cpp index 79e717a74..006c7ff87 100644 --- a/src/tests/unit_tests/uifontpickerdialog_tests.cpp +++ b/src/tests/unit_tests/uifontpickerdialog_tests.cpp @@ -1,6 +1,10 @@ #include "utest.hpp" +#include +#include +#include #include #include +#include #include #include #include @@ -9,8 +13,33 @@ using namespace EE; using namespace EE::UI; +using namespace EE::UI::Abstract; using namespace EE::UI::Tools; +static std::string expectedStretchLabel( FontStretch stretch ) { + switch ( stretch ) { + case FontStretch::UltraCondensed: + return "Ultra Condensed"; + case FontStretch::ExtraCondensed: + return "Extra Condensed"; + case FontStretch::Condensed: + return "Condensed"; + case FontStretch::SemiCondensed: + return "Semi Condensed"; + case FontStretch::Normal: + return ""; + case FontStretch::SemiExpanded: + return "Semi Expanded"; + case FontStretch::Expanded: + return "Expanded"; + case FontStretch::ExtraExpanded: + return "Extra Expanded"; + case FontStretch::UltraExpanded: + return "Ultra Expanded"; + } + return ""; +} + template static void pumpUntil( UISceneNode* sceneNode, Predicate predicate ) { for ( size_t i = 0; i < 1000 && !predicate(); i++ ) { sceneNode->update( Time::Zero ); @@ -67,6 +96,92 @@ UTEST( UIFontPickerDialog, AsyncLoadPreservesExternalFontPreselection ) { EXPECT_FALSE( dialog->getStyleList()->getSelection().isEmpty() ); } +UTEST( UIFontPickerDialog, DefaultColorComesFromTheme ) { + UIApplication app( + WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + UIFontPickerDialog* dialog = UIFontPickerDialog::New(); + + EXPECT_TRUE( dialog->getSelection().color != Color::White ); + EXPECT_TRUE( dialog->getSelection().color.a != 0 ); +} + +UTEST( UIFontPickerDialog, IncludesDiskFontsLoadedInFontManager ) { + UIApplication app( + WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + const std::string fontPath = Sys::getProcessPath() + "assets/fonts/NotoSansKR-Regular.ttf"; + ASSERT_TRUE( FileSystem::fileExists( fontPath ) ); + + FontTrueType* font = FontTrueType::New( "UIFontPickerDialogManagedNotoSansKR", fontPath ); + ASSERT_TRUE( font != nullptr ); + ASSERT_TRUE( font->loaded() ); + ASSERT_FALSE( font->getInfo().family.empty() ); + + UIFontPickerDialog* dialog = UIFontPickerDialog::New(); + const ModelIndex familyIndex = dialog->getFamilyList()->findRowWithText( + font->getInfo().family, true, UIAbstractView::FindRowWithTextMatchKind::Equals ); + ASSERT_TRUE( familyIndex.isValid() ); + dialog->getFamilyList()->setSelection( familyIndex ); + + bool foundManagedFont = false; + Model* styleModel = dialog->getStyleList()->getModel(); + ASSERT_TRUE( styleModel != nullptr ); + for ( size_t row = 0; row < styleModel->rowCount(); row++ ) { + dialog->getStyleList()->setSelection( styleModel->index( row ) ); + if ( dialog->getSelection().font.path == fontPath && + dialog->getSelection().font.faceIndex == font->getFaceIndex() ) { + foundManagedFont = true; + break; + } + } + + EXPECT_TRUE( foundManagedFont ); +} + +UTEST( UIFontPickerDialog, StyleLabelsIncludeFontStretch ) { + std::vector fonts = SystemFontResolver::instance()->enumerate(); + auto fontIt = std::find_if( fonts.begin(), fonts.end(), []( const FontDesc& font ) { + return font.stretch != FontStretch::Normal && !font.family.empty() && !font.path.empty(); + } ); + if ( fontIt == fonts.end() ) + UTEST_SKIP( "no non-normal font stretch available" ); + + UIApplication app( + WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + UIFontPickerDialog* dialog = UIFontPickerDialog::New(); + const ModelIndex familyIndex = dialog->getFamilyList()->findRowWithText( + fontIt->family, true, UIAbstractView::FindRowWithTextMatchKind::Equals ); + ASSERT_TRUE( familyIndex.isValid() ); + dialog->getFamilyList()->setSelection( familyIndex ); + + const std::string stretchText( expectedStretchLabel( fontIt->stretch ) ); + ASSERT_FALSE( stretchText.empty() ); + + bool foundStyle = false; + Model* styleModel = dialog->getStyleList()->getModel(); + ASSERT_TRUE( styleModel != nullptr ); + for ( size_t row = 0; row < styleModel->rowCount(); row++ ) { + ModelIndex index = styleModel->index( row ); + dialog->getStyleList()->setSelection( index ); + if ( dialog->getSelection().font.sameFile( *fontIt ) && + dialog->getSelection().font.sameStyle( *fontIt ) ) { + foundStyle = true; + EXPECT_TRUE( index.data().toString().find( stretchText ) != std::string::npos ); + break; + } + } + + EXPECT_TRUE( foundStyle ); +} + UTEST( UIFontPickerDialog, ApplyButtonEmitsOnApply ) { UIApplication app( WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 31ac7a91c..dee51bd74 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3,6 +3,7 @@ #include "customwidgets.hpp" #include "datetimecontroller.hpp" #include "featureshealth.hpp" +#include "fontpickercontroller.hpp" #include "keybindingshelper.hpp" #include "pathhelper.hpp" #include "settingsactions.hpp" @@ -442,89 +443,8 @@ void App::openFolderDialog() { void App::openFontDialog( std::string& fontPath, bool loadingMonoFont, bool terminalFont, std::function onFinish ) { - std::string absoluteFontPath( fontPath ); - if ( FileSystem::isRelativePath( absoluteFontPath ) ) - absoluteFontPath = mResPath + fontPath; - UIFileDialog* dialog = UIFileDialog::New( - UIFileDialog::DefaultFlags | - ( mConfig.ui.nativeFileDialogs ? UIFileDialog::UseNativeFileDialog : 0 ), - "*.ttf; *.otf; *.woff; *.woff2; *.otb; *.bdf; *.ttc", - FileSystem::fileRemoveFileName( absoluteFontPath ) ); - if ( dialog->getMultiView() ) { - ModelIndex index = dialog->getMultiView()->getListView()->findRowWithText( - FileSystem::fileNameFromPath( fontPath ), true, - UIAbstractView::FindRowWithTextMatchKind::Equals ); - if ( index.isValid() ) - dialog->runOnMainThread( - [dialog, index]() { dialog->getMultiView()->setSelection( index ); } ); - } - dialog->setWindowFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); - dialog->setTitle( i18n( "select_font_file", "Select Font File" ) ); - dialog->setCloseShortcut( KEY_ESCAPE ); - dialog->setSingleClickNavigation( mConfig.editor.singleClickNavigation ); - dialog->on( Event::OnWindowClose, [this]( const Event* ) { - if ( App::instance() && mSplitter && mSplitter->getCurWidget() && - !SceneManager::instance()->isShuttingDown() ) { - mSplitter->getCurWidget()->setFocus(); - } - } ); - dialog->on( Event::OpenFile, [this, &fontPath, loadingMonoFont, terminalFont, - onFinish]( const Event* event ) { - auto newPath = event->getNode()->asType()->getFullPath(); - if ( String::startsWith( newPath, mResPath ) ) - newPath = newPath.substr( mResPath.size() ); - if ( fontPath != newPath ) { - if ( !loadingMonoFont ) { - fontPath = newPath; - if ( onFinish ) - onFinish(); - return; - } - auto fontName = - FileSystem::fileRemoveExtension( FileSystem::fileNameFromPath( newPath ) ); - FontTrueType* fontMono = loadFont( fontName, newPath ); - if ( fontMono ) { - auto loadMonoFont = [this, &fontPath, newPath, - terminalFont]( FontTrueType* fontMono ) { - fontPath = newPath; - if ( terminalFont ) - mTerminalFont = fontMono; - else - mFontMono = fontMono; - fontMono->setEnableDynamicMonospace( true ); - fontMono->setBoldAdvanceSameAsRegular( true ); - FontFamily::loadFromRegular( fontMono ); - if ( mSplitter ) { - if ( terminalFont ) { - mSplitter->forEachWidgetType( - UI_TYPE_TERMINAL, [fontMono]( UIWidget* term ) { - term->asType()->setFont( fontMono ); - } ); - } else { - mSplitter->forEachEditor( [fontMono]( UICodeEditor* editor ) { - editor->setFont( fontMono ); - } ); - - if ( auto buildOutputEditor = - mUISceneNode->find( "build_output_output" ) ) - buildOutputEditor->setFont( fontMono ); - - if ( auto appOutputEditor = - mUISceneNode->find( "app_output_output" ) ) - appOutputEditor->setFont( fontMono ); - - if ( mConfig.ui.editorFontInInputFields ) - updateInputFonts(); - } - } - }; - - loadMonoFont( fontMono ); - } - } - } ); - dialog->center(); - dialog->show(); + mFontPickerController->openFontDialog( fontPath, loadingMonoFont, terminalFont, + std::move( onFinish ) ); } void App::updateInputFonts() { @@ -1109,6 +1029,7 @@ App::App( const size_t& jobs, const std::vector& args ) : mThreadPool( ThreadPool::createShared( jobs > 0 ? jobs : eemax( 4, Sys::getCPUCount() ) ) ), mDateTimeController( std::make_unique( this ) ), + mFontPickerController( std::make_unique( this ) ), mSettingsActions( std::make_unique( this ) ) {} static void fsRemoveAll( const std::string& fpath ) { diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 587fb6c1e..a6f20ec70 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -32,6 +32,7 @@ class AutoCompletePlugin; class LinterPlugin; class FormatterPlugin; class DateTimeController; +class FontPickerController; class SettingsMenu; class UITreeViewFS; @@ -655,6 +656,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { size_t getMenuIconSize() const { return mMenuIconSize; } protected: + friend class FontPickerController; + std::vector mArgs; EE::Window::Window* mWindow{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; @@ -750,6 +753,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::unique_ptr mPluginManager; std::unique_ptr mSettings; std::unique_ptr mDateTimeController; + std::unique_ptr mFontPickerController; std::string mFileToOpen; UITheme* mTheme{ nullptr }; UIStatusBar* mStatusBar{ nullptr }; diff --git a/src/tools/ecode/fontpickercontroller.cpp b/src/tools/ecode/fontpickercontroller.cpp new file mode 100644 index 000000000..81a5a8ed8 --- /dev/null +++ b/src/tools/ecode/fontpickercontroller.cpp @@ -0,0 +1,161 @@ +#include "fontpickercontroller.hpp" +#include "ecode.hpp" +#include +#include +#include +#include +#include +#include + +namespace ecode { + +struct MonospaceFontPreview { + std::string originalPath; + std::string previewPath; + FontTrueType* originalFont{ nullptr }; + bool confirmed{ false }; + UnorderedMap loadedFonts; +}; + +void FontPickerController::openFontDialog( std::string& fontPath, bool loadingMonoFont, + bool terminalFont, std::function onFinish ) { + std::string absoluteFontPath( fontPath ); + if ( FileSystem::isRelativePath( absoluteFontPath ) ) + absoluteFontPath = mApp->resPath() + fontPath; + + const auto normalizedFontPath = [this]( std::string path ) { + if ( String::startsWith( path, mApp->resPath() ) ) + path = path.substr( mApp->resPath().size() ); + return path; + }; + + const auto applyMonospaceFont = [this, terminalFont]( FontTrueType* fontMono ) { + if ( !fontMono ) + return; + + if ( terminalFont ) + mApp->mTerminalFont = fontMono; + else + mApp->mFontMono = fontMono; + + fontMono->setEnableDynamicMonospace( true ); + fontMono->setBoldAdvanceSameAsRegular( true ); + FontFamily::loadFromRegular( fontMono ); + + if ( !mApp->getSplitter() ) + return; + + if ( terminalFont ) { + mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, [fontMono]( UIWidget* term ) { + term->asType()->setFont( fontMono ); + } ); + } else { + mApp->getSplitter()->forEachEditor( + [fontMono]( UICodeEditor* editor ) { editor->setFont( fontMono ); } ); + + if ( auto buildOutputEditor = + mApp->uiSceneNode()->find( "build_output_output" ) ) + buildOutputEditor->setFont( fontMono ); + + if ( auto appOutputEditor = + mApp->uiSceneNode()->find( "app_output_output" ) ) + appOutputEditor->setFont( fontMono ); + + if ( mApp->getConfig().ui.editorFontInInputFields ) + mApp->updateInputFonts(); + } + }; + + std::shared_ptr preview; + if ( loadingMonoFont ) { + preview = std::make_shared(); + preview->originalPath = normalizedFontPath( fontPath ); + preview->previewPath = preview->originalPath; + preview->originalFont = static_cast( terminalFont ? mApp->getTerminalFont() + : mApp->getFontMono() ); + } + + const auto loadPreviewFont = [this, preview]( const std::string& newPath ) -> FontTrueType* { + if ( !preview ) + return nullptr; + + auto found = preview->loadedFonts.find( newPath ); + if ( found != preview->loadedFonts.end() ) + return found->second; + + auto fontName = FileSystem::fileRemoveExtension( FileSystem::fileNameFromPath( newPath ) ); + FontTrueType* fontMono = mApp->loadFont( fontName, newPath ); + if ( fontMono ) + preview->loadedFonts[newPath] = fontMono; + return fontMono; + }; + + const Uint32 flags = UIFontPickerDialog::DefaultFlags | + ( loadingMonoFont ? UIFontPickerDialog::MonospaceOnly : 0 ); + UIFontPickerDialog* dialog = UIFontPickerDialog::New( flags ); + dialog->setTitle( mApp->i18n( "select_font", "Select Font" ) ); + dialog->setCloseShortcut( KEY_ESCAPE ); + dialog->on( Event::OnWindowClose, [this, preview, applyMonospaceFont]( const Event* ) { + if ( preview && !preview->confirmed ) + applyMonospaceFont( preview->originalFont ); + + if ( App::instance() && mApp->getSplitter() && mApp->getSplitter()->getCurWidget() && + !SceneManager::instance()->isShuttingDown() ) { + mApp->getSplitter()->getCurWidget()->setFocus(); + } + } ); + if ( loadingMonoFont ) { + dialog->setOnFontSelectionChanged( + [preview, normalizedFontPath, loadPreviewFont, + applyMonospaceFont]( const UIFontSelection& selection ) { + auto newPath = normalizedFontPath( selection.font.path ); + if ( !preview || newPath.empty() || preview->previewPath == newPath ) + return; + + if ( newPath == preview->originalPath ) { + applyMonospaceFont( preview->originalFont ); + preview->previewPath = newPath; + return; + } + + FontTrueType* fontMono = loadPreviewFont( newPath ); + if ( fontMono ) { + applyMonospaceFont( fontMono ); + preview->previewPath = newPath; + } + } ); + } + dialog->setOnFontPicked( [&fontPath, loadingMonoFont, onFinish, preview, normalizedFontPath, + loadPreviewFont, + applyMonospaceFont]( const UIFontSelection& selection ) { + auto newPath = normalizedFontPath( selection.font.path ); + if ( newPath.empty() ) + return; + if ( fontPath != newPath ) { + if ( !loadingMonoFont ) { + fontPath = newPath; + if ( onFinish ) + onFinish(); + return; + } + + FontTrueType* fontMono = preview && newPath == preview->originalPath + ? preview->originalFont + : loadPreviewFont( newPath ); + if ( fontMono ) { + fontPath = newPath; + if ( preview ) + preview->confirmed = true; + applyMonospaceFont( fontMono ); + } + } else if ( preview ) { + preview->confirmed = true; + applyMonospaceFont( preview->originalFont ); + } + } ); + dialog->setSelectedFont( absoluteFontPath ); + dialog->center(); + dialog->show(); +} + +} // namespace ecode diff --git a/src/tools/ecode/fontpickercontroller.hpp b/src/tools/ecode/fontpickercontroller.hpp new file mode 100644 index 000000000..e8533cb1d --- /dev/null +++ b/src/tools/ecode/fontpickercontroller.hpp @@ -0,0 +1,24 @@ +#ifndef ECODE_FONTPICKERCONTROLLER_HPP +#define ECODE_FONTPICKERCONTROLLER_HPP + +#include +#include + +namespace ecode { + +class App; + +class FontPickerController { + public: + explicit FontPickerController( App* app ) : mApp( app ) {} + + void openFontDialog( std::string& fontPath, bool loadingMonoFont, bool terminalFont = false, + std::function onFinish = {} ); + + private: + App* mApp{ nullptr }; +}; + +} // namespace ecode + +#endif