Merge branch 'dev-treeview' into develop

This commit is contained in:
Martín Lucas Golini
2020-07-11 23:14:41 -03:00
48 changed files with 3152 additions and 898 deletions

View File

@@ -736,6 +736,48 @@ Splitter::separator:hover {
background-color: var(--primary);
}
table::header {
background-color: var(--back);
}
table::header::column {
background-color: var(--back);
border-right-color: var(--tab-line);
border-right-width: 1dp;
border-bottom-color: var(--tab-line);
border-bottom-width: 1dp;
border-type: inside;
color: var(--font);
padding: 2dp 6dp 2dp 6dp;
transition: all 0.125s;
text-align: left;
}
table::header::column:hover {
background-color: var(--tab-hover);
}
table::row {
background-color: var(--list-back);
transition: all 0.125s;
}
table::row:hover {
background-color: var(--back);
}
table::row:selected {
background-color: var(--primary);
}
TreeView {
background-color: var(--list-back);
}
TreeView > ScrollBar {
background-color: var(--back);
}
.appbackground {
background-color: var(--back);
}

View File

@@ -73,6 +73,7 @@ class EE_API Event {
OnSelectionChanged,
OnNodeDropped,
OnSave,
OnModelEvent,
UserEvent,
NoEvent = eeINDEX_NOT_FOUND
};

View File

@@ -390,6 +390,10 @@ class EE_API Node : public Transformable {
void forEachNode( std::function<void( Node* )> func );
void forEachChild( std::function<void( Node* )> func );
virtual void nodeDraw();
protected:
typedef std::map<Uint32, std::map<Uint32, EventCallback>> EventsMap;
friend class EventDispatcher;
@@ -488,8 +492,6 @@ class EE_API Node : public Transformable {
virtual Uint32 onFocusLoss();
virtual void internalDraw();
void clipEnd();
void updateScreenPos();

View File

@@ -0,0 +1,184 @@
#ifndef EE_UI_MODEL_MODEL_HPP
#define EE_UI_MODEL_MODEL_HPP
#include <eepp/core/core.hpp>
#include <eepp/graphics/drawable.hpp>
#include <eepp/math/rect.hpp>
#include <eepp/ui/abstract/modelindex.hpp>
#include <functional>
#include <string>
#include <unordered_set>
using namespace EE::Graphics;
using namespace EE::Math;
namespace EE { namespace UI { namespace Abstract {
enum class SortOrder { None, Ascending, Descending };
class UIAbstractView;
class Variant {
public:
enum class Type {
Invalid,
DataPtr,
String,
Bool,
Float,
Int,
Int64,
Drawable,
Vector2f,
Rectf,
cstr
};
Variant() : mType( Type::Invalid ) {}
explicit Variant( const std::string& string ) : mType( Type::String ) {
mValue.asString = eeNew( std::string, ( string ) );
}
Variant( Drawable* drawable, bool ownDrawable = false ) : mType( Type::Drawable ) {
mValue.asDrawable = drawable;
mOwnsObject = ownDrawable;
}
Variant( const Vector2f& v ) : mType( Type::Vector2f ) {
mValue.asVector2f = eeNew( Vector2f, ( v ) );
}
Variant( void* data ) : mType( Type::DataPtr ) { mValue.asDataPtr = data; }
Variant( const Rectf& r ) : mType( Type::Rectf ) { mValue.asRectf = eeNew( Rectf, ( r ) ); }
Variant( bool val ) : mType( Type::Bool ) { mValue.asBool = val; }
Variant( const Float& val ) : mType( Type::Float ) { mValue.asFloat = val; }
Variant( const int& val ) : mType( Type::Int ) { mValue.asInt = val; }
Variant( const Int64& val ) : mType( Type::Int64 ) { mValue.asInt64 = val; }
explicit Variant( const char* data ) : mType( Type::cstr ) { mValue.asCStr = data; }
~Variant() { reset(); }
const std::string& asString() const { return *mValue.asString; }
Drawable* asDrawable() const { return mValue.asDrawable; }
const bool& asBool() const { return mValue.asBool; }
const Float& asFloat() const { return mValue.asFloat; }
const int& asInt() const { return mValue.asInt; }
const Int64& asInt64() const { return mValue.asInt64; }
const Vector2f& asVector2f() const { return *mValue.asVector2f; }
const Rectf& asRectf() const { return *mValue.asRectf; }
const char* asCStr() const { return mValue.asCStr; }
bool is( const Type& type ) const { return type == mType; }
void reset() {
switch ( mType ) {
case Type::String:
eeSAFE_DELETE( mValue.asString );
break;
case Type::Drawable:
if ( mOwnsObject )
eeSAFE_DELETE( mValue.asDrawable );
break;
case Type::Vector2f:
eeSAFE_DELETE( mValue.asVector2f );
break;
case Type::Rectf:
eeSAFE_DELETE( mValue.asRectf );
break;
default:
break;
}
mType = Type::Invalid;
}
bool isValid() { return mType != Type::Invalid; }
private:
union {
void* asDataPtr;
Drawable* asDrawable;
std::string* asString;
bool asBool;
Float asFloat;
int asInt;
Int64 asInt64;
Vector2f* asVector2f;
Rectf* asRectf;
const char* asCStr;
} mValue;
Type mType;
bool mOwnsObject{false};
};
class EE_API Model {
public:
enum UpdateFlag {
DontInvalidateIndexes = 0,
InvalidateAllIndexes = 1 << 0,
};
enum class Role {
Display,
Icon,
};
virtual ~Model(){};
virtual size_t rowCount( const ModelIndex& = ModelIndex() ) const = 0;
virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const = 0;
virtual std::string columnName( const size_t& /*column*/ ) const { return {}; }
virtual Variant data( const ModelIndex&, Role = Role::Display ) const = 0;
virtual void update() = 0;
virtual ModelIndex parentIndex( const ModelIndex& ) const { return {}; }
virtual ModelIndex index( int row, int column = 0, const ModelIndex& = ModelIndex() ) const {
return createIndex( row, column );
}
virtual ModelIndex sibling( int row, int column, const ModelIndex& parent ) const;
virtual void setData( const ModelIndex&, const Variant& ) {}
virtual size_t treeColumn() const { return 0; }
virtual bool acceptsDrag( const ModelIndex&, const std::string& dataType );
virtual bool isColumnSortable( const size_t& /*columnIndex*/ ) const { return true; }
virtual std::string dragDataType() const { return {}; }
bool isValid( const ModelIndex& index ) const {
auto parentIndex = this->parentIndex( index );
return index.row() >= 0 && index.row() < (Int64)rowCount( parentIndex ) &&
index.column() >= 0 && index.column() < (Int64)columnCount( parentIndex );
}
virtual int keyColumn() const { return -1; }
virtual SortOrder sortOrder() const { return SortOrder::None; }
virtual void setKeyColumnAndSortOrder( const size_t& /*column*/, const SortOrder& /*order*/ ) {}
void registerView( UIAbstractView* );
void unregisterView( UIAbstractView* );
void setOnUpdate( const std::function<void()>& onUpdate );
protected:
Model(){};
void forEachView( std::function<void( UIAbstractView* )> );
void onModelUpdate( unsigned flags = UpdateFlag::InvalidateAllIndexes );
ModelIndex createIndex( int row, int column, const void* data = nullptr ) const;
private:
std::unordered_set<UIAbstractView*> mViews;
std::function<void()> mOnUpdate;
};
inline ModelIndex ModelIndex::parent() const {
return mModel ? mModel->parentIndex( *this ) : ModelIndex();
}
}}} // namespace EE::UI::Abstract
#endif // EE_UI_MODEL_MODEL_HPP

View File

@@ -0,0 +1,48 @@
#ifndef EE_UI_MODELEDITINGDELEGATE_HPP
#define EE_UI_MODELEDITINGDELEGATE_HPP
#include <eepp/ui/abstract/model.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <memory>
namespace EE { namespace UI { namespace Abstract {
class EE_API ModelEditingDelegate {
public:
virtual ~ModelEditingDelegate() {}
void bind( std::shared_ptr<Model> model, const ModelIndex& index ) {
if ( mModel.get() == model.get() && mIndex == index )
return;
mModel = model;
mIndex = index;
mWidget = createWidget();
}
UIWidget* getWidget() { return mWidget; }
UIWidget* getWidget() const { return mWidget; }
std::function<void()> onCommit;
virtual Variant getValue() const = 0;
virtual void setValue( const Variant& ) = 0;
virtual void willBeginEditing() {}
protected:
ModelEditingDelegate() {}
virtual UIWidget* createWidget() = 0;
void commit() {
if ( onCommit )
onCommit();
}
std::shared_ptr<Model> mModel;
ModelIndex mIndex;
UIWidget* mWidget;
};
}}} // namespace EE::UI::Abstract
#endif // EE_UI_MODELEDITINGDELEGATE_HPP

View File

@@ -0,0 +1,50 @@
#ifndef EE_UI_MODEL_MODELINDEX_HPP
#define EE_UI_MODEL_MODELINDEX_HPP
#include <eepp/config.hpp>
namespace EE { namespace UI { namespace Abstract {
class Model;
class EE_API ModelIndex {
public:
ModelIndex() {}
bool isValid() const { return mRow != -1 && mColumn != -1; }
const Int64& row() const { return mRow; }
const Int64& column() const { return mColumn; }
void* data() const { return mData; }
ModelIndex parent() const;
bool hasParent() const { return parent().isValid(); }
bool operator<( const ModelIndex& other ) const {
return mRow != other.mRow || mColumn != other.mColumn;
}
bool operator==( const ModelIndex& other ) const {
return mModel == other.mModel && mRow == other.mRow && mColumn == other.mColumn &&
mData == other.mData;
}
bool operator!=( const ModelIndex& other ) const { return !( *this == other ); }
protected:
friend class Model;
const Model* mModel{nullptr};
Int64 mRow{-1};
Int64 mColumn{-1};
void* mData{nullptr};
ModelIndex( const Model& model, int row, int column, void* internalData ) :
mModel( &model ), mRow( row ), mColumn( column ), mData( internalData ) {}
};
}}} // namespace EE::UI::Abstract
#endif // EE_UI_MODEL_MODELINDEX_HPP

View File

@@ -0,0 +1,68 @@
#ifndef EE_UI_MODEL_MODELSELECTION_HPP
#define EE_UI_MODEL_MODELSELECTION_HPP
#include <algorithm>
#include <eepp/ui/abstract/modelindex.hpp>
#include <functional>
#include <unordered_set>
namespace EE { namespace UI { namespace Abstract {
class UIAbstractView;
class EE_API ModelSelection {
public:
ModelSelection( UIAbstractView* view ) : mView( view ) {}
int size() const { return mIndexes.size(); }
bool isEmpty() const { return mIndexes.empty(); }
bool contains( const ModelIndex& index ) const {
return std::find( mIndexes.begin(), mIndexes.end(), index ) != mIndexes.end();
}
bool containsRow( int row ) const {
for ( auto& index : mIndexes ) {
if ( index.row() == row )
return true;
}
return false;
}
void set( const ModelIndex& );
void add( const ModelIndex& );
void toggle( const ModelIndex& );
bool remove( const ModelIndex& );
void clear();
template <typename Callback> void forEachIndex( Callback callback ) {
for ( auto& index : indexes() )
callback( index );
}
template <typename Callback> void forEachIndex( Callback callback ) const {
for ( auto& index : indexes() )
callback( index );
}
std::vector<ModelIndex> indexes() const {
std::vector<ModelIndex> selectedIndexes;
for ( auto& index : mIndexes )
selectedIndexes.push_back( index );
return selectedIndexes;
}
ModelIndex first() const {
if ( mIndexes.empty() )
return {};
return *mIndexes.begin();
}
void removeMatching( std::function<bool( const ModelIndex& )> );
protected:
UIAbstractView* mView;
std::vector<ModelIndex> mIndexes;
};
}}} // namespace EE::UI::Abstract
#endif // EE_UI_MODEL_MODELSELECTION_HPP

View File

@@ -0,0 +1,82 @@
#ifndef EE_UI_UIABSTRACTTABLEVIEW_HPP
#define EE_UI_UIABSTRACTTABLEVIEW_HPP
#include <eepp/math/rect.hpp>
#include <eepp/ui/abstract/uiabstractview.hpp>
#include <eepp/ui/uitableheadercolumn.hpp>
using namespace EE::Math;
namespace EE { namespace UI {
class UIPushButton;
class UILinearLayout;
}} // namespace EE::UI
namespace EE { namespace UI { namespace Abstract {
class EE_API UIAbstractTableView : public UIAbstractView {
public:
Uint32 getType() const;
bool isType( const Uint32& type ) const;
virtual Float getRowHeight() const { return getHeaderHeight(); }
virtual Float getHeaderHeight() const;
virtual Sizef getContentSize() const;
bool areHeadersVisible() const;
void setHeadersVisible( bool visible );
bool isColumnHidden( const size_t& column ) const;
void setColumnHidden( const size_t& column, bool hidden );
virtual void selectAll();
const Float& getDragBorderDistance() const;
void setDragBorderDistance( const Float& dragBorderDistance );
Vector2f getColumnPosition( const size_t& index );
protected:
friend class EE::UI::UITableHeaderColumn;
virtual ~UIAbstractTableView();
UIAbstractTableView( const std::string& tag );
struct ColumnData {
Float width{0};
bool visible{true};
UIPushButton* widget{nullptr};
};
ColumnData& columnData( const size_t& column ) const;
mutable std::vector<ColumnData> mColumn;
virtual size_t getItemCount() const;
virtual void onModelUpdate( unsigned flags );
virtual void createOrUpdateColumns();
virtual void onSizeChange();
virtual void onColumnSizeChange( const size_t& colIndex );
virtual void onColumnResizeToContent( const size_t& colIndex );
void updateHeaderSize();
UILinearLayout* mHeader;
Float mDragBorderDistance{8};
};
}}} // namespace EE::UI::Abstract
#endif // EE_UI_UIABSTRACTTABLEVIEW_HPP

View File

@@ -0,0 +1,80 @@
#ifndef EE_UI_UIABSTRACTVIEW_HPP
#define EE_UI_UIABSTRACTVIEW_HPP
#include <eepp/ui/abstract/model.hpp>
#include <eepp/ui/abstract/modeleditingdelegate.hpp>
#include <eepp/ui/abstract/modelselection.hpp>
#include <eepp/ui/uiscrollablewidget.hpp>
#include <memory>
namespace EE { namespace UI { namespace Abstract {
class ModelEvent : public Event {
public:
ModelEvent( Model* model, const ModelIndex& index, Node* node ) :
Event( node, Event::OnModelEvent ), model( model ), index( index ) {}
const Model* getModel() const { return model; }
const ModelIndex& getModelIndex() const { return index; }
protected:
const Model* model;
ModelIndex index;
};
class EE_API UIAbstractView : public UIScrollableWidget {
public:
Uint32 getType() const;
bool isType( const Uint32& type ) const;
void setModel( std::shared_ptr<Model> );
Model* getModel() { return mModel.get(); }
const Model* getModel() const { return mModel.get(); }
ModelSelection& getSelection() { return mSelection; }
const ModelSelection& getSelection() const { return mSelection; }
virtual void selectAll() = 0;
void notifySelectionChange();
std::function<void()> getOnSelectionChange() const;
void setOnSelectionChange( const std::function<void()>& onSelectionChange );
std::function<void( const ModelIndex& )> getOnSelection() const;
void setOnSelection( const std::function<void( const ModelIndex& )>& onSelection );
protected:
friend class Model;
virtual void onModelUpdate( unsigned flags );
virtual void onModelSelectionChange();
UIAbstractView( const std::string& tag );
virtual ~UIAbstractView();
bool mEditable{false};
ModelIndex mEditIndex;
UIWidget* mEditWidget;
Rect mEditWidgetContentRect;
std::shared_ptr<Model> mModel;
std::unique_ptr<ModelEditingDelegate> mEditingDelegate;
ModelSelection mSelection;
std::function<void()> mOnSelectionChange;
std::function<void( const ModelIndex& )> mOnSelection;
};
}}} // namespace EE::UI::Abstract
#endif // EE_UI_UIABSTRACTVIEW_HPP

View File

@@ -89,6 +89,10 @@ enum UINodeType {
UI_TYPE_ITEMCONTAINER,
UI_TYPE_CODEEDITOR,
UI_TYPE_SPLITTER,
UI_TYPE_ABSTRACTVIEW,
UI_TYPE_ABSTRACTTABLEVIEW,
UI_TYPE_TREEVIEW,
UI_TYPE_SCROLLABLEWIDGET,
UI_TYPE_USER = 10000
};

View File

@@ -59,7 +59,7 @@ template <class TContainer> void UIItemContainer<TContainer>::drawChilds() {
if ( tParent->mItems.size() ) {
for ( Uint32 i = tParent->mVisibleFirst; i <= tParent->mVisibleLast; i++ )
if ( NULL != tParent->mItems[i] )
tParent->mItems[i]->internalDraw();
tParent->mItems[i]->nodeDraw();
}
}

View File

@@ -7,6 +7,8 @@ namespace EE { namespace UI {
class EE_API UILinearLayout : public UILayout {
public:
static UILinearLayout* NewWithTag( const std::string& tag, const UIOrientation& orientation );
static UILinearLayout* New();
static UILinearLayout* NewVertical();

View File

@@ -36,7 +36,7 @@ class EE_API UIMenuItem : public UIPushButton {
virtual Uint32 onMouseClick( const Vector2i& pos, const Uint32& flags );
virtual UIWidget* getExtraInnerWidget();
virtual UIWidget* getExtraInnerWidget() const;
void createShortcutView();
};

View File

@@ -51,7 +51,7 @@ class EE_API UIMenuSubMenu : public UIMenuItem {
virtual void onAlphaChange();
virtual UIWidget* getExtraInnerWidget();
virtual UIWidget* getExtraInnerWidget() const;
void onSubMenuFocusLoss( const Event* Event );

View File

@@ -257,6 +257,8 @@ class EE_API UINode : public Node {
Rectf getLocalDpBounds() const;
virtual void nodeDraw();
protected:
Vector2f mDpPos;
Sizef mDpSize;
@@ -318,8 +320,6 @@ class EE_API UINode : public Node {
void checkClose();
virtual void internalDraw();
virtual void onWidgetFocusLoss();
void writeFlag( const Uint32& Flag, const Uint32& Val );

View File

@@ -11,6 +11,8 @@ class EE_API UIPushButton : public UIWidget {
public:
static UIPushButton* New();
static UIPushButton* NewWithTag( const std::string& tag );
UIPushButton();
virtual ~UIPushButton();
@@ -40,6 +42,10 @@ class EE_API UIPushButton : public UIWidget {
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex = 0 );
void setTextAlign( const Uint32& align );
virtual Sizef getContentSize() const;
protected:
UIImage* mIcon;
UITextView* mTextBox;
@@ -65,7 +71,7 @@ class EE_API UIPushButton : public UIWidget {
virtual Uint32 onKeyUp( const KeyEvent& Event );
virtual UIWidget* getExtraInnerWidget();
virtual UIWidget* getExtraInnerWidget() const;
};
}} // namespace EE::UI

View File

@@ -0,0 +1,80 @@
#ifndef EE_UI_UISCROLLABLEWIDGET_HPP
#define EE_UI_UISCROLLABLEWIDGET_HPP
#include <eepp/ui/uitouchdraggablewidget.hpp>
namespace EE { namespace UI {
class UIScrollBar;
class EE_API UIScrollableWidget : public UIWidget {
public:
enum ScrollViewType { Inclusive, Exclusive };
virtual Uint32 getType() const;
virtual bool isType( const Uint32& type ) const;
void setVerticalScrollMode( const ScrollBarMode& Mode );
const ScrollBarMode& getVerticalScrollMode();
void setHorizontalScrollMode( const ScrollBarMode& Mode );
const ScrollBarMode& getHorizontalScrollMode();
const ScrollViewType& getViewType() const;
void setViewType( const ScrollViewType& viewType );
UIScrollBar* getVerticalScrollBar() const;
UIScrollBar* getHorizontalScrollBar() const;
virtual bool applyProperty( const StyleSheetProperty& attribute );
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex = 0 );
virtual Sizef getContentSize() const = 0;
void scrollToTop();
void scrollToBottom();
Sizef getScrollableArea() const;
Sizef getVisibleArea() const;
protected:
ScrollViewType mViewType;
ScrollBarMode mVScrollMode;
ScrollBarMode mHScrollMode;
UIScrollBar* mVScroll;
UIScrollBar* mHScroll;
Uint32 mSizeChangeCb;
Uint32 mPosChangeCb;
Vector2f mScrollOffset;
UIScrollableWidget( const std::string& tag );
virtual Uint32 onMessage( const NodeMessage* Msg );
virtual void onSizeChange();
virtual void onAlphaChange();
virtual void onPaddingChange();
void onValueChangeCb( const Event* Event );
virtual void onContentSizeChange();
virtual void updateScroll();
virtual void onScrollChange();
};
}} // namespace EE::UI
#endif // EE_UI_UISCROLLABLEWIDGET_HPP

View File

@@ -62,7 +62,7 @@ class EE_API UITab : public UISelectButton {
virtual void onSizeChange();
virtual UIWidget* getExtraInnerWidget();
virtual UIWidget* getExtraInnerWidget() const;
void setOwnedNode();

View File

@@ -0,0 +1,38 @@
#ifndef EE_UI_UITABLEHEADERCOLUMN_HPP
#define EE_UI_UITABLEHEADERCOLUMN_HPP
#include <eepp/ui/uipushbutton.hpp>
namespace EE { namespace UI {
namespace Abstract {
class UIAbstractTableView;
}
using namespace Abstract;
class UITableHeaderColumn : public UIPushButton {
public:
UITableHeaderColumn( UIAbstractTableView* view, const size_t& colIndex );
protected:
UIAbstractTableView* mView;
size_t mColIndex;
Uint32 onCalculateDrag( const Vector2f& position, const Uint32& flags );
Uint32 onMouseDown( const Vector2i& position, const Uint32& flags );
Uint32 onDrag( const Vector2f& position, const Uint32&, const Sizef& dragDiff );
Uint32 onMouseLeave( const Vector2i& position, const Uint32& flags );
Uint32 onMouseMove( const Vector2i& position, const Uint32& flags );
Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags );
Uint32 onDragStop( const Vector2i& pos, const Uint32& flags );
};
}}
#endif // EE_UI_UITABLEHEADERCOLUMN_HPP

View File

@@ -86,6 +86,7 @@ class EE_API UITextView : public UIWidget {
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex = 0 );
void setTextAlign( const Uint32& align );
protected:
Text* mTextCache;
String mString;

View File

@@ -0,0 +1,95 @@
#ifndef EE_UI_UITREEVIEW_HPP
#define EE_UI_UITREEVIEW_HPP
#include <eepp/ui/abstract/uiabstracttableview.hpp>
#include <memory>
#include <unordered_map>
using namespace EE::UI::Abstract;
namespace EE { namespace UI {
class UITableRow;
class EE_API UITreeView : public UIAbstractTableView {
public:
static UITreeView* New();
Uint32 getType() const;
bool isType( const Uint32& type ) const;
const Float& getIndentWidth() const;
void setIndentWidth( const Float& indentWidth );
virtual Sizef getContentSize() const;
virtual void drawChilds();
virtual Node* overFind( const Vector2f& point );
bool isExpanded( const ModelIndex& index ) const;
Drawable* getExpandIcon() const;
void setExpandedIcon( Drawable* expandIcon );
Drawable* getContractIcon() const;
void setContractedIcon( Drawable* contractIcon );
protected:
enum class IterationDecision {
Continue,
Break,
Stop,
};
Float mIndentWidth;
Sizef mContentSize;
Drawable* mExpandIcon{nullptr};
Drawable* mContractIcon{nullptr};
UITreeView();
virtual void createOrUpdateColumns();
struct MetadataForIndex;
template <typename Callback> void traverseTree( Callback ) const;
mutable std::map<void*, std::unique_ptr<MetadataForIndex>> mViewMetadata;
mutable std::vector<std::map<int, UIWidget*>> mWidgets;
mutable std::vector<UITableRow*> mRows;
virtual size_t getItemCount() const;
UITreeView::MetadataForIndex& getIndexMetadata( const ModelIndex& index ) const;
virtual void onColumnSizeChange( const size_t& colIndex );
virtual UIWidget* updateCell( const int& rowIndex, const ModelIndex& index, const size_t& col,
const size_t& indentLevel, const Float& yOffset );
virtual UIWidget* updateRow( const int& rowIndex, const ModelIndex& index,
const Float& yOffset );
virtual UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index, const size_t& col );
virtual void onScrollChange();
virtual void onColumnResizeToContent( const size_t& colIndex );
virtual void onModelSelectionChange();
virtual Uint32 onKeyDown( const KeyEvent& event );
virtual void onOpenModelIndex( const ModelIndex& index );
void updateContentSize();
};
}} // namespace EE::UI
#endif // EE_UI_UITREEVIEW_HPP

View File

@@ -141,7 +141,7 @@ class EE_API UIWindow : public UIWidget {
virtual bool applyProperty( const StyleSheetProperty& attribute );
virtual void internalDraw();
virtual void nodeDraw();
void invalidate( Node* invalidator );

View File

@@ -433,7 +433,7 @@ function build_link_configuration( package_name, use_ee_icon )
end
fix_shared_lib_linking_path( package_name, "libeepp-debug" )
if not os.is_real("emscripten") then
targetname ( package_name .. "-debug" .. extension )
else
@@ -748,7 +748,7 @@ function build_eepp( build_name )
"src/eepp/scene/*.cpp",
"src/eepp/scene/actions/*.cpp",
"src/eepp/ui/*.cpp",
"src/eepp/ui/actions/*.cpp",
"src/eepp/ui/abstract/*.cpp",
"src/eepp/ui/css/*.cpp",
"src/eepp/ui/doc/*.cpp",
"src/eepp/ui/tools/*.cpp",

File diff suppressed because it is too large Load Diff

View File

@@ -82,7 +82,7 @@ CODE_SRCS := \
ui/*.cpp \
ui/css/*.cpp \
ui/doc/*.cpp \
ui/actions/*.cpp \
ui/model/*.cpp \
ui/tools/*.cpp \
maps/*.cpp \
maps/mapeditor/*.cpp

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.12.2, 2020-07-02T20:21:25. -->
<!-- Written by QtCreator 4.12.2, 2020-07-04T20:24:45. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>

View File

@@ -294,8 +294,14 @@
../../include/eepp/thirdparty/chipmunk/cpSpatialIndex.h
../../include/eepp/thirdparty/chipmunk/cpVect.h
../../include/eepp/thirdparty/PlusCallback/callback.hpp
../../include/eepp/ui/abstract/model.hpp
../../include/eepp/ui/abstract/uiabstracttableview.hpp
../../include/eepp/ui/abstract/uiabstractview.hpp
../../include/eepp/ui/base.hpp
../../include/eepp/ui.hpp
../../include/eepp/ui/abstract/modeleditingdelegate.hpp
../../include/eepp/ui/abstract/modelindex.hpp
../../include/eepp/ui/abstract/modelselection.hpp
../../include/eepp/ui/border.hpp
../../include/eepp/ui/css/animationdefinition.hpp
../../include/eepp/ui/css/drawableimageparser.hpp
@@ -331,9 +337,13 @@
../../include/eepp/ui/doc/undostack.hpp
../../include/eepp/ui/keyboardshortcut.hpp
../../include/eepp/ui/marginmove/scale.hpp
../../include/eepp/ui/abstract/model.hpp
../../include/eepp/ui/abstract/modelindex.hpp
../../include/eepp/ui/abstract/modelselection.hpp
../../include/eepp/ui/tools/textureatlaseditor.hpp
../../include/eepp/ui/tools/uicodeeditorsplitter.hpp
../../include/eepp/ui/tools/uicolorpicker.hpp
../../include/eepp/ui/uiabstractview.hpp
../../include/eepp/ui/uibackgrounddrawable.hpp
../../include/eepp/ui/uiborderdrawable.hpp
../../include/eepp/ui/uicheckbox.hpp
@@ -371,6 +381,7 @@
../../include/eepp/ui/uiradiobutton.hpp
../../include/eepp/ui/uirelativelayout.hpp
../../include/eepp/ui/uiscenenode.hpp
../../include/eepp/ui/uiscrollablewidget.hpp
../../include/eepp/ui/uiscrollbar.hpp
../../include/eepp/ui/uiscrollview.hpp
../../include/eepp/ui/uiselectbutton.hpp
@@ -385,6 +396,7 @@
../../include/eepp/ui/uitab.hpp
../../include/eepp/ui/uitablecell.hpp
../../include/eepp/ui/uitable.hpp
../../include/eepp/ui/uitableheadercolumn.hpp
../../include/eepp/ui/uitabwidget.hpp
../../include/eepp/ui/uitextedit.hpp
../../include/eepp/ui/uitextinput.hpp
@@ -395,6 +407,7 @@
../../include/eepp/ui/uithememanager.hpp
../../include/eepp/ui/uitooltip.hpp
../../include/eepp/ui/uitouchdraggablewidget.hpp
../../include/eepp/ui/uitreeview.hpp
../../include/eepp/ui/uiviewpager.hpp
../../include/eepp/ui/uiwidgetcreator.hpp
../../include/eepp/ui/uiwidget.hpp
@@ -754,6 +767,10 @@
../../src/eepp/system/translator.cpp
../../src/eepp/system/virtualfilesystem.cpp
../../src/eepp/system/zip.cpp
../../src/eepp/ui/abstract/model.cpp
../../src/eepp/ui/abstract/modelselection.cpp
../../src/eepp/ui/abstract/uiabstracttableview.cpp
../../src/eepp/ui/abstract/uiabstractview.cpp
../../src/eepp/ui/border.cpp
../../src/eepp/ui/css/animationdefinition.cpp
../../src/eepp/ui/css/drawableimageparser.cpp
@@ -784,6 +801,8 @@
../../src/eepp/ui/doc/textdocument.cpp
../../src/eepp/ui/doc/undostack.cpp
../../src/eepp/ui/keyboardshortcut.cpp
../../src/eepp/ui/abstract/model.cpp
../../src/eepp/ui/abstract/modelselection.cpp
../../src/eepp/ui/tools/textureatlaseditor.cpp
../../src/eepp/ui/tools/textureatlasnew.cpp
../../src/eepp/ui/tools/textureatlasnew.hpp
@@ -791,6 +810,7 @@
../../src/eepp/ui/tools/textureatlastextureregioneditor.hpp
../../src/eepp/ui/tools/uicodeeditorsplitter.cpp
../../src/eepp/ui/tools/uicolorpicker.cpp
../../src/eepp/ui/uiabstractview.cpp
../../src/eepp/ui/uibackgrounddrawable.cpp
../../src/eepp/ui/uiborderdrawable.cpp
../../src/eepp/ui/uicheckbox.cpp
@@ -825,6 +845,7 @@
../../src/eepp/ui/uiradiobutton.cpp
../../src/eepp/ui/uirelativelayout.cpp
../../src/eepp/ui/uiscenenode.cpp
../../src/eepp/ui/uiscrollablewidget.cpp
../../src/eepp/ui/uiscrollbar.cpp
../../src/eepp/ui/uiscrollview.cpp
../../src/eepp/ui/uiselectbutton.cpp
@@ -839,6 +860,7 @@
../../src/eepp/ui/uitab.cpp
../../src/eepp/ui/uitablecell.cpp
../../src/eepp/ui/uitable.cpp
../../src/eepp/ui/uitableheadercolumn.cpp
../../src/eepp/ui/uitabwidget.cpp
../../src/eepp/ui/uitextedit.cpp
../../src/eepp/ui/uitextinput.cpp
@@ -849,6 +871,7 @@
../../src/eepp/ui/uithememanager.cpp
../../src/eepp/ui/uitooltip.cpp
../../src/eepp/ui/uitouchdraggablewidget.cpp
../../src/eepp/ui/uitreeview.cpp
../../src/eepp/ui/uiviewpager.cpp
../../src/eepp/ui/uiwidget.cpp
../../src/eepp/ui/uiwidgetcreator.cpp

View File

@@ -48,7 +48,7 @@ Node::~Node() {
if ( NULL != mParentNode )
mParentNode->childRemove( this );
EventDispatcher* eventDispatcher = NULL != mSceneNode ? mSceneNode->getEventDispatcher() : NULL;
EventDispatcher* eventDispatcher = getEventDispatcher();
if ( NULL != eventDispatcher ) {
if ( eventDispatcher->getFocusNode() == this && mSceneNode != this ) {
@@ -489,7 +489,7 @@ void Node::drawChilds() {
while ( NULL != child ) {
if ( child->mVisible ) {
child->internalDraw();
child->nodeDraw();
}
child = child->mPrev;
@@ -499,7 +499,7 @@ void Node::drawChilds() {
while ( NULL != child ) {
if ( child->mVisible ) {
child->internalDraw();
child->nodeDraw();
}
child = child->mNext;
@@ -507,7 +507,7 @@ void Node::drawChilds() {
}
}
void Node::internalDraw() {
void Node::nodeDraw() {
if ( mVisible ) {
if ( mNodeFlags & NODE_FLAG_POSITION_DIRTY )
updateScreenPos();
@@ -912,6 +912,14 @@ void Node::forEachNode( std::function<void( Node* )> func ) {
}
}
void Node::forEachChild( std::function<void( Node* )> func ) {
Node* node = mChild;
while ( node ) {
func( node );
node = node->getNextNode();
}
}
void Node::onSceneChange() {
mSceneNode = findSceneNode();

View File

@@ -231,7 +231,7 @@ std::string Color::toHexString( const bool& prependHashtag ) const {
stream << std::setfill( '0' ) << std::setw( sizeof( Color ) * 2 ) << std::hex << getValue();
std::string str = stream.str();
if ( this->a == 255 )
return str.substr( 0, 6 );
return str.substr( 0, prependHashtag ? 7 : 6 );
return str;
}

View File

@@ -0,0 +1,46 @@
#include <eepp/ui/abstract/model.hpp>
#include <eepp/ui/abstract/uiabstractview.hpp>
namespace EE { namespace UI { namespace Abstract {
void Model::onModelUpdate( unsigned flags ) {
if ( mOnUpdate )
mOnUpdate();
forEachView( [&]( UIAbstractView* view ) { view->onModelUpdate( flags ); } );
}
void Model::forEachView( std::function<void( UIAbstractView* )> callback ) {
for ( auto view : mViews )
callback( view );
}
void Model::unregisterView( UIAbstractView* view ) {
mViews.erase( view );
}
void Model::registerView( UIAbstractView* view ) {
mViews.insert( view );
}
ModelIndex Model::createIndex( int row, int column, const void* data ) const {
return ModelIndex( *this, row, column, const_cast<void*>( data ) );
}
void Model::setOnUpdate( const std::function<void()>& onUpdate ) {
mOnUpdate = onUpdate;
}
ModelIndex Model::sibling( int row, int column, const ModelIndex& parent ) const {
if ( !parent.isValid() )
return index( row, column, {} );
int rowCount = this->rowCount( parent );
if ( row < 0 || row > rowCount )
return {};
return index( row, column, parent );
}
bool Model::acceptsDrag( const ModelIndex&, const std::string& ) {
return false;
}
}}} // namespace EE::UI::Abstract

View File

@@ -0,0 +1,62 @@
#include <eepp/core.hpp>
#include <eepp/ui/abstract/modelselection.hpp>
#include <eepp/ui/abstract/uiabstractview.hpp>
namespace EE { namespace UI { namespace Abstract {
void ModelSelection::removeMatching( std::function<bool( const ModelIndex& )> filter ) {
std::vector<std::vector<ModelIndex>::iterator> toRemove;
for ( auto it = mIndexes.begin(); it != mIndexes.end(); it++ ) {
if ( filter( *it ) )
toRemove.push_back( it );
}
for ( auto& index : toRemove )
mIndexes.erase( index );
}
void ModelSelection::set( const ModelIndex& index ) {
eeASSERT( index.isValid() );
if ( mIndexes.size() == 1 && contains( index ) )
return;
mIndexes.clear();
mIndexes.push_back( index );
mView->notifySelectionChange();
}
void ModelSelection::add( const ModelIndex& index ) {
eeASSERT( index.isValid() );
auto contains = std::find( mIndexes.begin(), mIndexes.end(), index );
if ( contains == mIndexes.end() )
return;
mIndexes.push_back( index );
mView->notifySelectionChange();
}
void ModelSelection::toggle( const ModelIndex& index ) {
eeASSERT( index.isValid() );
auto contains = std::find( mIndexes.begin(), mIndexes.end(), index );
if ( contains != mIndexes.end() )
mIndexes.erase( contains );
else
mIndexes.push_back( index );
mView->notifySelectionChange();
}
bool ModelSelection::remove( const ModelIndex& index ) {
eeASSERT( index.isValid() );
auto contains = std::find( mIndexes.begin(), mIndexes.end(), index );
if ( contains == mIndexes.end() )
return false;
mIndexes.erase( contains );
mView->notifySelectionChange();
return true;
}
void ModelSelection::clear() {
if ( mIndexes.empty() )
return;
mIndexes.clear();
mView->notifySelectionChange();
}
}}} // namespace EE::UI::Abstract

View File

@@ -0,0 +1,165 @@
#include <eepp/ui/abstract/uiabstracttableview.hpp>
#include <eepp/ui/uilinearlayout.hpp>
#include <eepp/ui/uipushbutton.hpp>
#include <eepp/ui/uiscenenode.hpp>
namespace EE { namespace UI { namespace Abstract {
UIAbstractTableView::UIAbstractTableView( const std::string& tag ) :
UIAbstractView( tag ), mDragBorderDistance( PixelDensity::dpToPx( 4 ) ) {
mHeader = UILinearLayout::NewWithTag( "table::header", UIOrientation::Horizontal );
mHeader->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
mHeader->setParent( this );
mHeader->setVisible( true );
mHeader->setEnabled( true );
}
UIAbstractTableView::~UIAbstractTableView() {}
Uint32 UIAbstractTableView::getType() const {
return UI_TYPE_ABSTRACTTABLEVIEW;
}
bool UIAbstractTableView::isType( const Uint32& type ) const {
return UIAbstractTableView::getType() == type ? true : UIAbstractView::isType( type );
}
void UIAbstractTableView::selectAll() {
getSelection().clear();
for ( size_t itemIndex = 0; itemIndex < getItemCount(); ++itemIndex ) {
auto index = getModel()->index( itemIndex );
getSelection().add( index );
}
}
size_t UIAbstractTableView::getItemCount() const {
if ( !getModel() )
return 0;
return getModel()->rowCount();
}
void UIAbstractTableView::onModelUpdate( unsigned flags ) {
UIAbstractView::onModelUpdate( flags );
createOrUpdateColumns();
}
void UIAbstractTableView::createOrUpdateColumns() {
Model* model = getModel();
if ( !model )
return;
size_t count = model->columnCount();
Float totalWidth = 0;
for ( size_t i = 0; i < count; i++ ) {
ColumnData& col = columnData( i );
if ( !col.widget ) {
col.widget = eeNew( UITableHeaderColumn, ( this, i ) );
col.widget->setParent( mHeader );
col.widget->setEnabled( true );
col.widget->setVisible( true );
}
col.visible = !isColumnHidden( i );
col.widget->setVisible( col.visible );
if ( !col.visible )
continue;
col.widget->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent );
col.widget->setText( model->columnName( i ) );
col.widget->reloadStyle( true, true, true );
col.width = eeceil( eemax( col.width, col.widget->getPixelsSize().getWidth() ) );
col.widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
col.widget->setPixelsSize( col.width, getHeaderHeight() );
totalWidth += col.width;
}
if ( count < mColumn.size() ) {
for ( size_t i = count; i < mColumn.size(); i++ ) {
ColumnData& col = columnData( i );
col.width = 0;
col.visible = false;
if ( col.widget ) {
col.widget->close();
col.widget = nullptr;
}
}
}
mHeader->setPixelsSize( totalWidth, getHeaderHeight() );
mHeader->updateLayout();
}
Float UIAbstractTableView::getHeaderHeight() const {
return areHeadersVisible() ? eeceil( columnData( 0 ).widget
? columnData( 0 ).widget->getPixelsSize().getHeight()
: 16 )
: 0;
}
Sizef UIAbstractTableView::getContentSize() const {
size_t count = getModel()->columnCount();
Sizef size;
for ( size_t i = 0; i < count; i++ )
if ( !isColumnHidden( i ) )
size.x += columnData( i ).width;
size.y = getHeaderHeight();
size.y += getItemCount() * getRowHeight();
return size;
}
bool UIAbstractTableView::areHeadersVisible() const {
return mHeader->isVisible();
}
void UIAbstractTableView::setHeadersVisible( bool visible ) {
mHeader->setVisible( visible );
}
void UIAbstractTableView::onSizeChange() {
UIAbstractView::onSizeChange();
createOrUpdateColumns();
}
void UIAbstractTableView::onColumnSizeChange( const size_t& ) {}
void UIAbstractTableView::onColumnResizeToContent( const size_t& ) {}
void UIAbstractTableView::updateHeaderSize() {
size_t count = getModel()->columnCount();
Float totalWidth = 0;
for ( size_t i = 0; i < count; i++ ) {
ColumnData& col = columnData( i );
totalWidth += col.width;
}
mHeader->setPixelsSize( totalWidth, getHeaderHeight() );
}
const Float& UIAbstractTableView::getDragBorderDistance() const {
return mDragBorderDistance;
}
void UIAbstractTableView::setDragBorderDistance( const Float& dragBorderDistance ) {
mDragBorderDistance = dragBorderDistance;
}
Vector2f UIAbstractTableView::getColumnPosition( const size_t& index ) {
return columnData( index ).widget->getPixelsPosition();
}
UIAbstractTableView::ColumnData& UIAbstractTableView::columnData( const size_t& column ) const {
if ( column >= mColumn.size() )
mColumn.resize( column + 1 );
return mColumn[column];
}
bool UIAbstractTableView::isColumnHidden( const size_t& column ) const {
return !columnData( column ).visible;
}
void UIAbstractTableView::setColumnHidden( const size_t& column, bool hidden ) {
if ( columnData( column ).visible != !hidden ) {
columnData( column ).visible = !hidden;
createOrUpdateColumns();
}
}
}}} // namespace EE::UI::Abstract

View File

@@ -0,0 +1,65 @@
#include <eepp/ui/abstract/uiabstractview.hpp>
namespace EE { namespace UI { namespace Abstract {
UIAbstractView::UIAbstractView( const std::string& tag ) :
UIScrollableWidget( tag ), mSelection( this ) {}
UIAbstractView::~UIAbstractView() {}
std::function<void( const ModelIndex& )> UIAbstractView::getOnSelection() const {
return mOnSelection;
}
void UIAbstractView::setOnSelection( const std::function<void( const ModelIndex& )>& onSelection ) {
mOnSelection = onSelection;
}
std::function<void()> UIAbstractView::getOnSelectionChange() const {
return mOnSelectionChange;
}
void UIAbstractView::setOnSelectionChange( const std::function<void()>& onSelectionChange ) {
mOnSelectionChange = onSelectionChange;
}
Uint32 UIAbstractView::getType() const {
return UI_TYPE_ABSTRACTVIEW;
}
bool UIAbstractView::isType( const Uint32& type ) const {
return UIAbstractView::getType() == type ? true : UIScrollableWidget::isType( type );
}
void UIAbstractView::setModel( std::shared_ptr<Model> model ) {
if ( model.get() == mModel.get() )
return;
if ( mModel )
mModel->unregisterView( this );
mModel = model;
if ( mModel )
mModel->registerView( this );
onModelUpdate( Model::InvalidateAllIndexes );
}
void UIAbstractView::onModelUpdate( unsigned flags ) {
if ( !getModel() || ( flags & Model::InvalidateAllIndexes ) ) {
getSelection().clear();
} else {
getSelection().removeMatching(
[this]( auto& index ) { return !getModel()->isValid( index ); } );
}
}
void UIAbstractView::onModelSelectionChange() {
if ( getModel() && mOnSelection && getSelection().first().isValid() )
mOnSelection( getSelection().first() );
}
void UIAbstractView::notifySelectionChange() {
onModelSelectionChange();
if ( mOnSelectionChange )
mOnSelectionChange();
}
}}} // namespace EE::UI::Abstract

View File

@@ -130,7 +130,7 @@ void StyleSheetPropertyAnimation::tweenProperty( UIWidget* widget, const Float&
StyleSheetPropertyAnimation* StyleSheetPropertyAnimation::fromAnimationKeyframes(
const AnimationDefinition& animation, const KeyframesDefinition& keyframes,
const PropertyDefinition* propertyDef, UIWidget* widget, const Uint32& propertyIndex,
const AnimationOrigin& animationOrigin ) {
const AnimationOrigin& ) {
std::vector<std::string> properties;
std::vector<Float> times;
@@ -314,7 +314,7 @@ void StyleSheetPropertyAnimation::onStart() {
}
}
void StyleSheetPropertyAnimation::onUpdate( const Time& time ) {
void StyleSheetPropertyAnimation::onUpdate( const Time& ) {
if ( NULL != mNode && mNode->isWidget() ) {
UIWidget* widget = mNode->asType<UIWidget>();

View File

@@ -45,6 +45,9 @@ bool UIImage::isType( const Uint32& type ) const {
}
UIImage* UIImage::setDrawable( Drawable* drawable, bool ownIt ) {
if ( drawable == mDrawable )
return this;
safeDeleteDrawable();
mDrawable = drawable;

View File

@@ -4,6 +4,11 @@
namespace EE { namespace UI {
UILinearLayout* UILinearLayout::NewWithTag( const std::string& tag,
const UIOrientation& orientation ) {
return eeNew( UILinearLayout, ( tag, orientation ) );
}
UILinearLayout* UILinearLayout::New() {
return eeNew( UILinearLayout, () );
}

View File

@@ -72,7 +72,7 @@ Uint32 UIMenuItem::onMouseClick( const Vector2i&, const Uint32& flags ) {
return 1;
}
UIWidget* UIMenuItem::getExtraInnerWidget() {
UIWidget* UIMenuItem::getExtraInnerWidget() const {
return mShortcutView;
}

View File

@@ -60,7 +60,7 @@ void UIMenuSubMenu::onAlphaChange() {
mArrow->setAlpha( mAlpha );
}
UIWidget* UIMenuSubMenu::getExtraInnerWidget() {
UIWidget* UIMenuSubMenu::getExtraInnerWidget() const {
return mArrow;
}

View File

@@ -726,7 +726,7 @@ void UINode::drawBorder() {
}
}
void UINode::internalDraw() {
void UINode::nodeDraw() {
if ( mVisible ) {
if ( mNodeFlags & NODE_FLAG_POSITION_DIRTY )
updateScreenPos();
@@ -736,21 +736,21 @@ void UINode::internalDraw() {
matrixSet();
clipStart();
if ( mWorldBounds.intersect( mSceneNode->getWorldBounds() ) ) {
clipStart();
draw();
drawChilds();
if ( 0.f != mAlpha )
drawForeground();
clipEnd();
} else if ( !isClipped() ) {
drawChilds();
}
clipEnd();
drawBorder();
drawHighlightFocus();

View File

@@ -11,6 +11,10 @@ UIPushButton* UIPushButton::New() {
return eeNew( UIPushButton, () );
}
UIPushButton* UIPushButton::NewWithTag( const std::string& tag ) {
return eeNew( UIPushButton, ( tag ) );
}
UIPushButton::UIPushButton( const std::string& tag ) :
UIWidget( tag ), mIcon( NULL ), mTextBox( NULL ) {
mFlags |= ( UI_AUTO_SIZE | UI_VALIGN_CENTER | UI_HALIGN_CENTER );
@@ -221,6 +225,8 @@ void UIPushButton::onSizeChange() {
if ( NULL != eWidget && eWidget->isVisible() ) {
eWidget->setPixelsPosition( ePos );
}
UIWidget::onSizeChange();
}
void UIPushButton::setTheme( UITheme* Theme ) {
@@ -237,8 +243,10 @@ void UIPushButton::onThemeLoaded() {
}
UIPushButton* UIPushButton::setIcon( Drawable* Icon ) {
mIcon->setDrawable( Icon );
onSizeChange();
if ( mIcon->getDrawable() != Icon ) {
mIcon->setDrawable( Icon );
onSizeChange();
}
return this;
}
@@ -318,10 +326,41 @@ const Sizei& UIPushButton::getIconMinimumSize() const {
return mIconMinSize;
}
UIWidget* UIPushButton::getExtraInnerWidget() {
UIWidget* UIPushButton::getExtraInnerWidget() const {
return NULL;
}
void UIPushButton::setTextAlign( const Uint32& align ) {
mFlags &= ~( UI_HALIGN_CENTER | UI_HALIGN_RIGHT );
mFlags |= align;
onAlignChange();
}
Sizef UIPushButton::getContentSize() const {
Float sH = getSkinSize().getHeight();
Float sHS = getSkinSize( UIState::StateFlagSelected ).getHeight();
Float tH = mTextBox->getPixelsSize().getHeight();
Float eH =
NULL != getExtraInnerWidget() ? getExtraInnerWidget()->getPixelsSize().getHeight() : 0;
Float minHeight = eeceil( eemax( eemax( PixelDensity::dpToPx( eemax( sH, sHS ) ), tH ), eH ) );
Int32 txtW = mTextBox->getPixelsSize().getWidth();
Int32 iconSize = mIcon->getPixelsSize().getWidth() > 0
? mIcon->getPixelsSize().getWidth() +
PixelDensity::dpToPxI( mIcon->getLayoutMargin().Left +
mIcon->getLayoutMargin().Right )
: 0;
UIWidget* eWidget = getExtraInnerWidget();
Int32 eWidgetSize = NULL != eWidget ? PixelDensity::dpToPxI( eWidget->getSize().getWidth() +
eWidget->getLayoutMargin().Left +
eWidget->getLayoutMargin().Right )
: 0;
Int32 minWidth = txtW + iconSize + eWidgetSize + mRealPadding.Left + mRealPadding.Right +
( NULL != getSkin() ? PixelDensity::dpToPxI( getSkin()->getBorderSize().Left +
getSkin()->getBorderSize().Right )
: 0 );
return Sizef( minWidth, minHeight );
}
std::string UIPushButton::getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex ) {
if ( NULL == propertyDef )
@@ -336,6 +375,11 @@ std::string UIPushButton::getPropertyString( const PropertyDefinition* propertyD
case PropertyId::MinIconSize:
return String::format( "%ddp", mIconMinSize.getWidth() ) + " " +
String::format( "%ddp", mIconMinSize.getHeight() );
case PropertyId::TextAlign:
return Font::getHorizontalAlign( getFlags() ) == UI_HALIGN_CENTER
? "center"
: ( Font::getHorizontalAlign( getFlags() ) == UI_HALIGN_RIGHT ? "right"
: "left" );
case PropertyId::Color:
case PropertyId::ShadowColor:
case PropertyId::SelectionColor:
@@ -347,7 +391,6 @@ std::string UIPushButton::getPropertyString( const PropertyDefinition* propertyD
case PropertyId::TextStrokeWidth:
case PropertyId::TextStrokeColor:
case PropertyId::TextSelection:
case PropertyId::TextAlign:
return mTextBox->getPropertyString( propertyDef, propertyIndex );
default:
return UIWidget::getPropertyString( propertyDef, propertyIndex );
@@ -381,6 +424,16 @@ bool UIPushButton::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::MinIconSize:
setIconMinimumSize( attribute.asSizei() );
break;
case PropertyId::TextAlign: {
std::string align = String::toLower( attribute.value() );
if ( align == "center" )
setTextAlign( UI_HALIGN_CENTER );
else if ( align == "left" )
setTextAlign( UI_HALIGN_LEFT );
else if ( align == "right" )
setTextAlign( UI_HALIGN_RIGHT );
break;
}
case PropertyId::Color:
case PropertyId::ShadowColor:
case PropertyId::SelectionColor:
@@ -392,7 +445,6 @@ bool UIPushButton::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::TextStrokeWidth:
case PropertyId::TextStrokeColor:
case PropertyId::TextSelection:
case PropertyId::TextAlign:
attributeSet = mTextBox->applyProperty( attribute );
break;
default:

View File

@@ -0,0 +1,303 @@
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uiscrollablewidget.hpp>
#include <eepp/ui/uiscrollbar.hpp>
namespace EE { namespace UI {
UIScrollableWidget::UIScrollableWidget( const std::string& tag ) :
UIWidget( tag ),
mViewType( Exclusive ),
mVScrollMode( ScrollBarMode::Auto ),
mHScrollMode( ScrollBarMode::Auto ),
mVScroll( UIScrollBar::NewVertical() ),
mHScroll( UIScrollBar::NewHorizontal() ),
mSizeChangeCb( 0 ),
mPosChangeCb( 0 ) {
mFlags |= UI_OWNS_CHILDS_POSITION;
mVScroll->setParent( this );
mHScroll->setParent( this );
mVScroll->addEventListener( Event::OnValueChange,
cb::Make1( this, &UIScrollableWidget::onValueChangeCb ) );
mHScroll->addEventListener( Event::OnValueChange,
cb::Make1( this, &UIScrollableWidget::onValueChangeCb ) );
applyDefaultTheme();
}
Uint32 UIScrollableWidget::getType() const {
return UI_TYPE_SCROLLABLEWIDGET;
}
bool UIScrollableWidget::isType( const Uint32& type ) const {
return UIWidget::getType() == type ? true : UIWidget::isType( type );
}
void UIScrollableWidget::onSizeChange() {
onContentSizeChange();
UIWidget::onSizeChange();
}
void UIScrollableWidget::onAlphaChange() {
UIWidget::onAlphaChange();
mVScroll->setAlpha( mAlpha );
mHScroll->setAlpha( mAlpha );
}
void UIScrollableWidget::onPaddingChange() {
onContentSizeChange();
UIWidget::onPaddingChange();
}
void UIScrollableWidget::setVerticalScrollMode( const ScrollBarMode& Mode ) {
if ( Mode != mVScrollMode ) {
mVScrollMode = Mode;
onContentSizeChange();
}
}
const ScrollBarMode& UIScrollableWidget::getVerticalScrollMode() {
return mVScrollMode;
}
void UIScrollableWidget::setHorizontalScrollMode( const ScrollBarMode& Mode ) {
if ( Mode != mHScrollMode ) {
mHScrollMode = Mode;
onContentSizeChange();
}
}
const ScrollBarMode& UIScrollableWidget::getHorizontalScrollMode() {
return mHScrollMode;
}
const UIScrollableWidget::ScrollViewType& UIScrollableWidget::getViewType() const {
return mViewType;
}
void UIScrollableWidget::setViewType( const ScrollViewType& viewType ) {
if ( viewType != mViewType ) {
mViewType = viewType;
onContentSizeChange();
}
}
UIScrollBar* UIScrollableWidget::getVerticalScrollBar() const {
return mVScroll;
}
UIScrollBar* UIScrollableWidget::getHorizontalScrollBar() const {
return mHScroll;
}
void UIScrollableWidget::onContentSizeChange() {
Sizef contentSize( getContentSize() );
if ( ScrollBarMode::AlwaysOn == mHScrollMode ) {
mHScroll->setVisible( true );
mHScroll->setEnabled( true );
} else if ( ScrollBarMode::AlwaysOff == mHScrollMode ) {
mHScroll->setVisible( false );
mHScroll->setEnabled( false );
} else {
bool visible = contentSize.getWidth() >
getPixelsSize().getWidth() - getPixelsPadding().Left -
getPixelsPadding().Right - mVScroll->getPixelsSize().getWidth();
mHScroll->setVisible( visible );
mHScroll->setEnabled( visible );
}
if ( ScrollBarMode::AlwaysOn == mVScrollMode ) {
mVScroll->setVisible( true );
mVScroll->setEnabled( true );
} else if ( ScrollBarMode::AlwaysOff == mVScrollMode ) {
mVScroll->setVisible( false );
mVScroll->setEnabled( false );
} else {
bool visible = contentSize.getHeight() >
getPixelsSize().getHeight() - getPixelsPadding().Top -
getPixelsPadding().Bottom - mHScroll->getPixelsSize().getHeight();
mVScroll->setVisible( visible );
mVScroll->setEnabled( visible );
}
Sizef size = getPixelsSize() - mRealPadding;
if ( Exclusive == mViewType ) {
if ( mVScroll->isVisible() )
size.x -= mVScroll->getPixelsSize().getWidth();
if ( mHScroll->isVisible() )
size.y -= mHScroll->getPixelsSize().getHeight();
}
mVScroll->setPixelsPosition( getPixelsSize().getWidth() - mVScroll->getPixelsSize().getWidth() -
mRealPadding.Right,
mRealPadding.Top );
mHScroll->setPixelsPosition( mRealPadding.Left, getPixelsSize().getHeight() -
mHScroll->getPixelsSize().getHeight() -
mRealPadding.Bottom );
mVScroll->setPixelsSize( mVScroll->getPixelsSize().getWidth(),
getPixelsSize().getHeight() - mRealPadding.Top - mRealPadding.Bottom );
mHScroll->setPixelsSize(
getPixelsSize().getWidth() - mRealPadding.Left - mRealPadding.Right -
( mVScroll->isVisible() ? mVScroll->getPixelsSize().getWidth() : 0 ),
mHScroll->getPixelsSize().getHeight() );
if ( size.getWidth() > 0 )
mHScroll->setPageStep( size.getWidth() / contentSize.getWidth() );
if ( size.getHeight() > 0 )
mVScroll->setPageStep( size.getHeight() / contentSize.getHeight() );
updateScroll();
}
Sizef UIScrollableWidget::getScrollableArea() const {
Sizef contentSize( getContentSize() );
Sizef size = getVisibleArea();
return contentSize - size;
}
Sizef UIScrollableWidget::getVisibleArea() const {
Sizef size = getPixelsSize() - mRealPadding;
if ( mVScroll->isVisible() )
size.x -= mVScroll->getPixelsSize().getWidth();
if ( mHScroll->isVisible() )
size.y -= mHScroll->getPixelsSize().getHeight();
return size;
}
void UIScrollableWidget::updateScroll() {
Sizef totalScroll = getScrollableArea();
Vector2f initScroll( mScrollOffset );
mScrollOffset = Vector2f::Zero;
if ( mVScroll->isVisible() && totalScroll.y > 0 )
mScrollOffset.y = totalScroll.y * mVScroll->getValue();
if ( mHScroll->isVisible() && totalScroll.x > 0 )
mScrollOffset.x = totalScroll.x * mHScroll->getValue();
if ( initScroll != mScrollOffset )
onScrollChange();
}
void UIScrollableWidget::onScrollChange() {}
void UIScrollableWidget::onValueChangeCb( const Event* ) {
updateScroll();
}
std::string UIScrollableWidget::getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex ) {
if ( NULL == propertyDef )
return "";
switch ( propertyDef->getPropertyId() ) {
case PropertyId::VScrollMode:
return getVerticalScrollMode() == ScrollBarMode::Auto
? "auto"
: ( getVerticalScrollMode() == ScrollBarMode::AlwaysOn ? "on" : "off" );
case PropertyId::HScrollMode:
return getHorizontalScrollMode() == ScrollBarMode::Auto
? "auto"
: ( getHorizontalScrollMode() == ScrollBarMode::AlwaysOn ? "on" : "off" );
case PropertyId::ScrollBarStyle:
return mVScroll->getScrollBarType() == UIScrollBar::NoButtons ? "no-buttons"
: "two-buttons";
case PropertyId::ScrollBarMode:
return getViewType() == Inclusive ? "inclusive" : "exclusive";
default:
return UIWidget::getPropertyString( propertyDef, propertyIndex );
}
}
void UIScrollableWidget::scrollToTop() {
mVScroll->setValue( 0 );
}
void UIScrollableWidget::scrollToBottom() {
mVScroll->setValue( 1 );
}
bool UIScrollableWidget::applyProperty( const StyleSheetProperty& attribute ) {
if ( !checkPropertyDefinition( attribute ) )
return false;
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
case PropertyId::ScrollBarMode: {
std::string val( attribute.asString() );
String::toLowerInPlace( val );
if ( "inclusive" == val || "inside" == val )
setViewType( Inclusive );
else if ( "exclusive" == val || "outside" == val )
setViewType( Exclusive );
break;
}
case PropertyId::VScrollMode: {
std::string val( attribute.asString() );
String::toLowerInPlace( val );
if ( "on" == val )
setVerticalScrollMode( ScrollBarMode::AlwaysOn );
else if ( "off" == val )
setVerticalScrollMode( ScrollBarMode::AlwaysOn );
else if ( "auto" == val )
setVerticalScrollMode( ScrollBarMode::Auto );
break;
}
case PropertyId::HScrollMode: {
std::string val( attribute.asString() );
String::toLowerInPlace( val );
if ( "on" == val )
setHorizontalScrollMode( ScrollBarMode::AlwaysOn );
else if ( "off" == val )
setHorizontalScrollMode( ScrollBarMode::AlwaysOn );
else if ( "auto" == val )
setHorizontalScrollMode( ScrollBarMode::Auto );
break;
}
case PropertyId::ScrollBarStyle: {
std::string val( attribute.asString() );
String::toLowerInPlace( val );
if ( "no-buttons" == val || "nobuttons" == val ) {
mVScroll->setScrollBarStyle( UIScrollBar::NoButtons );
mHScroll->setScrollBarStyle( UIScrollBar::NoButtons );
} else if ( "two-buttons" == val || "twobuttons" == val ) {
mVScroll->setScrollBarStyle( UIScrollBar::TwoButtons );
mHScroll->setScrollBarStyle( UIScrollBar::NoButtons );
}
break;
}
default:
return UIWidget::applyProperty( attribute );
}
return true;
}
Uint32 UIScrollableWidget::onMessage( const NodeMessage* Msg ) {
switch ( Msg->getMsg() ) {
case NodeMessage::MouseUp: {
if ( mVScroll->isEnabled() ) {
if ( Msg->getFlags() & EE_BUTTON_WUMASK ) {
mVScroll->setValue( mVScroll->getValue() - mVScroll->getClickStep() );
return 1;
} else if ( Msg->getFlags() & EE_BUTTON_WDMASK ) {
mVScroll->setValue( mVScroll->getValue() + mVScroll->getClickStep() );
return 1;
}
}
}
}
return UIWidget::onMessage( Msg );
}
}} // namespace EE::UI

View File

@@ -125,7 +125,7 @@ void UITab::onSizeChange() {
UISelectButton::onSizeChange();
}
UIWidget* UITab::getExtraInnerWidget() {
UIWidget* UITab::getExtraInnerWidget() const {
return mCloseButton;
}

View File

@@ -0,0 +1,90 @@
#include <eepp/ui/abstract/uiabstracttableview.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uitableheadercolumn.hpp>
namespace EE { namespace UI {
UITableHeaderColumn::UITableHeaderColumn( UIAbstractTableView* view, const size_t& colIndex ) :
UIPushButton( "table::header::column" ), mView( view ), mColIndex( colIndex ) {
setDragEnabled( true );
}
Uint32 UITableHeaderColumn::onCalculateDrag( const Vector2f& position, const Uint32& flags ) {
if ( isDragEnabled() && isDragging() && NULL != getEventDispatcher() ) {
EventDispatcher* eventDispatcher = getEventDispatcher();
if ( !( flags /*press trigger*/ & mDragButton ) ) {
setDragging( false );
eventDispatcher->setNodeDragging( NULL );
return 1;
}
Vector2f pos( eefloor( position.x ), eefloor( position.y ) );
if ( mDragPoint != pos && std::abs( mDragPoint.x - pos.x ) > 1.f ) {
Sizef dragDiff( ( Float )( mDragPoint.x - pos.x ), 0 );
if ( onDrag( pos, flags, dragDiff ) ) {
mDragPoint = pos;
eventDispatcher->setNodeDragging( this );
}
}
}
return 1;
}
Uint32 UITableHeaderColumn::onMouseDown( const Vector2i& position, const Uint32& flags ) {
Vector2f localPos( convertToNodeSpace( position.asFloat() ) );
if ( NULL != getEventDispatcher() && !getEventDispatcher()->isNodeDragging() &&
!( getEventDispatcher()->getLastPressTrigger() & mDragButton ) &&
( flags & mDragButton ) && isDragEnabled() && !isDragging() &&
localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() ) {
startDragging( position.asFloat() );
}
pushState( UIState::StatePressed );
return Node::onMouseDown( position, flags );
}
Uint32 UITableHeaderColumn::onDrag( const Vector2f& position, const Uint32&,
const Sizef& dragDiff ) {
Vector2f localPos( convertToNodeSpace( position ) );
if ( isDragging() || localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() ) {
setPixelsSize( mSize.x - dragDiff.x, mSize.getHeight() );
if ( mSize.getWidth() != mView->columnData( mColIndex ).width ) {
mView->columnData( mColIndex ).width = mSize.getWidth();
mView->updateHeaderSize();
mView->onColumnSizeChange( mColIndex );
}
return 1;
}
return 0;
}
Uint32 UITableHeaderColumn::onMouseLeave( const Vector2i& position, const Uint32& flags ) {
if ( !isDragging() )
getUISceneNode()->setCursor( Cursor::Arrow );
return UIPushButton::onMouseLeave( position, flags );
}
Uint32 UITableHeaderColumn::onMouseMove( const Vector2i& position, const Uint32& flags ) {
Vector2f localPos( convertToNodeSpace( position.asFloat() ) );
if ( isDragging() || localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() ) {
getUISceneNode()->setCursor( Cursor::SizeWE );
} else if ( !isDragging() ) {
getUISceneNode()->setCursor( Cursor::Arrow );
}
return UIPushButton::onMouseMove( position, flags );
}
Uint32 UITableHeaderColumn::onMouseDoubleClick( const Vector2i& position, const Uint32& flags ) {
Vector2f localPos( convertToNodeSpace( position.asFloat() ) );
if ( localPos.x >= mSize.getWidth() - mView->getDragBorderDistance() )
mView->onColumnResizeToContent( mColIndex );
return UIPushButton::onMouseDoubleClick( position, flags );
}
Uint32 UITableHeaderColumn::onDragStop( const Vector2i& pos, const Uint32& flags ) {
getUISceneNode()->setCursor( Cursor::Arrow );
mView->columnData( mColIndex ).width = mSize.getWidth();
mView->updateHeaderSize();
mView->onColumnSizeChange( mColIndex );
return UIPushButton::onDragStop( pos, flags );
}
}} // namespace EE::UI

View File

@@ -649,11 +649,11 @@ bool UITextView::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::TextAlign: {
std::string align = String::toLower( attribute.value() );
if ( align == "center" )
setFlags( UI_HALIGN_CENTER );
setTextAlign( UI_HALIGN_CENTER );
else if ( align == "left" )
setFlags( UI_HALIGN_LEFT );
setTextAlign( UI_HALIGN_LEFT );
else if ( align == "right" )
setFlags( UI_HALIGN_RIGHT );
setTextAlign( UI_HALIGN_RIGHT );
break;
}
default:
@@ -703,4 +703,10 @@ std::string UITextView::getPropertyString( const PropertyDefinition* propertyDef
}
}
void UITextView::setTextAlign( const Uint32& align ) {
mFlags &= ~(UI_HALIGN_CENTER|UI_HALIGN_RIGHT);
mFlags |= align;
onAlignChange();
}
}} // namespace EE::UI

500
src/eepp/ui/uitreeview.cpp Normal file
View File

@@ -0,0 +1,500 @@
#include <eepp/graphics/renderer/renderer.hpp>
#include <eepp/ui/uilinearlayout.hpp>
#include <eepp/ui/uipushbutton.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uitreeview.hpp>
namespace EE { namespace UI {
struct UITreeView::MetadataForIndex {
bool open{false};
};
UITreeView* UITreeView::New() {
return eeNew( UITreeView, () );
}
UITreeView::UITreeView() : UIAbstractTableView( "treeview" ), mIndentWidth( 16 ) {
mExpandIcon = getUISceneNode()->findIcon( "tree-expanded" );
mContractIcon = getUISceneNode()->findIcon( "tree-contracted" );
}
Uint32 UITreeView::getType() const {
return UI_TYPE_TREEVIEW;
}
bool UITreeView::isType( const Uint32& type ) const {
return UITreeView::getType() == type ? true : UIAbstractTableView::isType( type );
}
UITreeView::MetadataForIndex& UITreeView::getIndexMetadata( const ModelIndex& index ) const {
eeASSERT( index.isValid() );
auto it = mViewMetadata.find( index.data() );
if ( it != mViewMetadata.end() )
return *it->second;
auto newMetadata = std::make_unique<MetadataForIndex>();
auto* ref = newMetadata.get();
mViewMetadata.insert( {index.data(), std::move( newMetadata )} );
return *ref;
}
template <typename Callback> void UITreeView::traverseTree( Callback callback ) const {
eeASSERT( getModel() );
auto& model = *getModel();
int indentLevel = 1;
Float yOffset = getHeaderHeight();
int rowIndex = -1;
std::function<IterationDecision( const ModelIndex& )> traverseIndex =
[&]( const ModelIndex& index ) {
if ( index.isValid() ) {
auto& metadata = getIndexMetadata( index );
rowIndex++;
IterationDecision decision = callback( rowIndex, index, indentLevel, yOffset );
if ( decision == IterationDecision::Break || decision == IterationDecision::Stop )
return decision;
yOffset += getRowHeight();
if ( !metadata.open ) {
return IterationDecision::Continue;
}
}
if ( indentLevel > 0 && !index.isValid() )
return IterationDecision::Continue;
++indentLevel;
int rowCount = model.rowCount( index );
for ( int i = 0; i < rowCount; ++i ) {
IterationDecision decision =
traverseIndex( model.index( i, model.treeColumn(), index ) );
if ( decision == IterationDecision::Break || decision == IterationDecision::Stop )
return decision;
}
--indentLevel;
return IterationDecision::Continue;
};
int rootCount = model.rowCount();
for ( int rootIndex = 0; rootIndex < rootCount; ++rootIndex ) {
IterationDecision decision =
traverseIndex( model.index( rootIndex, model.treeColumn(), ModelIndex() ) );
if ( decision == IterationDecision::Break || decision == IterationDecision::Stop )
break;
}
}
void UITreeView::createOrUpdateColumns() {
if ( !getModel() )
return;
UIAbstractTableView::createOrUpdateColumns();
updateContentSize();
}
size_t UITreeView::getItemCount() const {
size_t count = 0;
traverseTree( [&]( const int&, const ModelIndex&, const size_t&, const Float& ) {
count++;
return IterationDecision::Continue;
} );
return count;
}
void UITreeView::onColumnSizeChange( const size_t& ) {
updateContentSize();
}
class UITableRow : public UIWidget {
public:
UITableRow( const std::string& tag ) : UIWidget( tag ) {}
ModelIndex getCurIndex() const { return mCurIndex; }
void setCurIndex( const ModelIndex& curIndex ) { mCurIndex = curIndex; }
protected:
virtual Uint32 onMessage( const NodeMessage* msg ) {
if ( msg->getMsg() == NodeMessage::MouseDown && ( msg->getFlags() & EE_BUTTON_LMASK ) &&
( !getEventDispatcher()->getMouseDownNode() ||
getEventDispatcher()->getMouseDownNode() == this ||
isParentOf( getEventDispatcher()->getMouseDownNode() ) ) &&
getEventDispatcher()->getNodeDragging() == nullptr ) {
sendMouseEvent( Event::MouseDown, getEventDispatcher()->getMousePos(),
msg->getFlags() );
}
return 0;
}
ModelIndex mCurIndex;
};
UIWidget* UITreeView::updateRow( const int& rowIndex, const ModelIndex& index,
const Float& yOffset ) {
if ( rowIndex >= (int)mRows.size() )
mRows.resize( rowIndex + 1, nullptr );
UITableRow* rowWidget = nullptr;
if ( mRows[rowIndex] == nullptr ) {
rowWidget = eeNew( UITableRow, ( "table::row" ) );
rowWidget->clipEnable();
rowWidget->setParent( this );
rowWidget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
rowWidget->reloadStyle( true, true, true );
rowWidget->addEventListener( Event::MouseDown, [&]( const Event* event ) {
if ( ( !getEventDispatcher()->getMouseDownNode() ||
getEventDispatcher()->getMouseDownNode() == this ||
isParentOf( getEventDispatcher()->getMouseDownNode() ) ) &&
getEventDispatcher()->getNodeDragging() == nullptr ) {
getSelection().set( event->getNode()->asType<UITableRow>()->getCurIndex() );
}
} );
mRows[rowIndex] = rowWidget;
} else {
rowWidget = mRows[rowIndex];
}
rowWidget->setCurIndex( index );
rowWidget->setPixelsSize( getContentSize().getWidth(), getRowHeight() );
rowWidget->setPixelsPosition( {-mScrollOffset.x, yOffset - mScrollOffset.y} );
if ( getSelection().first() == index ) {
rowWidget->pushState( UIState::StateSelected );
} else {
rowWidget->popState( UIState::StateSelected );
}
return rowWidget;
}
UIWidget* UITreeView::createCell( UIWidget* rowWidget, const ModelIndex&, const size_t& col ) {
UIPushButton* widget = UIPushButton::NewWithTag( "table::cell" );
widget->setParent( rowWidget );
widget->unsetFlags( UI_AUTO_SIZE );
widget->clipEnable();
widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
widget->asType<UIPushButton>()->setTextAlign( UI_HALIGN_LEFT );
if ( col == getModel()->treeColumn() ) {
widget->addEventListener( Event::MouseDoubleClick, [&]( const Event* event ) {
auto mouseEvent = static_cast<const MouseEvent*>( event );
auto idx = mouseEvent->getNode()->getParent()->asType<UITableRow>()->getCurIndex();
if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) {
if ( getModel()->rowCount( idx ) ) {
auto& data = getIndexMetadata( idx );
data.open = !data.open;
createOrUpdateColumns();
} else {
onOpenModelIndex( idx );
}
}
} );
widget->addEventListener( Event::MouseClick, [&]( const Event* event ) {
auto mouseEvent = static_cast<const MouseEvent*>( event );
UIImage* icon = mouseEvent->getNode()->asType<UIPushButton>()->getIcon();
if ( icon ) {
Vector2f pos( icon->convertToNodeSpace( mouseEvent->getPosition().asFloat() ) );
if ( pos >= Vector2f::Zero && pos <= icon->getPixelsSize() ) {
auto idx =
mouseEvent->getNode()->getParent()->asType<UITableRow>()->getCurIndex();
auto& data = getIndexMetadata( idx );
if ( getModel()->rowCount( idx ) ) {
data.open = !data.open;
createOrUpdateColumns();
}
}
}
} );
}
return widget;
}
void UITreeView::onScrollChange() {
mHeader->setPixelsPosition( -mScrollOffset.x, 0 );
invalidateDraw();
}
void UITreeView::updateContentSize() {
Sizef oldSize( mContentSize );
mContentSize = UIAbstractTableView::getContentSize();
if ( oldSize != mContentSize )
onContentSizeChange();
}
UIWidget* UITreeView::updateCell( const int& rowIndex, const ModelIndex& index, const size_t& col,
const size_t& indentLevel, const Float& yOffset ) {
if ( rowIndex >= (int)mWidgets.size() )
mWidgets.resize( rowIndex + 1 );
auto* widget = mWidgets[rowIndex][col];
if ( !widget ) {
UIWidget* rowWidget = updateRow( rowIndex, index, yOffset );
widget = createCell( rowWidget, index, col );
mWidgets[rowIndex][col] = widget;
}
widget->setPixelsSize( columnData( col ).width, getRowHeight() );
widget->setPixelsPosition( {getColumnPosition( col ).x, 0} );
if ( col == getModel()->treeColumn() )
widget->setPaddingLeft( getIndentWidth() * indentLevel );
ModelIndex idx( getModel()->index( index.row(), col, index.parent() ) );
Variant txt( getModel()->data( idx, Model::Role::Display ) );
if ( widget->isType( UI_TYPE_PUSHBUTTON ) ) {
UIPushButton* pushButton = widget->asType<UIPushButton>();
if ( txt.isValid() ) {
if ( txt.is( Variant::Type::String ) )
pushButton->setText( txt.asString() );
else if ( txt.is( Variant::Type::cstr ) )
pushButton->setText( txt.asCStr() );
}
pushButton->setIcon( nullptr );
if ( col == getModel()->treeColumn() )
pushButton->setIcon( getIndexMetadata( index ).open ? mExpandIcon : mContractIcon );
Variant icon( getModel()->data( idx, Model::Role::Icon ) );
if ( icon.is( Variant::Type::Drawable ) )
pushButton->setIcon( icon.asDrawable() );
}
return widget;
}
const Float& UITreeView::getIndentWidth() const {
return mIndentWidth;
}
void UITreeView::setIndentWidth( const Float& indentWidth ) {
if ( mIndentWidth != indentWidth ) {
mIndentWidth = indentWidth;
createOrUpdateColumns();
}
}
Sizef UITreeView::getContentSize() const {
return mContentSize;
}
void UITreeView::drawChilds() {
traverseTree( [&]( const int& rowIndex, const ModelIndex& index, const size_t& indentLevel,
const Float& yOffset ) {
if ( yOffset - mScrollOffset.y > mSize.getHeight() )
return IterationDecision::Stop;
if ( yOffset - mScrollOffset.y + getRowHeight() < 0 )
return IterationDecision::Continue;
for ( size_t colIndex = 0; colIndex < getModel()->columnCount(); colIndex++ )
updateCell( rowIndex, index, colIndex, indentLevel, yOffset );
updateRow( rowIndex, index, yOffset )->nodeDraw();
return IterationDecision::Continue;
} );
if ( mHeader && mHeader->isVisible() )
mHeader->nodeDraw();
if ( mHScroll->isVisible() )
mHScroll->nodeDraw();
if ( mVScroll->isVisible() )
mVScroll->nodeDraw();
}
Node* UITreeView::overFind( const Vector2f& point ) {
Node* pOver = NULL;
if ( mEnabled && mVisible ) {
updateWorldPolygon();
if ( mWorldBounds.contains( point ) && mPoly.pointInside( point ) ) {
writeNodeFlag( NODE_FLAG_MOUSEOVER_ME_OR_CHILD, 1 );
mSceneNode->addMouseOverNode( this );
if ( mHScroll->isVisible() && ( pOver = mHScroll->overFind( point ) ) )
return pOver;
if ( mVScroll->isVisible() && ( pOver = mVScroll->overFind( point ) ) )
return pOver;
if ( mHeader && ( pOver = mHeader->overFind( point ) ) )
return pOver;
traverseTree( [&, point]( int rowIndex, const ModelIndex& index, const size_t&,
const Float& yOffset ) {
if ( yOffset - mScrollOffset.y > mSize.getHeight() )
return IterationDecision::Stop;
if ( yOffset - mScrollOffset.y + getRowHeight() < 0 )
return IterationDecision::Continue;
pOver = updateRow( rowIndex, index, yOffset )->overFind( point );
if ( pOver )
return IterationDecision::Stop;
return IterationDecision::Continue;
} );
if ( !pOver )
pOver = this;
}
}
return pOver;
}
bool UITreeView::isExpanded( const ModelIndex& index ) const {
return getIndexMetadata( index ).open;
}
Drawable* UITreeView::getExpandIcon() const {
return mExpandIcon;
}
void UITreeView::setExpandedIcon( Drawable* expandIcon ) {
if ( mExpandIcon != expandIcon ) {
mExpandIcon = expandIcon;
createOrUpdateColumns();
}
}
Drawable* UITreeView::getContractIcon() const {
return mContractIcon;
}
void UITreeView::setContractedIcon( Drawable* contractIcon ) {
if ( mContractIcon != contractIcon ) {
mContractIcon = contractIcon;
createOrUpdateColumns();
}
}
void UITreeView::onColumnResizeToContent( const size_t& colIndex ) {
UIWidget* lWidget = nullptr;
Float lWidth = 0;
getUISceneNode()->setIsLoading( true );
traverseTree( [&, colIndex]( const int& rowIndex, const ModelIndex& index,
const size_t& indentLevel, const Float& yOffset ) {
UIWidget* widget = updateCell( rowIndex, index, colIndex, indentLevel, yOffset );
if ( widget->isType( UI_TYPE_PUSHBUTTON ) ) {
Float w = widget->asType<UIPushButton>()->getContentSize().getWidth();
if ( w > lWidth ) {
lWidget = widget;
lWidth = w;
}
}
return IterationDecision::Continue;
} );
getUISceneNode()->setIsLoading( false );
if ( lWidget ) {
columnData( colIndex ).width = lWidth;
createOrUpdateColumns();
}
}
void UITreeView::onModelSelectionChange() {
UIAbstractTableView::onModelSelectionChange();
invalidateDraw();
}
Uint32 UITreeView::onKeyDown( const KeyEvent& event ) {
if ( event.getMod() != 0 )
return 0;
auto curIndex = getSelection().first();
switch ( event.getKeyCode() ) {
case KEY_UP: {
ModelIndex prevIndex;
ModelIndex foundIndex;
Float curY;
traverseTree(
[&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) {
if ( index == curIndex ) {
foundIndex = prevIndex;
curY = offsetY;
return IterationDecision::Break;
}
prevIndex = index;
return IterationDecision::Continue;
} );
if ( foundIndex.isValid() ) {
getSelection().set( foundIndex );
if ( curY < mScrollOffset.y + getHeaderHeight() + getRowHeight() ||
curY > mScrollOffset.y + getPixelsSize().getHeight() - mRealPadding.Top -
mRealPadding.Bottom - getRowHeight() ) {
curY -= getHeaderHeight() + getRowHeight();
mVScroll->setValue( eemin<Float>(
1.f, eemax<Float>( 0.f, curY / getScrollableArea().getHeight() ) ) );
}
}
break;
}
case KEY_DOWN: {
ModelIndex prevIndex;
ModelIndex foundIndex;
Float curY;
traverseTree(
[&]( const int&, const ModelIndex& index, const size_t&, const Float& offsetY ) {
if ( prevIndex == curIndex ) {
foundIndex = index;
curY = offsetY;
return IterationDecision::Break;
}
prevIndex = index;
return IterationDecision::Continue;
} );
if ( foundIndex.isValid() ) {
getSelection().set( foundIndex );
if ( curY < mScrollOffset.y ||
curY > mScrollOffset.y + getPixelsSize().getHeight() - mRealPadding.Top -
mRealPadding.Bottom - getRowHeight() ) {
curY -=
eefloor( getVisibleArea().getHeight() / getRowHeight() ) * getRowHeight() -
getRowHeight();
mVScroll->setValue(
eemin<Float>( 1.f, curY / getScrollableArea().getHeight() ) );
}
break;
}
}
case KEY_END: {
scrollToBottom();
ModelIndex lastIndex;
traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& ) {
lastIndex = index;
return IterationDecision::Continue;
} );
getSelection().set( lastIndex );
break;
}
case KEY_HOME: {
scrollToTop();
getSelection().set( getModel()->index( 0, 0 ) );
break;
}
case KEY_RIGHT: {
if ( curIndex.isValid() && getModel()->rowCount( curIndex ) ) {
auto& metadata = getIndexMetadata( curIndex );
if ( !metadata.open ) {
metadata.open = true;
createOrUpdateColumns();
return 0;
}
getSelection().set( getModel()->index( 0, getModel()->treeColumn(), curIndex ) );
}
break;
}
case KEY_LEFT: {
if ( curIndex.isValid() && getModel()->rowCount( curIndex ) ) {
auto& metadata = getIndexMetadata( curIndex );
if ( metadata.open ) {
metadata.open = false;
createOrUpdateColumns();
return 0;
}
}
if ( curIndex.isValid() && curIndex.parent().isValid() ) {
getSelection().set( curIndex.parent() );
return 0;
}
break;
}
case KEY_RETURN:
case KEY_SPACE: {
if ( curIndex.isValid() ) {
if ( getModel()->rowCount( curIndex ) ) {
auto& metadata = getIndexMetadata( curIndex );
metadata.open = !metadata.open;
createOrUpdateColumns();
} else {
onOpenModelIndex( curIndex );
}
}
break;
}
default:
break;
}
return 0;
}
void UITreeView::onOpenModelIndex( const ModelIndex& index ) {
ModelEvent event( getModel(), index, this );
sendEvent( &event );
}
}} // namespace EE::UI

View File

@@ -29,6 +29,7 @@
#include <eepp/ui/uitextureregion.hpp>
#include <eepp/ui/uitextview.hpp>
#include <eepp/ui/uitouchdraggablewidget.hpp>
#include <eepp/ui/uitreeview.hpp>
#include <eepp/ui/uiviewpager.hpp>
#include <eepp/ui/uiwidgetcreator.hpp>
#include <eepp/ui/uiwindow.hpp>
@@ -80,6 +81,7 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["viewpager"] = UIViewPager::New;
registeredWidget["codeeditor"] = UICodeEditor::New;
registeredWidget["splitter"] = UISplitter::New;
registeredWidget["treeview"] = UITreeView::New;
registeredWidget["hbox"] = UILinearLayout::NewHorizontal;
registeredWidget["vbox"] = UILinearLayout::NewVertical;

View File

@@ -60,7 +60,7 @@ UIWindow::UIWindow( UIWindow::WindowBaseContainerType type, const StyleConfig& w
switch ( type ) {
case LINEAR_LAYOUT:
mContainer = UILinearLayout::NewWithTag( "window::container" );
mContainer = UILinearLayout::NewWithTag( "window::container", UIOrientation::Vertical );
break;
case RELATIVE_LAYOUT:
mContainer = UIRelativeLayout::NewWithTag( "window::container" );
@@ -1212,7 +1212,7 @@ Uint32 UIWindow::onMouseDoubleClick( const Vector2i&, const Uint32& Flags ) {
return 1;
}
void UIWindow::internalDraw() {
void UIWindow::nodeDraw() {
if ( mVisible && 0 != mAlpha ) {
updateScreenPos();

View File

@@ -1,4 +1,104 @@
#include <eepp/ee.hpp>
#include <eepp/ui/abstract/model.hpp>
#include <eepp/ui/abstract/uiabstracttableview.hpp>
#include <eepp/ui/uitreeview.hpp>
using namespace EE::UI::Abstract;
class TestModel : public Model {
public:
struct NodeT {
std::vector<NodeT*> children;
NodeT* parent{nullptr};
ModelIndex index( const TestModel& model, int column ) const {
if ( !parent )
return {};
for ( size_t row = 0; row < parent->children.size(); ++row ) {
if ( parent->children[row] == this )
return model.createIndex( row, column, const_cast<NodeT*>( this ) );
}
return {};
}
};
size_t getRows() const { return 10000; }
size_t getCols() const { return 4; }
size_t getChilds() const { return 50; }
TestModel() : Model() {
for ( size_t row = 0; row < getRows(); ++row ) {
NodeT* n = new NodeT();
n->parent = &mRoot;
for ( size_t i = 0; i < getChilds(); i++ ) {
NodeT* c = new NodeT();
c->parent = n;
n->children.push_back( c );
}
mRoot.children.push_back( n );
}
}
virtual ModelIndex parentIndex( const ModelIndex& index ) const {
if ( !index.isValid() )
return {};
auto& node = this->node( index );
if ( !node.parent ) {
eeASSERT( &node == &mRoot );
return {};
}
return node.parent->index( *this, index.column() );
}
virtual size_t rowCount( const ModelIndex& index = ModelIndex() ) const {
auto& node = this->node( index );
return node.children.size();
}
virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const { return getCols(); }
NodeT mRoot;
const NodeT& node( const ModelIndex& index ) const {
if ( !index.isValid() )
return mRoot;
return *(NodeT*)index.data();
}
ModelIndex index( int row, int column, const ModelIndex& parent ) const {
if ( row < 0 || column < 0 )
return {};
auto& node = this->node( parent );
if ( static_cast<size_t>( row ) >= node.children.size() )
return {};
return createIndex( row, column, node.children[row] );
}
virtual std::string columnName( const size_t& column ) const {
return String::format( "Column %ld", column );
}
virtual Variant data( const ModelIndex& index, Role role = Role::Display ) const {
switch ( role ) {
case Role::Display: {
return Variant( String::format( "Test %lld-%lld", index.row(), index.column() ) );
}
case Role::Icon: {
if ( index.column() == 0 && rowCount( index ) == 0 ) {
return Variant( SceneManager::instance()
->getUISceneNode()
->getUIIconThemeManager()
->getCurrentTheme()
->getIcon( "file" ) );
}
}
default: {
}
}
return Variant();
};
virtual void update() {}
};
// This file is used to test some UI related stuffs.
// It's not a benchmark or a real test suite.
@@ -13,6 +113,21 @@ void mainLoop() {
win->close();
}
UISceneNode* uiSceneNode = SceneManager::instance()->getUISceneNode();
if ( win->getInput()->isKeyUp( KEY_F6 ) ) {
uiSceneNode->setHighlightFocus( !uiSceneNode->getHighlightFocus() );
uiSceneNode->setHighlightOver( !uiSceneNode->getHighlightOver() );
}
if ( win->getInput()->isKeyUp( KEY_F7 ) ) {
uiSceneNode->setDrawBoxes( !uiSceneNode->getDrawBoxes() );
}
if ( win->getInput()->isKeyUp( KEY_F8 ) ) {
uiSceneNode->setDrawDebugData( !uiSceneNode->getDrawDebugData() );
}
// Update the UI scene.
SceneManager::instance()->update();
@@ -39,9 +154,23 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
Engine::instance()->getDisplayManager()->getDisplayIndex( 0 )->getPixelDensity() );
FontTrueType* font =
FontTrueType::New( "NotoSans-Regular", "assets/fonts/NotoSans-Regular.ttf" );
FontTrueType* iconFont = FontTrueType::New( "icon", "assets/fonts/remixicon.ttf" );
UIIconTheme* iconTheme = UIIconTheme::New( "remixicon" );
auto addIcon = [iconTheme, iconFont]( const std::string& name, const Uint32& codePoint,
const Uint32& size ) -> Drawable* {
Drawable* ic = iconFont->getGlyphDrawable( codePoint, size );
iconTheme->add( name, ic );
return ic;
};
Drawable* closed = addIcon( "folder", 0xed6a, 16 );
Drawable* open = addIcon( "folder-open", 0xed70, 16 );
addIcon( "tree-expanded", 0xea50, 24 );
addIcon( "tree-contracted", 0xea54, 24 );
addIcon( "file", 0xecc3, 24 );
UISceneNode* uiSceneNode = UISceneNode::New();
SceneManager::instance()->add( uiSceneNode );
uiSceneNode->getUIThemeManager()->setDefaultFont( font );
uiSceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme );
/*StyleSheetParser styleSheetParser;
styleSheetParser.loadFromFile( "assets/ui/breeze.css" );
uiSceneNode->setStyleSheet( styleSheetParser.getStyleSheet() );*/
@@ -50,9 +179,10 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
pd = "1.5x";
else if ( PixelDensity::getPixelDensity() >= 2.f )
pd = "2x";
UITheme* theme =
/*UITheme* theme =
UITheme::load( "uitheme" + pd, "uitheme" + pd, "assets/ui/uitheme" + pd + ".eta", font,
"assets/ui/uitheme.css" );
"assets/ui/uitheme.css" );*/
UITheme* theme = UITheme::load( "breeze", "breeze", "", font, "assets/ui/breeze.css" );
uiSceneNode->setStyleSheet( theme->getStyleSheet() );
uiSceneNode->getUIThemeManager()
->setDefaultEffectsEnabled( true )
@@ -63,6 +193,17 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
auto* vlay = UILinearLayout::NewVertical();
vlay->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
Clock clock;
auto model = std::make_shared<TestModel>();
UITreeView* view = UITreeView::New();
view->setId( "treeview" );
view->setExpandedIcon( open );
view->setContractedIcon( closed );
view->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
view->setParent( vlay );
view->setModel( model );
eePRINTL( "Total time: %.2fms", clock.getElapsedTime().asMilliseconds() );
/* ListBox test */ /*
std::vector<String> strings;
for ( size_t i = 0; i < 10000; i++ )
@@ -101,7 +242,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
uiSceneNode->getRoot()->childsCloseAll();
SceneManager::instance()->update();*/
total.restart();
/*total.restart();
for ( size_t i = 0; i < 100000; i++ ) {
auto* widget = UIWidget::New();
widget->setParent( vlay );
@@ -115,7 +256,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
widget->setBackgroundColor( Color::fromHsv( col ) );
}
std::cout << "Time UIWidget total: " << total.getElapsedTime().asMilliseconds() << " ms"
<< std::endl;
<< std::endl;*/
/*UIWindow* wind = UIWindow::New();
wind->setSize( 500, 500 );