From ec7f588ad6c42f74460654bdd8b817fcb7b40461 Mon Sep 17 00:00:00 2001 From: Shivam Kunwar Date: Sun, 2 Jun 2024 12:43:51 +0530 Subject: [PATCH] Allow having bindings referencing const Properties --- src/kdbindings/binding.h | 36 ++++++++++++++++++++++ src/kdbindings/make_node.h | 11 +++++++ src/kdbindings/node.h | 58 +++++++++++++++++++++++++++++++++++ tests/binding/tst_binding.cpp | 35 +++++++++++++++++++++ 4 files changed, 140 insertions(+) diff --git a/src/kdbindings/binding.h b/src/kdbindings/binding.h index 4bcd1cc..400889c 100644 --- a/src/kdbindings/binding.h +++ b/src/kdbindings/binding.h @@ -125,6 +125,24 @@ inline std::unique_ptr> makeBinding(EvaluatorT &evaluator return std::make_unique>(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> 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 +inline std::unique_ptr> makeBinding(EvaluatorT &evaluator, const Property &property) +{ + return std::make_unique>(Private::makeNode(property), evaluator); +} + /** * @brief Helper function to create a Binding from a root Node. * @@ -227,6 +245,24 @@ inline std::unique_ptr> makeBinding(Proper return std::make_unique>(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> 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 +inline std::unique_ptr> makeBinding(const Property &property) +{ + return std::make_unique>(Private::makeNode(property)); +} + /** * @brief Helper function to create an immediate mode Binding from a root Node. * diff --git a/src/kdbindings/make_node.h b/src/kdbindings/make_node.h index 22bd374..6ac65e3 100644 --- a/src/kdbindings/make_node.h +++ b/src/kdbindings/make_node.h @@ -28,6 +28,11 @@ struct bindable_value_type_> { using type = T; }; +template +struct bindable_value_type_> { + using type = T; +}; + template struct bindable_value_type_> { using type = T; @@ -69,6 +74,12 @@ inline Node makeNode(Property &property) return Node(std::make_unique>(property)); } +template +inline Node makeNode(const Property &property) +{ + return Node(std::make_unique>(property)); +} + template inline Node makeNode(Node &&node) { diff --git a/src/kdbindings/node.h b/src/kdbindings/node.h index aafe039..5f5ebdc 100644 --- a/src/kdbindings/node.h +++ b/src/kdbindings/node.h @@ -223,6 +223,64 @@ class PropertyNode : public NodeInterface mutable bool m_dirty; }; +template +class ConstPropertyNode : public NodeInterface +{ +public: + explicit ConstPropertyNode(const Property &property) + : m_parent(nullptr), m_dirty(false), m_property(&property) + { + m_valueChangedHandle = property.valueChanged().connect(&ConstPropertyNode::markDirty, this); + m_destroyedHandle = property.destroyed().connect(&ConstPropertyNode::propertyDestroyed, this); + } + + // Copy constructor + ConstPropertyNode(const ConstPropertyNode &other) + : m_parent(nullptr), m_dirty(false), m_property(other.m_property) + { + if (m_property) { + m_valueChangedHandle = m_property->valueChanged().connect(&ConstPropertyNode::markDirty, this); + m_destroyedHandle = m_property->destroyed().connect(&ConstPropertyNode::propertyDestroyed, this); + } + } + + // Move constructor is deleted to prevent moving a const reference + ConstPropertyNode(ConstPropertyNode &&) = delete; + + virtual ~ConstPropertyNode() + { + m_valueChangedHandle.disconnect(); + m_destroyedHandle.disconnect(); + } + + const PropertyType &evaluate() const override + { + if (!m_property) { + throw PropertyDestroyedError("The Property this node refers to no longer exists!"); + } + + m_dirty = false; + return m_property->get(); + } + + void propertyDestroyed() + { + m_property = nullptr; + } + +protected: + Dirtyable **parentVariable() override { return &m_parent; } + const bool *dirtyVariable() const override { return &m_dirty; } + +private: + const Property *m_property; + ConnectionHandle m_valueChangedHandle; + ConnectionHandle m_destroyedHandle; + + Dirtyable *m_parent; + mutable bool m_dirty; +}; + template class OperatorNode : public NodeInterface { diff --git a/tests/binding/tst_binding.cpp b/tests/binding/tst_binding.cpp index 22010d1..8e818d8 100644 --- a/tests/binding/tst_binding.cpp +++ b/tests/binding/tst_binding.cpp @@ -309,6 +309,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 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 constSource(5); + auto bound = makeBoundProperty(constSource); + + REQUIRE_THROWS_AS(bound.set(10), ReadOnlyProperty); + } + + SUBCASE("Updates to const property are reflected in the binding") + { + Property source(5); + const Property &constRefSource = source; // Create a const reference to a non-const Property + auto bound = makeBoundProperty(constRefSource); + + REQUIRE(bound.get() == 5); + + bool called = false; + 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")