diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index 0f1700bcb..0af3734cb 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -762,6 +762,11 @@ class EE_API String { **/ String( const String& str ); + /** @brief Move constructor + ** @param str Instance to move + **/ + String( String&& str ) noexcept; + /** @brief Copy constructor ** @param str Instance to copy **/ diff --git a/include/eepp/ui/models/variant.hpp b/include/eepp/ui/models/variant.hpp index 15d30c646..086ee01c8 100644 --- a/include/eepp/ui/models/variant.hpp +++ b/include/eepp/ui/models/variant.hpp @@ -1,12 +1,16 @@ #ifndef EE_UI_MODEL_VARIANT_HPP #define EE_UI_MODEL_VARIANT_HPP +#include #include #include #include #include #include +#include #include +#include +#include using namespace EE::Graphics; using namespace EE::Math; @@ -31,56 +35,407 @@ class EE_API Variant { Vector2f, Rectf, cstr, - StringPtr + StringPtr, + StdStringPtr }; - Variant() : mType( Type::Invalid ) {} - explicit Variant( const std::string& string ) : mType( Type::StdString ) { - mValue.asStdString = eeNew( std::string, ( string ) ); + + Variant() = default; + + explicit Variant( const std::string& string ) { + new ( &mValue.asStdString ) std::string( string ); + mType = Type::StdString; } - explicit Variant( const String& string ) : mType( Type::String ) { - mValue.asString = eeNew( String, ( string ) ); + + explicit Variant( std::string&& string ) { + new ( &mValue.asStdString ) std::string( std::move( string ) ); + mType = Type::StdString; } + + explicit Variant( const std::string* string ) : mType( Type::StdStringPtr ) { + mValue.asStdStringPtr = string; + } + + explicit Variant( const String& string ) { + new ( &mValue.asString ) String( string ); + mType = Type::String; + } + + explicit Variant( String&& string ) { + new ( &mValue.asString ) String( std::move( string ) ); + mType = Type::String; + } + explicit Variant( const String* string ) : mType( Type::StringPtr ) { mValue.asStringPtr = string; } - Variant( DrawablePtr drawable ) : mDrawable( std::move( drawable ) ), mType( Type::Drawable ) {} - Variant( UIIcon* icon ) : mType( Type::Icon ) { mValue.asIcon = icon; } - 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 unsigned int& val ) : mType( Type::Uint ) { mValue.asUint = val; } - Variant( const Int64& val ) : mType( Type::Int64 ) { mValue.asInt64 = val; } - Variant( const Uint64& val ) : mType( Type::Uint64 ) { mValue.asUint64 = val; } - explicit Variant( const char* data ) : mType( Type::cstr ) { mValue.asCStr = data; } - ~Variant() { reset(); } - Variant( const Variant& other ) : mType( Type::Invalid ) { - switch ( other.mType ) { + Variant( DrawablePtr drawable ) { + new ( &mValue.asDrawable ) DrawablePtr( std::move( drawable ) ); + mType = Type::Drawable; + } + + Variant( UIIcon* icon ) : mType( Type::Icon ) { mValue.asIcon = icon; } + + Variant( const Vector2f& v ) { + new ( &mValue.asVector2f ) Vector2f( v ); + mType = Type::Vector2f; + } + + Variant( void* data ) : mType( Type::DataPtr ) { mValue.asDataPtr = data; } + + Variant( const Rectf& r ) { + new ( &mValue.asRectf ) Rectf( r ); + mType = Type::Rectf; + } + + 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 unsigned int& val ) : mType( Type::Uint ) { mValue.asUint = val; } + + Variant( const Int64& val ) : mType( Type::Int64 ) { mValue.asInt64 = val; } + + Variant( const Uint64& val ) : mType( Type::Uint64 ) { mValue.asUint64 = val; } + + explicit Variant( const char* data ) : mType( Type::cstr ) { mValue.asCStr = data; } + + ~Variant() { destroy(); } + + Variant( const Variant& other ) { copyConstruct( other ); } + + Variant( Variant&& other ) { moveConstruct( std::move( other ) ); } + + Variant& operator=( const Variant& other ) { + if ( this == &other ) + return *this; + if ( mType == other.mType ) { + copyAssignSameType( other ); + } else { + destroy(); + copyConstruct( other ); + } + return *this; + } + + Variant& operator=( Variant&& other ) { + if ( this == &other ) + return *this; + if ( mType == other.mType ) { + moveAssignSameType( std::move( other ) ); + } else { + destroy(); + moveConstruct( std::move( other ) ); + } + return *this; + } + + static Variant fromRef( const std::string& string ) { return Variant( &string ); } + + const std::string& asStdString() const { return mValue.asStdString; } + + const std::string& asStdStringPtr() const { return *mValue.asStdStringPtr; } + + const String& asString() const { return mValue.asString; } + + const String& asStringPtr() const { return *mValue.asStringPtr; } + + const DrawablePtr& 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 unsigned int& asUint() const { return mValue.asUint; } + + const Int64& asInt64() const { return mValue.asInt64; } + + const Uint64& asUint64() const { return mValue.asUint64; } + + const Vector2f& asVector2f() const { return mValue.asVector2f; } + + const Rectf& asRectf() const { return mValue.asRectf; } + + const char* asCStr() const { return mValue.asCStr; } + + UIIcon* asIcon() const { return mValue.asIcon; } + + void* asDataPtr() const { return mValue.asDataPtr; } + + Type getType() const { return mType; } + + bool is( const Type& type ) const { return type == mType; } + + bool isString() const { + return mType == Type::StdString || mType == Type::StdStringPtr || mType == Type::cstr || + mType == Type::String || mType == Type::StringPtr; + } + + bool isStdStringLike() const { + return mType == Type::StdString || mType == Type::StdStringPtr || mType == Type::cstr; + } + + std::string_view asStdStringView() const { + switch ( mType ) { case Type::StdString: - mValue.asStdString = eeNew( std::string, ( *other.mValue.asStdString ) ); + return asStdString(); + case Type::StdStringPtr: + return asStdStringPtr(); + case Type::cstr: + return asCStr(); + default: + return {}; + } + } + + void reset() { destroy(); } + + bool isValid() const { return mType != Type::Invalid; } + + std::string toString() const { + switch ( mType ) { + case Type::Bool: + return asBool() ? "true" : "false"; + case Type::Int: + return String::toString( asInt() ); + case Type::Uint: + return String::toString( asUint() ); + case Type::Int64: + return String::toString( asInt64() ); + case Type::Uint64: + return String::toString( asUint64() ); + case Type::Float: + return String::toString( asFloat() ); + case Type::StdString: + return asStdString(); + case Type::StdStringPtr: + return asStdStringPtr(); + case Type::String: + return asString(); + case Type::StringPtr: + return asStringPtr(); + case Type::Drawable: + return asDrawable()->isDrawableResource() + ? static_cast( asDrawable().get() )->getName() + : "Drawable"; + case Type::Icon: + return asIcon()->getName(); + case Type::DataPtr: + return String::format( "%p", asDataPtr() ); + case Type::Vector2f: + return String::format( "%.2f-%.2f", asVector2f().x, asVector2f().y ); + case Type::Rectf: + return String::format( "%.2f-%.2f-%.2f-%.2f", asRectf().Top, asRectf().Right, + asRectf().Bottom, asRectf().Left ); + case Type::cstr: + return asCStr(); + case Type::Invalid: + break; + } + return ""; + } + + bool operator<( const Variant& other ) const { + if ( mType != other.mType ) { + if ( isStdStringLike() && other.isStdStringLike() ) + return asStdStringView() < other.asStdStringView(); + return toString() < other.toString(); + } + switch ( mType ) { + case Type::Bool: + return asBool() < other.asBool(); + case Type::Int: + return asInt() < other.asInt(); + case Type::Uint: + return asUint() < other.asUint(); + case Type::Int64: + return asInt64() < other.asInt64(); + case Type::Uint64: + return asUint64() < other.asUint64(); + case Type::Float: + return asFloat() < other.asFloat(); + case Type::StdString: + return asStdString() < other.asStdString(); + case Type::StdStringPtr: + return asStdStringPtr() < other.asStdStringPtr(); + case Type::String: + return asString() < other.asString(); + case Type::StringPtr: + return asStringPtr() < other.asStringPtr(); + case Type::Drawable: + return asDrawable() < other.asDrawable(); + case Type::Icon: + return asIcon() < other.asIcon(); + case Type::DataPtr: + return asDataPtr() < other.asDataPtr(); + case Type::Vector2f: + return asVector2f() < other.asVector2f(); + case Type::Rectf: + return asRectf().getSize() < other.asRectf().getSize(); + case Type::cstr: + return std::strcmp( asCStr(), other.asCStr() ) < 0; + case Type::Invalid: + break; + } + return false; + } + + bool operator==( const Variant& other ) const { + if ( mType != other.mType ) { + if ( isStdStringLike() && other.isStdStringLike() ) + return asStdStringView() == other.asStdStringView(); + return toString() == other.toString(); + } + switch ( mType ) { + case Type::Bool: + return asBool() == other.asBool(); + case Type::Int: + return asInt() == other.asInt(); + case Type::Uint: + return asUint() == other.asUint(); + case Type::Int64: + return asInt64() == other.asInt64(); + case Type::Uint64: + return asUint64() == other.asUint64(); + case Type::Float: + return asFloat() == other.asFloat(); + case Type::StdString: + return asStdString() == other.asStdString(); + case Type::StdStringPtr: + return asStdStringPtr() == other.asStdStringPtr(); + case Type::String: + return asString() == other.asString(); + case Type::StringPtr: + return asStringPtr() == other.asStringPtr(); + case Type::Drawable: + return asDrawable() == other.asDrawable(); + case Type::Icon: + return asIcon() == other.asIcon(); + case Type::DataPtr: + return asDataPtr() == other.asDataPtr(); + case Type::Vector2f: + return asVector2f() == other.asVector2f(); + case Type::Rectf: + return asRectf().getSize() == other.asRectf().getSize(); + case Type::cstr: + return std::strcmp( asCStr(), other.asCStr() ) == 0; + case Type::Invalid: + break; + } + return false; + } + + size_t size() const { + switch ( mType ) { + case Type::Bool: + return 1; + case Type::Int: + return sizeof( int ); + case Type::Uint: + return sizeof( unsigned int ); + case Type::Int64: + return sizeof( Int64 ); + case Type::Uint64: + return sizeof( Uint64 ); + case Type::Float: + return sizeof( Float ); + case Type::StdString: + return asStdString().size(); + case Type::StdStringPtr: + return asStdStringPtr().size(); + case Type::String: + return asString().size(); + case Type::StringPtr: + return asStringPtr().size(); + case Type::Drawable: + return sizeof( mValue.asDrawable ); + case Type::Icon: + return asIcon()->getName().size(); + case Type::DataPtr: + return sizeof( mValue.asDataPtr ); + case Type::Vector2f: + return sizeof( mValue.asVector2f ); + case Type::Rectf: + return sizeof( mValue.asRectf ); + case Type::cstr: + return std::strlen( asCStr() ); + case Type::Invalid: + break; + } + return 0; + } + + private: + union Storage { + void* asDataPtr; + UIIcon* asIcon; + std::string asStdString; + const std::string* asStdStringPtr; + String asString; + const String* asStringPtr; + DrawablePtr asDrawable; + bool asBool; + Float asFloat; + int asInt; + unsigned int asUint; + Int64 asInt64; + Uint64 asUint64; + Vector2f asVector2f; + Rectf asRectf; + const char* asCStr; + + Storage() {} + ~Storage() {} + } mValue; + + Type mType{ Type::Invalid }; + + void destroy() { + switch ( mType ) { + case Type::StdString: + mValue.asStdString.~basic_string(); break; case Type::String: - mValue.asString = eeNew( String, ( *other.mValue.asString ) ); + mValue.asString.~String(); + break; + case Type::Drawable: + mValue.asDrawable.~DrawablePtr(); + break; + default: + break; + } + mType = Type::Invalid; + } + + void copyConstruct( const Variant& other ) { + switch ( other.mType ) { + case Type::StdString: + new ( &mValue.asStdString ) std::string( other.mValue.asStdString ); + break; + case Type::StdStringPtr: + mValue.asStdStringPtr = other.mValue.asStdStringPtr; + break; + case Type::String: + new ( &mValue.asString ) String( other.mValue.asString ); break; case Type::StringPtr: mValue.asStringPtr = other.mValue.asStringPtr; break; case Type::Drawable: - mDrawable = other.mDrawable; + new ( &mValue.asDrawable ) DrawablePtr( other.mValue.asDrawable ); break; case Type::Icon: mValue.asIcon = other.mValue.asIcon; break; case Type::Vector2f: - mValue.asVector2f = eeNew( Vector2f, ( *other.mValue.asVector2f ) ); + new ( &mValue.asVector2f ) Vector2f( other.mValue.asVector2f ); break; case Type::Rectf: - mValue.asRectf = eeNew( Rectf, ( *other.mValue.asRectf ) ); + new ( &mValue.asRectf ) Rectf( other.mValue.asRectf ); break; case Type::Bool: mValue.asBool = other.mValue.asBool; @@ -112,254 +467,101 @@ class EE_API Variant { mType = other.mType; } - Variant( Variant&& other ) noexcept : - mValue( other.mValue ), mDrawable( std::move( other.mDrawable ) ), mType( other.mType ) { - other.mType = Type::Invalid; - other.mValue = {}; - } - - Variant& operator=( const Variant& other ) { - if ( this != &other ) { - reset(); - new ( this ) Variant( other ); // reuse copy constructor - } - return *this; - } - - Variant& operator=( Variant&& other ) noexcept { - if ( this != &other ) { - reset(); - mType = other.mType; - mDrawable = std::move( other.mDrawable ); - mValue = other.mValue; - other.mType = Type::Invalid; - other.mValue = {}; - } - return *this; - } - - const std::string& asStdString() const { return *mValue.asStdString; } - const String& asString() const { return *mValue.asString; } - const String& asStringPtr() const { return *mValue.asStringPtr; } - const DrawablePtr& asDrawable() const { return mDrawable; } - const bool& asBool() const { return mValue.asBool; } - const Float& asFloat() const { return mValue.asFloat; } - const int& asInt() const { return mValue.asInt; } - const unsigned int& asUint() const { return mValue.asUint; } - const Int64& asInt64() const { return mValue.asInt64; } - const Uint64& asUint64() const { return mValue.asUint64; } - const Vector2f& asVector2f() const { return *mValue.asVector2f; } - const Rectf& asRectf() const { return *mValue.asRectf; } - const char* asCStr() const { return mValue.asCStr; } - UIIcon* asIcon() const { return mValue.asIcon; } - void* asDataPtr() const { return mValue.asDataPtr; } - bool is( const Type& type ) const { return type == mType; } - bool isString() const { - return mType == Type::StdString || mType == Type::cstr || mType == Type::String || - mType == Type::StringPtr; - } - void reset() { - switch ( mType ) { + void moveConstruct( Variant&& other ) { + switch ( other.mType ) { case Type::StdString: - eeSAFE_DELETE( mValue.asStdString ); + new ( &mValue.asStdString ) std::string( std::move( other.mValue.asStdString ) ); + break; + case Type::StdStringPtr: + mValue.asStdStringPtr = other.mValue.asStdStringPtr; break; case Type::String: - eeSAFE_DELETE( mValue.asString ); + new ( &mValue.asString ) String( std::move( other.mValue.asString ) ); + break; + case Type::StringPtr: + mValue.asStringPtr = other.mValue.asStringPtr; break; case Type::Drawable: - mDrawable.reset(); + new ( &mValue.asDrawable ) DrawablePtr( std::move( other.mValue.asDrawable ) ); + break; + case Type::Icon: + mValue.asIcon = other.mValue.asIcon; break; case Type::Vector2f: - eeSAFE_DELETE( mValue.asVector2f ); + new ( &mValue.asVector2f ) Vector2f( std::move( other.mValue.asVector2f ) ); break; case Type::Rectf: - eeSAFE_DELETE( mValue.asRectf ); + new ( &mValue.asRectf ) Rectf( std::move( other.mValue.asRectf ) ); + break; + case Type::Bool: + mValue.asBool = other.mValue.asBool; + break; + case Type::Float: + mValue.asFloat = other.mValue.asFloat; + break; + case Type::Int: + mValue.asInt = other.mValue.asInt; + break; + case Type::Uint: + mValue.asUint = other.mValue.asUint; + break; + case Type::Int64: + mValue.asInt64 = other.mValue.asInt64; + break; + case Type::Uint64: + mValue.asUint64 = other.mValue.asUint64; + break; + case Type::cstr: + mValue.asCStr = other.mValue.asCStr; + break; + case Type::DataPtr: + mValue.asDataPtr = other.mValue.asDataPtr; + break; + case Type::Invalid: + break; + } + mType = other.mType; + other.destroy(); + } + + void copyAssignSameType( const Variant& other ) { + switch ( mType ) { + case Type::StdString: + mValue.asStdString = other.mValue.asStdString; + break; + case Type::String: + mValue.asString = other.mValue.asString; + break; + case Type::Drawable: + mValue.asDrawable = other.mValue.asDrawable; break; default: + destroy(); + copyConstruct( other ); break; } - mType = Type::Invalid; } - bool isValid() const { return mType != Type::Invalid; } - std::string toString() const { + void moveAssignSameType( Variant&& other ) { switch ( mType ) { - case Type::Bool: - return asBool() ? "true" : "false"; - case Type::Int: - return String::toString( asInt() ); - case Type::Uint: - return String::toString( asUint() ); - case Type::Int64: - return String::toString( asInt64() ); - case Type::Uint64: - return String::toString( asUint64() ); - case Type::Float: - return String::toString( asFloat() ); case Type::StdString: - return asStdString(); + mValue.asStdString = std::move( other.mValue.asStdString ); + other.destroy(); + break; case Type::String: - return asString(); - case Type::StringPtr: - return asStringPtr(); + mValue.asString = std::move( other.mValue.asString ); + other.destroy(); + break; case Type::Drawable: - return asDrawable()->isDrawableResource() - ? static_cast( asDrawable().get() )->getName() - : "Drawable"; - case Type::Icon: - return asIcon()->getName(); - case Type::DataPtr: - return String::format( "%p", asDataPtr() ); - case Type::Vector2f: - return String::format( "%.2f-%.2f", asVector2f().x, asVector2f().y ); - case Type::Rectf: - return String::format( "%.2f-%.2f-%.2f-%.2f", asRectf().Top, asRectf().Right, - asRectf().Bottom, asRectf().Left ); - case Type::cstr: - return asCStr(); - case Type::Invalid: + mValue.asDrawable = std::move( other.mValue.asDrawable ); + other.destroy(); + break; + default: + destroy(); + moveConstruct( std::move( other ) ); break; } - return ""; } - - bool operator<( const Variant& other ) const { - if ( mType != other.mType ) - return toString() < other.toString(); - switch ( mType ) { - case Type::Bool: - return asBool() < other.asBool(); - case Type::Int: - return asInt() < other.asInt(); - case Type::Uint: - return asUint() < other.asUint(); - case Type::Int64: - return asInt64() < other.asInt64(); - case Type::Uint64: - return asUint64() < other.asUint64(); - case Type::Float: - return asFloat() < other.asFloat(); - case Type::StdString: - return asStdString() < other.asStdString(); - case Type::String: - return asString() < other.asString(); - case Type::StringPtr: - return asStringPtr() < other.asStringPtr(); - case Type::Drawable: - return asDrawable() < other.asDrawable(); - case Type::Icon: - return asIcon() < other.asIcon(); - case Type::DataPtr: - return asDataPtr() < other.asDataPtr(); - case Type::Vector2f: - return asVector2f() < other.asVector2f(); - case Type::Rectf: - return asRectf().getSize() < other.asRectf().getSize(); - case Type::cstr: - return strcmp( asCStr(), other.asCStr() ) < 0; - case Type::Invalid: - break; - } - return false; - } - - bool operator==( const Variant& other ) const { - if ( mType != other.mType ) - return toString() == other.toString(); - switch ( mType ) { - case Type::Bool: - return asBool() == other.asBool(); - case Type::Int: - return asInt() == other.asInt(); - case Type::Uint: - return asUint() == other.asUint(); - case Type::Int64: - return asInt64() == other.asInt64(); - case Type::Uint64: - return asUint64() == other.asUint64(); - case Type::Float: - return asFloat() == other.asFloat(); - case Type::StdString: - return asStdString() == other.asStdString(); - case Type::String: - return asString() == other.asString(); - case Type::StringPtr: - return asStringPtr() == other.asStringPtr(); - case Type::Drawable: - return asDrawable() == other.asDrawable(); - case Type::Icon: - return asIcon() == other.asIcon(); - case Type::DataPtr: - return asDataPtr() == other.asDataPtr(); - case Type::Vector2f: - return asVector2f() == other.asVector2f(); - case Type::Rectf: - return asRectf().getSize() == other.asRectf().getSize(); - case Type::cstr: - return strcmp( asCStr(), other.asCStr() ) == 0; - case Type::Invalid: - break; - } - return false; - } - - size_t size() const { - switch ( mType ) { - case Type::Bool: - return 1; - case Type::Int: - return sizeof( int ); - case Type::Uint: - return sizeof( unsigned int ); - case Type::Int64: - return sizeof( Int64 ); - case Type::Uint64: - return sizeof( Uint64 ); - case Type::Float: - return sizeof( Float ); - case Type::StdString: - return asStdString().size(); - case Type::String: - return asString().size(); - case Type::StringPtr: - return asStringPtr().size(); - case Type::Drawable: - return sizeof( mDrawable ); - case Type::Icon: - return asIcon()->getName().size(); - case Type::DataPtr: - return sizeof( mValue.asDataPtr ); - case Type::Vector2f: - return sizeof( mValue.asVector2f ); - case Type::Rectf: - return sizeof( mValue.asRectf ); - case Type::cstr: - return strlen( asCStr() ); - case Type::Invalid: - break; - } - return 0; - } - - private: - union { - void* asDataPtr{ nullptr }; - UIIcon* asIcon; - std::string* asStdString; - String* asString; - const String* asStringPtr; - bool asBool; - Float asFloat; - int asInt; - unsigned int asUint; - Int64 asInt64; - Uint64 asUint64; - Vector2f* asVector2f; - Rectf* asRectf; - const char* asCStr; - } mValue; - DrawablePtr mDrawable; - Type mType; }; }}} // namespace EE::UI::Models diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 94e343586..4544fa3b5 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -2081,6 +2081,8 @@ String::String( const StringType& utf32String ) : mString( utf32String ) {} String::String( const String& str ) : mString( str.mString ) {} +String::String( String&& str ) noexcept : mString( std::move( str.mString ) ) {} + String::String( const String::View& utf32String ) : mString( utf32String ) {} String String::fromUtf16( const char* utf16String, const size_t& utf16StringSize, diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 5f22d5c12..75724c23a 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -15,6 +15,29 @@ namespace EE { namespace UI { namespace Abstract { static constexpr String::HashType onModelUpdateTag = String::hash( "onModelUpdate" ); +template static void consumeVariantText( const Variant& value, Callback&& cb ) { + switch ( value.getType() ) { + case Variant::Type::String: + cb( value.asString() ); + break; + case Variant::Type::StringPtr: + cb( value.asStringPtr() ); + break; + case Variant::Type::StdString: + cb( value.asStdString() ); + break; + case Variant::Type::StdStringPtr: + cb( value.asStdStringPtr() ); + break; + case Variant::Type::cstr: + cb( value.asCStr() ); + break; + default: + cb( value.toString() ); + break; + } +} + UIAbstractTableView::UIAbstractTableView( const std::string& tag ) : UIAbstractView( tag ), mDragBorderDistance( PixelDensity::dpToPx( 4 ) ), @@ -1009,19 +1032,26 @@ void UIAbstractTableView::updateTableCellData( UITableCell* cell, const ModelInd Variant cls( getModel()->data( index, ModelRole::Class ) ); cell->setLoadingState( true ); if ( cls.isValid() ) { - bool hasClass = false; - - hasClass = - ( cls.is( Variant::Type::cstr ) && - cell->hasClass( std::string_view{ cls.asCStr() } ) ) || - ( cls.is( Variant::Type::StdString ) && cell->hasClass( cls.asStdString() ) ) || - cell->hasClass( cls.toString() ); + const bool isStdStringLike = cls.isStdStringLike(); + std::string convertedClass; + const std::string_view className = + isStdStringLike ? cls.asStdStringView() + : std::string_view{ convertedClass = cls.toString() }; + const bool hasClass = cell->hasClass( className ); needsReloadStyle = cell->getClasses().empty() || cell->getClasses().size() != 1 || !hasClass; - if ( !hasClass ) - cell->setClass( cls.toString() ); + if ( !hasClass ) { + if ( cls.is( Variant::Type::StdString ) ) + cell->setClass( cls.asStdString() ); + else if ( cls.is( Variant::Type::StdStringPtr ) ) + cell->setClass( cls.asStdStringPtr() ); + else if ( isStdStringLike ) + cell->setClass( std::string{ className } ); + else + cell->setClass( std::move( convertedClass ) ); + } } else { needsReloadStyle = !cell->getClasses().empty(); cell->resetClass(); @@ -1033,25 +1063,14 @@ void UIAbstractTableView::updateTableCellData( UITableCell* cell, const ModelInd if ( getModel()->tooltipModelRoleEnabled() ) { Variant tooltip( getModel()->data( index, ModelRole::Tooltip ) ); - if ( tooltip.isValid() ) { - if ( tooltip.is( Variant::Type::String ) ) - cell->setTooltipText( tooltip.asString() ); - else if ( tooltip.is( Variant::Type::StringPtr ) ) - cell->setTooltipText( tooltip.asStringPtr() ); - else - cell->setTooltipText( tooltip.toString() ); - } + if ( tooltip.isValid() ) + consumeVariantText( tooltip, + [cell]( const auto& text ) { cell->setTooltipText( text ); } ); } Variant txt( getModel()->data( index, ModelRole::Display ) ); - if ( txt.isValid() ) { - if ( txt.is( Variant::Type::String ) ) - cell->setText( txt.asString() ); - else if ( txt.is( Variant::Type::StringPtr ) ) - cell->setText( txt.asStringPtr() ); - else - cell->setText( txt.toString() ); - } + if ( txt.isValid() ) + consumeVariantText( txt, [cell]( const auto& text ) { cell->setText( text ); } ); } void UIAbstractTableView::moveSelection( int steps ) { diff --git a/src/eepp/ui/models/sortingproxymodel.cpp b/src/eepp/ui/models/sortingproxymodel.cpp index 371a8963b..a89e49e26 100644 --- a/src/eepp/ui/models/sortingproxymodel.cpp +++ b/src/eepp/ui/models/sortingproxymodel.cpp @@ -218,13 +218,13 @@ bool SortingProxyModel::lessThan( const ModelIndex& index1, const ModelIndex& in auto data1 = mSource->data( index1, mSortRole ); auto data2 = mSource->data( index2, mSortRole ); if ( data1.isString() && data2.isString() ) { - if ( data1.is( Variant::Type::StdString ) && data2.is( Variant::Type::StdString ) ) - return String::toLower( data1.asStdString() ) < String::toLower( data2.asStdString() ); + if ( data1.isStdStringLike() && data2.isStdStringLike() ) + return String::toLower( std::string{ data1.asStdStringView() } ) < + String::toLower( std::string{ data2.asStdStringView() } ); if ( data1.is( Variant::Type::String ) && data2.is( Variant::Type::String ) ) return String::toLower( data1.asString() ) < String::toLower( data2.asString() ); - if ( data1.is( Variant::Type::cstr ) && data2.is( Variant::Type::cstr ) ) - return String::toLower( std::string( data1.asCStr() ) ) < - String::toLower( std::string( data2.asCStr() ) ); + if ( data1.is( Variant::Type::StringPtr ) && data2.is( Variant::Type::StringPtr ) ) + return String::toLower( data1.asStringPtr() ) < String::toLower( data2.asStringPtr() ); } return data1 < data2; } diff --git a/src/eepp/ui/uitableview.cpp b/src/eepp/ui/uitableview.cpp index 38936a666..2c55a8ad3 100644 --- a/src/eepp/ui/uitableview.cpp +++ b/src/eepp/ui/uitableview.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace EE { namespace UI { @@ -392,19 +393,22 @@ ModelIndex UITableView::findRowWithText( const std::string& text, const bool& ca : ( model->treeColumn() >= 0 ? model->treeColumn() : 0 ) ); Variant var = model->data( index ); if ( var.isValid() ) { + std::string convertedValue; + const std::string_view value = + var.isStdStringLike() ? var.asStdStringView() + : std::string_view{ convertedValue = var.toString() }; bool matches = false; switch ( matchKind ) { case Abstract::UIAbstractView::FindRowWithTextMatchKind::Equals: - matches = var.toString() == text; + matches = value == text; break; case Abstract::UIAbstractView::FindRowWithTextMatchKind::StartsWith: - matches = String::startsWith( caseSensitive ? var.toString() - : String::toLower( var.toString() ), - caseSensitive ? text : String::toLower( text ) ); + matches = caseSensitive ? String::startsWith( value, text ) + : String::istartsWith( value, text ); break; case Abstract::UIAbstractView::FindRowWithTextMatchKind::Contains: - matches = caseSensitive ? String::contains( var.toString(), text ) - : String::icontains( var.toString(), text ); + matches = caseSensitive ? String::contains( value, text ) + : String::icontains( value, text ); break; } diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index b3e786cdb..2dc92427f 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace EE { namespace UI { @@ -811,19 +812,22 @@ ModelIndex UITreeView::findRowWithText( const std::string& text, const bool& cas traverseTree( [&]( const int&, const ModelIndex& index, const size_t&, const Float& ) { Variant var = model->data( index ); if ( var.isValid() ) { + std::string convertedValue; + const std::string_view value = + var.isStdStringLike() ? var.asStdStringView() + : std::string_view{ convertedValue = var.toString() }; bool matches = false; switch ( matchKind ) { case Abstract::UIAbstractView::FindRowWithTextMatchKind::Equals: - matches = var.toString() == text; + matches = value == text; break; case Abstract::UIAbstractView::FindRowWithTextMatchKind::StartsWith: - matches = String::startsWith( caseSensitive ? var.toString() - : String::toLower( var.toString() ), - caseSensitive ? text : String::toLower( text ) ); + matches = caseSensitive ? String::startsWith( value, text ) + : String::istartsWith( value, text ); break; case Abstract::UIAbstractView::FindRowWithTextMatchKind::Contains: - matches = caseSensitive ? String::contains( var.toString(), text ) - : String::icontains( var.toString(), text ); + matches = caseSensitive ? String::contains( value, text ) + : String::icontains( value, text ); break; } @@ -847,18 +851,21 @@ ModelIndex UITreeView::openRowWithPath( const std::vector& pathTree ModelIndex foundIndex = {}; const auto& part = pathTree[i]; - traverseTree( - [&model, &foundIndex, &part, &parentIndex, - i]( const int&, const ModelIndex& index, const size_t& indentLevel, const Float& ) { - Variant var = model->data( index ); - if ( i == indentLevel && var.isValid() && var.toString() == part ) { - if ( !parentIndex.isValid() || parentIndex == index.parent() ) { - foundIndex = index; - return IterationDecision::Stop; - } + traverseTree( [&model, &foundIndex, &part, &parentIndex, + i]( const int&, const ModelIndex& index, const size_t& indentLevel, + const Float& ) { + Variant var = model->data( index ); + const bool matches = + i == indentLevel && var.isValid() && + ( var.isStdStringLike() ? var.asStdStringView() == part : var.toString() == part ); + if ( matches ) { + if ( !parentIndex.isValid() || parentIndex == index.parent() ) { + foundIndex = index; + return IterationDecision::Stop; } - return IterationDecision::Continue; - } ); + } + return IterationDecision::Continue; + } ); if ( foundIndex == ModelIndex() ) break; diff --git a/src/tests/unit_tests/variant_tests.cpp b/src/tests/unit_tests/variant_tests.cpp new file mode 100644 index 000000000..8eb645abb --- /dev/null +++ b/src/tests/unit_tests/variant_tests.cpp @@ -0,0 +1,229 @@ +#include "utest.hpp" + +#include +#include +#include +#include + +using namespace EE; +using namespace EE::Graphics; +using namespace EE::Math; +using namespace EE::UI; +using namespace EE::UI::Models; + +static_assert( static_cast( Variant::Type::Invalid ) == 0 ); +static_assert( static_cast( Variant::Type::StringPtr ) == 15 ); +static_assert( static_cast( Variant::Type::StdStringPtr ) == 16 ); +static_assert( std::is_nothrow_move_constructible_v ); +static_assert( std::is_nothrow_move_constructible_v ); +static_assert( std::is_nothrow_move_constructible_v ); + +UTEST( Variant, ownedStdStringLifecycle ) { + const std::string shortString = "ecode"; + const std::string longString( 256, 'x' ); + + Variant shortValue( shortString ); + EXPECT_TRUE( shortValue.is( Variant::Type::StdString ) ); + EXPECT_TRUE( shortValue.isString() ); + EXPECT_TRUE( shortValue.isStdStringLike() ); + EXPECT_TRUE( shortValue.asStdString() == shortString ); + EXPECT_TRUE( shortValue.asStdStringView() == shortString ); + EXPECT_EQ( shortValue.size(), shortString.size() ); + + Variant copied( shortValue ); + EXPECT_TRUE( copied == shortValue ); + + Variant longValue( longString ); + const char* longData = longValue.asStdString().data(); + Variant moved( std::move( longValue ) ); + EXPECT_TRUE( !longValue.isValid() ); + EXPECT_TRUE( moved.asStdString() == longString ); + EXPECT_EQ( moved.asStdString().data(), longData ); + + Variant copyAssigned( std::string( 512, 'z' ) ); + copyAssigned = moved; + EXPECT_TRUE( copyAssigned.asStdString() == longString ); + + Variant moveAssigned( std::string( 128, 'q' ) ); + const char* movedData = moved.asStdString().data(); + moveAssigned = std::move( moved ); + EXPECT_TRUE( !moved.isValid() ); + EXPECT_EQ( moveAssigned.asStdString().data(), movedData ); + EXPECT_TRUE( moveAssigned.asStdString() == longString ); + + copyAssigned = copyAssigned; + EXPECT_TRUE( copyAssigned.asStdString() == longString ); + Variant* self = &moveAssigned; + moveAssigned = std::move( *self ); + EXPECT_TRUE( moveAssigned.asStdString() == longString ); + + Variant empty( std::string{} ); + EXPECT_TRUE( empty.asStdString().empty() ); + empty.reset(); + EXPECT_TRUE( !empty.isValid() ); +} + +UTEST( Variant, ownedStringLifecycle ) { + String source( "héllo" ); + Variant value( source ); + EXPECT_TRUE( value.is( Variant::Type::String ) ); + EXPECT_TRUE( value.asString() == source ); + + Variant copied( value ); + EXPECT_TRUE( copied.asString() == source ); + + String movedSource( "moved" ); + Variant movedValue( std::move( movedSource ) ); + EXPECT_TRUE( movedValue.asString() == String( "moved" ) ); + + Variant moved( std::move( value ) ); + EXPECT_TRUE( !value.isValid() ); + EXPECT_TRUE( moved.asString() == source ); + + Variant copyAssigned( String( "capacity" ) ); + copyAssigned = copied; + EXPECT_TRUE( copyAssigned.asString() == source ); + + Variant moveAssigned( String( "old" ) ); + moveAssigned = std::move( copied ); + EXPECT_TRUE( !copied.isValid() ); + EXPECT_TRUE( moveAssigned.asString() == source ); + + moveAssigned.reset(); + EXPECT_TRUE( !moveAssigned.isValid() ); +} + +UTEST( Variant, borrowedStringsRemainBorrowed ) { + std::string stdString = "borrowed std::string"; + String string = "borrowed String"; + + Variant stdPtr = Variant::fromRef( stdString ); + EXPECT_TRUE( stdPtr.is( Variant::Type::StdStringPtr ) ); + EXPECT_EQ( &stdPtr.asStdStringPtr(), &stdString ); + EXPECT_TRUE( stdPtr.asStdStringView() == stdString ); + EXPECT_TRUE( stdPtr.toString() == stdString ); + EXPECT_EQ( stdPtr.size(), stdString.size() ); + + Variant stdPtrCopy( stdPtr ); + Variant stdPtrMove( std::move( stdPtrCopy ) ); + EXPECT_TRUE( !stdPtrCopy.isValid() ); + EXPECT_EQ( &stdPtrMove.asStdStringPtr(), &stdString ); + stdPtrMove.reset(); + EXPECT_TRUE( stdString == "borrowed std::string" ); + + Variant stringPtr( &string ); + EXPECT_TRUE( stringPtr.is( Variant::Type::StringPtr ) ); + EXPECT_EQ( &stringPtr.asStringPtr(), &string ); + EXPECT_TRUE( stringPtr.toString() == string.toUtf8() ); + stringPtr.reset(); + EXPECT_TRUE( string == String( "borrowed String" ) ); + + Variant cstr( "borrowed cstr" ); + EXPECT_TRUE( cstr.is( Variant::Type::cstr ) ); + EXPECT_TRUE( cstr.asStdStringView() == "borrowed cstr" ); + EXPECT_EQ( cstr.size(), 13u ); +} + +UTEST( Variant, stringComparisonAndOrdering ) { + std::string borrowed = "beta"; + Variant owned( std::string( "beta" ) ); + Variant pointer = Variant::fromRef( borrowed ); + Variant cstr( "beta" ); + Variant alpha( std::string( "alpha" ) ); + + EXPECT_TRUE( owned == pointer ); + EXPECT_TRUE( pointer == cstr ); + EXPECT_TRUE( alpha < pointer ); + EXPECT_TRUE( owned.toString() == "beta" ); +} + +UTEST( Variant, valueAndPointerAlternatives ) { + int pointedValue = 42; + void* data = &pointedValue; + auto* icon = reinterpret_cast( &pointedValue ); + const Vector2f vector( 10.f, 20.f ); + const Rectf rect( 1.f, 2.f, 3.f, 4.f ); + + Variant boolValue( true ); + Variant floatValue( 1.5f ); + Variant intValue( -42 ); + Variant uintValue( std::numeric_limits::max() ); + Variant int64Value( Int64( -9000000000 ) ); + Variant uint64Value( Uint64( 18000000000ULL ) ); + Variant dataValue( data ); + Variant iconValue( icon ); + Variant vectorValue( vector ); + Variant rectValue( rect ); + + EXPECT_TRUE( boolValue.asBool() ); + EXPECT_EQ( floatValue.asFloat(), 1.5f ); + EXPECT_EQ( intValue.asInt(), -42 ); + EXPECT_EQ( uintValue.asUint(), std::numeric_limits::max() ); + EXPECT_EQ( int64Value.asInt64(), Int64( -9000000000 ) ); + EXPECT_EQ( uint64Value.asUint64(), Uint64( 18000000000ULL ) ); + EXPECT_EQ( dataValue.asDataPtr(), data ); + EXPECT_EQ( iconValue.asIcon(), icon ); + EXPECT_TRUE( vectorValue.asVector2f() == vector ); + EXPECT_TRUE( rectValue.asRectf() == rect ); + EXPECT_EQ( vectorValue.size(), sizeof( Vector2f ) ); + EXPECT_EQ( rectValue.size(), sizeof( Rectf ) ); + + Variant copied( vectorValue ); + EXPECT_TRUE( copied.asVector2f() == vector ); + Variant moved( std::move( copied ) ); + EXPECT_TRUE( !copied.isValid() ); + EXPECT_TRUE( moved.asVector2f() == vector ); + moved = rectValue; + EXPECT_TRUE( moved.is( Variant::Type::Rectf ) ); + EXPECT_TRUE( moved.asRectf() == rect ); +} + +UTEST( Variant, typeTransitions ) { + Variant value( std::string( "text" ) ); + value = Variant( 42 ); + EXPECT_TRUE( value.is( Variant::Type::Int ) ); + EXPECT_EQ( value.asInt(), 42 ); + + value = Variant( String( "unicode" ) ); + EXPECT_TRUE( value.is( Variant::Type::String ) ); + value = Variant( DrawablePtr{} ); + EXPECT_TRUE( value.is( Variant::Type::Drawable ) ); + value.reset(); + EXPECT_TRUE( !value.isValid() ); + + value = Variant( Vector2f( 2.f, 3.f ) ); + value = Variant( std::string( "again" ) ); + EXPECT_TRUE( value.is( Variant::Type::StdString ) ); + EXPECT_TRUE( value.asStdString() == "again" ); +} + +UTEST( Variant, drawableOwnershipLifecycle ) { + auto drawable = makeResource( Vector2f( 1.f, 2.f ), Sizef( 3.f, 4.f ) ); + ASSERT_TRUE( drawable != nullptr ); + const auto initialRefs = drawable.use_count(); + + Variant original( drawable ); + EXPECT_EQ( drawable.use_count(), initialRefs + 1 ); + { + Variant copied( original ); + EXPECT_EQ( drawable.use_count(), initialRefs + 2 ); + Variant moved( std::move( copied ) ); + EXPECT_TRUE( !copied.isValid() ); + EXPECT_EQ( drawable.use_count(), initialRefs + 2 ); + Variant moveAssigned( DrawablePtr{} ); + moveAssigned = std::move( moved ); + EXPECT_TRUE( !moved.isValid() ); + EXPECT_EQ( drawable.use_count(), initialRefs + 2 ); + + Variant assigned( DrawablePtr{} ); + assigned = original; + EXPECT_EQ( drawable.use_count(), initialRefs + 3 ); + assigned = Variant( 7 ); + EXPECT_EQ( drawable.use_count(), initialRefs + 2 ); + moveAssigned.reset(); + EXPECT_EQ( drawable.use_count(), initialRefs + 1 ); + } + + original.reset(); + EXPECT_EQ( drawable.use_count(), initialRefs ); +} diff --git a/src/tools/eproc/eproc.cpp b/src/tools/eproc/eproc.cpp index 0fc6a0e2c..fbf4ccc78 100644 --- a/src/tools/eproc/eproc.cpp +++ b/src/tools/eproc/eproc.cpp @@ -7,6 +7,7 @@ #include #include +#include #if EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_MACOS || \ EE_PLATFORM == EE_PLATFORM_BSD #include @@ -153,6 +154,21 @@ const char* usernameClass( const ProcessInfo& process ) { return "eproc-process-username-other"; } +void setCellClassEnabled( UITableCell& cell, std::string_view className, bool enabled ) { + const bool hasClass = cell.hasClass( className ); + if ( enabled == hasClass ) + return; + if ( enabled ) + cell.addClass( std::string{ className } ); + else + cell.removeClass( std::string{ className } ); +} + +constexpr std::array kUsernameClasses = { + "eproc-process-username-ended", "eproc-process-username-traced", "eproc-process-username-own", + "eproc-process-username-system", "eproc-process-username-other", +}; + } // namespace App::App() { @@ -174,7 +190,7 @@ App::App() { mApp = std::make_unique( ws ); if ( mApp->getUI() && mApp->getWindow() ) mApp->getWindow()->setTitle( - mApp->getUI()->i18n( "eproc_window_title", "System Monitor" ) ); + mApp->getUI()->i18n( "eproc_window_title", "eproc - System Monitor" ) ); } App::~App() {} @@ -504,6 +520,8 @@ void App::setupUI() { UIWidgetInspector::create( mApp->getUI() ); } } ); + + mSearchInput->setFocus(); } void App::setupProcessTable() { @@ -515,23 +533,20 @@ void App::setupProcessTable() { mTableView->setColumnsHidden( std::vector( kOptionalProcessColumns.begin(), kOptionalProcessColumns.end() ), true ); - mTableView->setOnUpdateCellCb( [this]( UITableCell* cell, Model* model ) { - if ( !cell || !model ) + mTableView->setOnUpdateCellCb( [this]( UITableCell* cell, Model* ) { + if ( !cell ) return; const ModelIndex index = cell->getCurIndex(); const ProcessInfo* process = processForProxyIndex( index ); - const Variant columnClass = model->data( index, ModelRole::Class ); - std::vector classes; - if ( columnClass.isValid() ) - classes.emplace_back( columnClass.toString() ); - if ( process ) { - if ( process->status == ProcessStatus::Ended ) - classes.emplace_back( "eproc-process-ended" ); - if ( index.column() == ProcessModel::ColUsername ) - classes.emplace_back( usernameClass( *process ) ); - } - cell->setClasses( classes ); + setCellClassEnabled( *cell, "eproc-process-ended", + process && process->status == ProcessStatus::Ended ); + + const std::string_view desiredUsernameClass = + process && index.column() == ProcessModel::ColUsername ? usernameClass( *process ) + : std::string_view{}; + for ( const auto className : kUsernameClasses ) + setCellClassEnabled( *cell, className, className == desiredUsernameClass ); } ); mTableView->setRowHeight( 28 ); // The flexible column is Name; the icon column is fixed so every row lines up. diff --git a/src/tools/eproc/process_model.cpp b/src/tools/eproc/process_model.cpp index bfd292185..de4641b47 100644 --- a/src/tools/eproc/process_model.cpp +++ b/src/tools/eproc/process_model.cpp @@ -193,15 +193,13 @@ Variant ProcessModel::data( const ModelIndex& index, ModelRole role ) const { switch ( index.column() ) { case ColName: - return Variant( proc->name ); + return Variant::fromRef( proc->name ); case ColPid: return Variant( String::toString( static_cast( proc->pid ) ) ); case ColUsername: - return Variant( proc->username ); - case ColCpu: { - std::string cpu = proc->formatCpu(); - return Variant( cpu ); - } + return Variant::fromRef( proc->username ); + case ColCpu: + return Variant( proc->formatCpu() ); case ColMemory: return Variant( proc->formatMemory() ); case ColSharedMem: @@ -215,7 +213,7 @@ Variant ProcessModel::data( const ModelIndex& index, ModelRole role ) const { case ColUpload: return Variant( proc->formatUpload() ); case ColCommand: - return Variant( proc->command ); + return Variant::fromRef( proc->command ); case ColTotalMemory: return Variant( formatKiB( proc->vmRSS ) ); case ColVirtualSize: @@ -228,7 +226,7 @@ Variant ProcessModel::data( const ModelIndex& index, ModelRole role ) const { return Variant( proc->formatRelativeStartTime( mSystemInfo.uptimeSeconds, mSystemInfo.clockTicksPerSecond ) ); case ColTty: - return Variant( proc->tty ); + return Variant::fromRef( proc->tty ); case ColIoRead: return Variant( formatBytes( proc->ioReadBytes ) ); case ColIoWrite: