Skip to content

Commit

Permalink
Allow having bindings referencing const Properties (#72)
Browse files Browse the repository at this point in the history
* Allow having bindings referencing const Properties

* Have a single node type for const/non-const property

* remove const property node class

* Fix CI

* remove the special template parameter for constness
  • Loading branch information
phyBrackets authored Jul 2, 2024
1 parent fd6ec5f commit dcb3831
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 9 deletions.
36 changes: 36 additions & 0 deletions src/kdbindings/binding.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ inline std::unique_ptr<Binding<T, EvaluatorT>> makeBinding(EvaluatorT &evaluator
return std::make_unique<Binding<T, EvaluatorT>>(Private::makeNode(property), evaluator);
}

/**
* @brief Creates a binding from a const property using a specified evaluator.
*
* @tparam T The type of the value that the Binding expression evaluates to.
* @tparam EvaluatorT The type of the evaluator that is used to evaluate the Binding.
* @param evaluator The evaluator that is used to evaluate the Binding.
* @param property The const Property to create a Binding from.
* @return std::unique_ptr<Binding<T, EvaluatorT>> A new Binding that is powered by the evaluator.
*
* *Note: Using a const Property ensures that the source cannot be modified through this binding,
* maintaining data integrity and supporting scenarios where data should only be observed, not altered.*
*/
template<typename T, typename EvaluatorT>
inline std::unique_ptr<Binding<T, EvaluatorT>> makeBinding(EvaluatorT &evaluator, const Property<T> &property)
{
return std::make_unique<Binding<T, EvaluatorT>>(Private::makeNode(property), evaluator);
}

/**
* @brief Helper function to create a Binding from a root Node.
*
Expand Down Expand Up @@ -227,6 +245,24 @@ inline std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> makeBinding(Proper
return std::make_unique<Binding<T, ImmediateBindingEvaluator>>(Private::makeNode(property));
}

/**
* @brief Creates an immediate mode binding from a const property.
*
* @tparam T The type of the value that the Binding expression evaluates to.
* @param property The const Property to create a Binding from.
* @return std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> A new Binding that is powered by an
* immediate mode evaluator, ensuring quick updates to changes.
*
* *Note: This binding type is especially suited for scenarios where you need to reflect
* changes in a property to some dependent components without the need to alter the
* source property itself.*
*/
template<typename T>
inline std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> makeBinding(const Property<T> &property)
{
return std::make_unique<Binding<T, ImmediateBindingEvaluator>>(Private::makeNode(property));
}

/**
* @brief Helper function to create an immediate mode Binding from a root Node.
*
Expand Down
11 changes: 11 additions & 0 deletions src/kdbindings/make_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ struct bindable_value_type_<Property<T>> {
using type = T;
};

template<typename T>
struct bindable_value_type_<const Property<T>> {
using type = T;
};

template<typename T>
struct bindable_value_type_<NodeInterface<T>> {
using type = T;
Expand Down Expand Up @@ -69,6 +74,12 @@ inline Node<T> makeNode(Property<T> &property)
return Node<T>(std::make_unique<PropertyNode<T>>(property));
}

template<typename T>
inline Node<T> makeNode(const Property<T> &property)
{
return Node<T>(std::make_unique<PropertyNode<T>>(property));
}

template<typename T>
inline Node<T> makeNode(Node<T> &&node)
{
Expand Down
17 changes: 9 additions & 8 deletions src/kdbindings/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ template<typename PropertyType>
class PropertyNode : public NodeInterface<PropertyType>
{
public:
explicit PropertyNode(Property<PropertyType> &property)
explicit PropertyNode(const Property<PropertyType> &property)
: m_parent(nullptr), m_dirty(false)
{
setProperty(property);
Expand Down Expand Up @@ -184,8 +184,7 @@ class PropertyNode : public NodeInterface<PropertyType>
return m_property->get();
}

// This must currently take a const reference, as the "moved" signal emits a const&
void propertyMoved(Property<PropertyType> &property)
void propertyMoved(const Property<PropertyType> &property)
{
if (&property != m_property) {
m_property = &property;
Expand All @@ -206,15 +205,17 @@ class PropertyNode : public NodeInterface<PropertyType>
const bool *dirtyVariable() const override { return &m_dirty; }

private:
void setProperty(Property<PropertyType> &property)
void setProperty(const Property<PropertyType> &property)
{
m_property = &property;
m_valueChangedHandle = m_property->valueChanged().connect(&PropertyNode<PropertyType>::markDirty, this);
m_movedHandle = m_property->m_moved.connect(&PropertyNode<PropertyType>::propertyMoved, this);
m_destroyedHandle = m_property->destroyed().connect(&PropertyNode<PropertyType>::propertyDestroyed, this);

// Connect to all signals, even for const properties
m_valueChangedHandle = m_property->valueChanged().connect([this]() { this->markDirty(); });
m_movedHandle = m_property->m_moved.connect([this](const Property<PropertyType> &newProp) { this->propertyMoved(newProp); });
m_destroyedHandle = m_property->destroyed().connect([this]() { this->propertyDestroyed(); });
}

Property<PropertyType> *m_property;
const Property<PropertyType> *m_property;
ConnectionHandle m_movedHandle;
ConnectionHandle m_valueChangedHandle;
ConnectionHandle m_destroyedHandle;
Expand Down
2 changes: 1 addition & 1 deletion src/kdbindings/property.h
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ class Property
// keep track of moved Properties.
template<typename PropertyType>
friend class Private::PropertyNode;
Signal<Property<T> &> m_moved;
mutable Signal<Property<T> &> m_moved;

mutable Signal<> m_destroyed;
std::unique_ptr<PropertyUpdater<T>> m_updater;
Expand Down
35 changes: 35 additions & 0 deletions tests/binding/tst_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,41 @@ TEST_CASE("Create property bindings using helper function")
}
}

TEST_CASE("Binding with const Property")
{
SUBCASE("A binding created from a const property reflects its value")
{
const Property<int> constSource(5);
auto bound = makeBoundProperty(constSource);

REQUIRE(bound.get() == 5); // Ensure the bound property reflects the const source
}

SUBCASE("A binding to a const property does not allow modification through set")
{
const Property<int> constSource(5);
auto bound = makeBoundProperty(constSource);

REQUIRE_THROWS_AS(bound.set(10), ReadOnlyProperty);
}

SUBCASE("Updates to const property are reflected in the binding")
{
Property<int> source(5);
const Property<int> &constRefSource = source; // Create a const reference to a non-const Property
auto bound = makeBoundProperty(constRefSource);

REQUIRE(bound.get() == 5);

bool called = false;
(void)bound.valueChanged().connect([&called]() { called = true; });

source = 10; // Change the original non-const property
REQUIRE(called);
REQUIRE(bound.get() == 10);
}
}

TEST_CASE("Binding reassignment")
{
SUBCASE("A binding can not be override by a constant value")
Expand Down

0 comments on commit dcb3831

Please sign in to comment.