Minor changes in DropDownList style.

Added StringMapModel and an example.
This commit is contained in:
Martín Lucas Golini
2026-02-09 13:37:18 -03:00
parent 3d3327c5a1
commit 7977cd99dd
9 changed files with 265 additions and 1 deletions

View File

@@ -339,6 +339,12 @@
"command": "${project_root}/bin/eepp-richtext-debug",
"name": "eepp-richtext-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-treeviewmodel-debug",
"name": "eepp-treeviewmodel-debug",
"working_dir": "${project_root}/bin"
}
],
"var": {

View File

@@ -687,6 +687,11 @@ Window::border::bottom {
background-color: var(--separator);
}
DropDownList {
padding-right: 16dp;
text-overflow: ellipsis;
}
DropDownList,
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>");

View File

@@ -86,6 +86,7 @@
#include <eepp/ui/models/model.hpp>
#include <eepp/ui/models/modelselection.hpp>
#include <eepp/ui/models/sortingproxymodel.hpp>
#include <eepp/ui/models/stringmapmodel.hpp>
#include <eepp/ui/models/widgettreemodel.hpp>
#include <eepp/ui/uidatabind.hpp>

View File

@@ -0,0 +1,144 @@
#ifndef EE_UI_MODELS_STRINGMAPMODEL_HPP
#define EE_UI_MODELS_STRINGMAPMODEL_HPP
#include <algorithm>
#include <eepp/core/string.hpp>
#include <eepp/ui/models/model.hpp>
#include <map>
#include <string>
#include <vector>
#include <deque>
namespace EE { namespace UI { namespace Models {
template <typename StringType = std::string> class StringMapModel : public Model {
public:
struct Node {
const StringType* text;
Node* parent = nullptr;
std::vector<Node*> children;
std::vector<Node*> visibleChildren;
Node( const StringType* text, Node* parent ) : text( text ), parent( parent ) {}
size_t childCount() const { return visibleChildren.size(); }
};
static std::shared_ptr<StringMapModel<StringType>>
create( const std::map<StringType, std::vector<StringType>>& map ) {
return std::make_shared<StringMapModel<StringType>>( map );
}
explicit StringMapModel( const std::map<StringType, std::vector<StringType>>& map ) : mMap( map ) {
mNodes.emplace_back( &mEmptyString, nullptr );
mRoot = &mNodes.back();
for ( const auto& [category, items] : mMap ) {
mNodes.emplace_back( &category, mRoot );
Node* catNode = &mNodes.back();
mRoot->children.push_back( catNode );
for ( const auto& item : items ) {
mNodes.emplace_back( &item, catNode );
catNode->children.push_back( &mNodes.back() );
}
}
resetFilter();
}
virtual ~StringMapModel() {}
virtual size_t rowCount( const ModelIndex& parent = ModelIndex() ) const override {
if ( !parent.isValid() )
return mRoot->visibleChildren.size();
Node* node = static_cast<Node*>( parent.internalData() );
return node->visibleChildren.size();
}
virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const override { return 1; }
virtual ModelIndex index( int row, int column,
const ModelIndex& parent = ModelIndex() ) const override {
if ( row < 0 || column < 0 || row >= (int)rowCount( parent ) ||
column >= (int)columnCount( parent ) )
return {};
Node* parentNode =
parent.isValid() ? static_cast<Node*>( parent.internalData() ) : mRoot;
if ( row < (int)parentNode->visibleChildren.size() )
return createIndex( row, column, parentNode->visibleChildren[row] );
return {};
}
virtual ModelIndex parent( const ModelIndex& index ) const {
if ( !index.isValid() )
return {};
Node* node = static_cast<Node*>( index.internalData() );
if ( node->parent == mRoot || node->parent == nullptr )
return {};
Node* grandParent = node->parent->parent;
if ( !grandParent )
return {};
// Find row of parent in grandparent's visible children
auto it = std::find( grandParent->visibleChildren.begin(),
grandParent->visibleChildren.end(), node->parent );
if ( it != grandParent->visibleChildren.end() ) {
int row = std::distance( grandParent->visibleChildren.begin(), it );
return createIndex( row, 0, node->parent );
}
return {};
}
virtual Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const override {
if ( !index.isValid() )
return {};
if ( role == ModelRole::Display ) {
Node* node = static_cast<Node*>( index.internalData() );
return Variant( *node->text );
}
return {};
}
void filter( const std::string_view& filterText ) {
if ( filterText.empty() ) {
resetFilter();
return;
}
mRoot->visibleChildren.clear();
for ( const auto& cat : mRoot->children ) {
bool catMatches = String::icontains( *cat->text, filterText );
cat->visibleChildren.clear();
for ( const auto& item : cat->children ) {
if ( catMatches || String::icontains( *item->text, filterText ) ) {
cat->visibleChildren.push_back( item );
}
}
if ( catMatches || !cat->visibleChildren.empty() ) {
mRoot->visibleChildren.push_back( cat );
}
}
invalidate( Model::UpdateFlag::InvalidateAllIndexes );
}
void resetFilter() {
mRoot->visibleChildren.clear();
for ( const auto& cat : mRoot->children ) {
cat->visibleChildren.clear();
for ( const auto& item : cat->children ) {
cat->visibleChildren.push_back( item );
}
mRoot->visibleChildren.push_back( cat );
}
invalidate( Model::UpdateFlag::InvalidateAllIndexes );
}
private:
std::map<StringType, std::vector<StringType>> mMap;
StringType mEmptyString;
std::deque<Node> mNodes;
Node* mRoot{ nullptr };
};
}}} // namespace EE::UI::Models
#endif // EE_UI_MODELS_STRINGMAPMODEL_HPP

View File

@@ -1607,6 +1607,12 @@ solution "eepp"
files { "src/examples/7guis/cells/*.cpp" }
build_link_configuration( "eepp-7guis-cells", true )
project "eepp-treeviewmodel"
set_kind()
language "C++"
files { "src/examples/ui_treeview_model/*.cpp" }
build_link_configuration( "eepp-treeviewmodel", true )
-- Tools
project "eepp-textureatlaseditor"
set_kind()

View File

@@ -1483,6 +1483,12 @@ workspace "eepp"
files { "src/examples/7guis/cells/*.cpp" }
build_link_configuration( "eepp-7guis-cells", true )
project "eepp-treeviewmodel"
set_kind()
language "C++"
files { "src/examples/ui_treeview_model/*.cpp" }
build_link_configuration( "eepp-treeviewmodel", true )
-- Tools
project "eepp-textureatlaseditor"
set_kind()

View File

@@ -0,0 +1,39 @@
#include <eepp/ee.hpp>
using namespace EE;
using namespace EE::UI;
using namespace EE::UI::Models;
EE_MAIN_FUNC int main( int, char** ) {
UIApplication app( { 800, 600, "eepp - StringMapModel Example" } );
std::map<std::string, std::vector<std::string>> data = {
{ "Category 1", { "Item 1.1", "Item 1.2", "Item 1.3" } },
{ "Category 2", { "Item 2.1", "Item 2.2", "Something else" } },
{ "Fruits", { "Apple", "Banana", "Orange", "Grape" } },
{ "Programming Languages", { "C++", "Lua", "Python", "Rust" } } };
auto model = StringMapModel<std::string>::create( data );
UIWidget* vBox = app.getUI()->loadLayoutFromString( R"xml(
<vbox layout_width="match_parent" layout_height="match_parent">
<TextInput id="filter_input" layout_width="match_parent" layout_height="wrap_content" hint="Filter: " />
<TreeView id="tree_view" layout_width="match_parent" layout_height="0dp" layout_weight="1" />
</vbox>
)xml" );
auto treeView = vBox->find<UITreeView>( "tree_view" );
auto filterInput = vBox->find<UITextInput>( "filter_input" );
treeView->setHeadersVisible( false );
treeView->setAutoExpandOnSingleColumn( true );
treeView->setModel( model );
treeView->expandAll();
filterInput->on( Event::OnTextChanged, [model, filterInput]( const Event* ) {
model->filter( filterInput->getText().toUtf8() );
} );
filterInput->setFocus();
return app.run();
}

View File

@@ -0,0 +1,54 @@
#include <eepp/ui/models/stringmapmodel.hpp>
#include <eepp/ui/models/modelindex.hpp>
#include "utest.hpp"
using namespace EE::UI::Models;
UTEST( StringMapModel, InitialState ) {
std::map<std::string, std::vector<std::string>> data = {
{ "A", { "1", "2" } },
{ "B", { "3" } }
};
auto model = StringMapModel<std::string>::create( data );
ASSERT_EQ( (int)model->rowCount(), 2 );
ASSERT_EQ( (int)model->columnCount(), 1 );
ModelIndex idxA = model->index( 0, 0 );
ModelIndex idxB = model->index( 1, 0 );
ASSERT_TRUE( idxA.isValid() );
ASSERT_TRUE( idxB.isValid() );
// Ordering is map ordering, so A then B.
ASSERT_STDSTREQ( model->data( idxA ).asStdString(), "A" );
ASSERT_STDSTREQ( model->data( idxB ).asStdString(), "B" );
ASSERT_EQ( (int)model->rowCount( idxA ), 2 );
ASSERT_EQ( (int)model->rowCount( idxB ), 1 );
ModelIndex idxA1 = model->index( 0, 0, idxA );
ASSERT_TRUE( idxA1.isValid() );
ASSERT_STDSTREQ( model->data( idxA1 ).asStdString(), "1" );
}
UTEST( StringMapModel, Filter ) {
std::map<std::string, std::vector<std::string>> data = {
{ "Cat", { "Item 1" } },
{ "Dog", { "Item 2" } }
};
auto model = StringMapModel<std::string>::create( data );
model->filter( "Cat" );
ASSERT_EQ( (int)model->rowCount(), 1 );
ModelIndex idx = model->index( 0, 0 );
ASSERT_STDSTREQ( model->data( idx ).asStdString(), "Cat" );
model->filter( "Item 2" );
ASSERT_EQ( (int)model->rowCount(), 1 );
idx = model->index( 0, 0 );
ASSERT_STDSTREQ( model->data( idx ).asStdString(), "Dog" );
model->filter( "" );
ASSERT_EQ( (int)model->rowCount(), 2 );
}

View File

@@ -72,9 +72,12 @@ LLMChat::Role LLMChat::stringToRole( UIPushButton* userBut ) {
static const char* DEFAULT_LAYOUT = R"xml(
<style>
.llm_chatui DropDownList {
border: 0;
border-color: transparent;
background-color: var(--tab-back);
}
.llm_chatui DropDownList:hover {
border-color: var(--primary);
}
.llm_conversation {
margin-bottom: 8dp;
}