Implemented UIFontPickerDialog.

Fixed alignment in UILinearLayout when padding is used.
This commit is contained in:
Martín Lucas Golini
2026-06-29 20:32:08 -03:00
parent 3a1c75f4f6
commit 57cdab7b7f
15 changed files with 1298 additions and 17 deletions

View File

@@ -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": {

View File

@@ -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;

View File

@@ -131,6 +131,7 @@ class EE_API Event {
OnFocusWithin,
OnFocusWithinLoss,
OnItemsCountChange,
OnApply,
NoEvent = eeINDEX_NOT_FOUND
};

View File

@@ -74,6 +74,7 @@
#include <eepp/ui/tools/uicolorpicker.hpp>
#include <eepp/ui/tools/uidiffview.hpp>
#include <eepp/ui/tools/uidocfindreplace.hpp>
#include <eepp/ui/tools/uifontpickerdialog.hpp>
#include <eepp/ui/tools/uiimageviewer.hpp>
#include <eepp/ui/tools/uiwidgetinspector.hpp>
#include <eepp/ui/uiapplication.hpp>
@@ -141,7 +142,6 @@
#include <eepp/ui/uiscrollablewidget.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uiscrollview.hpp>
#include <eepp/ui/uiwebview.hpp>
#include <eepp/ui/uiselectbutton.hpp>
#include <eepp/ui/uiskin.hpp>
#include <eepp/ui/uiskinstate.hpp>
@@ -172,6 +172,7 @@
#include <eepp/ui/uitouchdraggablewidget.hpp>
#include <eepp/ui/uitreeview.hpp>
#include <eepp/ui/uiviewpager.hpp>
#include <eepp/ui/uiwebview.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <eepp/ui/uiwidgetcreator.hpp>
#include <eepp/ui/uiwidgettable.hpp>

View File

@@ -0,0 +1,192 @@
#ifndef EE_UI_TOOLS_UIFONTPICKERDIALOG_HPP
#define EE_UI_TOOLS_UIFONTPICKERDIALOG_HPP
#include <atomic>
#include <eepp/graphics/systemfontresolver.hpp>
#include <eepp/system/color.hpp>
#include <eepp/ui/keyboardshortcut.hpp>
#include <eepp/ui/models/model.hpp>
#include <eepp/ui/uiwindow.hpp>
#include <functional>
#include <memory>
#include <string>
#include <vector>
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<void( const UIFontSelection& )>;
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<FontDesc> mFonts;
std::vector<std::string> mFamilies;
std::vector<FontStyleEntry> mStyles;
std::vector<Uint32> mSizes;
std::shared_ptr<Models::Model> mFamilyModel;
std::shared_ptr<Models::Model> mStyleModel;
std::shared_ptr<Models::Model> 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<bool> alive{ true };
std::atomic<UIFontPickerDialog*> dialog{ nullptr };
};
std::shared_ptr<LoadFontsRequest> 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<FontDesc> 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

View File

@@ -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,

View File

@@ -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++"

View File

@@ -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++"

View File

@@ -111,7 +111,8 @@ FontTrueType* FontManager::getOrLoadSystemFallbackFont( const FontDesc& desc ) {
for ( auto* font : mSystemFallbackFonts ) {
if ( font->getType() == FontType::TTF ) {
auto* ttf = static_cast<FontTrueType*>( 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;
}
}

View File

@@ -0,0 +1,845 @@
#include <algorithm>
#include <cctype>
#include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/graphics/text.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/threadpool.hpp>
#include <eepp/ui/abstract/uiabstractview.hpp>
#include <eepp/ui/css/stylesheetparser.hpp>
#include <eepp/ui/models/itemlistmodel.hpp>
#include <eepp/ui/models/model.hpp>
#include <eepp/ui/tools/uicolorpicker.hpp>
#include <eepp/ui/tools/uifontpickerdialog.hpp>
#include <eepp/ui/uicheckbox.hpp>
#include <eepp/ui/uifiledialog.hpp>
#include <eepp/ui/uilistview.hpp>
#include <eepp/ui/uiloader.hpp>
#include <eepp/ui/uipushbutton.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uitextinput.hpp>
#include <eepp/ui/uitextview.hpp>
#include <eepp/ui/uitheme.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <set>
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(
<LinearLayout id="font_picker" orientation="vertical" layout_width="match_parent" layout_height="match_parent" clip="none">
<LinearLayout orientation="horizontal" layout_width="match_parent" layout_height="wrap_content">
<TextInput id="search_input" layout_width="0dp" layout_weight="1" layout_height="match_parent" />
<PushButton id="browse_button" layout_width="wrap_content" layout_height="wrap_content" margin-left="8dp" text='@string(font_picker_browse_ellipsis, "Browse...")' />
</LinearLayout>
<RelativeLayout layout_width="match_parent" layout_height="0dp" layout_weight="1" margin-top="8dp">
<LinearLayout id="font_content" orientation="horizontal" layout_width="match_parent" layout_height="match_parent">
<LinearLayout orientation="vertical" layout_width="0dp" layout_weight="0.28" layout_height="match_parent">
<TextView class="column_label" layout_width="match_parent" layout_height="wrap_content" text='@string(font_picker_font_family, "Font Family")' />
<ListView id="family_list" layout_width="match_parent" layout_height="0dp" layout_weight="1" />
</LinearLayout>
<LinearLayout orientation="vertical" layout_width="0dp" layout_weight="0.18" layout_height="match_parent" margin-left="8dp">
<TextView class="column_label" layout_width="match_parent" layout_height="wrap_content" text='@string(font_picker_style, "Style")' />
<ListView id="style_list" layout_width="match_parent" layout_height="0dp" layout_weight="1" />
</LinearLayout>
<LinearLayout id="size_column" orientation="vertical" layout_width="120dp" layout_height="match_parent" margin-left="8dp">
<TextView class="column_label" layout_width="match_parent" layout_height="wrap_content" text='@string(font_picker_size, "Size")' />
<ListView id="size_list" layout_width="match_parent" layout_height="0dp" layout_weight="1" />
</LinearLayout>
<LinearLayout orientation="vertical" layout_width="0dp" layout_weight="0.54" layout_height="match_parent" margin-left="8dp">
<TextView class="column_label" layout_width="match_parent" layout_height="wrap_content" text='@string(font_picker_preview, "Preview")' />
<TextView id="preview_text" class="preview_text" layout_width="match_parent" layout_height="0dp" layout_weight="1" text='The quick brown fox&#10;jumps over the lazy dog' gravity="center" />
<TextInput id="preview_input" layout_width="match_parent" layout_height="28dp" margin-top="8dp" />
</LinearLayout>
</LinearLayout>
<Loader id="font_loader" lw="64dp" lh="64dp" outline-thickness="6dp" lg="center" visible="false" />
</RelativeLayout>
<LinearLayout id="options_row" class="options_panel" orientation="horizontal" layout_width="match_parent" layout_height="wrap_content" margin-top="8dp">
<CheckBox id="monospace_only" layout_width="wrap_content" layout_height="wrap_content" layout_gravity="center" text='@string(font_picker_monospace_only, "Monospace only")' />
<CheckBox id="antialiasing" layout_width="wrap_content" layout_height="wrap_content" layout_gravity="center" margin-left="16dp" text='@string(font_picker_antialiasing, "Antialiasing")' />
<CheckBox id="underline" layout_width="wrap_content" layout_height="wrap_content" layout_gravity="center" margin-left="16dp" text='@string(font_picker_underline, "Underline")' />
<CheckBox id="strike_through" layout_width="wrap_content" layout_height="wrap_content" layout_gravity="center" margin-left="16dp" text='@string(font_picker_strikeout, "Strikeout")' />
<Widget class="separator" layout_width="1dp" layout_height="match_parent" layout_gravity="center" margin-left="16dp" />
<TextView id="color_label" layout_width="wrap_content" layout_height="wrap_content" layout_gravity="center" margin-left="16dp" text='@string(font_picker_color, "Color:")' gravity="center" />
<PushButton id="color_button" class="color_button" layout_width="48dp" layout_height="wrap_content" layout_gravity="center" margin-left="8dp" />
</LinearLayout>
<TextView id="details_text" class="details" layout_width="match_parent" layout_height="wrap_content" text="" />
<LinearLayout class="footer" orientation="horizontal" layout_width="match_parent" layout_height="wrap_content" clip="none">
<Widget layout_width="0dp" layout_weight="1" layout_height="1dp" />
<PushButton id="cancel_button" class="footer_button" layout_width="80dp" layout_height="wrap_content" text='@string(msg_box_cancel, "Cancel")' />
<PushButton id="apply_button" class="footer_button" layout_width="80dp" layout_height="wrap_content" text='@string(msg_box_apply, "Apply")' />
<PushButton id="ok_button" class="footer_button" layout_width="80dp" layout_height="wrap_content" text='@string(msg_box_ok, "Ok")' />
</LinearLayout>
</LinearLayout>
)xml";
class StyleListModel final : public Model {
public:
explicit StyleListModel( const std::vector<UIFontPickerDialog::FontStyleEntry>* 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<int>( 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<Int64>( mData->size() ) )
return Variant( ( *mData )[index.row()].label );
return {};
}
private:
const std::vector<UIFontPickerDialog::FontStyleEntry>* mData{ nullptr };
};
static Uint64 loadFontsTaskTag( const UIFontPickerDialog* dialog ) {
return reinterpret_cast<Uint64>( 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<char>( std::toupper( label[0] ) );
for ( size_t i = 1; i < label.size(); i++ ) {
if ( label[i - 1] == ' ' )
label[i] = static_cast<char>( 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<UIWidget>( "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<UIWidget>( "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<LoadFontsRequest>();
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<FontDesc> 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<Int64>( mFamilies.size() ) )
previousFamily = mFamilies[mFamilyList->getSelection().first().row()];
const std::string query = String::toLower( mSearchInput->getText().toUtf8() );
std::set<std::string> 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<std::string>::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<Int64>( 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<StyleListModel>( &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<Int64>( mStyles.size() ) )
mSelection.font = mStyles[row].desc;
}
if ( !mSizeList->getSelection().isEmpty() ) {
const Int64 row = mSizeList->getSelection().first().row();
if ( row >= 0 && row < static_cast<Int64>( 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<Uint32>::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<UIFileDialog>();
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

View File

@@ -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;

View File

@@ -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<UIWidget>()->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 ) {

View File

@@ -0,0 +1,34 @@
#include <eepp/ee.hpp>
EE_MAIN_FUNC int main( int, char** ) {
UIApplication app( { 1280, 720, "eepp - UIFontPickerDialog Example" } );
std::shared_ptr<ThreadPool> threadPool(
ThreadPool::createShared( eemax<int>( 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();
}

View File

@@ -0,0 +1,90 @@
#include "utest.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/system/threadpool.hpp>
#include <eepp/ui/tools/uifontpickerdialog.hpp>
#include <eepp/ui/uiapplication.hpp>
#include <eepp/ui/uilistview.hpp>
#include <eepp/ui/uipushbutton.hpp>
#include <eepp/ui/uiscenenode.hpp>
using namespace EE;
using namespace EE::UI;
using namespace EE::UI::Tools;
template <typename Predicate> 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 );
}

View File

@@ -0,0 +1,82 @@
#include "utest.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/ui/uiapplication.hpp>
#include <eepp/ui/uilinearlayout.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uiwidget.hpp>
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 );
}