diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 9e6098a1d..4486118bc 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -381,6 +381,12 @@ "command": "${project_root}/bin/eepp-ui-html-debug", "name": "eepp-ui-html-debug", "working_dir": "${project_root}/bin" + }, + { + "args": "", + "command": "${project_root}/bin/eepp-ui-font-picker-debug", + "name": "eepp-ui-font-picker-debug", + "working_dir": "${project_root}/bin" } ], "var": { diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index 30fd8bd25..149798b8f 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -35,6 +35,8 @@ class EE_API FontTrueType : public Font { const Font::Info& getInfo() const; + const Uint32& getFaceIndex() const { return mFaceIndex; } + Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic, Float outlineThickness = 0 ) const; diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index 3e0acb855..3be68ae37 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -131,6 +131,7 @@ class EE_API Event { OnFocusWithin, OnFocusWithinLoss, OnItemsCountChange, + OnApply, NoEvent = eeINDEX_NOT_FOUND }; diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index 908beb8f6..ee836c590 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -74,6 +74,7 @@ #include #include #include +#include #include #include #include @@ -141,7 +142,6 @@ #include #include #include -#include #include #include #include @@ -172,6 +172,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eepp/ui/tools/uifontpickerdialog.hpp b/include/eepp/ui/tools/uifontpickerdialog.hpp new file mode 100644 index 000000000..a8cce3b35 --- /dev/null +++ b/include/eepp/ui/tools/uifontpickerdialog.hpp @@ -0,0 +1,192 @@ +#ifndef EE_UI_TOOLS_UIFONTPICKERDIALOG_HPP +#define EE_UI_TOOLS_UIFONTPICKERDIALOG_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EE { namespace UI { +class UICheckBox; +class UIFileDialog; +class UILoader; +class UIListView; +class UIPushButton; +class UITextInput; +class UITextView; +}} // namespace EE::UI + +namespace EE { namespace UI { namespace Tools { + +class UIColorPicker; + +struct UIFontSelection { + FontDesc font; + Uint32 size{ 12 }; + bool underline{ false }; + bool strikeThrough{ false }; + bool antialiasing{ true }; + Color color{ Color::White }; +}; + +class EE_API UIFontPickerDialog : public UIWindow { + public: + using FontPickedCb = std::function; + + enum Flags { + MonospaceOnly = 1 << 0, + ShowSize = 1 << 1, + ShowEffects = 1 << 2, + ShowColor = 1 << 3, + ShowApplyButton = 1 << 4, + DefaultFlags = ShowSize | ShowEffects | ShowColor, + }; + + struct FontStyleEntry { + std::string label; + FontDesc desc; + }; + + static UIFontPickerDialog* New( Uint32 flags = DefaultFlags ); + + virtual ~UIFontPickerDialog(); + + virtual Uint32 getType() const; + + virtual bool isType( const Uint32& type ) const; + + virtual void setTheme( UITheme* theme ); + + void setSelectedFont( const FontDesc& desc ); + + void setSelectedFont( const std::string& path, Uint32 faceIndex = 0 ); + + const UIFontSelection& getSelection() const; + + void setSelection( const UIFontSelection& selection ); + + void setOnFontPicked( FontPickedCb cb ); + + UIPushButton* getButtonOK() const; + + UIPushButton* getButtonCancel() const; + + UIPushButton* getButtonApply() const; + + UIPushButton* getButtonBrowse() const; + + UITextInput* getSearchInput() const; + + UIListView* getFamilyList() const; + + UIListView* getStyleList() const; + + UIListView* getSizeList() const; + + const KeyBindings::Shortcut& getCloseShortcut() const; + + void setCloseShortcut( const KeyBindings::Shortcut& closeShortcut ); + + virtual bool show(); + + protected: + Uint32 mFlags{ DefaultFlags }; + KeyBindings::Shortcut mCloseShortcut{ KEY_UNKNOWN }; + UIFontSelection mSelection; + FontPickedCb mFontPickedCb; + std::vector mFonts; + std::vector mFamilies; + std::vector mStyles; + std::vector mSizes; + std::shared_ptr mFamilyModel; + std::shared_ptr mStyleModel; + std::shared_ptr mSizeModel; + UIWidget* mFontContent{ nullptr }; + UILoader* mFontLoader{ nullptr }; + UITextInput* mSearchInput{ nullptr }; + UIListView* mFamilyList{ nullptr }; + UIListView* mStyleList{ nullptr }; + UIListView* mSizeList{ nullptr }; + UITextView* mPreviewText{ nullptr }; + UITextInput* mPreviewInput{ nullptr }; + UITextView* mDetailsText{ nullptr }; + UIColorPicker* mColorPicker{ nullptr }; + UIFileDialog* mBrowseDialog{ nullptr }; + Uint32 mColorPickerCloseCb{ 0 }; + Uint32 mBrowseDialogCloseCb{ 0 }; + Uint32 mBrowseDialogOpenCb{ 0 }; + UICheckBox* mMonospaceOnly{ nullptr }; + UICheckBox* mAntialiasing{ nullptr }; + UICheckBox* mUnderline{ nullptr }; + UICheckBox* mStrikeThrough{ nullptr }; + UIPushButton* mColorButton{ nullptr }; + UIPushButton* mButtonOK{ nullptr }; + UIPushButton* mButtonCancel{ nullptr }; + UIPushButton* mButtonApply{ nullptr }; + UIPushButton* mButtonBrowse{ nullptr }; + bool mUpdating{ false }; + bool mLoadingFonts{ false }; + Uint64 mLoadFontsTaskId{ 0 }; + + struct LoadFontsRequest { + std::atomic alive{ true }; + std::atomic dialog{ nullptr }; + }; + std::shared_ptr mLoadFontsRequest; + + UIFontPickerDialog( Uint32 flags = DefaultFlags ); + + virtual void onWindowReady(); + + virtual Uint32 onKeyUp( const KeyEvent& event ); + + virtual Uint32 onMessage( const NodeMessage* msg ); + + void loadWidgets(); + + void loadFonts(); + + void setFonts( std::vector fonts ); + + void sortFonts(); + + void setFontsLoading( bool loading ); + + void updateFamilies(); + + void updateStyles(); + + void updateSelectionFromLists(); + + void updatePreview(); + + void selectInitialRows(); + + void selectFamily( const std::string& family ); + + void selectStyle( const FontDesc& desc ); + + void selectSize( Uint32 size ); + + void emitPicked(); + + void pickColor(); + + void browseFont(); + + bool addExternalFont( const std::string& path, Uint32 faceIndex = 0 ); + + void clearBrowseDialog(); + + bool wantsMonospaceOnly() const; +}; + +}}} // namespace EE::UI::Tools + +#endif diff --git a/include/eepp/ui/uihelper.hpp b/include/eepp/ui/uihelper.hpp index d71fd0af3..9566aca49 100644 --- a/include/eepp/ui/uihelper.hpp +++ b/include/eepp/ui/uihelper.hpp @@ -138,6 +138,7 @@ enum UINodeType { UI_TYPE_WEBVIEW, UI_TYPE_SVG, UI_TYPE_TEXTNODE, + UI_TYPE_FONTPICKERDIALOG, UI_TYPE_MODULES = 10000, UI_TYPE_TERMINAL = 10001, UI_TYPE_USER = 200000, diff --git a/premake4.lua b/premake4.lua index 5b524fb29..f80b8a1d1 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1669,6 +1669,12 @@ solution "eepp" files { "src/examples/ui_application_hello_world/*.cpp" } build_link_configuration( "eepp-ui-application-hello-world", true ) + project "eepp-ui-font-picker" + set_kind() + language "C++" + files { "src/examples/ui_font_picker/*.cpp" } + build_link_configuration( "eepp-ui-font-picker", true ) + project "eepp-ui-dropdownmodellist" set_kind() language "C++" diff --git a/premake5.lua b/premake5.lua index 2a7c981c2..481a8f2e8 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1662,6 +1662,12 @@ workspace "eepp" files { "src/examples/ui_application_hello_world/*.cpp" } build_link_configuration( "eepp-ui-application-hello-world", true ) + project "eepp-ui-font-picker" + set_kind() + language "C++" + files { "src/examples/ui_font_picker/*.cpp" } + build_link_configuration( "eepp-ui-font-picker", true ) + project "eepp-ui-dropdownmodellist" set_kind() language "C++" diff --git a/src/eepp/graphics/fontmanager.cpp b/src/eepp/graphics/fontmanager.cpp index 0e131e2da..9d785ec6d 100644 --- a/src/eepp/graphics/fontmanager.cpp +++ b/src/eepp/graphics/fontmanager.cpp @@ -111,7 +111,8 @@ FontTrueType* FontManager::getOrLoadSystemFallbackFont( const FontDesc& desc ) { for ( auto* font : mSystemFallbackFonts ) { if ( font->getType() == FontType::TTF ) { auto* ttf = static_cast( font ); - if ( ttf->getInfo().fontpath + ttf->getInfo().filename == desc.path ) + if ( ttf->getInfo().fontpath + ttf->getInfo().filename == desc.path && + ttf->getFaceIndex() == desc.faceIndex ) return ttf; } } diff --git a/src/eepp/ui/tools/uifontpickerdialog.cpp b/src/eepp/ui/tools/uifontpickerdialog.cpp new file mode 100644 index 000000000..7fc3794d6 --- /dev/null +++ b/src/eepp/ui/tools/uifontpickerdialog.cpp @@ -0,0 +1,845 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace EE::UI::Abstract; +using namespace EE::UI::Models; + +namespace EE { namespace UI { namespace Tools { + +static const Uint32 FONT_PICKER_STYLE_MARKER = String::hash( "UIFontPickerDialog" ); + +static const char* FONT_PICKER_STYLE = R"css( +#font_picker { + margin: 8dp; + clip: none; +} +#font_picker .column_label { + margin-bottom: 4dp; +} +#font_picker .footer { + margin-top: 8dp; + clip: none; +} +#font_picker .preview_text { + padding-left: 24dp; + padding-right: 24dp; + word-wrap: true; +} +#font_picker .details { + margin-top: 4dp; +} +#font_picker PushButton.footer_button { + min-width: 80dp; + margin-left: 8dp; +} +#font_picker .separator { + background-color: #FFFFFF44; +} +#font_picker .color_button { + min-width: 48dp; +} +#font_picker .options_panel { + background-color: var(--list-back); + border-color: var(--button-border); + border-radius: var(--button-radius); + border-width: var(--border-width); + padding-bottom: 4dp; + padding-left: 8dp; + padding-right: 8dp; + padding-top: 4dp; +} +)css"; + +static const char* FONT_PICKER_LAYOUT = R"xml( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)xml"; + +class StyleListModel final : public Model { + public: + explicit StyleListModel( const std::vector* data ) : + mData( data ) {} + + size_t rowCount( const ModelIndex& ) const override { return mData ? mData->size() : 0; } + + size_t columnCount( const ModelIndex& ) const override { return 1; } + + ModelIndex index( int row, int column, + const ModelIndex& parent = ModelIndex() ) const override { + if ( row >= static_cast( rowCount( parent ) ) || column >= 1 ) + return {}; + return Model::index( row, column, parent ); + } + + Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const override { + if ( role == ModelRole::Display && mData && + index.row() < static_cast( mData->size() ) ) + return Variant( ( *mData )[index.row()].label ); + return {}; + } + + private: + const std::vector* mData{ nullptr }; +}; + +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"; + } + if ( desc.faceIndex != 0 ) + label += " #" + String::toString( desc.faceIndex ); + return label; +} + +static Uint32 styleFlags( const UIFontSelection& selection ) { + Uint32 style = selection.font.italic ? Text::Italic : Text::Regular; + if ( selection.font.weight >= FontWeight::SemiBold ) + style |= Text::Bold; + if ( selection.underline ) + style |= Text::Underlined; + if ( selection.strikeThrough ) + style |= Text::StrikeThrough; + return style; +} + +UIFontPickerDialog* UIFontPickerDialog::New( Uint32 flags ) { + return eeNew( UIFontPickerDialog, ( flags ) ); +} + +UIFontPickerDialog::UIFontPickerDialog( Uint32 flags ) : UIWindow(), mFlags( flags ) { + mVisible = false; + mStyleConfig.WinFlags = UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL; + updateWinFlags(); + + mSizes = { 8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 32, 48, 64, 72 }; + mSelection.size = 12; + + setTitle( i18n( "font_picker_select_font", "Select Font" ) ); + + const Sizef sceneSize( getUISceneNode()->getSize() ); + const Sizef maxSize( eemax( 320.f, sceneSize.getWidth() - PixelDensity::dpToPx( 32 ) ), + eemax( 320.f, sceneSize.getHeight() - PixelDensity::dpToPx( 32 ) ) ); + setMinWindowSize( + Sizef( eemin( 720.f, maxSize.getWidth() ), eemin( 440.f, maxSize.getHeight() ) ) ); + setSizeWithDecoration( + Sizef( eemin( 940.f, maxSize.getWidth() ), eemin( 620.f, maxSize.getHeight() ) ) ); + + loadWidgets(); + if ( getUISceneNode()->getUIThemeManager()->getDefaultTheme() ) + setTheme( getUISceneNode()->getUIThemeManager()->getDefaultTheme() ); + selectInitialRows(); + loadFonts(); +} + +UIFontPickerDialog::~UIFontPickerDialog() { + if ( mLoadFontsRequest ) { + mLoadFontsRequest->alive = false; + mLoadFontsRequest->dialog.store( nullptr ); + } + if ( mLoadFontsTaskId && getUISceneNode()->hasThreadPool() ) + getUISceneNode()->getThreadPool()->removeWithTag( loadFontsTaskTag( this ) ); + if ( mColorPicker && mColorPickerCloseCb ) + mColorPicker->getUIWindow()->removeEventListener( mColorPickerCloseCb ); + if ( mColorPicker && !SceneManager::instance()->isShuttingDown() ) + mColorPicker->closePicker(); + mColorPicker = nullptr; + mColorPickerCloseCb = 0; + clearBrowseDialog(); +} + +Uint32 UIFontPickerDialog::getType() const { + return UI_TYPE_FONTPICKERDIALOG; +} + +bool UIFontPickerDialog::isType( const Uint32& type ) const { + return type == UI_TYPE_FONTPICKERDIALOG ? true : UIWindow::isType( type ); +} + +void UIFontPickerDialog::setTheme( UITheme* theme ) { + UIWindow::setTheme( theme ); + + if ( mButtonOK ) { + if ( Drawable* icon = + getUISceneNode()->findIconDrawable( "ok", PixelDensity::dpToPxI( 16 ) ) ) + mButtonOK->setIcon( icon ); + } + + if ( mButtonCancel ) { + if ( Drawable* icon = + getUISceneNode()->findIconDrawable( "cancel", PixelDensity::dpToPxI( 16 ) ) ) + mButtonCancel->setIcon( icon ); + } + + if ( mButtonBrowse ) { + if ( Drawable* icon = getUISceneNode()->findIconDrawable( "document-open", + PixelDensity::dpToPxI( 16 ) ) ) + mButtonBrowse->setIcon( icon ); + } + + onThemeLoaded(); +} + +void UIFontPickerDialog::loadWidgets() { + UISceneNode* sceneNode = getUISceneNode(); + if ( !sceneNode->getStyleSheet().markerExists( FONT_PICKER_STYLE_MARKER ) ) { + CSS::StyleSheetParser parser; + parser.loadFromString( std::string_view{ FONT_PICKER_STYLE } ); + parser.getStyleSheet().setMarker( FONT_PICKER_STYLE_MARKER ); + sceneNode->combineStyleSheet( parser.getStyleSheet() ); + } + + UIWidget* root = sceneNode->loadLayoutFromString( FONT_PICKER_LAYOUT, mContainer ); + root->bind( "font_content", mFontContent ); + root->bind( "font_loader", mFontLoader ); + root->bind( "search_input", mSearchInput ); + root->bind( "family_list", mFamilyList ); + root->bind( "style_list", mStyleList ); + root->bind( "size_list", mSizeList ); + root->bind( "preview_text", mPreviewText ); + root->bind( "preview_input", mPreviewInput ); + root->bind( "details_text", mDetailsText ); + root->bind( "monospace_only", mMonospaceOnly ); + root->bind( "antialiasing", mAntialiasing ); + root->bind( "underline", mUnderline ); + root->bind( "strike_through", mStrikeThrough ); + root->bind( "color_button", mColorButton ); + root->bind( "ok_button", mButtonOK ); + root->bind( "cancel_button", mButtonCancel ); + root->bind( "apply_button", mButtonApply ); + root->bind( "browse_button", mButtonBrowse ); + + mSearchInput->setHint( i18n( "font_picker_search_fonts_ellipsis", "Search fonts..." ) ); + mPreviewInput->setText( "The quick brown fox jumps over the lazy dog" ); + mAntialiasing->setChecked( true ); + mMonospaceOnly->setChecked( ( mFlags & MonospaceOnly ) != 0 ); + mMonospaceOnly->setEnabled( ( mFlags & MonospaceOnly ) == 0 ); + + if ( ( mFlags & ShowSize ) == 0 ) { + if ( auto sizeColumn = root->find( "size_column" ) ) + sizeColumn->setVisible( false )->setEnabled( false ); + } + + if ( ( mFlags & ShowEffects ) == 0 ) { + mAntialiasing->setVisible( false )->setEnabled( false ); + mUnderline->setVisible( false )->setEnabled( false ); + mStrikeThrough->setVisible( false )->setEnabled( false ); + } + + if ( ( mFlags & ShowColor ) == 0 ) { + if ( auto colorLabel = root->find( "color_label" ) ) + colorLabel->setVisible( false )->setEnabled( false ); + mColorButton->setVisible( false )->setEnabled( false ); + } + + if ( ( mFlags & ShowApplyButton ) == 0 ) + mButtonApply->setVisible( false )->setEnabled( false ); + + mFamilyList->setHeadersVisible( false ); + mStyleList->setHeadersVisible( false ); + mSizeList->setHeadersVisible( false ); + mFamilyList->setAutoExpandOnSingleColumn( true ); + mStyleList->setAutoExpandOnSingleColumn( true ); + mSizeList->setAutoExpandOnSingleColumn( true ); + + mSearchInput->on( Event::OnTextChanged, [this]( const Event* ) { updateFamilies(); } ); + mFamilyList->setOnSelectionChange( [this] { + if ( !mUpdating ) { + updateStyles(); + updateSelectionFromLists(); + updatePreview(); + } + } ); + mStyleList->setOnSelectionChange( [this] { + if ( !mUpdating ) { + updateSelectionFromLists(); + updatePreview(); + } + } ); + mSizeList->setOnSelectionChange( [this] { + if ( !mUpdating ) { + updateSelectionFromLists(); + updatePreview(); + } + } ); + + mMonospaceOnly->on( Event::OnValueChange, [this]( const Event* ) { updateFamilies(); } ); + mAntialiasing->on( Event::OnValueChange, [this]( const Event* ) { + mSelection.antialiasing = mAntialiasing->isChecked(); + updatePreview(); + } ); + mUnderline->on( Event::OnValueChange, [this]( const Event* ) { + mSelection.underline = mUnderline->isChecked(); + updatePreview(); + } ); + mStrikeThrough->on( Event::OnValueChange, [this]( const Event* ) { + mSelection.strikeThrough = mStrikeThrough->isChecked(); + updatePreview(); + } ); + mPreviewInput->on( Event::OnTextChanged, [this]( const Event* ) { updatePreview(); } ); + mColorButton->on( Event::MouseClick, [this]( const Event* ) { pickColor(); } ); + mButtonApply->on( Event::MouseClick, [this]( const Event* ) { + emitPicked(); + sendCommonEvent( Event::OnApply ); + } ); + mButtonBrowse->on( Event::MouseClick, [this]( const Event* ) { browseFont(); } ); +} + +void UIFontPickerDialog::loadFonts() { + setFontsLoading( true ); + + if ( getUISceneNode()->hasThreadPool() ) { + auto request = std::make_shared(); + request->dialog.store( this ); + mLoadFontsRequest = request; + UISceneNode* sceneNode = getUISceneNode(); + mLoadFontsTaskId = sceneNode->getThreadPool()->run( + [request, sceneNode] { + auto fonts = SystemFontResolver::instance()->enumerate(); + UIFontPickerDialog* dialog = request->dialog.load(); + sceneNode->runOnMainThread( + [request, fonts = std::move( fonts )]() mutable { + UIFontPickerDialog* dialog = request->dialog.load(); + if ( !request->alive || dialog == nullptr ) + return; + dialog->mLoadFontsTaskId = 0; + dialog->setFonts( std::move( fonts ) ); + }, + Time::Zero, loadFontsTaskTag( dialog ) ); + }, + []( const Uint64& ) {}, loadFontsTaskTag( this ) ); + return; + } + + setFonts( SystemFontResolver::instance()->enumerate() ); +} + +void UIFontPickerDialog::setFonts( std::vector fonts ) { + FontDesc selectedFont = mSelection.font; + 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() ) + fonts.push_back( font ); + } + + mFonts = std::move( fonts ); + sortFonts(); + setFontsLoading( false ); + updateFamilies(); + if ( !selectedFont.path.empty() || !selectedFont.family.empty() ) + setSelectedFont( selectedFont ); + else + updatePreview(); +} + +void UIFontPickerDialog::sortFonts() { + std::sort( mFonts.begin(), mFonts.end(), []( const auto& lhs, const auto& rhs ) { + if ( lhs.family != rhs.family ) + return String::toLower( lhs.family ) < String::toLower( rhs.family ); + if ( lhs.weight != rhs.weight ) + return lhs.weight < rhs.weight; + if ( lhs.italic != rhs.italic ) + return !lhs.italic && rhs.italic; + if ( lhs.path != rhs.path ) + return lhs.path < rhs.path; + return lhs.faceIndex < rhs.faceIndex; + } ); +} + +void UIFontPickerDialog::setFontsLoading( bool loading ) { + mLoadingFonts = loading; + if ( mFontContent ) + mFontContent->setVisible( !loading )->setEnabled( !loading ); + if ( mFontLoader ) + mFontLoader->setVisible( loading )->setEnabled( loading ); + if ( mSearchInput ) + mSearchInput->setEnabled( !loading ); + if ( mButtonBrowse ) + mButtonBrowse->setEnabled( !loading ); + if ( mButtonOK ) + mButtonOK->setEnabled( !loading ); + if ( mButtonApply ) + mButtonApply->setEnabled( !loading && ( mFlags & ShowApplyButton ) != 0 ); +} + +bool UIFontPickerDialog::wantsMonospaceOnly() const { + return mMonospaceOnly && mMonospaceOnly->isChecked(); +} + +void UIFontPickerDialog::updateFamilies() { + std::string previousFamily = mSelection.font.family; + if ( !mFamilyList->getSelection().isEmpty() && + mFamilyList->getSelection().first().row() < static_cast( mFamilies.size() ) ) + previousFamily = mFamilies[mFamilyList->getSelection().first().row()]; + + const std::string query = String::toLower( mSearchInput->getText().toUtf8() ); + std::set families; + for ( const auto& font : mFonts ) { + if ( wantsMonospaceOnly() && !font.monospace ) + continue; + if ( !query.empty() && String::toLower( font.family ).find( query ) == std::string::npos ) + continue; + families.insert( font.family ); + } + mFamilies.assign( families.begin(), families.end() ); + mFamilyModel = ItemListModel::create( mFamilies ); + + mUpdating = true; + mFamilyList->setModel( mFamilyModel ); + mUpdating = false; + + if ( !previousFamily.empty() ) + selectFamily( previousFamily ); + if ( mFamilyList->getSelection().isEmpty() && !mFamilies.empty() ) + mFamilyList->setSelection( mFamilyModel->index( 0 ) ); + + updateStyles(); + updateSelectionFromLists(); + updatePreview(); +} + +void UIFontPickerDialog::updateStyles() { + mStyles.clear(); + if ( !mFamilyList->getSelection().isEmpty() ) { + const Int64 row = mFamilyList->getSelection().first().row(); + 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 } ); + } + } + } + mStyleModel = std::make_shared( &mStyles ); + + mUpdating = true; + mStyleList->setModel( mStyleModel ); + mUpdating = false; + + if ( !mStyles.empty() ) { + selectStyle( mSelection.font ); + if ( mStyleList->getSelection().isEmpty() ) + mStyleList->setSelection( mStyleModel->index( 0 ) ); + } +} + +void UIFontPickerDialog::updateSelectionFromLists() { + if ( !mStyleList->getSelection().isEmpty() ) { + const Int64 row = mStyleList->getSelection().first().row(); + if ( row >= 0 && row < static_cast( mStyles.size() ) ) + mSelection.font = mStyles[row].desc; + } + if ( !mSizeList->getSelection().isEmpty() ) { + const Int64 row = mSizeList->getSelection().first().row(); + if ( row >= 0 && row < static_cast( mSizes.size() ) ) + mSelection.size = mSizes[row]; + } + mSelection.antialiasing = mAntialiasing->isChecked(); + mSelection.underline = mUnderline->isChecked(); + mSelection.strikeThrough = mStrikeThrough->isChecked(); +} + +void UIFontPickerDialog::updatePreview() { + if ( !mPreviewText ) + return; + + FontTrueType* font = FontManager::instance()->getOrLoadSystemFallbackFont( mSelection.font ); + if ( font ) { + mPreviewText->setFont( font ); + mPreviewInput->setFont( font ); + } + + mPreviewText->setFontSize( PixelDensity::dpToPxI( mSelection.size * 2 ) ); + mPreviewInput->setFontSize( PixelDensity::dpToPxI( 12 ) ); + mPreviewText->setFontStyle( styleFlags( mSelection ) ); + mPreviewInput->setFontStyle( styleFlags( mSelection ) ); + mPreviewText->setFontColor( mSelection.color ); + mPreviewInput->setFontColor( mSelection.color ); + if ( !mPreviewInput->getText().empty() ) + mPreviewText->setText( mPreviewInput->getText() ); + + if ( mColorButton ) + mColorButton->setBackgroundColor( mSelection.color ); + + if ( mDetailsText ) { + std::string faceIndexSuffix; + if ( mSelection.font.faceIndex != 0 ) + faceIndexSuffix = " #" + String::toString( mSelection.font.faceIndex ); + mDetailsText->setText( + String::format( "%s %s - %u pt - %s%s", mSelection.font.family.c_str(), + styleLabel( mSelection.font ).c_str(), mSelection.size, + mSelection.font.path.c_str(), faceIndexSuffix.c_str() ) ); + } +} + +void UIFontPickerDialog::selectInitialRows() { + mSizeModel = ItemListModel::create( mSizes ); + mSizeList->setModel( mSizeModel ); + selectSize( mSelection.size ); + if ( mSizeList->getSelection().isEmpty() ) + mSizeList->setSelection( mSizeModel->index( 4 ) ); +} + +void UIFontPickerDialog::selectFamily( const std::string& family ) { + if ( family.empty() || !mFamilyModel ) + return; + for ( size_t i = 0; i < mFamilies.size(); i++ ) { + if ( mFamilies[i] == family ) { + mFamilyList->setSelection( mFamilyModel->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 ) { + mStyleList->setSelection( mStyleModel->index( i ) ); + return; + } + } +} + +void UIFontPickerDialog::selectSize( Uint32 size ) { + if ( !mSizeModel ) + return; + for ( size_t i = 0; i < mSizes.size(); i++ ) { + if ( mSizes[i] == size ) { + mSizeList->setSelection( mSizeModel->index( i ) ); + return; + } + } +} + +void UIFontPickerDialog::emitPicked() { + updateSelectionFromLists(); + if ( mFontPickedCb ) + mFontPickedCb( mSelection ); +} + +void UIFontPickerDialog::pickColor() { + if ( mColorPicker ) { + mColorPicker->getUIWindow()->toFront(); + return; + } + + mColorPicker = UIColorPicker::NewWindow( [this]( Color color ) { + mSelection.color = color; + updatePreview(); + } ); + mColorPicker->setColor( mSelection.color ); + mColorPickerCloseCb = + mColorPicker->getUIWindow()->on( Event::OnWindowClose, [this]( const Event* ) { + mColorPicker = nullptr; + mColorPickerCloseCb = 0; + } ); +} + +void UIFontPickerDialog::browseFont() { + if ( mBrowseDialog ) { + mBrowseDialog->toFront(); + return; + } + + mBrowseDialog = + UIFileDialog::New( UIFileDialog::DefaultFlags, "*.ttf; *.otf; *.woff; *.woff2; *.otb; " + "*.bdf; *.ttc" ); + mBrowseDialog->setWindowFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); + mBrowseDialog->setTitle( i18n( "font_picker_browse_font", "Browse Font" ) ); + mBrowseDialog->setCloseShortcut( KEY_ESCAPE ); + mBrowseDialogOpenCb = mBrowseDialog->on( Event::OpenFile, [this]( const Event* event ) { + auto* dialog = event->getNode()->asType(); + const std::string path( dialog->getFullPath() ); + if ( addExternalFont( path ) ) + dialog->closeWindow(); + } ); + mBrowseDialogCloseCb = mBrowseDialog->on( Event::OnWindowClose, [this]( const Event* ) { + mBrowseDialog = nullptr; + mBrowseDialogOpenCb = 0; + mBrowseDialogCloseCb = 0; + } ); + mBrowseDialog->center(); + mBrowseDialog->show(); +} + +bool UIFontPickerDialog::addExternalFont( const std::string& path, Uint32 faceIndex ) { + if ( path.empty() ) + return false; + + auto found = std::find_if( mFonts.begin(), mFonts.end(), [&]( const auto& desc ) { + return desc.path == path && desc.faceIndex == faceIndex; + } ); + + if ( found != mFonts.end() ) { + if ( !mSearchInput->getText().empty() ) + mSearchInput->setText( "" ); + setSelectedFont( *found ); + return true; + } + + const std::string fontName( + FileSystem::fileRemoveExtension( FileSystem::fileNameFromPath( path ) ) ); + FontTrueType* font = FontTrueType::New( fontName ); + if ( !font || !font->loadFromFile( path, faceIndex ) ) { + eeSAFE_DELETE( font ); + return false; + } + + 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(); + eeSAFE_DELETE( font ); + + mFonts.push_back( desc ); + sortFonts(); + + if ( !mSearchInput->getText().empty() ) + mSearchInput->setText( "" ); + updateFamilies(); + setSelectedFont( desc ); + return true; +} + +void UIFontPickerDialog::clearBrowseDialog() { + if ( !mBrowseDialog ) + return; + if ( mBrowseDialogOpenCb ) + mBrowseDialog->removeEventListener( mBrowseDialogOpenCb ); + if ( mBrowseDialogCloseCb ) + mBrowseDialog->removeEventListener( mBrowseDialogCloseCb ); + if ( !SceneManager::instance()->isShuttingDown() ) + mBrowseDialog->closeWindow(); + mBrowseDialog = nullptr; + mBrowseDialogOpenCb = 0; + mBrowseDialogCloseCb = 0; +} + +void UIFontPickerDialog::setSelectedFont( const FontDesc& desc ) { + 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; + } ); + if ( found == mFonts.end() && addExternalFont( desc.path, desc.faceIndex ) ) + return; + if ( found != mFonts.end() ) + selection = *found; + } + + mSelection.font = selection; + if ( selection.monospace && mMonospaceOnly ) + mMonospaceOnly->setChecked( true ); + selectFamily( selection.family ); + updateStyles(); + selectStyle( selection ); + 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; + } ); + if ( found != mFonts.end() ) { + setSelectedFont( *found ); + return; + } + + addExternalFont( path, faceIndex ); +} + +const UIFontSelection& UIFontPickerDialog::getSelection() const { + return mSelection; +} + +void UIFontPickerDialog::setSelection( const UIFontSelection& selection ) { + mSelection = selection; + if ( mAntialiasing ) + mAntialiasing->setChecked( selection.antialiasing ); + if ( mUnderline ) + mUnderline->setChecked( selection.underline ); + if ( mStrikeThrough ) + mStrikeThrough->setChecked( selection.strikeThrough ); + selectSize( selection.size ); + setSelectedFont( selection.font ); +} + +void UIFontPickerDialog::setOnFontPicked( FontPickedCb cb ) { + mFontPickedCb = std::move( cb ); +} + +UIPushButton* UIFontPickerDialog::getButtonOK() const { + return mButtonOK; +} + +UIPushButton* UIFontPickerDialog::getButtonCancel() const { + return mButtonCancel; +} + +UIPushButton* UIFontPickerDialog::getButtonApply() const { + return mButtonApply; +} + +UIPushButton* UIFontPickerDialog::getButtonBrowse() const { + return mButtonBrowse; +} + +UITextInput* UIFontPickerDialog::getSearchInput() const { + return mSearchInput; +} + +UIListView* UIFontPickerDialog::getFamilyList() const { + return mFamilyList; +} + +UIListView* UIFontPickerDialog::getStyleList() const { + return mStyleList; +} + +UIListView* UIFontPickerDialog::getSizeList() const { + return mSizeList; +} + +const KeyBindings::Shortcut& UIFontPickerDialog::getCloseShortcut() const { + return mCloseShortcut; +} + +void UIFontPickerDialog::setCloseShortcut( const KeyBindings::Shortcut& closeShortcut ) { + mCloseShortcut = closeShortcut; +} + +bool UIFontPickerDialog::show() { + bool ret = UIWindow::show(); + if ( mSearchInput && mSearchInput->isEnabled() ) + mSearchInput->setFocus(); + return ret; +} + +void UIFontPickerDialog::onWindowReady() { + UIWindow::onWindowReady(); + if ( mSearchInput && mSearchInput->isEnabled() ) + mSearchInput->setFocus(); +} + +Uint32 UIFontPickerDialog::onKeyUp( const KeyEvent& event ) { + if ( mCloseShortcut && event.getKeyCode() == mCloseShortcut && + ( mCloseShortcut.mod == 0 || ( event.getMod() & mCloseShortcut.mod ) ) ) { + sendCommonEvent( Event::OnDiscard ); + sendCommonEvent( Event::OnCancel ); + closeWindow(); + } + + return UIWindow::onKeyUp( event ); +} + +Uint32 UIFontPickerDialog::onMessage( const NodeMessage* msg ) { + switch ( msg->getMsg() ) { + case NodeMessage::MouseClick: + if ( msg->getFlags() & EE_BUTTON_LMASK ) { + if ( msg->getSender() == mButtonOK ) { + emitPicked(); + sendCommonEvent( Event::OnConfirm ); + closeWindow(); + } else if ( msg->getSender() == mButtonCancel ) { + sendCommonEvent( Event::OnDiscard ); + sendCommonEvent( Event::OnCancel ); + closeWindow(); + } + } + break; + } + + return UIWindow::onMessage( msg ); +} + +}}} // namespace EE::UI::Tools diff --git a/src/eepp/ui/uilinearlayout.cpp b/src/eepp/ui/uilinearlayout.cpp index 0f5838bc2..2eb90a4fd 100644 --- a/src/eepp/ui/uilinearlayout.cpp +++ b/src/eepp/ui/uilinearlayout.cpp @@ -208,12 +208,13 @@ void UILinearLayout::packVertical() { switch ( Font::getHorizontalAlign( widget->getLayoutGravity() ) ) { case UI_HALIGN_CENTER: - pos.x = ( getPixelsSize().getWidth() - mPaddingPx.Left - mPaddingPx.Right - + pos.x = mPaddingPx.Left + + ( getPixelsSize().getWidth() - mPaddingPx.Left - mPaddingPx.Right - widget->getPixelsSize().getWidth() ) / - 2; + 2; break; case UI_HALIGN_RIGHT: - pos.x = getPixelsSize().getWidth() - mPaddingPx.Left - mPaddingPx.Right - + pos.x = getPixelsSize().getWidth() - mPaddingPx.Right - widget->getPixelsSize().getWidth() - widget->getLayoutPixelsMargin().Right; break; @@ -342,12 +343,13 @@ void UILinearLayout::packHorizontal() { switch ( Font::getVerticalAlign( widget->getLayoutGravity() ) ) { case UI_VALIGN_CENTER: - pos.y = ( getPixelsSize().getHeight() - mPaddingPx.Top - mPaddingPx.Bottom - + pos.y = mPaddingPx.Top + + ( getPixelsSize().getHeight() - mPaddingPx.Top - mPaddingPx.Bottom - widget->getPixelsSize().getHeight() ) / - 2; + 2; break; case UI_VALIGN_BOTTOM: - pos.y = getPixelsSize().getHeight() - mPaddingPx.Top - mPaddingPx.Bottom - + pos.y = getPixelsSize().getHeight() - mPaddingPx.Bottom - widget->getPixelsSize().getHeight() - widget->getLayoutPixelsMargin().Bottom; break; diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index da315b2e4..14a78c728 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -816,34 +816,46 @@ void UIWidget::updateAnchors( const Vector2f& sizeChange ) { } void UIWidget::alignAgainstLayout() { - Vector2f pos = mDpPos; + Vector2f pos = mPosition; + const Sizef parentSize( getParent()->getPixelsSize() ); + const Sizef size( getPixelsSize() ); + Rectf parentContentOffset = Rectf::Zero; + + if ( getParent()->isWidget() ) + parentContentOffset = getParent()->asType()->getPixelsContentOffset(); + + const Float parentContentWidth = + parentSize.getWidth() - parentContentOffset.Left - parentContentOffset.Right; + const Float parentContentHeight = + parentSize.getHeight() - parentContentOffset.Top - parentContentOffset.Bottom; switch ( Font::getHorizontalAlign( mLayoutGravity ) ) { case UI_HALIGN_CENTER: - pos.x = ( getParent()->getSize().getWidth() - getSize().getWidth() ) / 2; + pos.x = parentContentOffset.Left + ( parentContentWidth - size.getWidth() ) / 2; break; case UI_HALIGN_RIGHT: - pos.x = getParent()->getSize().getWidth() - getSize().getWidth() - mLayoutMargin.Right; + pos.x = parentSize.getWidth() - parentContentOffset.Right - size.getWidth() - + mLayoutMarginPx.Right; break; case UI_HALIGN_LEFT: - pos.x = mLayoutMargin.Left; + pos.x = parentContentOffset.Left + mLayoutMarginPx.Left; break; } switch ( Font::getVerticalAlign( mLayoutGravity ) ) { case UI_VALIGN_CENTER: - pos.y = ( getParent()->getSize().getHeight() - getSize().getHeight() ) / 2; + pos.y = parentContentOffset.Top + ( parentContentHeight - size.getHeight() ) / 2; break; case UI_VALIGN_BOTTOM: - pos.y = - getParent()->getSize().getHeight() - getSize().getHeight() - mLayoutMargin.Bottom; + pos.y = parentSize.getHeight() - parentContentOffset.Bottom - size.getHeight() - + mLayoutMarginPx.Bottom; break; case UI_VALIGN_TOP: - pos.y = mLayoutMargin.Top; + pos.y = parentContentOffset.Top + mLayoutMarginPx.Top; break; } - setPosition( pos ); + setPixelsPosition( pos ); } void UIWidget::reportStyleStateChange( bool disableAnimations, bool forceReApplyStyles ) { diff --git a/src/examples/ui_font_picker/ui_font_picker.cpp b/src/examples/ui_font_picker/ui_font_picker.cpp new file mode 100644 index 000000000..112343102 --- /dev/null +++ b/src/examples/ui_font_picker/ui_font_picker.cpp @@ -0,0 +1,34 @@ +#include + +EE_MAIN_FUNC int main( int, char** ) { + UIApplication app( { 1280, 720, "eepp - UIFontPickerDialog Example" } ); + + std::shared_ptr threadPool( + ThreadPool::createShared( eemax( 4, Sys::getCPUCount() ) ) ); + + if ( !app.getWindow()->isOpen() ) + return EXIT_FAILURE; + + app.getUI()->setThreadPool( threadPool ); + app.getUI()->getUIIconThemeManager()->setCurrentTheme( + IconManager::init( "icons", FontTrueType::New( "icon", "assets/fonts/remixicon.ttf" ), + FontTrueType::New( "nonicons", "assets/fonts/nonicons.ttf" ), + FontTrueType::New( "codicon", "assets/fonts/codicon.ttf" ) ) ); + + UIFontPickerDialog* dialog = UIFontPickerDialog::New(); + dialog->setCloseShortcut( KEY_ESCAPE ); + dialog->setOnFontPicked( []( const UIFontSelection& selection ) { + Log::info( "Selected font: %s (%s, face index: %u, size: %u)", + selection.font.family.c_str(), selection.font.path.c_str(), + selection.font.faceIndex, selection.size ); + } ); + dialog->center(); + dialog->show(); + + app.getUI()->on( Event::KeyUp, [&app]( const Event* event ) { + if ( event->asKeyEvent()->getKeyCode() == KEY_F11 ) + UIWidgetInspector::create( app.getUI() ); + } ); + + return app.run(); +} diff --git a/src/tests/unit_tests/uifontpickerdialog_tests.cpp b/src/tests/unit_tests/uifontpickerdialog_tests.cpp new file mode 100644 index 000000000..79e717a74 --- /dev/null +++ b/src/tests/unit_tests/uifontpickerdialog_tests.cpp @@ -0,0 +1,90 @@ +#include "utest.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::UI; +using namespace EE::UI::Tools; + +template static void pumpUntil( UISceneNode* sceneNode, Predicate predicate ) { + for ( size_t i = 0; i < 1000 && !predicate(); i++ ) { + sceneNode->update( Time::Zero ); + Sys::sleep( Milliseconds( 5 ) ); + } +} + +UTEST( UIFontPickerDialog, PreselectsExternalFontPath ) { + 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 ) ); + + UIFontPickerDialog* dialog = UIFontPickerDialog::New(); + dialog->setSelectedFont( fontPath ); + + EXPECT_STDSTREQ( fontPath, dialog->getSelection().font.path ); + EXPECT_FALSE( dialog->getSelection().font.family.empty() ); + EXPECT_FALSE( dialog->getFamilyList()->getSelection().isEmpty() ); + EXPECT_FALSE( dialog->getStyleList()->getSelection().isEmpty() ); + + UIFontPickerDialog* selectionDialog = UIFontPickerDialog::New(); + UIFontSelection selection; + selection.font.path = fontPath; + selectionDialog->setSelection( selection ); + + EXPECT_STDSTREQ( fontPath, selectionDialog->getSelection().font.path ); + EXPECT_FALSE( selectionDialog->getSelection().font.family.empty() ); +} + +UTEST( UIFontPickerDialog, AsyncLoadPreservesExternalFontPreselection ) { + UIApplication app( + WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + app.getUI()->setThreadPool( ThreadPool::createShared( 1 ) ); + + const std::string fontPath = Sys::getProcessPath() + "assets/fonts/NotoSansKR-Regular.ttf"; + ASSERT_TRUE( FileSystem::fileExists( fontPath ) ); + + UIFontPickerDialog* dialog = UIFontPickerDialog::New(); + EXPECT_FALSE( dialog->getButtonOK()->isEnabled() ); + + dialog->setSelectedFont( fontPath ); + pumpUntil( app.getUI(), [dialog] { return dialog->getButtonOK()->isEnabled(); } ); + + EXPECT_TRUE( dialog->getButtonOK()->isEnabled() ); + EXPECT_STDSTREQ( fontPath, dialog->getSelection().font.path ); + EXPECT_FALSE( dialog->getSelection().font.family.empty() ); + EXPECT_FALSE( dialog->getFamilyList()->getSelection().isEmpty() ); + EXPECT_FALSE( dialog->getStyleList()->getSelection().isEmpty() ); +} + +UTEST( UIFontPickerDialog, ApplyButtonEmitsOnApply ) { + UIApplication app( + WindowSettings( 320, 240, "eepp - UIFontPickerDialog Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + UIFontPickerDialog* dialog = UIFontPickerDialog::New( UIFontPickerDialog::DefaultFlags | + UIFontPickerDialog::ShowApplyButton ); + bool applied = false; + bool picked = false; + bool confirmed = false; + dialog->on( Event::OnApply, [&applied]( const Event* ) { applied = true; } ); + dialog->on( Event::OnConfirm, [&confirmed]( const Event* ) { confirmed = true; } ); + dialog->setOnFontPicked( [&picked]( const UIFontSelection& ) { picked = true; } ); + + dialog->getButtonApply()->sendCommonEvent( Event::MouseClick ); + + EXPECT_TRUE( applied ); + EXPECT_TRUE( picked ); + EXPECT_FALSE( confirmed ); +} diff --git a/src/tests/unit_tests/uilayout_tests.cpp b/src/tests/unit_tests/uilayout_tests.cpp new file mode 100644 index 000000000..54eb87017 --- /dev/null +++ b/src/tests/unit_tests/uilayout_tests.cpp @@ -0,0 +1,82 @@ +#include "utest.hpp" +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::UI; + +UTEST( UILinearLayout, AlignAgainstLayoutUsesParentPadding ) { + UIApplication app( + WindowSettings( 320, 240, "eepp - UILinearLayout Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + UIWidget* parent = UIWidget::New(); + parent->setPixelsSize( 200, 120 ); + parent->setPadding( { 10, 20, 30, 40 } ); + parent->setParent( app.getUI()->getRoot() ); + + UILinearLayout* layout = UILinearLayout::NewVertical(); + layout->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + layout->setPixelsSize( 50, 20 ); + layout->setLayoutGravity( UI_HALIGN_CENTER | UI_VALIGN_CENTER ); + layout->setParent( parent ); + + app.getUI()->updateDirtyLayouts(); + + EXPECT_NEAR( 65.f, layout->getPixelsPosition().x, 0.1f ); + EXPECT_NEAR( 40.f, layout->getPixelsPosition().y, 0.1f ); +} + +UTEST( UILinearLayout, CrossAxisAlignmentUsesOwnPadding ) { + UIApplication app( + WindowSettings( 320, 240, "eepp - UILinearLayout Test", WindowStyle::Default, + WindowBackend::Default, 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + + UILinearLayout* vertical = UILinearLayout::NewVertical(); + vertical->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + vertical->setPixelsSize( 200, 120 ); + vertical->setPadding( { 10, 20, 30, 40 } ); + vertical->setParent( app.getUI()->getRoot() ); + + UIWidget* centered = UIWidget::New(); + centered->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + centered->setPixelsSize( 50, 20 ); + centered->setLayoutGravity( UI_HALIGN_CENTER ); + centered->setParent( vertical ); + + UIWidget* right = UIWidget::New(); + right->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + right->setPixelsSize( 50, 20 ); + right->setLayoutGravity( UI_HALIGN_RIGHT ); + right->setParent( vertical ); + + UILinearLayout* horizontal = UILinearLayout::NewHorizontal(); + horizontal->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + horizontal->setPixelsSize( 200, 120 ); + horizontal->setPadding( { 10, 20, 30, 40 } ); + horizontal->setParent( app.getUI()->getRoot() ); + + UIWidget* middle = UIWidget::New(); + middle->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + middle->setPixelsSize( 50, 20 ); + middle->setLayoutGravity( UI_VALIGN_CENTER ); + middle->setParent( horizontal ); + + UIWidget* bottom = UIWidget::New(); + bottom->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + bottom->setPixelsSize( 50, 20 ); + bottom->setLayoutGravity( UI_VALIGN_BOTTOM ); + bottom->setParent( horizontal ); + + app.getUI()->updateDirtyLayouts(); + + EXPECT_NEAR( 65.f, centered->getPixelsPosition().x, 0.1f ); + EXPECT_NEAR( 120.f, right->getPixelsPosition().x, 0.1f ); + EXPECT_NEAR( 40.f, middle->getPixelsPosition().y, 0.1f ); + EXPECT_NEAR( 60.f, bottom->getPixelsPosition().y, 0.1f ); +}