Redesigned LLM model selection in AI Assistant chat UI (now it's possible to search by filtering its name).

Redesigned UIDropDownList to inherit from UIDropDown which is a base class to handle different types of drop-downs. Added UIDropDownModelList which is the same as UIDropDownList but uses a UIListView by default so it's a model/view based DropDown.
Fix crash when changing states of the buttons in the build panel.
Increased the default animations speed.
This commit is contained in:
Martín Lucas Golini
2026-03-21 15:02:50 -03:00
parent 1aa015bfca
commit 05d6d3e2a3
24 changed files with 1274 additions and 473 deletions

View File

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

View File

@@ -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,<svg viewBox='0 0 24 24' fill='white'><path d='M12 15.6315L20.9679 10.8838L20.0321 9.11619L12 13.3685L3.96788 9.11619L3.0321 10.8838L12 15.6315Z'></path></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);

View File

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

View File

@@ -0,0 +1,89 @@
#ifndef EE_UI_UIDROPDOWN_HPP
#define EE_UI_UIDROPDOWN_HPP
#include <eepp/ui/uitextinput.hpp>
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<PropertyId> 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

View File

@@ -1,30 +1,15 @@
#ifndef EE_UICUIDROPDOWNLIST_HPP
#define EE_UICUIDROPDOWNLIST_HPP
#include <eepp/ui/uidropdown.hpp>
#include <eepp/ui/uilistbox.hpp>
#include <eepp/ui/uitextinput.hpp>
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<PropertyId> 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();
};

View File

@@ -0,0 +1,68 @@
#ifndef EE_UI_UIDROPDOWNMODELLIST_HPP
#define EE_UI_UIDROPDOWNMODELLIST_HPP
#include <eepp/ui/abstract/uiabstracttableview.hpp>
#include <eepp/ui/uidropdown.hpp>
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<Model> getModel() const;
virtual void setModel( std::shared_ptr<Model> model );
UIDropDownModelList* showList();
virtual UIDropDownModelList* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex = 0 ) const;
virtual std::vector<PropertyId> getPropertiesImplemented() const;
protected:
UIAbstractTableView* mListView;
Uint32 mListViewCloseCb{ 0 };
std::shared_ptr<Model> 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

View File

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

View File

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

View File

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

View File

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

View File

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

339
src/eepp/ui/uidropdown.cpp Normal file
View File

@@ -0,0 +1,339 @@
#include <eepp/scene/actions/actions.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/scene/scenenode.hpp>
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uidropdown.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uithememanager.hpp>
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<Float>( 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<const KeyEvent*>( 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<PropertyId> 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

View File

@@ -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<Float>( 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<const KeyEvent*>( 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<PropertyId> 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

View File

@@ -0,0 +1,267 @@
#include <eepp/scene/actions/actions.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/scene/scenenode.hpp>
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uidropdownmodellist.hpp>
#include <eepp/ui/uilistview.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uithememanager.hpp>
#define PUGIXML_HEADER_ONLY
#include <pugixml/pugixml.hpp>
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<UIAbstractTableView*>( 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<Model> UIDropDownModelList::getModel() const {
return mModel;
}
void UIDropDownModelList::setModel( std::shared_ptr<Model> 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<const ModelEvent*>( 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<PropertyId> 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

View File

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

View File

@@ -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 ) {}

View File

@@ -5,6 +5,7 @@
#include <eepp/ui/uicombobox.hpp>
#include <eepp/ui/uiconsole.hpp>
#include <eepp/ui/uidropdownlist.hpp>
#include <eepp/ui/uidropdownmodellist.hpp>
#include <eepp/ui/uigridlayout.hpp>
#include <eepp/ui/uihtmltable.hpp>
#include <eepp/ui/uiimage.hpp>
@@ -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;

View File

@@ -0,0 +1,51 @@
#include <eepp/ee.hpp>
#include <eepp/ui/models/itemlistmodel.hpp>
#include <eepp/ui/uidropdownmodellist.hpp>
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(
<LinearLayout layout_width="match_parent"
layout_height="match_parent"
orientation="vertical"
padding="16dp">
<TextView layout_width="match_parent"
layout_height="wrap_content"
text="Model-Based Dropdown Example"
margin_bottom="16dp" />
<DropDownModelList id="dropdown"
layout_width="250dp"
layout_height="wrap_content"
pop-up-to-root="true"
max-visible-items="4" />
</LinearLayout>
)xml" );
if ( !app.getWindow()->isOpen() )
return EXIT_FAILURE;
UIDropDownModelList* dropDown = app.getUI()->find<UIDropDownModelList>( "dropdown" );
if ( dropDown ) {
std::vector<std::string> 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<std::string>::create( options );
dropDown->setModel( model );
dropDown->addEventListener( Event::OnItemSelected, []( const Event* event ) {
UIDropDownModelList* dropDown = event->getNode()->asType<UIDropDownModelList>();
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();
}

View File

@@ -0,0 +1,43 @@
#include "utest.h"
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/sys.hpp>
#include <eepp/ui/models/itemlistmodel.hpp>
#include <eepp/ui/uiapplication.hpp>
#include <eepp/ui/uidropdownmodellist.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/window/engine.hpp>
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<std::string> items = { "Item 1", "Item 2", "Item 3" };
auto model = ItemListOwnerModel<std::string>::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 );
}

View File

@@ -98,7 +98,10 @@ static std::map<std::string, LLMProvider> parseLLMProviders( const nlohmann::jso
model.cacheConfiguration = cache;
}
provider.models.push_back( model );
model.hash = hashCombine( std::hash<std::string>()( model.name ),
std::hash<std::string>()( model.provider ) );
provider.models.emplace_back( std::move( model ) );
}
}

View File

@@ -30,6 +30,107 @@ using namespace EE::Window;
namespace ecode {
class LLMModelsModel : public Model {
public:
enum Columns { Name, Provider, Hash };
LLMModelsModel( const std::vector<LLMModel>& 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<size_t>( 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<Uint64>( 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<const LLMModel*>& getCurModels() const { return mCurModels; }
void refresh() { setFilter( mCurFilter ); }
protected:
const std::vector<LLMModel>& mModels;
std::vector<const LLMModel*> 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(
<style>
.llm_chatui DropDownList {
border-color: transparent;
background-color: var(--tab-back);
}
.llm_chatui DropDownList:hover {
border-color: var(--primary);
}
<![CDATA[
.llm_conversation {
margin-bottom: 8dp;
}
@@ -137,14 +232,17 @@ DropDownList.role_ui {
.llm_chatui .image {
tint: var(--font);
}
.llm_chat_attach {
.llm_chat_attach,
.llm_chat_select_model {
padding: 8dp 8dp 38dp 8dp;
}
.llm_chat_locate_input {
.llm_chat_locate_input,
.llm_chat_select_model_input {
margin-bottom: 2dp;
padding: 0 0 0 4dp;
}
.llm_chat_attach_locate {
.llm_chat_attach_locate,
.llm_chat_model_locate {
border-radius: 8dp;
margin-bottom: 4dp;
}
@@ -155,6 +253,27 @@ DropDownList.role_ui {
background-image: icon(spy-line, 16dp);
background-position: top 8dp right 8dp;
}
.llm_chatui DropDownList,
.model_ui {
border-color: transparent;
background-color: var(--tab-back);
}
.llm_chatui DropDownList:hover,
.model_ui:hover {
border-color: var(--primary);
}
.model_ui {
padding-right: 16dp;
text-align: left;
text-overflow: ellipsis;
expand-text: true;
foreground-image: url("data:image/svg,<svg viewBox='0 0 24 24' fill='white'><path d='M12 15.6315L20.9679 10.8838L20.0321 9.11619L12 13.3685L3.96788 9.11619L3.0321 10.8838L12 15.6315Z'></path></svg>");
foreground-position-x: right 6dp;
foreground-position-y: center 1dp;
foreground-size: 12dp 16dp;
foreground-tint: var(--icon);
}
]]>
</style>
<Splitter lw="mp" lh="mp" orientation="vertical" splitter-partition="75%" padding="4dp">
<RelativeLayout lw="mp">
@@ -172,17 +291,17 @@ DropDownList.role_ui {
<TableView lw="mp" lh="0dp" lw8="1" class="llm_chat_attach_locate" />
<TextInput class="llm_chat_locate_input" lw="mp" lh="18dp" hint='@string(type_to_locate, "Type to Locate")' />
</vboxce>
<vboxce class="llm_chat_select_model" lw="mp" lh="mp" visible="false">
<TableView lw="mp" lh="0dp" lw8="1" class="llm_chat_model_locate" />
<TextInput class="llm_chat_select_model_input" lw="mp" lh="18dp" hint='@string(select_a_model_ellipsis, "Select a model...")' />
</vboxce>
<hbox lw="mp" lh="wc" layout_gravity="bottom|left" layout_margin="8dp" clip="false">
<PushButton id="llm_user" class="llm_button" text="@string(user, User)" tooltip="@string(change_role, Change Role)" min-width="60dp" margin-right="4dp" />
<PushButton id="llm_attach" class="llm_button" text="@string(add_context, Add Context)" tooltip="@string(add_context, Add Context)" icon="icon(attach, 14dp)" min-width="32dp" margin-right="4dp" />
<PushButton id="llm_chat_history" class="llm_button" text="@string(chat_history, Chat History)" tooltip="@string(chat_history, Chat History)" icon="icon(chat-history, 14dp)" min-width="32dp" margin-right="4dp" />
<PushButton id="llm_more" class="llm_button" tooltip="@string(more_options, More Options)" icon="icon(more-fill, 14dp)" min-width="32dp" />
<hbox lw="0" lw8="1" lh="mp" layout_gravity="center" padding-left="4dp" padding-right="4dp">
<DropDownList class="model_ui" menu-width-mode="expand-if-needed-centered" lw="0" lw8="1" selected-index="0"></DropDownList>
<!-- <PushButton id="refresh_model_ui" tooltip="@string(refresh_model_ui, Refresh Local Models)" icon="icon(refresh, 14dp)" /> -->
</hbox>
<PushButton class="model_ui" lw="0" lw8="1" lh="mp" margin-left="4dp" margin-right="4dp" tooltip="@string(select_model, Select Model)" />
<PushButton id="llm_settings_but" class="llm_button" text="@string(settings, Settings)" tooltip="@string(settings, Settings)" icon="icon(settings, 14dp)" min-width="32dp" margin-right="4dp" />
<!-- <SelectButton id="llm_private_chat" class="llm_button" tooltip="@string(private_chat, Toggle Private Chat)" icon="icon(chat-private, 14dp)" min-width="32dp" margin-right="8dp" select-on-click="true" /> -->
<PushButton id="llm_add_chat" class="llm_button" text="@string(add, Add)" tooltip="@string(add_message, Add Message)" icon="icon(add, 15dp)" min-width="32dp" margin-right="4dp" />
<PushButton id="llm_run" class="llm_button primary" text="@string(run, Run)" tooltip="@string(add_message_and_run_prompt, Add Message and Run Prompt)" icon="icon(play-filled, 14dp)" />
<PushButton id="llm_stop" class="llm_button primary" text="@string(stop, Stop)" icon="icon(stop, 12dp)" min-width="32dp" visible="false" enabled="false" />
@@ -219,10 +338,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
->asType<UISplitter>();
mChatsList = findByClass( "llm_chats" );
mModelDDL = findByClass<UIDropDownList>( "model_ui" );
// mRefreshModels = find<UIPushButton>( "refresh_model_ui" );
// mRefreshModels->onClick( [this]( auto ) { execute( "ai-refresh-local-models" ); } );
mModelBtn = findByClass<UIPushButton>( "model_ui" );
mChatMore = find<UIPushButton>( "llm_more" );
mChatMore->onClick( [this]( auto ) { execute( "ai-show-menu" ); } );
@@ -276,10 +392,6 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mChatUserRole = find<UIPushButton>( "llm_user" );
mChatUserRole->onClick( [this]( auto ) { execute( "ai-chat-toggle-role" ); } );
/* mChatPrivate = find<UISelectButton>( "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<LLMModelsModel*>( mLocateModelTable->getModel() )->setFilter( "" );
}
showSelectModel();
mLocateModelTable->runOnMainThread( [this] {
auto model = static_cast<LLMModelsModel*>( 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<UIPushButton>( "llm_chat_history" );
mChatHistory->onClick( [this]( auto ) { showChatHistory(); } );
@@ -541,10 +670,13 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mChatAttach = find<UIPushButton>( "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<LLMModel> LLMChatUI::getModel( const std::string& provider,
return {};
}
std::optional<LLMModel> 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<std::string>()( model.name ),
std::hash<std::string>()( 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<String> 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<String> 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<LLMModel> model ) {
if ( model ) {
mModelBtn->setText( getModelDisplayName( *model ) );
mCurModel = *model;
return true;
}
return false;
}
void LLMChatUI::fillModelDropDownList( UIDropDownList* modelDDL ) {
std::vector<String> 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<LLMModelsModel>( mModels, ctx->getUISceneNode() ) );
static_cast<LLMModelsModel*>( mLocateModelTable->getModel() )
->setFilter( mLocateModelInput->getText() );
}
void LLMChatUI::showSelectModel() {
if ( getPlugin() == nullptr )
return;
hideAttachFile();
if ( nullptr == mLocateModelTable->getModel() )
loadSelectModel();
static_cast<LLMModelsModel*>( mLocateModelTable->getModel() )
->setFilter( mLocateModelInput->getText() );
mLocateModelBarLayout->setVisible( true );
mLocateModelInput->setFocus();
updateLocateModelBarColumns();
}
void LLMChatUI::hideSelectModel() {
mLocateModelBarLayout->setVisible( false );
}
void LLMChatUI::initSelectModel() {
mLocateModelBarLayout = findByClass<UIVLinearLayoutCommandExecuter>( "llm_chat_select_model" );
mLocateModelInput = findByClass<UITextInput>( "llm_chat_select_model_input" );
mLocateModelTable = findByClass<UITableView>( "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<const KeyEvent*>( 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<const KeyEvent*>( event );
if ( keyEvent->getKeyCode() == KEY_ESCAPE )
mLocateModelBarLayout->execute( "close-locatebar" );
} );
mLocateModelTable->on( Event::OnModelEvent, [this]( const Event* event ) {
const ModelEvent* modelEvent = static_cast<const ModelEvent*>( 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

View File

@@ -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<LLMChatCompletionRequest> mRequest;
std::unique_ptr<LLMChatCompletionRequest> mSummaryRequest;
LLMProviders mProviders;
LLMModel mCurModel;
std::unordered_map<String::HashType, LLMModel> mModelsMap;
std::vector<LLMModel> mModels;
int mPendingModelsToLoad{ 0 };
bool mChatIsPrivate{ false };
bool mChatLocked{ false };
bool mLinkMode{ false };
std::vector<LLMModel> 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<LLMModel> model );
void fillModelDropDownList( UIDropDownList* modelDDL );
void fillModelDropDownList();
void resizeToFit( UICodeEditor* editor );
@@ -173,6 +181,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
std::optional<LLMModel> getModel( const std::string& provider, const std::string& modelName );
std::optional<LLMModel> 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<TextDocument> cdoc );
void replaceFileLinksToContents( std::string& text );

View File

@@ -14,6 +14,7 @@ struct LLMCacheConfiguration {
};
struct LLMModel {
std::size_t hash{ 0 };
std::string name;
std::string provider;
std::optional<std::string> displayName;

View File

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