diff --git a/.ecode/project_build.json b/.ecode/project_build.json
index e1b483ec7..b32e7a0e1 100644
--- a/.ecode/project_build.json
+++ b/.ecode/project_build.json
@@ -369,6 +369,12 @@
"command": "${project_root}/bin/eepp-ui-markdownview-debug",
"name": "eepp-ui-markdownview-debug",
"working_dir": "${project_root}/bin"
+ },
+ {
+ "args": "",
+ "command": "${project_root}/bin/eepp-ui-dropdownmodellist-debug",
+ "name": "eepp-ui-dropdownmodellist-debug",
+ "working_dir": "${project_root}/bin"
}
],
"var": {
diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css
index f0758618a..49e06d3fc 100644
--- a/bin/assets/ui/breeze.css
+++ b/bin/assets/ui/breeze.css
@@ -244,7 +244,8 @@ Anchor:hover {
PushButton,
SelectButton,
-DropDownList {
+DropDownList,
+DropDownModelList {
padding-left: var(--base-horizontal-padding);
padding-right: var(--base-horizontal-padding);
padding-top: var(--base-vertical-padding);
@@ -259,6 +260,7 @@ DropDownList {
}
DropDownList,
+DropDownModelList,
ComboBox::DropDownList {
max-visible-items: 6;
}
@@ -267,6 +269,8 @@ PushButton:hover,
PushButton:focus,
DropDownList:hover,
DropDownList:focus,
+DropDownModelList:hover,
+DropDownModelList:focus,
SelectButton:hover,
SelectButton:focus,
ComboBox:hover,
@@ -800,12 +804,14 @@ Window::border::bottom {
background-color: var(--separator);
}
-DropDownList {
+DropDownList,
+DropDownModelList {
padding-right: 16dp;
text-overflow: ellipsis;
}
DropDownList,
+DropDownModelList,
ComboBox::Button {
foreground-image: url("data:image/svg,");
foreground-position-x: right 6dp;
@@ -816,6 +822,8 @@ ComboBox::Button {
DropDownList:hover,
DropDownList:focus,
+DropDownModelList:hover,
+DropDownModelList:focus,
ComboBox::Button:focus,
ComboBox::Button:hover {
foreground-tint: var(--icon-active);
diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp
index 9c85f953a..4df910bc2 100644
--- a/include/eepp/ui/abstract/uiabstracttableview.hpp
+++ b/include/eepp/ui/abstract/uiabstracttableview.hpp
@@ -13,6 +13,7 @@ using namespace EE::Math;
namespace EE { namespace UI {
class UIPushButton;
class UILinearLayout;
+class UIDropDownModelList;
}} // namespace EE::UI
namespace EE { namespace UI { namespace Abstract {
@@ -146,6 +147,7 @@ class EE_API UIAbstractTableView : public UIAbstractView {
protected:
friend class EE::UI::UITableHeaderColumn;
+ friend class EE::UI::UIDropDownModelList;
struct ColumnData {
Float minWidth{ 0 };
diff --git a/include/eepp/ui/uidropdown.hpp b/include/eepp/ui/uidropdown.hpp
new file mode 100644
index 000000000..06659d6db
--- /dev/null
+++ b/include/eepp/ui/uidropdown.hpp
@@ -0,0 +1,89 @@
+#ifndef EE_UI_UIDROPDOWN_HPP
+#define EE_UI_UIDROPDOWN_HPP
+
+#include
+
+namespace EE { namespace UI {
+
+class EE_API UIDropDown : public UITextInput {
+ public:
+ enum class MenuWidthMode {
+ DropDown,
+ Contents,
+ ContentsCentered,
+ ExpandIfNeeded,
+ ExpandIfNeededCentered
+ };
+
+ static MenuWidthMode menuWidthModeFromString( std::string_view str );
+
+ static std::string menuWidthModeToString( MenuWidthMode rule );
+
+ struct StyleConfig {
+ Uint32 MaxNumVisibleItems = 10;
+ bool PopUpToRoot = false;
+ MenuWidthMode menuWidthRule{ MenuWidthMode::DropDown };
+ };
+
+ virtual ~UIDropDown();
+
+ virtual Uint32 getType() const;
+ virtual bool isType( const Uint32& type ) const;
+
+ virtual void setTheme( UITheme* Theme );
+
+ virtual UIDropDown* showList();
+
+ bool getPopUpToRoot() const;
+ UIDropDown* setPopUpToRoot( bool popUpToRoot );
+
+ Uint32 getMaxNumVisibleItems() const;
+ virtual UIDropDown* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
+
+ const StyleConfig& getStyleConfig() const;
+ UIDropDown* setStyleConfig( const StyleConfig& styleConfig );
+
+ UIDropDown* setMenuWidthMode( MenuWidthMode rule );
+ MenuWidthMode getMenuWidthMode() const;
+
+ virtual bool applyProperty( const StyleSheetProperty& attribute );
+ virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex = 0 ) const;
+ virtual std::vector getPropertiesImplemented() const;
+
+ protected:
+ StyleConfig mStyleConfig;
+ UINode* mFriendNode{ nullptr };
+
+ UIDropDown( const std::string& tag );
+
+ virtual UIWidget* getPopUpWidget() const;
+
+ void onPopUpFocusLoss( const Event* Event );
+
+ virtual void onItemSelected( const Event* Event );
+ virtual void show();
+ virtual void hide();
+
+ virtual Uint32 onMouseOver( const Vector2i& position, const Uint32& flags );
+ virtual Uint32 onMouseLeave( const Vector2i& position, const Uint32& flags );
+ virtual Uint32 onMouseClick( const Vector2i& position, const Uint32& flags );
+
+ virtual void onItemClicked( const Event* Event );
+ virtual void onItemKeyDown( const Event* Event );
+ virtual void onWidgetClear( const Event* Event );
+ virtual Uint32 onKeyDown( const KeyEvent& Event );
+
+ virtual void onSizeChange();
+ virtual void onAutoSize();
+ virtual void onThemeLoaded();
+
+ void setFriendNode( UINode* friendNode );
+
+ Float getPopUpWidth( Float contentsWidth ) const;
+ void alignPopUp( UIWidget* widget );
+};
+
+}} // namespace EE::UI
+
+#endif
diff --git a/include/eepp/ui/uidropdownlist.hpp b/include/eepp/ui/uidropdownlist.hpp
index 66f1a619e..2ed5f4daa 100644
--- a/include/eepp/ui/uidropdownlist.hpp
+++ b/include/eepp/ui/uidropdownlist.hpp
@@ -1,30 +1,15 @@
#ifndef EE_UICUIDROPDOWNLIST_HPP
#define EE_UICUIDROPDOWNLIST_HPP
+#include
#include
-#include
namespace EE { namespace UI {
-class EE_API UIDropDownList : public UITextInput {
+class EE_API UIDropDownList : public UIDropDown {
public:
- enum class MenuWidthMode {
- DropDown,
- Contents,
- ContentsCentered,
- ExpandIfNeeded,
- ExpandIfNeededCentered
- };
-
- static MenuWidthMode menuWidthModeFromString( std::string_view str );
-
- static std::string menuWidthModeToString( MenuWidthMode rule );
-
- struct StyleConfig {
- Uint32 MaxNumVisibleItems = 10;
- bool PopUpToRoot = false;
- MenuWidthMode menuWidthRule{ MenuWidthMode::DropDown };
- };
+ using MenuWidthMode = UIDropDown::MenuWidthMode;
+ using StyleConfig = UIDropDown::StyleConfig;
static UIDropDownList* NewWithTag( const std::string& tag );
@@ -33,84 +18,39 @@ class EE_API UIDropDownList : public UITextInput {
virtual ~UIDropDownList();
virtual Uint32 getType() const;
-
virtual bool isType( const Uint32& type ) const;
- virtual void setTheme( UITheme* Theme );
-
UIListBox* getListBox() const;
UIDropDownList* showList();
- bool getPopUpToRoot() const;
-
- UIDropDownList* setPopUpToRoot( bool popUpToRoot );
-
- Uint32 getMaxNumVisibleItems() const;
-
- UIDropDownList* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
-
- const StyleConfig& getStyleConfig() const;
-
- UIDropDownList* setStyleConfig( const StyleConfig& styleConfig );
+ virtual UIDropDownList* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
virtual bool applyProperty( const StyleSheetProperty& attribute );
-
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex = 0 ) const;
-
virtual std::vector getPropertiesImplemented() const;
virtual void loadFromXmlNode( const pugi::xml_node& node );
- UIDropDownList* setMenuWidthMode( MenuWidthMode rule );
-
- MenuWidthMode getMenuWidthMode() const;
-
protected:
friend class UIComboBox;
- StyleConfig mStyleConfig;
UIListBox* mListBox;
- UINode* mFriendNode;
Uint32 mListBoxCloseCb{ 0 };
UIDropDownList( const std::string& tag = "dropdownlist" );
- void onListBoxFocusLoss( const Event* Event );
+ virtual UIWidget* getPopUpWidget() const;
virtual void onItemSelected( const Event* Event );
- virtual void show();
-
- virtual void hide();
-
- virtual Uint32 onMouseOver( const Vector2i& position, const Uint32& flags );
-
- virtual Uint32 onMouseLeave( const Vector2i& position, const Uint32& flags );
-
virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
- virtual Uint32 onMouseClick( const Vector2i& position, const Uint32& flags );
-
- virtual void onItemClicked( const Event* Event );
-
- virtual void onItemKeyDown( const Event* Event );
-
- virtual void onWidgetClear( const Event* Event );
-
virtual Uint32 onKeyDown( const KeyEvent& Event );
virtual void onClassChange();
- virtual void onSizeChange();
-
- virtual void onAutoSize();
-
- virtual void onThemeLoaded();
-
- void setFriendNode( UINode* friendNode );
-
void destroyListBox();
};
diff --git a/include/eepp/ui/uidropdownmodellist.hpp b/include/eepp/ui/uidropdownmodellist.hpp
new file mode 100644
index 000000000..4fa61f217
--- /dev/null
+++ b/include/eepp/ui/uidropdownmodellist.hpp
@@ -0,0 +1,68 @@
+#ifndef EE_UI_UIDROPDOWNMODELLIST_HPP
+#define EE_UI_UIDROPDOWNMODELLIST_HPP
+
+#include
+#include
+
+namespace EE { namespace UI {
+
+class EE_API UIDropDownModelList : public UIDropDown {
+ public:
+ using MenuWidthMode = UIDropDown::MenuWidthMode;
+ using StyleConfig = UIDropDown::StyleConfig;
+
+ static UIDropDownModelList* NewWithTag( const std::string& tag );
+
+ static UIDropDownModelList* New();
+
+ virtual ~UIDropDownModelList();
+
+ virtual Uint32 getType() const;
+ virtual bool isType( const Uint32& type ) const;
+
+ UIAbstractTableView* getListView() const;
+
+ void setListView( UIAbstractTableView* listView );
+
+ std::shared_ptr getModel() const;
+
+ virtual void setModel( std::shared_ptr model );
+
+ UIDropDownModelList* showList();
+
+ virtual UIDropDownModelList* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
+
+ virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex = 0 ) const;
+
+ virtual std::vector getPropertiesImplemented() const;
+
+ protected:
+ UIAbstractTableView* mListView;
+ Uint32 mListViewCloseCb{ 0 };
+ std::shared_ptr mModel;
+
+ UIDropDownModelList( const std::string& tag = "dropdownmodellist" );
+
+ virtual UIWidget* getPopUpWidget() const;
+
+ virtual void onItemSelected( const Event* Event );
+
+ virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
+
+ virtual Uint32 onKeyDown( const KeyEvent& Event );
+
+ virtual void onItemClicked( const Event* Event );
+
+ virtual void onClassChange();
+
+ void destroyListView();
+
+ UIWidget* createDefaultListView();
+
+ void updateSelectionIndex();
+};
+
+}} // namespace EE::UI
+
+#endif
diff --git a/include/eepp/ui/uihelper.hpp b/include/eepp/ui/uihelper.hpp
index 7e87df911..e1aa2bc5b 100644
--- a/include/eepp/ui/uihelper.hpp
+++ b/include/eepp/ui/uihelper.hpp
@@ -65,6 +65,7 @@ enum UINodeType {
UI_TYPE_PROGRESSBAR,
UI_TYPE_LISTBOX,
UI_TYPE_LISTBOXITEM,
+ UI_TYPE_DROPDOWN,
UI_TYPE_DROPDOWNLIST,
UI_TYPE_MENU_SEPARATOR,
UI_TYPE_COMBOBOX,
@@ -117,6 +118,7 @@ enum UINodeType {
UI_TYPE_HTML_TABLE_FOOTER,
UI_TYPE_HTML_TABLE_ROW,
UI_TYPE_HTML_TABLE_CELL,
+ UI_TYPE_DROPDOWNMODELLIST,
UI_TYPE_MODULES = 10000,
UI_TYPE_TERMINAL = 10001,
UI_TYPE_USER = 200000,
diff --git a/include/eepp/ui/uilistview.hpp b/include/eepp/ui/uilistview.hpp
index b80d89237..c1c933d30 100644
--- a/include/eepp/ui/uilistview.hpp
+++ b/include/eepp/ui/uilistview.hpp
@@ -9,6 +9,8 @@ class EE_API UIListView : public UITableView {
public:
static UIListView* New();
+ static UIListView* NewWithTag( const std::string& tag );
+
Uint32 getType() const;
bool isType( const Uint32& type ) const;
@@ -16,7 +18,7 @@ class EE_API UIListView : public UITableView {
void setTheme( UITheme* Theme );
protected:
- UIListView();
+ UIListView( const std::string& tag = "listview" );
};
}} // namespace EE::UI
diff --git a/premake4.lua b/premake4.lua
index a864173f7..ef9f966d1 100644
--- a/premake4.lua
+++ b/premake4.lua
@@ -1580,6 +1580,12 @@ solution "eepp"
files { "src/examples/ui_application_hello_world/*.cpp" }
build_link_configuration( "eepp-ui-application-hello-world", true )
+ project "eepp-ui-dropdownmodellist"
+ set_kind()
+ language "C++"
+ files { "src/examples/ui_dropdownmodellist/*.cpp" }
+ build_link_configuration( "eepp-ui-dropdownmodellist", true )
+
project "eepp-ui-richtext"
set_kind()
language "C++"
diff --git a/premake5.lua b/premake5.lua
index 124efc44e..38a5aef5b 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -1466,6 +1466,12 @@ workspace "eepp"
files { "src/examples/ui_application_hello_world/*.cpp" }
build_link_configuration( "eepp-ui-application-hello-world", true )
+ project "eepp-ui-dropdownmodellist"
+ set_kind()
+ language "C++"
+ files { "src/examples/ui_dropdownmodellist/*.cpp" }
+ build_link_configuration( "eepp-ui-dropdownmodellist", true )
+
project "eepp-ui-richtext"
set_kind()
language "C++"
diff --git a/src/eepp/ui/doc/languages/css.cpp b/src/eepp/ui/doc/languages/css.cpp
index adee86d4f..c98e26a4a 100644
--- a/src/eepp/ui/doc/languages/css.cpp
+++ b/src/eepp/ui/doc/languages/css.cpp
@@ -384,6 +384,7 @@ void addCSS() {
{ "RadioButton", "keyword" },
{ "ComboBox", "keyword" },
{ "DropDownList", "keyword" },
+ { "DropDownModelList", "keyword" },
{ "Image", "keyword" },
{ "ListBox", "keyword" },
{ "MenuBar", "keyword" },
@@ -425,6 +426,7 @@ void addCSS() {
{ "PopUpMenu", "keyword" },
{ "ImageViewer", "keyword" },
{ "AudioPlayer", "keyword" },
+ { "Table", "keyword" },
},
"",
diff --git a/src/eepp/ui/uidropdown.cpp b/src/eepp/ui/uidropdown.cpp
new file mode 100644
index 000000000..7186e574e
--- /dev/null
+++ b/src/eepp/ui/uidropdown.cpp
@@ -0,0 +1,339 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace EE { namespace UI {
+
+UIDropDown::MenuWidthMode UIDropDown::menuWidthModeFromString( std::string_view str ) {
+ if ( "contents" == str || "fit-to-contents" == str )
+ return MenuWidthMode::Contents;
+ if ( "contents-centered" == str || "fit-to-contents-centered" == str )
+ return MenuWidthMode::ContentsCentered;
+ if ( "expand-if-needed" == str || "fit-to-drop-down-expand-if-needed" == str )
+ return MenuWidthMode::ExpandIfNeeded;
+ if ( "expand-if-needed-centered" == str || "fit-to-drop-down-expand-if-needed-centered" == str )
+ return MenuWidthMode::ExpandIfNeededCentered;
+ return MenuWidthMode::DropDown; // "dropdown"
+}
+
+std::string UIDropDown::menuWidthModeToString( MenuWidthMode rule ) {
+ switch ( rule ) {
+ case MenuWidthMode::DropDown:
+ return "dropdown";
+ case MenuWidthMode::Contents:
+ return "contents";
+ case MenuWidthMode::ContentsCentered:
+ return "contents-centered";
+ case MenuWidthMode::ExpandIfNeeded:
+ return "expand-if-needed";
+ case MenuWidthMode::ExpandIfNeededCentered:
+ return "expand-if-needed-centered";
+ }
+ return "dropdown";
+}
+
+UIDropDown::UIDropDown( const std::string& tag ) : UITextInput( tag ) {
+ mEnabledCreateContextMenu = false;
+ setClipType( ClipType::ContentBox );
+ setFlags( UI_AUTO_SIZE | UI_AUTO_PADDING | UI_SCROLLABLE );
+ unsetFlags( UI_TEXT_SELECTION_ENABLED );
+ setAllowEditing( false );
+}
+
+UIDropDown::~UIDropDown() {}
+
+Uint32 UIDropDown::getType() const {
+ return UI_TYPE_DROPDOWN;
+}
+
+bool UIDropDown::isType( const Uint32& type ) const {
+ return UIDropDown::getType() == type ? true : UITextInput::isType( type );
+}
+
+void UIDropDown::setTheme( UITheme* Theme ) {
+ UIWidget::setTheme( Theme );
+ setThemeSkin( Theme, "dropdownlist" );
+ onThemeLoaded();
+}
+
+void UIDropDown::onSizeChange() {
+ onAutoSize();
+ UITextInput::onSizeChange();
+}
+
+void UIDropDown::onThemeLoaded() {
+ autoPadding();
+ onAutoSize();
+}
+
+void UIDropDown::setFriendNode( UINode* friendNode ) {
+ mFriendNode = friendNode;
+}
+
+void UIDropDown::onAutoSize() {
+ Float max = eemax( PixelDensity::dpToPxI( getSkinSize().getHeight() ),
+ mTextCache.getLineSpacing() );
+
+ if ( mHeightPolicy == SizePolicy::WrapContent ) {
+ setInternalPixelsHeight( eeceil( max + mPaddingPx.Top + mPaddingPx.Bottom ) );
+ } else if ( ( ( mFlags & UI_AUTO_SIZE ) || 0 == getSize().getHeight() ) && max > 0 ) {
+ setInternalPixelsHeight( eeceil( max ) );
+ }
+}
+
+UIWidget* UIDropDown::getPopUpWidget() const {
+ return nullptr;
+}
+
+Uint32 UIDropDown::onMouseClick( const Vector2i& Pos, const Uint32& Flags ) {
+ if ( ( Flags & EE_BUTTON_LMASK ) && NULL == mFriendNode )
+ showList();
+
+ if ( NULL != mFriendNode ) {
+ UITextInput::onMouseClick( Pos, Flags );
+ }
+
+ return 1;
+}
+
+UIDropDown* UIDropDown::showList() {
+ return this;
+}
+
+Float UIDropDown::getPopUpWidth( Float contentsWidth ) const {
+ Float width = NULL != mFriendNode ? mFriendNode->getSize().getWidth() : getSize().getWidth();
+
+ if ( mStyleConfig.menuWidthRule == MenuWidthMode::Contents ||
+ mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ) {
+ width = contentsWidth;
+ }
+
+ if ( ( mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeeded ||
+ mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered ) &&
+ contentsWidth > width ) {
+ width = contentsWidth;
+ }
+
+ return width;
+}
+
+void UIDropDown::alignPopUp( UIWidget* widget ) {
+ if ( !mStyleConfig.PopUpToRoot )
+ widget->setParent( getWindowContainer() );
+ else
+ widget->setParent( getUISceneNode()->getRoot() );
+
+ widget->toFront();
+
+ bool center = mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ||
+ mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered;
+
+ Float width = widget->getSize().getWidth();
+ Float offsetX = center ? eefloor( ( getSize().getWidth() - width ) * 0.5f ) : 0;
+
+ Vector2f pos( mDpPos.x + offsetX, mDpPos.y + getSize().getHeight() );
+ Vector2f posCpy( pos );
+ nodeToWorld( posCpy );
+
+ if ( !getUISceneNode()->getWorldBounds().contains( Rectf( posCpy, widget->getSize() ) ) ) {
+ pos = Vector2f( mDpPos.x + offsetX, mDpPos.y - widget->getSize().getHeight() );
+ }
+
+ if ( mStyleConfig.PopUpToRoot ) {
+ getParent()->nodeToWorld( pos );
+ pos = PixelDensity::pxToDp( pos );
+ } else {
+ Node* parentNode = getParent();
+ Node* rp = getWindowContainer();
+ while ( rp != parentNode ) {
+ pos += parentNode->getPosition();
+ parentNode = parentNode->getParent();
+ }
+ }
+
+ widget->setPosition( pos );
+ show();
+ widget->setFocus();
+}
+
+bool UIDropDown::getPopUpToRoot() const {
+ return mStyleConfig.PopUpToRoot;
+}
+
+UIDropDown* UIDropDown::setPopUpToRoot( bool popUpToRoot ) {
+ mStyleConfig.PopUpToRoot = popUpToRoot;
+ return this;
+}
+
+Uint32 UIDropDown::getMaxNumVisibleItems() const {
+ return mStyleConfig.MaxNumVisibleItems;
+}
+
+UIDropDown* UIDropDown::setMaxNumVisibleItems( const Uint32& maxNumVisibleItems ) {
+ mStyleConfig.MaxNumVisibleItems = maxNumVisibleItems;
+ return this;
+}
+
+const UIDropDown::StyleConfig& UIDropDown::getStyleConfig() const {
+ return mStyleConfig;
+}
+
+UIDropDown* UIDropDown::setStyleConfig( const StyleConfig& styleConfig ) {
+ mStyleConfig = styleConfig;
+
+ setMaxNumVisibleItems( mStyleConfig.MaxNumVisibleItems );
+ setPopUpToRoot( mStyleConfig.PopUpToRoot );
+ setMenuWidthMode( mStyleConfig.menuWidthRule );
+ return this;
+}
+
+void UIDropDown::onWidgetClear( const Event* ) {
+ setText( "" );
+ sendCommonEvent( Event::OnClear );
+}
+
+void UIDropDown::onItemKeyDown( const Event* Event ) {
+ const KeyEvent* KEvent = reinterpret_cast( Event );
+
+ if ( KEvent->getKeyCode() == KEY_RETURN )
+ onItemClicked( Event );
+ else if ( KEvent->getKeyCode() == KEY_ESCAPE ) {
+ hide();
+ setFocus();
+ }
+}
+
+void UIDropDown::onPopUpFocusLoss( const Event* ) {
+ if ( NULL == getEventDispatcher() )
+ return;
+
+ bool frienIsFocus = NULL != mFriendNode && mFriendNode == getEventDispatcher()->getFocusNode();
+ bool isChildFocus = isChild( getEventDispatcher()->getFocusNode() );
+
+ if ( getEventDispatcher()->getFocusNode() != this && !isChildFocus && !frienIsFocus ) {
+ hide();
+ }
+}
+
+void UIDropDown::onItemClicked( const Event* ) {
+ hide();
+ setFocus();
+}
+
+void UIDropDown::onItemSelected( const Event* ) {}
+
+void UIDropDown::show() {
+ UIWidget* widget = getPopUpWidget();
+ if ( NULL == widget )
+ return;
+
+ widget->setEnabled( true );
+ widget->setVisible( true );
+
+ if ( NULL != getUISceneNode() &&
+ getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
+ widget->runAction( Actions::Sequence::New(
+ Actions::Fade::New( 255.f == widget->getAlpha() ? 0.f : widget->getAlpha(), 255.f,
+ getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
+ Actions::Spawn::New( Actions::Enable::New(), Actions::Visible::New( true ) ) ) );
+ }
+}
+
+void UIDropDown::hide() {
+ UIWidget* widget = getPopUpWidget();
+ if ( NULL == widget )
+ return;
+
+ if ( NULL != getUISceneNode() &&
+ getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
+ widget->runAction( Actions::Sequence::New(
+ Actions::FadeOut::New( getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
+ Actions::Spawn::New( Actions::Disable::New(), Actions::Visible::New( false ) ) ) );
+ } else {
+ widget->setEnabled( false );
+ widget->setVisible( false );
+ }
+}
+
+Uint32 UIDropDown::onMouseOver( const Vector2i& position, const Uint32& flags ) {
+ if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
+ return UITextInput::onMouseOver( position, flags );
+ } else {
+ return UITextView::onMouseOver( position, flags );
+ }
+}
+
+Uint32 UIDropDown::onMouseLeave( const Vector2i& position, const Uint32& flags ) {
+ if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
+ return UITextInput::onMouseLeave( position, flags );
+ } else {
+ return UITextView::onMouseLeave( position, flags );
+ }
+}
+
+Uint32 UIDropDown::onKeyDown( const KeyEvent& Event ) {
+ return UITextInput::onKeyDown( Event );
+}
+
+bool UIDropDown::applyProperty( const StyleSheetProperty& attribute ) {
+ if ( !checkPropertyDefinition( attribute ) )
+ return false;
+
+ switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
+ case PropertyId::PopUpToRoot:
+ setPopUpToRoot( attribute.asBool() );
+ break;
+ case PropertyId::MaxVisibleItems:
+ setMaxNumVisibleItems( attribute.asUint() );
+ break;
+ case PropertyId::MenuWidthMode:
+ setMenuWidthMode( menuWidthModeFromString( attribute.getValue() ) );
+ break;
+ default:
+ return UITextInput::applyProperty( attribute );
+ }
+
+ return true;
+}
+
+std::string UIDropDown::getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex ) const {
+ if ( NULL == propertyDef )
+ return "";
+
+ switch ( propertyDef->getPropertyId() ) {
+ case PropertyId::PopUpToRoot:
+ return mStyleConfig.PopUpToRoot ? "true" : "false";
+ case PropertyId::MaxVisibleItems:
+ return String::toString( mStyleConfig.MaxNumVisibleItems );
+ case PropertyId::MenuWidthMode:
+ return menuWidthModeToString( mStyleConfig.menuWidthRule );
+ default:
+ return UITextInput::getPropertyString( propertyDef, propertyIndex );
+ }
+
+ return "";
+}
+
+std::vector UIDropDown::getPropertiesImplemented() const {
+ auto props = UITextInput::getPropertiesImplemented();
+ auto local = { PropertyId::PopUpToRoot, PropertyId::MaxVisibleItems,
+ PropertyId::MenuWidthMode };
+ props.insert( props.end(), local.begin(), local.end() );
+ return props;
+}
+
+UIDropDown* UIDropDown::setMenuWidthMode( MenuWidthMode rule ) {
+ mStyleConfig.menuWidthRule = rule;
+ return this;
+}
+
+UIDropDown::MenuWidthMode UIDropDown::getMenuWidthMode() const {
+ return mStyleConfig.menuWidthRule;
+}
+
+}} // namespace EE::UI
diff --git a/src/eepp/ui/uidropdownlist.cpp b/src/eepp/ui/uidropdownlist.cpp
index ba5c7fe21..cc6cab9e3 100644
--- a/src/eepp/ui/uidropdownlist.cpp
+++ b/src/eepp/ui/uidropdownlist.cpp
@@ -10,34 +10,6 @@
namespace EE { namespace UI {
-UIDropDownList::MenuWidthMode UIDropDownList::menuWidthModeFromString( std::string_view str ) {
- if ( "contents" == str || "fit-to-contents" == str )
- return MenuWidthMode::Contents;
- if ( "contents-centered" == str || "fit-to-contents-centered" == str )
- return MenuWidthMode::ContentsCentered;
- if ( "expand-if-needed" == str || "fit-to-drop-down-expand-if-needed" == str )
- return MenuWidthMode::ExpandIfNeeded;
- if ( "expand-if-needed-centered" == str || "fit-to-drop-down-expand-if-needed-centered" == str )
- return MenuWidthMode::ExpandIfNeededCentered;
- return MenuWidthMode::DropDown; // "dropdown"
-}
-
-std::string UIDropDownList::menuWidthModeToString( MenuWidthMode rule ) {
- switch ( rule ) {
- case MenuWidthMode::DropDown:
- return "dropdown";
- case MenuWidthMode::Contents:
- return "contents";
- case MenuWidthMode::ContentsCentered:
- return "contents-centered";
- case MenuWidthMode::ExpandIfNeeded:
- return "expand-if-needed";
- case MenuWidthMode::ExpandIfNeededCentered:
- return "expand-if-needed-centered";
- }
- return "dropdown";
-}
-
UIDropDownList* UIDropDownList::NewWithTag( const std::string& tag ) {
return eeNew( UIDropDownList, ( tag ) );
}
@@ -46,15 +18,7 @@ UIDropDownList* UIDropDownList::New() {
return eeNew( UIDropDownList, () );
}
-UIDropDownList::UIDropDownList( const std::string& tag ) :
- UITextInput( tag ), mListBox( NULL ), mFriendNode( NULL ) {
- mEnabledCreateContextMenu = false;
- setClipType( ClipType::ContentBox );
- setFlags( UI_AUTO_SIZE | UI_AUTO_PADDING | UI_SCROLLABLE );
- unsetFlags( UI_TEXT_SELECTION_ENABLED );
-
- setAllowEditing( false );
-
+UIDropDownList::UIDropDownList( const std::string& tag ) : UIDropDown( tag ), mListBox( NULL ) {
applyDefaultTheme();
mListBox = UIListBox::NewWithTag( mTag + "::listbox" );
@@ -65,7 +29,7 @@ UIDropDownList::UIDropDownList( const std::string& tag ) :
// This will force to change the parent when shown, and force the CSS style reload.
mListBox->setParent( this );
- mListBox->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onListBoxFocusLoss( event ); } );
+ mListBox->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onPopUpFocusLoss( event ); } );
mListBox->on( Event::OnItemSelected, [this]( auto event ) { onItemSelected( event ); } );
mListBox->on( Event::OnItemClicked, [this]( auto event ) { onItemClicked( event ); } );
mListBox->on( Event::OnItemKeyDown, [this]( auto event ) { onItemKeyDown( event ); } );
@@ -95,42 +59,11 @@ Uint32 UIDropDownList::getType() const {
}
bool UIDropDownList::isType( const Uint32& type ) const {
- return UIDropDownList::getType() == type ? true : UITextInput::isType( type );
+ return UIDropDownList::getType() == type ? true : UIDropDown::isType( type );
}
-void UIDropDownList::setTheme( UITheme* Theme ) {
- UIWidget::setTheme( Theme );
-
- setThemeSkin( Theme, "dropdownlist" );
-
- onThemeLoaded();
-}
-
-void UIDropDownList::onSizeChange() {
- onAutoSize();
-
- UITextInput::onSizeChange();
-}
-
-void UIDropDownList::onThemeLoaded() {
- autoPadding();
-
- onAutoSize();
-}
-
-void UIDropDownList::setFriendNode( UINode* friendNode ) {
- mFriendNode = friendNode;
-}
-
-void UIDropDownList::onAutoSize() {
- Float max = eemax( PixelDensity::dpToPxI( getSkinSize().getHeight() ),
- mTextCache.getLineSpacing() );
-
- if ( mHeightPolicy == SizePolicy::WrapContent ) {
- setInternalPixelsHeight( eeceil( max + mPaddingPx.Top + mPaddingPx.Bottom ) );
- } else if ( ( ( mFlags & UI_AUTO_SIZE ) || 0 == getSize().getHeight() ) && max > 0 ) {
- setInternalPixelsHeight( eeceil( max ) );
- }
+UIWidget* UIDropDownList::getPopUpWidget() const {
+ return mListBox;
}
UIListBox* UIDropDownList::getListBox() const {
@@ -152,18 +85,14 @@ Uint32 UIDropDownList::onMouseUp( const Vector2i& Pos, const Uint32& Flags ) {
}
}
- return UITextInput::onMouseUp( Pos, Flags );
+ return UIDropDown::onMouseUp( Pos, Flags );
}
-Uint32 UIDropDownList::onMouseClick( const Vector2i& Pos, const Uint32& Flags ) {
- if ( ( Flags & EE_BUTTON_LMASK ) && NULL == mFriendNode )
- showList();
+Uint32 UIDropDownList::onKeyDown( const KeyEvent& Event ) {
+ if ( NULL != mListBox )
+ mListBox->onKeyDown( Event );
- if ( NULL != mFriendNode ) {
- UITextInput::onMouseClick( Pos, Flags );
- }
-
- return 1;
+ return UIDropDown::onKeyDown( Event );
}
UIDropDownList* UIDropDownList::showList() {
@@ -173,85 +102,30 @@ UIDropDownList* UIDropDownList::showList() {
if ( !mListBox->isVisible() ) {
if ( mListBox->getItemsCount() ) {
Rectf tPadding = mListBox->getContainerPadding();
-
Float sliderValue = mListBox->getVerticalScrollBar()->getValue();
- Float width =
- NULL != mFriendNode ? mFriendNode->getSize().getWidth() : getSize().getWidth();
+ Float contentsWidth = eeceil( PixelDensity::pxToDp(
+ mListBox->getMaxTextWidth() +
+ PixelDensity::dpToPx( mListBox->getContainerPadding().getWidth() ) +
+ mListBox->getReferenceItem()->getPixelsPadding().getWidth() +
+ mListBox->getVerticalScrollBar()->getPixelsSize().getWidth() ) );
- bool center = mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered;
+ Float width = getPopUpWidth( contentsWidth );
- Float contentsWidth = 0;
- if ( mStyleConfig.menuWidthRule == MenuWidthMode::Contents ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeeded ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered ) {
- contentsWidth = eeceil( PixelDensity::pxToDp(
- mListBox->getMaxTextWidth() +
- PixelDensity::dpToPx( mListBox->getContainerPadding().getWidth() ) +
- mListBox->getReferenceItem()->getPixelsPadding().getWidth() +
- mListBox->getVerticalScrollBar()->getPixelsSize().getWidth() ) );
- }
-
- if ( mStyleConfig.menuWidthRule == MenuWidthMode::Contents ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ) {
- width = contentsWidth;
- }
-
- if ( ( mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeeded ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered ) &&
- contentsWidth > width ) {
- width = contentsWidth;
- }
-
- mListBox->setSize(
- width, (Int32)( eemin( mListBox->getItemsCount(), mStyleConfig.MaxNumVisibleItems ) *
- mListBox->getRowHeight() ) +
- tPadding.Top + tPadding.Bottom +
- ( mListBox->getHorizontalScrollBar() &&
- mListBox->getHorizontalScrollBar()->isVisible() &&
- PixelDensity::dpToPx( width ) < mListBox->getMaxTextWidth()
- ? mListBox->getHorizontalScrollBar()->getSize().getHeight()
- : 0.f ) );
+ Float height =
+ (Int32)( eemin( mListBox->getItemsCount(), mStyleConfig.MaxNumVisibleItems ) *
+ mListBox->getRowHeight() ) +
+ tPadding.Top + tPadding.Bottom +
+ ( mListBox->getHorizontalScrollBar() &&
+ mListBox->getHorizontalScrollBar()->isVisible() &&
+ PixelDensity::dpToPx( width ) < mListBox->getMaxTextWidth()
+ ? mListBox->getHorizontalScrollBar()->getSize().getHeight()
+ : 0.f );
+ mListBox->setSize( width, height );
mListBox->getVerticalScrollBar()->setValue( sliderValue );
- if ( !mStyleConfig.PopUpToRoot )
- mListBox->setParent( getWindowContainer() );
- else
- mListBox->setParent( getUISceneNode()->getRoot() );
-
- mListBox->toFront();
-
- Float offsetX = center ? eefloor( ( getSize().getWidth() - width ) * 0.5f ) : 0;
-
- Vector2f pos( mDpPos.x + offsetX, mDpPos.y + getSize().getHeight() );
- Vector2f posCpy( pos );
- nodeToWorld( posCpy );
-
- if ( !getUISceneNode()->getWorldBounds().contains(
- Rectf( posCpy, mListBox->getSize() ) ) ) {
- pos = Vector2f( mDpPos.x + offsetX, mDpPos.y - mListBox->getSize().getHeight() );
- }
-
- if ( mStyleConfig.PopUpToRoot ) {
- getParent()->nodeToWorld( pos );
- pos = PixelDensity::pxToDp( pos );
- } else {
- Node* parentNode = getParent();
- Node* rp = getWindowContainer();
- while ( rp != parentNode ) {
- pos += parentNode->getPosition();
- parentNode = parentNode->getParent();
- }
- }
-
- mListBox->setPosition( pos );
-
- show();
-
- mListBox->setFocus();
+ alignPopUp( mListBox );
}
} else {
hide();
@@ -259,19 +133,6 @@ UIDropDownList* UIDropDownList::showList() {
return this;
}
-bool UIDropDownList::getPopUpToRoot() const {
- return mStyleConfig.PopUpToRoot;
-}
-
-UIDropDownList* UIDropDownList::setPopUpToRoot( bool popUpToRoot ) {
- mStyleConfig.PopUpToRoot = popUpToRoot;
- return this;
-}
-
-Uint32 UIDropDownList::getMaxNumVisibleItems() const {
- return mStyleConfig.MaxNumVisibleItems;
-}
-
UIDropDownList* UIDropDownList::setMaxNumVisibleItems( const Uint32& maxNumVisibleItems ) {
if ( maxNumVisibleItems != mStyleConfig.MaxNumVisibleItems ) {
mStyleConfig.MaxNumVisibleItems = maxNumVisibleItems;
@@ -284,52 +145,6 @@ UIDropDownList* UIDropDownList::setMaxNumVisibleItems( const Uint32& maxNumVisib
return this;
}
-const UIDropDownList::StyleConfig& UIDropDownList::getStyleConfig() const {
- return mStyleConfig;
-}
-
-UIDropDownList* UIDropDownList::setStyleConfig( const StyleConfig& styleConfig ) {
- mStyleConfig = styleConfig;
-
- setMaxNumVisibleItems( mStyleConfig.MaxNumVisibleItems );
- setPopUpToRoot( mStyleConfig.PopUpToRoot );
- setMenuWidthMode( mStyleConfig.menuWidthRule );
- return this;
-}
-
-void UIDropDownList::onWidgetClear( const Event* ) {
- setText( "" );
- sendCommonEvent( Event::OnClear );
-}
-
-void UIDropDownList::onItemKeyDown( const Event* Event ) {
- const KeyEvent* KEvent = reinterpret_cast( Event );
-
- if ( KEvent->getKeyCode() == KEY_RETURN )
- onItemClicked( Event );
- else if ( KEvent->getKeyCode() == KEY_ESCAPE ) {
- hide();
- setFocus();
- }
-}
-
-void UIDropDownList::onListBoxFocusLoss( const Event* ) {
- if ( NULL == getEventDispatcher() )
- return;
-
- bool frienIsFocus = NULL != mFriendNode && mFriendNode == getEventDispatcher()->getFocusNode();
- bool isChildFocus = isChild( getEventDispatcher()->getFocusNode() );
-
- if ( getEventDispatcher()->getFocusNode() != this && !isChildFocus && !frienIsFocus ) {
- hide();
- }
-}
-
-void UIDropDownList::onItemClicked( const Event* ) {
- hide();
- setFocus();
-}
-
void UIDropDownList::onItemSelected( const Event* ) {
setText( mListBox->getItemSelectedText() );
@@ -341,60 +156,6 @@ void UIDropDownList::onItemSelected( const Event* ) {
sendCommonEvent( Event::OnSelectionChanged );
}
-void UIDropDownList::show() {
- if ( NULL == mListBox )
- return;
-
- mListBox->setEnabled( true );
- mListBox->setVisible( true );
-
- if ( NULL != getUISceneNode() &&
- getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
- mListBox->runAction( Actions::Sequence::New(
- Actions::Fade::New( 255.f == mListBox->getAlpha() ? 0.f : mListBox->getAlpha(), 255.f,
- getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
- Actions::Spawn::New( Actions::Enable::New(), Actions::Visible::New( true ) ) ) );
- }
-}
-
-void UIDropDownList::hide() {
- if ( NULL == mListBox )
- return;
-
- if ( NULL != getUISceneNode() &&
- getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
- mListBox->runAction( Actions::Sequence::New(
- Actions::FadeOut::New( getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
- Actions::Spawn::New( Actions::Disable::New(), Actions::Visible::New( false ) ) ) );
- } else {
- mListBox->setEnabled( false );
- mListBox->setVisible( false );
- }
-}
-
-Uint32 UIDropDownList::onMouseOver( const Vector2i& position, const Uint32& flags ) {
- if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
- return UITextInput::onMouseOver( position, flags );
- } else {
- return UITextView::onMouseOver( position, flags );
- }
-}
-
-Uint32 UIDropDownList::onMouseLeave( const Vector2i& position, const Uint32& flags ) {
- if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
- return UITextInput::onMouseLeave( position, flags );
- } else {
- return UITextView::onMouseLeave( position, flags );
- }
-}
-
-Uint32 UIDropDownList::onKeyDown( const KeyEvent& Event ) {
- if ( NULL != mListBox )
- mListBox->onKeyDown( Event );
-
- return UITextInput::onKeyDown( Event );
-}
-
void UIDropDownList::destroyListBox() {
if ( !SceneManager::instance()->isShuttingDown() && NULL != mListBox &&
mListBox->getParent() != this ) {
@@ -407,15 +168,6 @@ bool UIDropDownList::applyProperty( const StyleSheetProperty& attribute ) {
return false;
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
- case PropertyId::PopUpToRoot:
- setPopUpToRoot( attribute.asBool() );
- break;
- case PropertyId::MaxVisibleItems:
- setMaxNumVisibleItems( attribute.asUint() );
- break;
- case PropertyId::MenuWidthMode:
- setMenuWidthMode( menuWidthModeFromString( attribute.getValue() ) );
- break;
case PropertyId::SelectedIndex:
case PropertyId::SelectedText:
case PropertyId::ScrollBarStyle:
@@ -427,7 +179,7 @@ bool UIDropDownList::applyProperty( const StyleSheetProperty& attribute ) {
else
return false;
default:
- return UITextInput::applyProperty( attribute );
+ return UIDropDown::applyProperty( attribute );
}
return true;
@@ -439,12 +191,6 @@ std::string UIDropDownList::getPropertyString( const PropertyDefinition* propert
return "";
switch ( propertyDef->getPropertyId() ) {
- case PropertyId::PopUpToRoot:
- return mStyleConfig.PopUpToRoot ? "true" : "false";
- case PropertyId::MaxVisibleItems:
- return String::toString( mStyleConfig.MaxNumVisibleItems );
- case PropertyId::MenuWidthMode:
- return menuWidthModeToString( mStyleConfig.menuWidthRule );
case PropertyId::SelectedIndex:
case PropertyId::SelectedText:
case PropertyId::ScrollBarStyle:
@@ -454,18 +200,16 @@ std::string UIDropDownList::getPropertyString( const PropertyDefinition* propert
if ( NULL != mListBox )
return mListBox->getPropertyString( propertyDef, propertyIndex );
default:
- return UITextInput::getPropertyString( propertyDef, propertyIndex );
+ return UIDropDown::getPropertyString( propertyDef, propertyIndex );
}
return "";
}
std::vector UIDropDownList::getPropertiesImplemented() const {
- auto props = UITextInput::getPropertiesImplemented();
- auto local = {
- PropertyId::PopUpToRoot, PropertyId::MaxVisibleItems, PropertyId::SelectedIndex,
- PropertyId::SelectedText, PropertyId::ScrollBarStyle, PropertyId::RowHeight,
- PropertyId::VScrollMode, PropertyId::HScrollMode, PropertyId::MenuWidthMode };
+ auto props = UIDropDown::getPropertiesImplemented();
+ auto local = { PropertyId::SelectedIndex, PropertyId::SelectedText, PropertyId::ScrollBarStyle,
+ PropertyId::RowHeight, PropertyId::VScrollMode, PropertyId::HScrollMode };
props.insert( props.end(), local.begin(), local.end() );
return props;
}
@@ -476,7 +220,7 @@ void UIDropDownList::loadFromXmlNode( const pugi::xml_node& node ) {
if ( NULL != mListBox )
mListBox->loadItemsFromXmlNode( node );
- UITextInput::loadFromXmlNode( node );
+ UIDropDown::loadFromXmlNode( node );
endAttributesTransaction();
}
@@ -486,13 +230,4 @@ void UIDropDownList::onClassChange() {
mListBox->setClasses( getClasses() );
}
-UIDropDownList* UIDropDownList::setMenuWidthMode( MenuWidthMode rule ) {
- mStyleConfig.menuWidthRule = rule;
- return this;
-}
-
-UIDropDownList::MenuWidthMode UIDropDownList::getMenuWidthMode() const {
- return mStyleConfig.menuWidthRule;
-}
-
}} // namespace EE::UI
diff --git a/src/eepp/ui/uidropdownmodellist.cpp b/src/eepp/ui/uidropdownmodellist.cpp
new file mode 100644
index 000000000..9eed3e2b5
--- /dev/null
+++ b/src/eepp/ui/uidropdownmodellist.cpp
@@ -0,0 +1,267 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define PUGIXML_HEADER_ONLY
+#include
+
+namespace EE { namespace UI {
+
+UIDropDownModelList* UIDropDownModelList::NewWithTag( const std::string& tag ) {
+ return eeNew( UIDropDownModelList, ( tag ) );
+}
+
+UIDropDownModelList* UIDropDownModelList::New() {
+ return eeNew( UIDropDownModelList, () );
+}
+
+UIDropDownModelList::UIDropDownModelList( const std::string& tag ) :
+ UIDropDown( tag ), mListView( NULL ) {
+ mListView = static_cast( createDefaultListView() );
+ mListView->setEnabled( false );
+ mListView->setVisible( false );
+ mListView->setParent( this );
+ mListView->setSingleClickNavigation( true );
+
+ mListView->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onPopUpFocusLoss( event ); } );
+ mListView->on( Event::OnModelEvent, [this]( auto event ) { onItemSelected( event ); } );
+ mListView->on( Event::KeyDown, [this]( auto event ) { onItemKeyDown( event ); } );
+ mListView->on( Event::OnClear, [this]( auto event ) { onWidgetClear( event ); } );
+ mListViewCloseCb =
+ mListView->on( Event::OnClose, [this]( const Event* ) { mListView = nullptr; } );
+
+ mListView->setOnSelectionChange( [this]() {
+ if ( !mListView->getSelection().isEmpty() ) {
+ updateSelectionIndex();
+ sendCommonEvent( Event::OnSelectionChanged );
+ }
+ } );
+
+ applyDefaultTheme();
+}
+
+UIDropDownModelList::~UIDropDownModelList() {
+ if ( mListView != nullptr && mListViewCloseCb )
+ mListView->removeEventListener( mListViewCloseCb );
+ destroyListView();
+}
+
+UIWidget* UIDropDownModelList::createDefaultListView() {
+ return UIListView::NewWithTag( mTag + "::listview" );
+}
+
+Uint32 UIDropDownModelList::getType() const {
+ return UI_TYPE_DROPDOWNMODELLIST;
+}
+
+bool UIDropDownModelList::isType( const Uint32& type ) const {
+ return UIDropDownModelList::getType() == type ? true : UIDropDown::isType( type );
+}
+
+UIWidget* UIDropDownModelList::getPopUpWidget() const {
+ return mListView;
+}
+
+UIAbstractTableView* UIDropDownModelList::getListView() const {
+ return mListView;
+}
+
+void UIDropDownModelList::setListView( UIAbstractTableView* listView ) {
+ if ( listView == mListView )
+ return;
+
+ if ( mListView != nullptr ) {
+ if ( mListViewCloseCb )
+ mListView->removeEventListener( mListViewCloseCb );
+ mListView->close();
+ }
+
+ mListView = listView;
+ mListView->setEnabled( false );
+ mListView->setVisible( false );
+ mListView->setParent( this );
+
+ mListView->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onPopUpFocusLoss( event ); } );
+ mListView->on( Event::OnModelEvent, [this]( auto event ) { onItemSelected( event ); } );
+ mListView->on( Event::KeyDown, [this]( auto event ) { onItemKeyDown( event ); } );
+ mListView->on( Event::OnClear, [this]( auto event ) { onWidgetClear( event ); } );
+ mListViewCloseCb =
+ mListView->on( Event::OnClose, [this]( const Event* ) { mListView = nullptr; } );
+
+ mListView->setOnSelectionChange( [this]() {
+ if ( !mListView->getSelection().isEmpty() ) {
+ sendCommonEvent( Event::OnSelectionChanged );
+ }
+ } );
+
+ if ( mModel ) {
+ mListView->setModel( mModel );
+ }
+}
+
+std::shared_ptr UIDropDownModelList::getModel() const {
+ return mModel;
+}
+
+void UIDropDownModelList::setModel( std::shared_ptr model ) {
+ mModel = model;
+ if ( mListView ) {
+ mListView->setModel( mModel );
+ }
+}
+
+Uint32 UIDropDownModelList::onMouseUp( const Vector2i& Pos, const Uint32& Flags ) {
+ if ( mEnabled && mVisible && isMouseOver() && NULL != mListView && mModel ) {
+ if ( Flags & EE_BUTTONS_WUWD ) {
+ if ( Flags & EE_BUTTON_WUMASK ) {
+ mListView->moveSelection( -1 );
+ updateSelectionIndex();
+ } else if ( Flags & EE_BUTTON_WDMASK ) {
+ if ( !mListView->getSelection().isEmpty() ) {
+ mListView->moveSelection( 1 );
+ updateSelectionIndex();
+ } else if ( mModel->hasChildren() ) {
+ mListView->getSelection().set( mModel->index( 0, 0 ) );
+ updateSelectionIndex();
+ }
+ }
+ }
+ }
+
+ return UIDropDown::onMouseUp( Pos, Flags );
+}
+
+Uint32 UIDropDownModelList::onKeyDown( const KeyEvent& Event ) {
+ if ( NULL != mListView )
+ mListView->onKeyDown( Event );
+
+ return UIDropDown::onKeyDown( Event );
+}
+
+UIDropDownModelList* UIDropDownModelList::showList() {
+ if ( NULL == mListView || NULL == mModel )
+ return this;
+
+ if ( !mListView->isVisible() ) {
+ if ( !mModel->hasChildren() )
+ return this;
+
+ Rectf tPadding = mListView->getPadding();
+
+ Float sliderValue = 0;
+ if ( mListView->getVerticalScrollBar() )
+ sliderValue = mListView->getVerticalScrollBar()->getValue();
+
+ Float contentsWidth = eeceil( PixelDensity::pxToDp(
+ mListView->getMaxColumnContentWidth( 0, true ) +
+ PixelDensity::dpToPx( mListView->getPadding().getWidth() ) +
+ ( mListView->getVerticalScrollBar()
+ ? mListView->getVerticalScrollBar()->getPixelsSize().getWidth()
+ : 0.f ) ) );
+
+ Float width = getPopUpWidth( contentsWidth );
+
+ Float height =
+ (Int32)( eemin( (Uint32)mModel->rowCount(), mStyleConfig.MaxNumVisibleItems ) *
+ mListView->getRowHeight() ) +
+ tPadding.Top + tPadding.Bottom + mListView->getHeaderHeight() +
+ ( mListView->getHorizontalScrollBar() &&
+ mListView->getHorizontalScrollBar()->isVisible()
+ ? mListView->getHorizontalScrollBar()->getSize().getHeight()
+ : 0.f );
+
+ mListView->setSize( width, height );
+
+ if ( mListView->getVerticalScrollBar() )
+ mListView->getVerticalScrollBar()->setValue( sliderValue );
+
+ alignPopUp( mListView );
+ } else {
+ hide();
+ }
+ return this;
+}
+
+UIDropDownModelList*
+UIDropDownModelList::setMaxNumVisibleItems( const Uint32& maxNumVisibleItems ) {
+ if ( maxNumVisibleItems != mStyleConfig.MaxNumVisibleItems ) {
+ mStyleConfig.MaxNumVisibleItems = maxNumVisibleItems;
+
+ if ( NULL != mListView && mModel )
+ mListView->setSize( getSize().getWidth(), std::min( mStyleConfig.MaxNumVisibleItems,
+ (Uint32)mModel->rowCount() ) *
+ mListView->getRowHeight() );
+ }
+ return this;
+}
+
+void UIDropDownModelList::onItemClicked( const Event* ) {
+ updateSelectionIndex();
+ hide();
+ setFocus();
+}
+
+void UIDropDownModelList::updateSelectionIndex() {
+ if ( mListView->getSelection().isEmpty() )
+ return;
+ ModelIndex idx = mListView->getSelection().first();
+ if ( idx.isValid() ) {
+ Variant var = mModel->data( idx, ModelRole::Display );
+ if ( var.isValid() && var.isString() )
+ setText( var.toString() );
+ sendCommonEvent( Event::OnItemSelected );
+ sendCommonEvent( Event::OnValueChange );
+ }
+}
+
+void UIDropDownModelList::onItemSelected( const Event* event ) {
+ if ( event->getType() == Event::OnModelEvent ) {
+ const ModelEvent* modelEvent = static_cast( event );
+ if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
+ updateSelectionIndex();
+ hide();
+ setFocus();
+ }
+ }
+}
+
+void UIDropDownModelList::destroyListView() {
+ if ( !SceneManager::instance()->isShuttingDown() && NULL != mListView &&
+ mListView->getParent() != this ) {
+ mListView->setParent( this );
+ }
+}
+
+std::string UIDropDownModelList::getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex ) const {
+ if ( NULL == propertyDef )
+ return "";
+
+ std::string res = UIDropDown::getPropertyString( propertyDef, propertyIndex );
+ if ( res.empty() && NULL != mListView ) {
+ res = mListView->getPropertyString( propertyDef, propertyIndex );
+ }
+ return res;
+}
+
+std::vector UIDropDownModelList::getPropertiesImplemented() const {
+ auto props = UIDropDown::getPropertiesImplemented();
+ if ( mListView ) {
+ auto listProps = mListView->getPropertiesImplemented();
+ props.insert( props.end(), listProps.begin(), listProps.end() );
+ }
+ return props;
+}
+
+void UIDropDownModelList::onClassChange() {
+ if ( mListView )
+ mListView->setClasses( getClasses() );
+}
+
+}} // namespace EE::UI
diff --git a/src/eepp/ui/uilistview.cpp b/src/eepp/ui/uilistview.cpp
index b922b6e8b..5be3ae1a7 100644
--- a/src/eepp/ui/uilistview.cpp
+++ b/src/eepp/ui/uilistview.cpp
@@ -6,7 +6,11 @@ UIListView* UIListView::New() {
return eeNew( UIListView, () );
}
-UIListView::UIListView() : UITableView( "listview" ) {
+UIListView* UIListView::NewWithTag( const std::string& tag ) {
+ return eeNew( UIListView, () );
+}
+
+UIListView::UIListView( const std::string& tag ) : UITableView( tag ) {
setHeadersVisible( false );
setAutoExpandOnSingleColumn( true );
applyDefaultTheme();
diff --git a/src/eepp/ui/uithememanager.cpp b/src/eepp/ui/uithememanager.cpp
index ff322973e..5e1a9bf14 100644
--- a/src/eepp/ui/uithememanager.cpp
+++ b/src/eepp/ui/uithememanager.cpp
@@ -14,8 +14,8 @@ UIThemeManager::UIThemeManager() :
mThemeDefault( NULL ),
mAutoApplyDefaultTheme( true ),
mEnableDefaultEffects( false ),
- mFadeInTime( Milliseconds( 100.f ) ),
- mFadeOutTime( Milliseconds( 100.f ) ),
+ mFadeInTime( Milliseconds( 25.f ) ),
+ mFadeOutTime( Milliseconds( 25.f ) ),
mTooltipTimeToShow( Milliseconds( 400 ) ),
mTooltipFollowMouse( false ),
mCursorSize( 16, 16 ) {}
diff --git a/src/eepp/ui/uiwidgetcreator.cpp b/src/eepp/ui/uiwidgetcreator.cpp
index c80b0c112..7c4a164d7 100644
--- a/src/eepp/ui/uiwidgetcreator.cpp
+++ b/src/eepp/ui/uiwidgetcreator.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -67,6 +68,7 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["radiobutton"] = UIRadioButton::New;
registeredWidget["combobox"] = UIComboBox::New;
registeredWidget["dropdownlist"] = UIDropDownList::New;
+ registeredWidget["dropdownmodellist"] = UIDropDownModelList::New;
registeredWidget["image"] = UIImage::New;
registeredWidget["listbox"] = UIListBox::New;
registeredWidget["menubar"] = UIMenuBar::New;
diff --git a/src/examples/ui_dropdownmodellist/ui_dropdownmodellist.cpp b/src/examples/ui_dropdownmodellist/ui_dropdownmodellist.cpp
new file mode 100644
index 000000000..48e1ad7c2
--- /dev/null
+++ b/src/examples/ui_dropdownmodellist/ui_dropdownmodellist.cpp
@@ -0,0 +1,51 @@
+#include
+#include
+#include
+
+using namespace EE::UI;
+using namespace EE::UI::Models;
+
+EE_MAIN_FUNC int main( int, char** ) {
+ UIApplication app( { 640, 480, "eepp - UIDropDownModelList Example" } );
+ app.getUI()->loadLayoutFromString( R"xml(
+
+
+
+
+
+ )xml" );
+
+ if ( !app.getWindow()->isOpen() )
+ return EXIT_FAILURE;
+
+ UIDropDownModelList* dropDown = app.getUI()->find( "dropdown" );
+ if ( dropDown ) {
+ std::vector options = { "Option 1: OpenGL", "Option 2: Vulkan",
+ "Option 3: Direct3D 11", "Option 4: Direct3D 12",
+ "Option 5: Metal", "Option 6: WebGL",
+ "Option 7: Software" };
+
+ auto model = ItemListOwnerModel::create( options );
+ dropDown->setModel( model );
+ dropDown->addEventListener( Event::OnItemSelected, []( const Event* event ) {
+ UIDropDownModelList* dropDown = event->getNode()->asType();
+ ModelIndex index = dropDown->getListView()->getSelection().first();
+ if ( index.isValid() ) {
+ String text = dropDown->getListView()->getModel()->data( index ).toString();
+ Log::info( "Selected item index: %d, value: %s", (int)index.row(), text.c_str() );
+ }
+ } );
+ }
+
+ return app.run();
+}
diff --git a/src/tests/unit_tests/uidropdownmodellist.cpp b/src/tests/unit_tests/uidropdownmodellist.cpp
new file mode 100644
index 000000000..97854e7a4
--- /dev/null
+++ b/src/tests/unit_tests/uidropdownmodellist.cpp
@@ -0,0 +1,43 @@
+#include "utest.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace EE;
+using namespace EE::Window;
+using namespace EE::Scene;
+using namespace EE::UI;
+using namespace EE::UI::Models;
+
+UTEST( UIDropDownModelList, basicFunctionality ) {
+ UIApplication app(
+ WindowSettings( 800, 600, "eepp - UIDropDownModelList Test", WindowStyle::Default,
+ WindowBackend::Default, 32, {}, 1, false, true ),
+ UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1.5 ) );
+ FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
+
+ UISceneNode* sceneNode = app.getUI();
+
+ UIDropDownModelList* dropDown = UIDropDownModelList::New();
+ dropDown->setParent( sceneNode );
+
+ std::vector items = { "Item 1", "Item 2", "Item 3" };
+ auto model = ItemListOwnerModel::create( items );
+ dropDown->setModel( model );
+
+ // Model should be set
+ EXPECT_TRUE( dropDown->getModel() == model );
+
+ // Items count should match
+ EXPECT_EQ( dropDown->getListView()->getModel()->rowCount(), 3ul );
+
+ // Max visible items
+ dropDown->setMaxNumVisibleItems( 2 );
+ EXPECT_EQ( dropDown->getMaxNumVisibleItems(), 2ul );
+}
diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
index ce6a0241b..7ff601c00 100644
--- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
+++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
@@ -98,7 +98,10 @@ static std::map parseLLMProviders( const nlohmann::jso
model.cacheConfiguration = cache;
}
- provider.models.push_back( model );
+ model.hash = hashCombine( std::hash()( model.name ),
+ std::hash()( model.provider ) );
+
+ provider.models.emplace_back( std::move( model ) );
}
}
diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp
index f45b43f7e..a3925f1fc 100644
--- a/src/tools/ecode/plugins/aiassistant/chatui.cpp
+++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp
@@ -30,6 +30,107 @@ using namespace EE::Window;
namespace ecode {
+class LLMModelsModel : public Model {
+ public:
+ enum Columns { Name, Provider, Hash };
+
+ LLMModelsModel( const std::vector& models, UISceneNode* uiSceneNode = nullptr ) :
+ mModels( models ), mUISceneNode( uiSceneNode ) {
+ mCurModels.reserve( mModels.size() );
+ for ( const auto& model : mModels ) {
+ mCurModels.emplace_back( &model );
+ }
+ }
+
+ virtual size_t rowCount( const ModelIndex& = ModelIndex() ) const override {
+ return mCurModels.size();
+ }
+
+ virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const override { return 2; }
+
+ virtual std::string columnName( const size_t& column ) const override {
+ switch ( column ) {
+ case Columns::Name:
+ return mUISceneNode ? mUISceneNode->i18n( "name", "Name" ) : "Name";
+ case Columns::Provider:
+ return mUISceneNode ? mUISceneNode->i18n( "provider", "Provider" ) : "Provider";
+ case Columns::Hash:
+ return mUISceneNode ? mUISceneNode->i18n( "hash", "Hash" ) : "Hash";
+ }
+ return "";
+ }
+
+ virtual Variant data( const ModelIndex& index,
+ ModelRole role = ModelRole::Display ) const override {
+ if ( role != ModelRole::Display )
+ return {};
+
+ if ( index.row() < 0 || static_cast( index.row() ) >= mCurModels.size() )
+ return {};
+
+ const auto& model = *mCurModels[index.row()];
+
+ switch ( index.column() ) {
+ case Columns::Name: {
+ if ( model.displayName.has_value() && !model.displayName->empty() ) {
+ return Variant( model.displayName->c_str() );
+ }
+ return Variant( model.name.c_str() );
+ }
+ case Columns::Provider: {
+ return Variant( model.provider.c_str() );
+ }
+ case Columns::Hash: {
+ return Variant( static_cast( model.hash ) );
+ }
+ }
+
+ return {};
+ }
+
+ ModelIndex getFromHash( Uint64 hash ) {
+ auto it = std::find_if( mCurModels.begin(), mCurModels.end(),
+ [hash]( const LLMModel* model ) { return hash == model->hash; } );
+ return it != mCurModels.end() ? index( std::distance( mCurModels.begin(), it ) )
+ : index( 0 );
+ }
+
+ void setFilter( const std::string& filter ) {
+ if ( mCurFilter == filter )
+ return;
+ mCurFilter = filter;
+ mCurModels.clear();
+
+ for ( const auto& model : mModels ) {
+ if ( filter.empty() ) {
+ mCurModels.emplace_back( &model );
+ continue;
+ }
+
+ bool matchesName = String::icontains( model.name, filter );
+ bool matchesDisplayName =
+ model.displayName.has_value() && String::icontains( *model.displayName, filter );
+ bool matchesProvider = String::icontains( model.provider, filter );
+
+ if ( matchesName || matchesDisplayName || matchesProvider ) {
+ mCurModels.emplace_back( &model );
+ }
+ }
+
+ invalidate();
+ }
+
+ const std::vector& getCurModels() const { return mCurModels; }
+
+ void refresh() { setFilter( mCurFilter ); }
+
+ protected:
+ const std::vector& mModels;
+ std::vector mCurModels;
+ std::string mCurFilter;
+ UISceneNode* mUISceneNode;
+};
+
static const char* DEFAULT_PROVIDER = "google";
static const char* DEFAULT_MODEL = "gemini-2.5-flash";
@@ -71,13 +172,7 @@ LLMChat::Role LLMChat::stringToRole( UIPushButton* userBut ) {
static const char* DEFAULT_LAYOUT = R"xml(
@@ -172,17 +291,17 @@ DropDownList.role_ui {
+
+
+
+
-
-
-
-
+
-
@@ -219,10 +338,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
->asType();
mChatsList = findByClass( "llm_chats" );
- mModelDDL = findByClass( "model_ui" );
-
- // mRefreshModels = find( "refresh_model_ui" );
- // mRefreshModels->onClick( [this]( auto ) { execute( "ai-refresh-local-models" ); } );
+ mModelBtn = findByClass( "model_ui" );
mChatMore = find( "llm_more" );
mChatMore->onClick( [this]( auto ) { execute( "ai-show-menu" ); } );
@@ -276,10 +392,6 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mChatUserRole = find( "llm_user" );
mChatUserRole->onClick( [this]( auto ) { execute( "ai-chat-toggle-role" ); } );
- /* mChatPrivate = find( "llm_private_chat" );
- mChatPrivate->on( Event::OnValueChange,
- [this]( auto ) { mChatIsPrivate = mChatPrivate->isSelected(); } ); */
-
auto setCmd = [this]( const std::string& name, const CommandCallback& cb ) {
setCommand( name, cb );
mChatInput->getDocument().setCommand( name, cb );
@@ -387,9 +499,26 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mLocateInput->getDocument().selectAll();
} );
+ setCmd( "ai-select-model", [this] {
+ if ( !mLocateModelBarLayout->isVisible() ) {
+ if ( mLocateModelTable->getModel() ) {
+ mLocateModelInput->setText( "" );
+ static_cast( mLocateModelTable->getModel() )->setFilter( "" );
+ }
+
+ showSelectModel();
+
+ mLocateModelTable->runOnMainThread( [this] {
+ auto model = static_cast( mLocateModelTable->getModel() );
+ if ( model )
+ mLocateModelTable->setSelection( model->getFromHash( mCurModel.hash ) );
+ } );
+ } else
+ hideSelectModel();
+ } );
+
setCmd( "ai-toggle-private-chat", [this] {
mChatIsPrivate = !mChatIsPrivate;
- /* mChatPrivate->toggleSelection(); */
if ( mChatIsPrivate )
mChatInput->addClass( "incognito" );
@@ -533,7 +662,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
} );
} );
- setCmd( "ai-refresh-local-models", [this] { fillApiModels( mModelDDL ); } );
+ setCmd( "ai-refresh-local-models", [this] { fillApiModels(); } );
mChatHistory = find( "llm_chat_history" );
mChatHistory->onClick( [this]( auto ) { showChatHistory(); } );
@@ -541,10 +670,13 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mChatAttach = find( "llm_attach" );
mChatAttach->onClick( [this]( auto ) { execute( "ai-show-add-context-menu" ); } );
+ mModelBtn->onClick( [this]( auto ) { execute( "ai-select-model" ); } );
+
if ( getPlugin() == nullptr )
return;
initAttachFile();
+ initSelectModel();
auto providers = getPlugin()->getProviders();
setProviders( std::move( providers ) );
@@ -566,7 +698,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
}
}
- fillModelDropDownList( mModelDDL );
+ fillModelDropDownList();
const auto appendShortcutToTooltip = [this]( UIPushButton* but, const std::string& cmd ) {
auto kb = getKeyBindings().getCommandKeybindString( cmd );
@@ -583,10 +715,9 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
appendShortcutToTooltip( mChatStop, "ai-prompt" );
appendShortcutToTooltip( mChatAdd, "ai-add-chat" );
appendShortcutToTooltip( mChatSettings, "ai-settings" );
- // appendShortcutToTooltip( mChatPrivate, "ai-toggle-private-chat" );
appendShortcutToTooltip( mChatMore, "ai-show-menu" );
appendShortcutToTooltip( mChatUserRole, "ai-chat-toggle-role" );
- // appendShortcutToTooltip( mRefreshModels, "ai-refresh-local-models" );
+ appendShortcutToTooltip( mModelBtn, "ai-select-model" );
addKb( mChatInput, "mod+keypad enter", "ai-prompt", true, false );
addKb( mChatInput, "mod+shift+keypad enter", "ai-add-chat", true, false );
@@ -643,6 +774,7 @@ void LLMChatUI::bindCmds( UICodeEditor* editor, bool bindToChatUI ) {
addKb( editor, "mod+shift+l", "ai-refresh-local-models", bindToChatUI );
addKb( editor, "mod+shift+a", "ai-attach-file", bindToChatUI );
addKb( editor, "mod+shift+z", "ai-link-file", bindToChatUI );
+ addKb( editor, "mod+shift+x", "ai-select-model", bindToChatUI );
if ( bindToChatUI )
addKb( editor, "mod+shift+return", "ai-add-chat", bindToChatUI );
@@ -663,6 +795,14 @@ std::optional LLMChatUI::getModel( const std::string& provider,
return {};
}
+std::optional LLMChatUI::getModel( Uint64 hash ) {
+ auto modelIt = std::find_if( mModels.begin(), mModels.end(),
+ [hash]( const LLMModel& model ) { return hash == model.hash; } );
+ if ( modelIt != mModels.end() )
+ return *modelIt;
+ return {};
+}
+
void LLMChatUI::showChatHistory() {
auto plugin = getPlugin();
if ( plugin == nullptr )
@@ -832,8 +972,10 @@ void LLMChatUI::showChatHistory() {
} );
}
-void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) {
+void LLMChatUI::fillApiModels() {
mPendingModelsToLoad = 0;
+ mNewModels.clear();
+
for ( auto& [name, data] : mProviders ) {
if ( !data.enabled || !data.fetchModelsUrl )
continue;
@@ -869,6 +1011,8 @@ void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) {
model.displayName = el.value( "display_name", "" );
model.isEphemeral = true;
+ model.hash = hashCombine( std::hash()( model.name ),
+ std::hash()( model.provider ) );
if ( model.name.empty() )
continue;
@@ -877,39 +1021,25 @@ void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) {
model.maxOutputTokens = el.value( "max_context_length", 0 );
data.models.emplace_back( model );
+ mNewModels.push_back( model );
}
mPendingModelsToLoad++;
- std::string pname = name;
- modelDDL->runOnMainThread( [pname = std::move( pname ), modelDDL, this] {
- String providerName( pname );
- std::vector removeValues;
- size_t count = modelDDL->getListBox()->getItemsCount();
- for ( size_t i = 0; i < count; i++ ) {
- const String& txt = modelDDL->getListBox()->getItemText( i );
- if ( txt.contains( providerName ) )
- removeValues.emplace_back( txt );
- }
-
- for ( const auto& val : removeValues )
- modelDDL->getListBox()->removeListBoxItem( val );
-
- const auto& models = mProviders[pname].models;
- std::vector newModels;
- for ( const auto& model : models ) {
- if ( !model.isEphemeral )
- continue;
- newModels.emplace_back( String::format( "%s (%s)", model.name, pname ) );
- mModelsMap[newModels[newModels.size() - 1].getHash()] = model;
- }
-
- modelDDL->getListBox()->addListBoxItems( newModels );
-
+ runOnMainThread( [this] {
mPendingModelsToLoad--;
+ if ( mPendingModelsToLoad == 0 ) {
+ mModels.erase(
+ std::remove_if( mModels.begin(), mModels.end(),
+ []( const LLMModel& model ) { return model.isEphemeral; } ),
+ mModels.end() );
- if ( mPendingModelsToLoad == 0 )
+ mModels.insert( mModels.end(), mNewModels.begin(), mNewModels.end() );
+
+ if ( mLocateModelTable && mLocateModelTable->getModel() )
+ loadSelectModel();
onInit();
+ }
} );
}
@@ -926,46 +1056,28 @@ String LLMChatUI::getModelDisplayName( const LLMModel& model ) const {
data.displayName ? *data.displayName : String::capitalize( data.name ) );
}
-bool LLMChatUI::selectModel( UIDropDownList* modelDDL, const LLMModel& model ) {
- auto modelName = getModelDisplayName( model );
- auto index = modelDDL->getListBox()->getItemIndex( modelName );
- if ( index != eeINDEX_NOT_FOUND ) {
- modelDDL->getListBox()->setSelected( index );
+bool LLMChatUI::selectModel( std::optional model ) {
+ if ( model ) {
+ mModelBtn->setText( getModelDisplayName( *model ) );
+ mCurModel = *model;
return true;
}
return false;
}
-void LLMChatUI::fillModelDropDownList( UIDropDownList* modelDDL ) {
- std::vector models;
- std::size_t selectedIndex = 0;
+void LLMChatUI::fillModelDropDownList() {
+ mModels.clear();
+ std::size_t reserve = 0;
+ for ( const auto& [_, data] : mProviders )
+ reserve += data.models.size();
+ mModels.reserve( reserve + 8 /* extra space for local models */ );
for ( const auto& [name, data] : mProviders ) {
if ( !data.enabled )
continue;
-
- for ( const auto& model : data.models ) {
- String modelName( String::format(
- "%s (%s)", model.displayName ? *model.displayName : model.name,
- data.displayName ? *data.displayName : String::capitalize( data.name ) ) );
- mModelsMap[modelName.getHash()] = model;
- if ( model.provider == mCurModel.provider && model.name == mCurModel.name )
- selectedIndex = models.size();
- models.push_back( std::move( modelName ) );
- }
+ for ( const auto& model : data.models )
+ mModels.push_back( model );
}
- modelDDL->getListBox()->clear();
- modelDDL->getListBox()->addListBoxItems( std::move( models ) );
- modelDDL->getListBox()->setSelected( selectedIndex );
- modelDDL->on( Event::OnValueChange, [this, modelDDL]( auto ) {
- auto selectedModel =
- mModelsMap.find( modelDDL->getListBox()->getItemSelectedText().getHash() );
- if ( selectedModel != mModelsMap.end() ) {
- mCurModel = selectedModel->second;
- }
- } );
-
- modelDDL->getUISceneNode()->getThreadPool()->run(
- [this, modelDDL] { fillApiModels( modelDDL ); } );
+ getUISceneNode()->getThreadPool()->run( [this] { fillApiModels(); } );
}
void LLMChatUI::resizeToFit( UICodeEditor* editor ) {
@@ -1077,8 +1189,8 @@ std::string LLMChatUI::unserialize( const nlohmann::json& payload ) {
if ( mCurModel.name.empty() )
return payload.value( "input", "" );
- if ( !selectModel( mModelDDL, mCurModel ) )
- fillModelDropDownList( mModelDDL );
+ if ( !selectModel( mCurModel ) )
+ fillModelDropDownList();
if ( payload.contains( "chat" ) && payload["chat"].is_object() ) {
const auto& chat = payload["chat"];
@@ -1488,10 +1600,10 @@ const LLMModel& LLMChatUI::getCheapestModelFromCurrentProvider() const {
}
void LLMChatUI::onInit() {
- if ( !mModelDDL )
+ if ( !mModelBtn )
return;
- if ( getModelDisplayName( mCurModel ) != mModelDDL->getListBox()->getItemSelectedText() )
- selectModel( mModelDDL, mCurModel );
+ if ( getModelDisplayName( mCurModel ) != mModelBtn->getText() )
+ selectModel( mCurModel );
}
void LLMChatUI::updateTabTitle() {
@@ -1549,9 +1661,12 @@ void LLMChatUI::updateLocateBarColumns() {
mLocateTable->setColumnWidth( 1, width - mLocateTable->getColumnWidth( 0 ) );
}
+// File picker
+
void LLMChatUI::showAttachFile() {
if ( getPlugin() == nullptr )
return;
+ hideSelectModel();
auto text = mLocateInput->getText();
auto ctx = getPlugin()->getPluginContext();
if ( !ctx->isDirTreeReady() ) {
@@ -1687,4 +1802,90 @@ void LLMChatUI::initAttachFile() {
} );
}
+// Model Picker
+
+void LLMChatUI::updateLocateModelBarColumns() {
+ Float width = eeceil( mLocateModelTable->getPixelsSize().getWidth() );
+ width -= mLocateModelTable->getVerticalScrollBar()->getPixelsSize().getWidth();
+ mLocateModelTable->setColumnsVisible( { 0, 1 } );
+ mLocateModelTable->setColumnWidth( 0, eeceil( width * 0.8 ) );
+ mLocateModelTable->setColumnWidth( 1, width - mLocateModelTable->getColumnWidth( 0 ) );
+}
+
+void LLMChatUI::loadSelectModel() {
+ auto ctx = getPlugin()->getPluginContext();
+ mLocateModelTable->setModel(
+ std::make_shared( mModels, ctx->getUISceneNode() ) );
+
+ static_cast( mLocateModelTable->getModel() )
+ ->setFilter( mLocateModelInput->getText() );
+}
+
+void LLMChatUI::showSelectModel() {
+ if ( getPlugin() == nullptr )
+ return;
+ hideAttachFile();
+
+ if ( nullptr == mLocateModelTable->getModel() )
+ loadSelectModel();
+
+ static_cast( mLocateModelTable->getModel() )
+ ->setFilter( mLocateModelInput->getText() );
+
+ mLocateModelBarLayout->setVisible( true );
+ mLocateModelInput->setFocus();
+ updateLocateModelBarColumns();
+}
+
+void LLMChatUI::hideSelectModel() {
+ mLocateModelBarLayout->setVisible( false );
+}
+
+void LLMChatUI::initSelectModel() {
+ mLocateModelBarLayout = findByClass( "llm_chat_select_model" );
+ mLocateModelInput = findByClass( "llm_chat_select_model_input" );
+ mLocateModelTable = findByClass( "llm_chat_model_locate" );
+ mLocateModelTable->setHeadersVisible( false );
+
+ mLocateModelTable->on( Event::OnSizeChange,
+ [this]( const Event* ) { updateLocateModelBarColumns(); } );
+
+ mLocateModelInput->on( Event::OnTextChanged, [this]( const Event* ) {
+ showSelectModel();
+ updateLocateModelBarColumns();
+ } );
+ mLocateModelInput->on( Event::OnPressEnter, [this]( const Event* ) {
+ KeyEvent keyEvent( mLocateModelTable, Event::KeyDown, KEY_RETURN, SCANCODE_UNKNOWN, 0, 0 );
+ mLocateModelTable->forceKeyDown( keyEvent );
+ } );
+ mLocateModelInput->on( Event::KeyDown, [this]( const Event* event ) {
+ const KeyEvent* keyEvent = static_cast( event );
+ mLocateModelTable->forceKeyDown( *keyEvent );
+ } );
+ mLocateModelBarLayout->setCommand( "close-locatebar", [this] {
+ hideSelectModel();
+ if ( mChatInput )
+ mChatInput->setFocus();
+ } );
+ mLocateModelBarLayout->getKeyBindings().addKeybindsString( {
+ { "escape", "close-locatebar" },
+ } );
+ mLocateModelTable->on( Event::KeyDown, [this]( const Event* event ) {
+ const KeyEvent* keyEvent = static_cast( event );
+ if ( keyEvent->getKeyCode() == KEY_ESCAPE )
+ mLocateModelBarLayout->execute( "close-locatebar" );
+ } );
+ mLocateModelTable->on( Event::OnModelEvent, [this]( const Event* event ) {
+ const ModelEvent* modelEvent = static_cast( event );
+ if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
+ Variant vHash( modelEvent->getModel()->data(
+ modelEvent->getModel()->index( modelEvent->getModelIndex().row(),
+ LLMModelsModel::Hash ),
+ ModelRole::Display ) );
+ selectModel( getModel( vHash.asUint64() ) );
+ mLocateModelBarLayout->execute( "close-locatebar" );
+ }
+ } );
+}
+
} // namespace ecode
diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp
index f8e5edec6..0f3c2c73f 100644
--- a/src/tools/ecode/plugins/aiassistant/chatui.hpp
+++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp
@@ -110,20 +110,28 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
UIPushButton* mChatAttach{ nullptr };
UISelectButton* mChatPrivate{ nullptr };
UIScrollView* mChatScrollView{ nullptr };
- UIDropDownList* mModelDDL{ nullptr };
+ UIPushButton* mModelBtn{ nullptr };
+
+ // Locate file
UIVLinearLayoutCommandExecuter* mLocateBarLayout{ nullptr };
UITextInput* mLocateInput{ nullptr };
UITableView* mLocateTable{ nullptr };
+ // Select model
+ UIVLinearLayoutCommandExecuter* mLocateModelBarLayout{ nullptr };
+ UITextInput* mLocateModelInput{ nullptr };
+ UITableView* mLocateModelTable{ nullptr };
+
std::unique_ptr mRequest;
std::unique_ptr mSummaryRequest;
LLMProviders mProviders;
LLMModel mCurModel;
- std::unordered_map mModelsMap;
+ std::vector mModels;
int mPendingModelsToLoad{ 0 };
bool mChatIsPrivate{ false };
bool mChatLocked{ false };
bool mLinkMode{ false };
+ std::vector mNewModels;
LLMModel findModel( const std::string& provider, const std::string& model );
@@ -151,13 +159,13 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
UIWidget* addChatUI( LLMChat::Role role );
- void fillApiModels( UIDropDownList* modelDDL );
+ void fillApiModels();
String getModelDisplayName( const LLMModel& model ) const;
- bool selectModel( UIDropDownList* modelDDL, const LLMModel& model );
+ bool selectModel( std::optional model );
- void fillModelDropDownList( UIDropDownList* modelDDL );
+ void fillModelDropDownList();
void resizeToFit( UICodeEditor* editor );
@@ -173,6 +181,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
std::optional getModel( const std::string& provider, const std::string& modelName );
+ std::optional getModel( Uint64 hash );
+
void saveChat();
void onInit();
@@ -188,12 +198,22 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
void initAttachFile();
+ void initSelectModel();
+
void updateLocateBarColumns();
void showAttachFile();
void hideAttachFile();
+ void updateLocateModelBarColumns();
+
+ void loadSelectModel();
+
+ void showSelectModel();
+
+ void hideSelectModel();
+
void insertFileToDocument( std::string path, std::shared_ptr cdoc );
void replaceFileLinksToContents( std::string& text );
diff --git a/src/tools/ecode/plugins/aiassistant/protocol.hpp b/src/tools/ecode/plugins/aiassistant/protocol.hpp
index 0382f4add..9166bf2f2 100644
--- a/src/tools/ecode/plugins/aiassistant/protocol.hpp
+++ b/src/tools/ecode/plugins/aiassistant/protocol.hpp
@@ -14,6 +14,7 @@ struct LLMCacheConfiguration {
};
struct LLMModel {
+ std::size_t hash{ 0 };
std::string name;
std::string provider;
std::optional displayName;
diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp
index 827bed229..aa0eefb4c 100644
--- a/src/tools/ecode/statusbuildoutputcontroller.cpp
+++ b/src/tools/ecode/statusbuildoutputcontroller.cpp
@@ -217,16 +217,20 @@ void StatusBuildOutputController::runBuild( const std::string& buildName,
}
if ( enableBuildButton && buildButton )
- buildButton->setEnabled( true );
+ buildButton->ensureMainThread( [buildButton] { buildButton->setEnabled( true ); } );
if ( enableCleanButton && cleanButton )
- cleanButton->runOnMainThread( [cleanButton] { cleanButton->setEnabled( true ); } );
+ cleanButton->ensureMainThread( [cleanButton] { cleanButton->setEnabled( true ); } );
- if ( buildAndRunButton )
- buildAndRunButton->setEnabled( true );
+ if ( buildAndRunButton ) {
+ buildAndRunButton->ensureMainThread(
+ [buildAndRunButton] { buildAndRunButton->setEnabled( true ); } );
+ }
- mBuildButton->setEnabled( true );
- mStopButton->setEnabled( false );
+ mBuildButton->ensureMainThread( [this] {
+ mBuildButton->setEnabled( true );
+ mStopButton->setEnabled( false );
+ } );
};
auto res = pbm->build(