Added UIProperty to easily bind values with UI elements (very basic initial implementation).

This commit is contained in:
Martín Lucas Golini
2025-06-04 00:24:21 -03:00
parent 0417c51f5c
commit 61e2df83db
6 changed files with 173 additions and 50 deletions

View File

@@ -252,6 +252,42 @@
"command": "${project_root}/bin/eepp-7guis-cells-debug",
"name": "eepp-7guis-cells-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-7guis-temperature-converter-debug",
"name": "eepp-7guis-temperature-converter-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-7guis-circle-drawer-debug",
"name": "eepp-7guis-circle-drawer-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-7guis-counter-debug",
"name": "eepp-7guis-counter-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-7guis-crud-debug",
"name": "eepp-7guis-crud-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-7guis-flight-booker-debug",
"name": "eepp-7guis-flight-booker-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-7guis-timer-debug",
"name": "eepp-7guis-timer-debug",
"working_dir": "${project_root}/bin"
}
],
"var": {

View File

@@ -86,6 +86,7 @@
#include <eepp/ui/models/widgettreemodel.hpp>
#include <eepp/ui/uidatabind.hpp>
#include <eepp/ui/uiproperty.hpp>
#include <eepp/ui/undostack.hpp>

View File

@@ -4,7 +4,7 @@
#include <eepp/system/log.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <memory>
#include <set>
#include <unordered_set>
#include <variant>
namespace EE { namespace UI {
@@ -23,12 +23,33 @@ template <typename T> class UIDataBind {
};
static Converter converterDefault() {
return Converter( []( const UIDataBind<T>*, T& val,
const std::string& str ) { return String::fromString( val, str ); },
[]( const UIDataBind<T>*, std::string& str, const T& val ) {
str = String::toString( val );
return true;
} );
return Converter(
[]( const UIDataBind<T>* databind, T& val, const std::string& str ) {
if constexpr ( std::is_same_v<T, std::string> ) {
str = val;
return true;
} else if constexpr ( std::is_same_v<T, bool> ) {
val = StyleSheetProperty( databind->getPropertyDefinition(), str ).asBool();
return true;
} else {
return String::fromString( val, str );
}
},
[]( const UIDataBind<T>*, std::string& str, const T& val ) {
if constexpr ( std::is_same_v<T, std::string> ||
std::is_same_v<T, std::string_view> ) {
str = val;
} else if constexpr ( std::is_same_v<T, double> ) {
str = String::fromDouble( val );
} else if constexpr ( std::is_same_v<T, float> ) {
str = String::fromFloat( val );
} else if constexpr ( std::is_same_v<T, bool> ) {
str = val ? "true" : "false";
} else {
str = String::toString( val );
}
return true;
} );
}
static Converter converterString() {
@@ -56,7 +77,7 @@ template <typename T> class UIDataBind {
}
static std::unique_ptr<UIDataBind<T>>
New( T* t, const std::set<UIWidget*>& widgets,
New( T* t, const std::unordered_set<UIWidget*>& widgets,
const Converter& converter = UIDataBind<T>::converterDefault(),
const std::string& valueKey = "value",
const Event::EventType& eventType = Event::OnValueChange ) {
@@ -74,7 +95,7 @@ template <typename T> class UIDataBind {
UIDataBind() {}
UIDataBind( T* t, const std::set<UIWidget*>& widgets,
UIDataBind( T* t, const std::unordered_set<UIWidget*>& widgets,
const Converter& converter = UIDataBind<T>::converterDefault(),
const std::string& valueKey = "value",
const Event::EventType& eventType = Event::OnValueChange ) {
@@ -88,7 +109,7 @@ template <typename T> class UIDataBind {
init( t, { widget }, converter, valueKey, eventType );
}
void init( T* t, const std::set<UIWidget*>& widgets,
void init( T* t, const std::unordered_set<UIWidget*>& widgets,
const Converter& converter = UIDataBind<T>::converterDefault(),
const std::string& valueKey = "value",
const Event::EventType& eventType = Event::OnValueChange ) {
@@ -103,6 +124,8 @@ template <typename T> class UIDataBind {
}
void set( const T& t ) {
if ( t == *data )
return;
inSetValue = true;
*data = t;
setValueChange();
@@ -111,6 +134,17 @@ template <typename T> class UIDataBind {
onValueChangeCb( t );
}
void set( T&& t ) {
if ( t == *data )
return;
inSetValue = true;
*data = std::move( t );
setValueChange();
inSetValue = false;
if ( onValueChangeCb )
onValueChangeCb( t );
}
const T& get() const { return *data; }
void reset() {
@@ -151,11 +185,11 @@ template <typename T> class UIDataBind {
std::function<void( const T& newVal )> onValueChangeCb;
const std::set<UIWidget*>& getWidgets() const { return widgets; }
const std::unordered_set<UIWidget*>& getWidgets() const { return widgets; }
protected:
T* data{ nullptr };
std::set<UIWidget*> widgets;
std::unordered_set<UIWidget*> widgets;
std::unordered_map<UIWidget*, Uint32> valueCbs;
std::unordered_map<UIWidget*, Uint32> closeCbs;
bool inSetValue{ false };
@@ -215,7 +249,7 @@ class UIDataBindBool {
using Ptr = std::unique_ptr<UIDataBind<bool>>;
static Ptr
New( bool* t, const std::set<UIWidget*>& widgets,
New( bool* t, const std::unordered_set<UIWidget*>& widgets,
const UIDataBind<bool>::Converter& converter = UIDataBind<bool>::converterBool(),
const std::string& valueKey = "value" ) {
return UIDataBind<bool>::New( t, widgets, converter, valueKey );
@@ -233,7 +267,7 @@ class UIDataBindString {
public:
using Ptr = std::unique_ptr<UIDataBind<std::string>>;
static Ptr New( std::string* t, const std::set<UIWidget*>& widgets,
static Ptr New( std::string* t, const std::unordered_set<UIWidget*>& widgets,
const UIDataBind<std::string>::Converter& converter =
UIDataBind<std::string>::converterString(),
const std::string& valueKey = "text",

View File

@@ -0,0 +1,74 @@
#include <eepp/ui/uidatabind.hpp>
namespace EE { namespace UI {
template <typename T> class UIProperty {
public:
UIProperty() {}
UIProperty( T defaultValue, UIWidget* widget,
const UIDataBind<T>::Converter& converter = UIDataBind<T>::converterDefault(),
const std::string& valueKey = "value",
const Event::EventType& eventType = Event::OnValueChange ) :
mValue( std::move( defaultValue ) ),
mBindedData( &mValue, widget, converter, valueKey, eventType ) {}
UIProperty( T defaultValue, const std::unordered_set<UIWidget*>& widgets = {},
const UIDataBind<T>::Converter& converter = UIDataBind<T>::converterDefault(),
const std::string& valueKey = "value",
const Event::EventType& eventType = Event::OnValueChange ) :
mValue( std::move( defaultValue ) ),
mBindedData( &mValue, widgets, converter, valueKey, eventType ) {}
UIProperty( const std::unordered_set<UIWidget*>& widgets = {},
const UIDataBind<T>::Converter& converter = UIDataBind<T>::converterDefault(),
const std::string& valueKey = "value",
const Event::EventType& eventType = Event::OnValueChange ) :
mBindedData( &mValue, widgets, converter, valueKey, eventType ) {}
UIProperty( UIWidget* widget,
const UIDataBind<T>::Converter& converter = UIDataBind<T>::converterDefault(),
const std::string& valueKey = "value",
const Event::EventType& eventType = Event::OnValueChange ) :
mBindedData( &mValue, widget, converter, valueKey, eventType ) {}
void operator=( const T& newVal ) { mBindedData.set( newVal ); }
void operator=( T&& newVal ) { mBindedData.set( std::move( newVal ) ); }
const T& value() const { return mBindedData.get(); }
const UIDataBind<T>& databind() const { return mBindedData; }
UIProperty& connect( UIWidget* widget ) {
mBindedData.bind( widget );
return *this;
}
UIProperty& disconnect( UIWidget* widget ) {
mBindedData.unbind( widget );
return *this;
}
const T& operator*() const noexcept { return value(); }
const T* operator->() const noexcept { return &value(); }
operator const T&() const noexcept { return value(); }
UIProperty& changed( const std::function<void( const T& newVal )>& fn ) {
mBindedData.onValueChangeCb = fn;
return *this;
}
UIProperty& changed( std::function<void( const T& newVal )>&& fn ) {
mBindedData.onValueChangeCb = std::move( fn );
return *this;
}
protected:
T mValue{};
UIDataBind<T> mBindedData;
};
}} // namespace EE::UI

View File

@@ -687,6 +687,7 @@ bool UITextView::applyProperty( const StyleSheetProperty& attribute ) {
return false;
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
case PropertyId::Value:
case PropertyId::Text:
setText( getTranslatorString( attribute.value() ) );
break;
@@ -786,6 +787,7 @@ std::string UITextView::getPropertyString( const PropertyDefinition* propertyDef
return "";
switch ( propertyDef->getPropertyId() ) {
case PropertyId::Value:
case PropertyId::Text:
return getText().toUtf8();
case PropertyId::TextTransform:
@@ -829,21 +831,14 @@ std::string UITextView::getPropertyString( const PropertyDefinition* propertyDef
std::vector<PropertyId> UITextView::getPropertiesImplemented() const {
auto props = UIWidget::getPropertiesImplemented();
auto local = { PropertyId::Text,
PropertyId::TextTransform,
PropertyId::Color,
PropertyId::TextShadowColor,
PropertyId::TextShadowOffset,
PropertyId::SelectionColor,
PropertyId::SelectionBackColor,
PropertyId::FontFamily,
PropertyId::FontSize,
PropertyId::FontStyle,
PropertyId::Wordwrap,
PropertyId::TextStrokeWidth,
PropertyId::TextStrokeColor,
PropertyId::TextSelection,
PropertyId::TextAlign,
auto local = { PropertyId::Value, PropertyId::Text,
PropertyId::TextTransform, PropertyId::Color,
PropertyId::TextShadowColor, PropertyId::TextShadowOffset,
PropertyId::SelectionColor, PropertyId::SelectionBackColor,
PropertyId::FontFamily, PropertyId::FontSize,
PropertyId::FontStyle, PropertyId::Wordwrap,
PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor,
PropertyId::TextSelection, PropertyId::TextAlign,
PropertyId::TextOverflow };
props.insert( props.end(), local.begin(), local.end() );
return props;

View File

@@ -11,26 +11,9 @@ EE_MAIN_FUNC int main( int, char** ) {
<TextView text="Fahrenheit" layout_height="match_parent" padding="0dp 4dp 0dp 4dp" enabled="false" />
</hbox>
)xml" );
UITextInput* celsiusInput = hbox->find<UITextInput>( "celsius_input" );
UITextInput* fahrenheitInput = hbox->find<UITextInput>( "fahrenheit_input" );
const auto f2c = []( double f ) { return ( f - 32 ) * 5 / 9; };
const auto c2f = []( double c ) { return c * 9 / 5 + 32; };
bool converting = false;
const auto convert = [&]( bool fromCelsius ) {
if ( converting ) // Only process input value change and skip value change from setText
return;
BoolScopedOp op( converting, true );
auto sourceInput = fromCelsius ? celsiusInput : fahrenheitInput;
double val;
if ( !String::fromString( val, sourceInput->getText() ) )
return;
auto str( String::fromDouble( fromCelsius ? c2f( val ) : f2c( val ) ) );
if ( fromCelsius )
fahrenheitInput->setText( str );
else
celsiusInput->setText( str );
};
celsiusInput->setFocus()->on( Event::OnValueChange, [&convert]( auto ) { convert( true ); } );
fahrenheitInput->on( Event::OnValueChange, [&convert]( auto ) { convert( false ); } );
UIProperty<double> celsius( hbox->find( "celsius_input" )->setFocus()->asType<UIWidget>() );
UIProperty<double> fahrenheit( hbox->find<UITextInput>( "fahrenheit_input" ) );
celsius.changed( [&fahrenheit]( auto c ) { fahrenheit = c * 9 / 5 + 32; } );
fahrenheit.changed( [&celsius]( auto f ) { celsius = ( f - 32 ) * 5 / 9; } );
return app.run();
}