Add KDBindings to 3rdparty
To replace Qt in the backend.
This commit is contained in:
298
3rdparty/kdbindings/binding.h
vendored
Normal file
298
3rdparty/kdbindings/binding.h
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kdbindings/node.h>
|
||||
#include <kdbindings/node_operators.h>
|
||||
#include <kdbindings/node_functions.h>
|
||||
#include <kdbindings/make_node.h>
|
||||
#include <kdbindings/binding_evaluator.h>
|
||||
#include <kdbindings/property_updater.h>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
/**
|
||||
* @brief A combination of a root Node with an evaluator.
|
||||
*
|
||||
* A root Node is formed whenever multiple properties are combined inside
|
||||
* a expression and an evaluator is responsible for re-evaluating such
|
||||
* an expression whenever any of the constituent properties change.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
template<typename T, typename EvaluatorT = BindingEvaluator>
|
||||
class Binding : public PropertyUpdater<T>, public Private::Dirtyable
|
||||
{
|
||||
static_assert(
|
||||
std::is_base_of<BindingEvaluator, EvaluatorT>::value,
|
||||
"The EvaluatorT type must inherit from BindingEvaluator.");
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Binding with a specific evaluator.
|
||||
*
|
||||
* @param rootNode Represents that expression contained in the Binding.
|
||||
* @param evaluator Used to evaluate the expression contained in the Binding.
|
||||
*/
|
||||
explicit Binding(Private::Node<T> &&rootNode, EvaluatorT const &evaluator)
|
||||
: m_rootNode{ std::move(rootNode) }
|
||||
, m_evaluator{ evaluator }
|
||||
{
|
||||
m_bindingId = m_evaluator.insert(this);
|
||||
m_rootNode.setParent(this);
|
||||
}
|
||||
|
||||
/** Destructs the Binding by deregistering it from its evaluator. */
|
||||
~Binding() override
|
||||
{
|
||||
m_evaluator.remove(m_bindingId);
|
||||
}
|
||||
|
||||
/** A Binding is not default constructible. */
|
||||
Binding() = delete;
|
||||
|
||||
/** A Binding cannot be copy constructed. */
|
||||
Binding(Binding const &other) = delete;
|
||||
/** A Binding cannot be copy assigned. */
|
||||
Binding &operator=(Binding const &other) = delete;
|
||||
|
||||
// Move construction would invalidate the this pointer passed to the evaluator
|
||||
// in the constructor
|
||||
/** A Binding can not be move constructed. */
|
||||
Binding(Binding &&other) = delete;
|
||||
/** A Binding can not be move assigned. */
|
||||
Binding &operator=(Binding &&other) = delete;
|
||||
|
||||
/** Set the function that should be used to notify
|
||||
* associated properties when the Binding re-evaluates.
|
||||
*/
|
||||
void setUpdateFunction(std::function<void(T &&)> const &updateFunction) override
|
||||
{
|
||||
m_propertyUpdateFunction = updateFunction;
|
||||
}
|
||||
|
||||
/** Returns the current value of the Binding. */
|
||||
T get() const override { return m_rootNode.evaluate(); }
|
||||
|
||||
/** Re-evaluates the value of the Binding and notifies all dependants of the change. */
|
||||
void evaluate()
|
||||
{
|
||||
T value = m_rootNode.evaluate();
|
||||
|
||||
// Use this to update any associated property via the PropertyUpdater's update function
|
||||
m_propertyUpdateFunction(std::move(value));
|
||||
}
|
||||
|
||||
protected:
|
||||
Private::Dirtyable **parentVariable() override { return nullptr; }
|
||||
const bool *dirtyVariable() const override { return nullptr; }
|
||||
|
||||
/** The root Node of the Binding represents the expression contained by the Binding. */
|
||||
Private::Node<T> m_rootNode;
|
||||
/** The evaluator responsible for evaluating this Binding. */
|
||||
EvaluatorT m_evaluator;
|
||||
/** The function used to notify associated properties when the Binding re-evaluates */
|
||||
std::function<void(T &&)> m_propertyUpdateFunction = [](T &&) {};
|
||||
/** The id of the Binding, used for keeping track of the Binding in its evaluator. */
|
||||
int m_bindingId = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper function to create a Binding from a Property.
|
||||
*
|
||||
* @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 Property to create a Binding from.
|
||||
* @return std::unique_ptr<Binding<T, EvaluatorT>> A new Binding that is powered by the evaluator.
|
||||
*
|
||||
* *Note: For the difference between makeBinding and makeBoundProperty, see the
|
||||
* ["Reassigning a Binding"](../../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.*
|
||||
*/
|
||||
template<typename T, typename EvaluatorT>
|
||||
inline std::unique_ptr<Binding<T, EvaluatorT>> makeBinding(EvaluatorT &evaluator, 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.
|
||||
*
|
||||
* @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 rootNode Represents the expression that will be evaluated by the Binding.
|
||||
* @return std::unique_ptr<Binding<T, EvaluatorT>> A new Binding that combines the rootNode with the evaluator.
|
||||
*
|
||||
* *Note: For the difference between makeBinding and makeBoundProperty, see the
|
||||
* ["Reassigning a Binding"](../../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.*
|
||||
*/
|
||||
template<typename T, typename EvaluatorT>
|
||||
inline std::unique_ptr<Binding<T, EvaluatorT>> makeBinding(EvaluatorT &evaluator, Private::Node<T> &&rootNode)
|
||||
{
|
||||
return std::make_unique<Binding<T, EvaluatorT>>(std::move(rootNode), evaluator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to create a Binding from a function and its arguments.
|
||||
*
|
||||
* @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.
|
||||
* @tparam Func The type of the function - may be any type that implements operator().
|
||||
* @param func The function object.
|
||||
* @tparam Args The function argument types
|
||||
* @param args The function arguments - Possible values include: Properties, Constants and Nodes
|
||||
* They will be automatically unwrapped, i.e. a Property<T> will pass a value of type T to func.
|
||||
* @return std::unique_ptr<Binding<ReturnType, EvaluatorT>> where ReturnType is the type that results from evaluationg func with the given arguments.
|
||||
* The Binding will be powered by the new evaluator.
|
||||
*
|
||||
* *Note: For the difference between makeBinding and makeBoundProperty, see the
|
||||
* ["Reassigning a Binding"](../../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.*
|
||||
*/
|
||||
template<typename EvaluatorT, typename Func, typename... Args, typename = std::enable_if_t<sizeof...(Args) != 0>, typename ResultType = Private::operator_node_result_t<Func, Args...>>
|
||||
inline std::unique_ptr<Binding<ResultType, EvaluatorT>> makeBinding(EvaluatorT &evaluator, Func &&func, Args &&...args)
|
||||
{
|
||||
return std::make_unique<Binding<ResultType, EvaluatorT>>(Private::makeNode(std::forward<Func>(func), std::forward<Args>(args)...), evaluator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides a convenience for old-school, immediate mode Bindings.
|
||||
*
|
||||
* This works in conjunction with a do-nothing ImmediateBindingEvaluator class to update the
|
||||
* result of the Binding immediately upon any of the dependent bindables (i.e. Property instances)
|
||||
* notifying that they have changed. This can lead to a Property Binding being evaluated many
|
||||
* times before the result is ever used in a typical GUI application.
|
||||
*
|
||||
* @tparam T The type of the value that the Binding expression evaluates to.
|
||||
*/
|
||||
template<typename T>
|
||||
class Binding<T, ImmediateBindingEvaluator> : public Binding<T, BindingEvaluator>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Binding with an immediate mode evaluator.
|
||||
*
|
||||
* @param rootNode Represents that expression contained in the Binding.
|
||||
*/
|
||||
explicit Binding(Private::Node<T> &&rootNode)
|
||||
: Binding<T, BindingEvaluator>(std::move(rootNode), ImmediateBindingEvaluator::instance())
|
||||
{
|
||||
}
|
||||
|
||||
/** A Binding is not default constructible. */
|
||||
Binding() = delete;
|
||||
|
||||
virtual ~Binding() = default;
|
||||
|
||||
/** A Binding cannot be copy constructed. */
|
||||
Binding(Binding const &other) = delete;
|
||||
/** A Binding cannot be copy assigned. */
|
||||
Binding &operator=(Binding const &other) = delete;
|
||||
|
||||
/** A Binding can not be move constructed. */
|
||||
Binding(Binding &&other) = delete;
|
||||
/** A Binding can not be move assigned. */
|
||||
Binding &operator=(Binding &&other) = delete;
|
||||
|
||||
void markDirty() override
|
||||
{
|
||||
Binding::evaluate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper function to create an immediate mode Binding from a Property.
|
||||
*
|
||||
* @tparam T The type of the value that the Binding expression evaluates to.
|
||||
* @param property The Property to create a Binding from.
|
||||
* @return std::unique_ptr<Binding<T, ImmediateBindingEvaluator>>
|
||||
* An new Binding bound to an existing Property with immediate evaluation.
|
||||
*
|
||||
* *Note: For the difference between makeBinding and makeBoundProperty, see the
|
||||
* ["Reassigning a Binding"](../../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.*
|
||||
*/
|
||||
template<typename T>
|
||||
inline std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> makeBinding(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.
|
||||
*
|
||||
* @tparam T The type of the value that the Binding expression evaluates to.
|
||||
* @param rootNode Represents the expression that will be evaluated by the Binding.
|
||||
* Typically constructed from a unary/binary operator on a Property.
|
||||
* @return std::unique_ptr<Binding<<T, ImmediateBindingEvaluator>> An new Binding bound to a root Node with immediate evaluation.
|
||||
*
|
||||
* *Note: For the difference between makeBinding and makeBoundProperty, see the
|
||||
* ["Reassigning a Binding"](../../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.*
|
||||
*/
|
||||
template<typename T>
|
||||
inline std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> makeBinding(Private::Node<T> &&rootNode)
|
||||
{
|
||||
return std::make_unique<Binding<T, ImmediateBindingEvaluator>>(std::move(rootNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to create an immediate mode Binding from a function and its arguments.
|
||||
*
|
||||
* @tparam Func The type of the function - may be any type that implements operator().
|
||||
* @param func The function object.
|
||||
* @tparam Args The function argument types
|
||||
* @param args The function arguments - Possible values include: Properties, Constants and Nodes
|
||||
* They will be automatically unwrapped, i.e. a Property<T> will pass a value of type T to func.
|
||||
* @return std::unique_ptr<Binding<ReturnType, ImmediateBindingEvaluator>> where ReturnType is the type that results from evaluationg func with the given arguments.
|
||||
* The Binding will feature immediate evaluation.
|
||||
*
|
||||
* *Note: For the difference between makeBinding and makeBoundProperty, see the
|
||||
* ["Reassigning a Binding"](../../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.*
|
||||
*/
|
||||
template<typename Func, typename... Args, typename = std::enable_if_t<sizeof...(Args) != 0>, typename ResultType = Private::operator_node_result_t<Func, Args...>>
|
||||
inline std::unique_ptr<Binding<ResultType, ImmediateBindingEvaluator>> makeBinding(Func &&func, Args &&...args)
|
||||
{
|
||||
return std::make_unique<Binding<ResultType, ImmediateBindingEvaluator>>(Private::makeNode(std::forward<Func>(func), std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to create a Property with a Binding.
|
||||
*
|
||||
* This function can take:
|
||||
* - Another Property.
|
||||
* - A Node, typically created by combining Property instances using operators.
|
||||
* - A function with arguments (Nodes, Constants or Properties)
|
||||
* By default this will construct a Property with an immediate binding evaluation.
|
||||
*
|
||||
* Alternatively a BindingEvaluator can be passed as the first argument to this function to control
|
||||
* when evaluation takes place.
|
||||
*
|
||||
* See the documentation for the various overloads of the free @ref makeBinding function for a
|
||||
* detailed description of which arguments can be used in which order.
|
||||
*
|
||||
* Examples:
|
||||
* - @ref 05-property-bindings/main.cpp
|
||||
* - @ref 06-lazy-property-bindings/main.cpp
|
||||
*
|
||||
* @return Property A new Property that is bound to the inputs
|
||||
*
|
||||
* *Note: For the difference between makeBinding and makeBoundProperty, see the
|
||||
* ["Reassigning a Binding"](../../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.*
|
||||
*/
|
||||
template<typename... T>
|
||||
inline auto makeBoundProperty(T &&...args)
|
||||
{
|
||||
auto binding = makeBinding(std::forward<T>(args)...);
|
||||
return Property<decltype(binding->get())>(std::move(binding));
|
||||
}
|
||||
|
||||
} // namespace KDBindings
|
||||
162
3rdparty/kdbindings/binding_evaluator.h
vendored
Normal file
162
3rdparty/kdbindings/binding_evaluator.h
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
/**
|
||||
* @brief A BindingEvaluator provides a mechanism to control the exact time
|
||||
* when a KDBindings::Binding is reevaluated.
|
||||
*
|
||||
* A BindingEvaluator represents a collection of Binding instances that can be
|
||||
* selectively reevaluated.
|
||||
*
|
||||
* If a Binding is created using KDBindings::makeBoundProperty with a BindingEvaluator,
|
||||
* the Binding will only be evaluated if BindingEvaluator::evaluateAll is called
|
||||
* on the given evaluator.
|
||||
*
|
||||
* Note that instances of BindingEvaluator internally wrap their collection of
|
||||
* Bindings in such a way that copying a BindingEvaluator does not actually
|
||||
* copy the collection of Bindings. Therefore adding a Binding to a copy of a
|
||||
* BindingEvaluator will also add it to the original.
|
||||
* This is done for ease of use, so evaluators can be passed around easily throughout
|
||||
* the codebase.
|
||||
*
|
||||
* Examples:
|
||||
* - @ref 06-lazy-property-bindings/main.cpp
|
||||
*/
|
||||
class BindingEvaluator
|
||||
{
|
||||
// We use pimpl here so that we can pass evaluators around by value (copies)
|
||||
// yet each copy refers to the same set of data
|
||||
struct Private {
|
||||
// TODO: Use std::vector here?
|
||||
std::map<int, std::function<void()>> m_bindingEvalFunctions;
|
||||
int m_currentId;
|
||||
};
|
||||
|
||||
public:
|
||||
/** A BindingEvaluator can be default constructed */
|
||||
BindingEvaluator() = default;
|
||||
|
||||
/**
|
||||
* A BindingEvaluator can be copy constructed.
|
||||
*
|
||||
* Note that copying the evaluator will NOT create a new collection of
|
||||
* Binding instances, but the new evaluator will refer to the same collection,
|
||||
* so creating a new Binding with this evaluator will also modify the previous one.
|
||||
*/
|
||||
BindingEvaluator(const BindingEvaluator &) noexcept = default;
|
||||
|
||||
/**
|
||||
* A BindingEvaluator can be copy assigned.
|
||||
*
|
||||
* Note that copying the evaluator will NOT create a new collection of
|
||||
* Binding instances, but the new evaluator will refer to the same collection,
|
||||
* so creating a new Binding with this evaluator will also modify the previous one.
|
||||
*/
|
||||
BindingEvaluator &operator=(const BindingEvaluator &) noexcept = default;
|
||||
|
||||
/**
|
||||
* A BindingEvaluator can not be move constructed.
|
||||
*/
|
||||
BindingEvaluator(BindingEvaluator &&other) noexcept = delete;
|
||||
|
||||
/**
|
||||
* A BindingEvaluator can not be move assigned.
|
||||
*/
|
||||
BindingEvaluator &operator=(BindingEvaluator &&other) noexcept = delete;
|
||||
|
||||
/**
|
||||
* This function evaluates all Binding instances that were constructed with this
|
||||
* evaluator, in the order they were inserted.
|
||||
*
|
||||
* It will therefore update the associated Property instances as well.
|
||||
*/
|
||||
void evaluateAll() const
|
||||
{
|
||||
// a std::map's ordering is deterministic, so the bindings are evaluated
|
||||
// in the order they were inserted, ensuring correct transitive dependency
|
||||
// evaluation.
|
||||
for (auto &[id, func] : m_d->m_bindingEvalFunctions)
|
||||
func();
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename BindingType>
|
||||
int insert(BindingType *binding)
|
||||
{
|
||||
m_d->m_bindingEvalFunctions.insert({ ++(m_d->m_currentId),
|
||||
[=]() { binding->evaluate(); } });
|
||||
return m_d->m_currentId;
|
||||
}
|
||||
|
||||
void remove(int id)
|
||||
{
|
||||
m_d->m_bindingEvalFunctions.erase(id);
|
||||
}
|
||||
|
||||
std::shared_ptr<Private> m_d{ std::make_shared<Private>() };
|
||||
|
||||
template<typename T, typename UpdaterT>
|
||||
friend class Binding;
|
||||
};
|
||||
|
||||
/**
|
||||
* This subclass of BindingEvaluator doesn't do anything special on its own.
|
||||
* It is used together with a template specialization of Binding to provide
|
||||
* old-school, immediate mode Bindings.
|
||||
*
|
||||
* Any Binding that is constructed with an ImmediateBindingEvaluator will not wait
|
||||
* for the evaluator to call evaluateAll, but rather evaluate the Binding immediately
|
||||
* when any of its bindables (i.e. Property instances) change.
|
||||
* This can lead to a Property Binding being evaluated many
|
||||
* times before the result is ever used in a typical GUI application.
|
||||
*/
|
||||
class ImmediateBindingEvaluator final : public BindingEvaluator
|
||||
{
|
||||
public:
|
||||
static inline ImmediateBindingEvaluator instance()
|
||||
{
|
||||
static ImmediateBindingEvaluator evaluator;
|
||||
return evaluator;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace KDBindings
|
||||
|
||||
/**
|
||||
* @example 06-lazy-property-bindings/main.cpp
|
||||
*
|
||||
* An example of how to use KDBindings::BindingEvaluator together
|
||||
* with a KDBindings::Property to create a Property binding that is
|
||||
* only reevaluated on demand.
|
||||
*
|
||||
* The output of this example is:
|
||||
* ```
|
||||
* The initial size of the image = 1920000 bytes
|
||||
* The new size of the image = 8294400 bytes
|
||||
* ```
|
||||
*
|
||||
* Note the difference to @ref 05-property-bindings/main.cpp, where the
|
||||
* new size of the image is calculated twice.
|
||||
*
|
||||
* This feature is especially useful to reduce the performance impact of
|
||||
* bindings and to create bindings that only update in specific intervals.
|
||||
* <br/><!-- This <br/> is a workaround for a bug in doxybook2 that causes
|
||||
* the rendering of the example code to break because it is missing a
|
||||
* newline-->
|
||||
*/
|
||||
207
3rdparty/kdbindings/genindex_array.h
vendored
Normal file
207
3rdparty/kdbindings/genindex_array.h
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
This code has been adapted from MIT licensed code, originally by Jeremy burns and available at
|
||||
https://gist.github.com/jaburns/ca72487198832f6203e831133ffdfff4.
|
||||
The original license is provided below:
|
||||
|
||||
Copyright 2021 Jeremy Burns
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
namespace Private {
|
||||
|
||||
struct GenerationalIndex {
|
||||
uint32_t index = 0;
|
||||
uint32_t generation = 0;
|
||||
};
|
||||
|
||||
class GenerationalIndexAllocator
|
||||
{
|
||||
struct AllocatorEntry {
|
||||
bool isLive = false;
|
||||
uint32_t generation = 0;
|
||||
};
|
||||
|
||||
std::vector<AllocatorEntry> m_entries;
|
||||
std::vector<uint32_t> m_freeIndices;
|
||||
|
||||
public:
|
||||
GenerationalIndex allocate()
|
||||
{
|
||||
if (m_freeIndices.size() > 0) {
|
||||
uint32_t index = m_freeIndices.back();
|
||||
m_freeIndices.pop_back();
|
||||
|
||||
m_entries[index].generation += 1;
|
||||
m_entries[index].isLive = true;
|
||||
|
||||
return { index, m_entries[index].generation };
|
||||
} else {
|
||||
// check that we are still within the bounds of uint32_t
|
||||
if (m_entries.size() + 1 >= std::numeric_limits<uint32_t>::max()) {
|
||||
throw std::length_error(std::string("Maximum number of values inside GenerationalIndexArray reached: ") + std::to_string(m_entries.size()));
|
||||
}
|
||||
|
||||
m_entries.push_back({ true, 0 });
|
||||
return { static_cast<uint32_t>(m_entries.size()) - 1, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
bool deallocate(GenerationalIndex index)
|
||||
{
|
||||
if (isLive(index)) {
|
||||
m_entries[index.index].isLive = false;
|
||||
m_freeIndices.emplace_back(index.index);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isLive(GenerationalIndex index) const noexcept
|
||||
{
|
||||
return index.index < m_entries.size() &&
|
||||
m_entries[index.index].generation == index.generation &&
|
||||
m_entries[index.index].isLive;
|
||||
}
|
||||
};
|
||||
|
||||
// A GenerationalIndexArray stores elements in contiguous memory just like an std::vector
|
||||
// and also allows items to be retrieved in constant time through indexed access, but it keeps
|
||||
// track of the "version"/generation of values at indices so that it can inform an accessor
|
||||
// when the item at the index it is trying to access is no longer the item that it wants.
|
||||
template<typename T>
|
||||
class GenerationalIndexArray
|
||||
{
|
||||
struct Entry {
|
||||
uint32_t generation;
|
||||
T value;
|
||||
};
|
||||
|
||||
// TODO: m_entries never shrinks after an entry has been deleted, it might be
|
||||
// a good idea to add a "trim" function at some point if this becomes an issue
|
||||
|
||||
std::vector<std::optional<Entry>> m_entries;
|
||||
GenerationalIndexAllocator m_allocator;
|
||||
|
||||
public:
|
||||
// Sets the value at a specific index inside the array
|
||||
void set(const GenerationalIndex index, T &&value)
|
||||
{
|
||||
while (m_entries.size() <= index.index)
|
||||
m_entries.emplace_back(std::nullopt);
|
||||
|
||||
#ifndef NDEBUG
|
||||
uint32_t previousGeneration = 0;
|
||||
|
||||
const auto &previousEntry = m_entries[index.index];
|
||||
if (previousEntry)
|
||||
previousGeneration = previousEntry->generation;
|
||||
|
||||
assert(index.generation >= previousGeneration);
|
||||
#endif
|
||||
|
||||
m_entries[index.index] = std::optional<Entry>{ { index.generation, std::move(value) } };
|
||||
}
|
||||
|
||||
// Insert a value at the first free index and get the index back
|
||||
GenerationalIndex insert(T &&value)
|
||||
{
|
||||
const auto index = m_allocator.allocate();
|
||||
set(index, std::move(value));
|
||||
return index;
|
||||
}
|
||||
|
||||
// Erase the value at the specified index and free up the index again
|
||||
void erase(GenerationalIndex index)
|
||||
{
|
||||
if (m_allocator.deallocate(index))
|
||||
m_entries[index.index] = std::nullopt;
|
||||
}
|
||||
|
||||
// Get a pointer to the value at the specified index
|
||||
T *get(GenerationalIndex index)
|
||||
{
|
||||
if (index.index >= m_entries.size())
|
||||
return nullptr;
|
||||
|
||||
auto &entry = m_entries[index.index];
|
||||
if (entry && entry->generation == index.generation) {
|
||||
return &entry->value;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get a const pointer to the value at the specified index
|
||||
const T *get(GenerationalIndex index) const noexcept
|
||||
{
|
||||
return const_cast<const T *>(const_cast<GenerationalIndexArray *>(this)->get(index));
|
||||
}
|
||||
|
||||
// Erase all the values in the array and thus free up all indices too
|
||||
void clear()
|
||||
{
|
||||
const auto numEntries = entriesSize();
|
||||
|
||||
for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) {
|
||||
const auto index = indexAtEntry(i);
|
||||
|
||||
if (index != std::nullopt)
|
||||
erase(*index);
|
||||
}
|
||||
}
|
||||
|
||||
// The number entries currently in the array, not all necessarily correspond to valid indices,
|
||||
// use "indexAtEntry" to translate from an entry index to a optional GenerationalIndex
|
||||
uint32_t entriesSize() const noexcept
|
||||
{
|
||||
// this cast is safe because the allocator checks that we never exceed the capacity of uint32_t
|
||||
return static_cast<uint32_t>(m_entries.size());
|
||||
}
|
||||
|
||||
// Convert an entry index into a GenerationalIndex, if possible otherwise returns nullopt
|
||||
std::optional<GenerationalIndex> indexAtEntry(uint32_t entryIndex) const
|
||||
{
|
||||
if (entryIndex >= entriesSize())
|
||||
return std::nullopt;
|
||||
|
||||
const auto &entry = m_entries[entryIndex];
|
||||
if (!entry)
|
||||
return std::nullopt;
|
||||
|
||||
GenerationalIndex index = { entryIndex, entry->generation };
|
||||
|
||||
if (m_allocator.isLive(index))
|
||||
return index;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} //namespace Private
|
||||
|
||||
} // namespace KDBindings
|
||||
95
3rdparty/kdbindings/make_node.h
vendored
Normal file
95
3rdparty/kdbindings/make_node.h
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kdbindings/node.h>
|
||||
#include <type_traits>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
namespace Private {
|
||||
|
||||
template<typename T>
|
||||
struct bindable_value_type_ {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct bindable_value_type_<Property<T>> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct bindable_value_type_<NodeInterface<T>> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct bindable_value_type_<Node<T>> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct bindable_value_type : bindable_value_type_<std::decay_t<T>> {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using bindable_value_type_t = typename bindable_value_type<T>::type;
|
||||
|
||||
// Find the type of a Node wrapping an operator and arguments
|
||||
template<typename Operator, typename... Ts>
|
||||
using operator_node_result =
|
||||
std::decay<
|
||||
std::invoke_result_t<
|
||||
std::decay_t<Operator>,
|
||||
bindable_value_type_t<Ts>...>>;
|
||||
|
||||
template<typename Operator, typename... Ts>
|
||||
using operator_node_result_t = typename operator_node_result<Operator, Ts...>::type;
|
||||
|
||||
// Node creation helpers
|
||||
template<typename T>
|
||||
inline Node<std::decay_t<T>> makeNode(T &&value)
|
||||
{
|
||||
return Node<std::decay_t<T>>(std::make_unique<ConstantNode<std::decay_t<T>>>(std::move(value)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline Node<T> makeNode(Property<T> &property)
|
||||
{
|
||||
return Node<T>(std::make_unique<PropertyNode<T>>(property));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline Node<T> makeNode(Node<T> &&node)
|
||||
{
|
||||
return std::move(node);
|
||||
}
|
||||
|
||||
template<typename Operator, typename... Ts, typename = std::enable_if_t<sizeof...(Ts) >= 1>, typename ResultType = operator_node_result_t<Operator, Ts...>>
|
||||
inline Node<ResultType> makeNode(Operator &&op, Ts &&...args)
|
||||
{
|
||||
return Node<ResultType>(std::make_unique<OperatorNode<ResultType, std::decay_t<Operator>, bindable_value_type_t<Ts>...>>(
|
||||
std::forward<Operator>(op),
|
||||
makeNode(std::forward<Ts>(args))...));
|
||||
}
|
||||
|
||||
// Needed by function and operator helpers
|
||||
template<typename T>
|
||||
struct is_bindable : std::integral_constant<
|
||||
bool,
|
||||
is_property<T>::value || is_node<T>::value> {
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace KDBindings
|
||||
313
3rdparty/kdbindings/node.h
vendored
Normal file
313
3rdparty/kdbindings/node.h
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kdbindings/property.h>
|
||||
#include <kdbindings/signal.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
/**
|
||||
* @brief A PropertyDestroyedError is thrown whenever a binding is evaluated
|
||||
* that references a property that no longer exists.
|
||||
*/
|
||||
class PropertyDestroyedError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
PropertyDestroyedError() = delete;
|
||||
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
namespace Private {
|
||||
|
||||
class Dirtyable
|
||||
{
|
||||
public:
|
||||
virtual ~Dirtyable() = default;
|
||||
|
||||
Dirtyable() = default;
|
||||
|
||||
void setParent(Dirtyable *newParent)
|
||||
{
|
||||
auto **parentVar = parentVariable();
|
||||
if (parentVar) {
|
||||
*parentVar = newParent;
|
||||
}
|
||||
}
|
||||
|
||||
// overridden by Binding
|
||||
virtual void markDirty()
|
||||
{
|
||||
auto *dirtyVar = dirtyVariable();
|
||||
if (dirtyVar) {
|
||||
if (*dirtyVar) {
|
||||
return;
|
||||
// We are already dirty, don't bother marking the whole tree again.
|
||||
}
|
||||
|
||||
// we only want to have one override for dirtyVariable,
|
||||
// which is const, so we have to const cast here.
|
||||
*const_cast<bool *>(dirtyVar) = true;
|
||||
}
|
||||
|
||||
auto **parentVar = parentVariable();
|
||||
if (parentVar && *parentVar) {
|
||||
(*parentVar)->markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
bool isDirty() const
|
||||
{
|
||||
auto *dirtyVar = dirtyVariable();
|
||||
return dirtyVar && *dirtyVar;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual Dirtyable **parentVariable() = 0;
|
||||
virtual const bool *dirtyVariable() const = 0;
|
||||
};
|
||||
|
||||
template<typename ResultType>
|
||||
class NodeInterface : public Dirtyable
|
||||
{
|
||||
public:
|
||||
// Returns a reference, because we cache each evaluated value.
|
||||
// const, because it shouldn't modify the return value of the AST.
|
||||
// Requires mutable caches
|
||||
virtual const ResultType &evaluate() const = 0;
|
||||
|
||||
protected:
|
||||
NodeInterface() = default;
|
||||
};
|
||||
|
||||
template<typename ResultType>
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
Node(std::unique_ptr<NodeInterface<ResultType>> &&interface)
|
||||
: m_interface(std::move(interface))
|
||||
{
|
||||
}
|
||||
|
||||
const ResultType &evaluate() const
|
||||
{
|
||||
return m_interface->evaluate();
|
||||
}
|
||||
|
||||
void setParent(Dirtyable *newParent)
|
||||
{
|
||||
m_interface->setParent(newParent);
|
||||
}
|
||||
|
||||
bool isDirty() const
|
||||
{
|
||||
return m_interface->isDirty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<NodeInterface<ResultType>> m_interface;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ConstantNode : public NodeInterface<T>
|
||||
{
|
||||
public:
|
||||
explicit ConstantNode(const T &value)
|
||||
: m_value{ value }
|
||||
{
|
||||
}
|
||||
|
||||
const T &evaluate() const override
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
protected:
|
||||
// A constant can never be dirty, so it doesn't need to
|
||||
// know its parent, as it doesn't have to notify it.
|
||||
Dirtyable **parentVariable() override { return nullptr; }
|
||||
const bool *dirtyVariable() const override { return nullptr; }
|
||||
|
||||
private:
|
||||
T m_value;
|
||||
};
|
||||
|
||||
template<typename PropertyType>
|
||||
class PropertyNode : public NodeInterface<PropertyType>
|
||||
{
|
||||
public:
|
||||
explicit PropertyNode(Property<PropertyType> &property)
|
||||
: m_parent(nullptr), m_dirty(false)
|
||||
{
|
||||
setProperty(property);
|
||||
}
|
||||
|
||||
// PropertyNodes cannot be moved
|
||||
PropertyNode(PropertyNode<PropertyType> &&) = delete;
|
||||
|
||||
PropertyNode(const PropertyNode<PropertyType> &other)
|
||||
: Dirtyable(other.isDirty())
|
||||
{
|
||||
setProperty(*other.m_property);
|
||||
}
|
||||
|
||||
virtual ~PropertyNode()
|
||||
{
|
||||
m_valueChangedHandle.disconnect();
|
||||
m_movedHandle.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();
|
||||
}
|
||||
|
||||
// This must currently take a const reference, as the "moved" signal emits a const&
|
||||
void propertyMoved(Property<PropertyType> &property)
|
||||
{
|
||||
if (&property != m_property) {
|
||||
m_property = &property;
|
||||
} else {
|
||||
// Another property was moved into the property this node refers to.
|
||||
// Therefore it will no longer update this Node.
|
||||
m_property = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void propertyDestroyed()
|
||||
{
|
||||
m_property = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
Dirtyable **parentVariable() override { return &m_parent; }
|
||||
const bool *dirtyVariable() const override { return &m_dirty; }
|
||||
|
||||
private:
|
||||
void setProperty(Property<PropertyType> &property)
|
||||
{
|
||||
m_property = &property;
|
||||
m_valueChangedHandle = m_property->valueChanged().connect(&PropertyNode<PropertyType>::markDirty, this);
|
||||
m_movedHandle = m_property->moved().connect(&PropertyNode<PropertyType>::propertyMoved, this);
|
||||
m_destroyedHandle = m_property->destroyed().connect(&PropertyNode<PropertyType>::propertyDestroyed, this);
|
||||
}
|
||||
|
||||
Property<PropertyType> *m_property;
|
||||
ConnectionHandle m_movedHandle;
|
||||
ConnectionHandle m_valueChangedHandle;
|
||||
ConnectionHandle m_destroyedHandle;
|
||||
|
||||
Dirtyable *m_parent;
|
||||
mutable bool m_dirty;
|
||||
};
|
||||
|
||||
template<typename ResultType, typename Operator, typename... Ts>
|
||||
class OperatorNode : public NodeInterface<ResultType>
|
||||
{
|
||||
public:
|
||||
// add another typename template for the Operator type, so
|
||||
// it can be a universal reference.
|
||||
template<typename Op>
|
||||
explicit OperatorNode(Op &&op, Node<Ts> &&...arguments)
|
||||
: m_parent{ nullptr }, m_dirty{ true /*dirty until reevaluated*/ }, m_op{ std::move(op) }, m_values{ std::move(arguments)... }, m_result(reevaluate())
|
||||
{
|
||||
static_assert(
|
||||
std::is_convertible_v<decltype(m_op(std::declval<Ts>()...)), ResultType>,
|
||||
"The result of the Operator must be convertible to the ReturnType of the Node");
|
||||
|
||||
setParents<0>();
|
||||
}
|
||||
|
||||
template<std::size_t I>
|
||||
auto setParents() -> std::enable_if_t<I == sizeof...(Ts)>
|
||||
{
|
||||
}
|
||||
|
||||
// The enable_if_t confuses clang-format into thinking the
|
||||
// first "<" is a comparison, and not the second.
|
||||
// clang-format off
|
||||
template<std::size_t I>
|
||||
auto setParents() -> std::enable_if_t<I < sizeof...(Ts)>
|
||||
// clang-format on
|
||||
{
|
||||
std::get<I>(m_values).setParent(this);
|
||||
setParents<I + 1>();
|
||||
}
|
||||
|
||||
virtual ~OperatorNode() = default;
|
||||
|
||||
const ResultType &evaluate() const override
|
||||
{
|
||||
if (Dirtyable::isDirty()) {
|
||||
m_result = reevaluate();
|
||||
}
|
||||
|
||||
return m_result;
|
||||
}
|
||||
|
||||
protected:
|
||||
Dirtyable **parentVariable() override { return &m_parent; }
|
||||
const bool *dirtyVariable() const override { return &m_dirty; }
|
||||
|
||||
private:
|
||||
template<std::size_t... Is>
|
||||
ResultType reevaluate_helper(std::index_sequence<Is...>) const
|
||||
{
|
||||
return m_op(std::get<Is>(m_values).evaluate()...);
|
||||
}
|
||||
|
||||
ResultType reevaluate() const
|
||||
{
|
||||
m_dirty = false;
|
||||
|
||||
return reevaluate_helper(std::make_index_sequence<sizeof...(Ts)>());
|
||||
}
|
||||
|
||||
Dirtyable *m_parent;
|
||||
mutable bool m_dirty;
|
||||
|
||||
Operator m_op;
|
||||
std::tuple<Node<Ts>...> m_values;
|
||||
|
||||
// Note: it is important that m_result is evaluated last!
|
||||
// Otherwise the call to reevaluate in the constructor will fail.
|
||||
mutable ResultType m_result;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_node_helper : std::false_type {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_node_helper<Node<T>> : std::true_type {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_node : is_node_helper<T> {
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace KDBindings
|
||||
149
3rdparty/kdbindings/node_functions.h
vendored
Normal file
149
3rdparty/kdbindings/node_functions.h
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kdbindings/make_node.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
namespace Private {
|
||||
|
||||
template<typename... Ts>
|
||||
struct any_bindables;
|
||||
|
||||
// Check to see if a single type is a bindable (node or property)
|
||||
template<typename T>
|
||||
struct any_bindables<T> : is_bindable<T> {
|
||||
};
|
||||
|
||||
// Check the head of the typelist and recurse
|
||||
template<typename HEAD, typename... Ts>
|
||||
struct any_bindables<HEAD, Ts...> : std::integral_constant<
|
||||
bool,
|
||||
any_bindables<HEAD>::value || any_bindables<Ts...>::value> {
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
|
||||
/**
|
||||
* @brief KDBINDINGS_DECLARE_FUNCTION is a helper macro to declare and define functions for use in data binding.
|
||||
*
|
||||
* This macro can take any callable object or function reference and create a new function that may be used
|
||||
* in data binding expressions.
|
||||
* The result function that can be called with a Property or the result of a data binding expression
|
||||
* to create another data binding expression.
|
||||
*
|
||||
* Note that if a function is overloaded, it is impossible to reference all of its overloads at once.
|
||||
* Therefore we recommend declaring a struct with a templated operator() to use as the function object.
|
||||
* See the KDBindings::node_abs struct for an example of how to do this.
|
||||
*
|
||||
* @param NAME The name of the function to generate.
|
||||
* @param FUNC The function to wrap.
|
||||
*/
|
||||
#define KDBINDINGS_DECLARE_FUNCTION(NAME, FUNC) \
|
||||
template<typename... Ts> \
|
||||
inline auto NAME(Ts &&...args)->std::enable_if_t<KDBindings::Private::any_bindables<Ts...>::value, KDBindings::Private::Node<KDBindings::Private::operator_node_result_t<decltype(FUNC), Ts...>>> \
|
||||
{ \
|
||||
return Private::makeNode(FUNC, std::forward<Ts>(args)...); \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief An example struct that is used with a call to KDBINDINGS_DECLARE_FUNCTION to declare all overloads
|
||||
* of std::abs as usable in data binding.
|
||||
*
|
||||
* Because of the way node_abs overloads its operator(), it can be used in a call to KDBINDINGS_DECLARE_FUNCTION like this:
|
||||
* @code
|
||||
* KDBINDINGS_DECLARE_FUNCTION(abs, node_abs{})
|
||||
* @endcode
|
||||
*
|
||||
* To generate such a struct for another function, use the KDBINDINGS_DECLARE_FUNCTION_OBJECT macro.
|
||||
*/
|
||||
struct node_abs {
|
||||
/**
|
||||
* @brief The operator() is overloaded so the struct can be used as a function object.
|
||||
*
|
||||
* Because this operator is templated, a single instance of node_abs
|
||||
* can refer to all overloads of std::abs.
|
||||
*/
|
||||
template<typename... Ts>
|
||||
auto operator()(Ts &&...x) const
|
||||
{
|
||||
return std::abs(std::forward<Ts>(x)...);
|
||||
}
|
||||
};
|
||||
KDBINDINGS_DECLARE_FUNCTION(abs, node_abs{})
|
||||
|
||||
/**
|
||||
* @brief This macro declares a callable struct that wraps a function with all
|
||||
* its overloads.
|
||||
*
|
||||
* The declared struct can be used as the FUNCTION argument to
|
||||
* KDBINDINGS_DECLARE_FUNCTION(NAME, FUNCTION) to pass a function with
|
||||
* all its overloads to the macro.
|
||||
*
|
||||
* See the KDBindings::node_abs struct for an example of what this macro would generate.
|
||||
*
|
||||
* @param NAME The name of the resulting struct.
|
||||
* @param FUNCTION The function to wrap.
|
||||
*/
|
||||
#define KDBINDINGS_DECLARE_FUNCTION_OBJECT(NAME, FUNCTION) \
|
||||
struct NAME { \
|
||||
template<typename... Ts> \
|
||||
auto operator()(Ts &&...x) const \
|
||||
{ \
|
||||
return FUNCTION(std::forward<Ts>(x)...); \
|
||||
} \
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This macro allows you to declare any function in a non-nested namespace
|
||||
* as available in the context of data binding.
|
||||
*
|
||||
* @param NAMESPACE the name of the namespace the function is in.
|
||||
* @param NAME the name of the function to wrap.
|
||||
*
|
||||
* In comparison to KDBINDINGS_DECLARE_FUNCTION(NAME, FUNC), this macro will generate a
|
||||
* helper struct using #KDBINDINGS_DECLARE_FUNCTION_OBJECT, so all overloads of the function are
|
||||
* made available at once.
|
||||
*
|
||||
* #KDBINDINGS_DECLARE_STD_FUNCTION is basically just a call to this macro with
|
||||
* the NAMESPACE parameter set to `std`.
|
||||
*/
|
||||
#define KDBINDINGS_DECLARE_NAMESPACED_FUNCTION(NAMESPACE, NAME) \
|
||||
KDBINDINGS_DECLARE_FUNCTION_OBJECT(node_##NAMESPACE_##NAME, NAMESPACE::NAME) \
|
||||
KDBINDINGS_DECLARE_FUNCTION(NAME, node_##NAMESPACE_##NAME{})
|
||||
|
||||
/**
|
||||
* @brief This macro is based on KDBINDINGS_DECLARE_NAMESPACED_FUNCTION(NAMESPACE, FUNC)
|
||||
* to make it easier to declare any standard library function as available for data binding.
|
||||
*
|
||||
* It uses #KDBINDINGS_DECLARE_NAMESPACED_FUNCTION and can therefore make all overloads
|
||||
* of the `std::` function available at once.
|
||||
*
|
||||
* @param NAME The name of the function in the `std::` namespace.
|
||||
*/
|
||||
#define KDBINDINGS_DECLARE_STD_FUNCTION(NAME) \
|
||||
KDBINDINGS_DECLARE_NAMESPACED_FUNCTION(std, NAME)
|
||||
|
||||
// Define some common and useful functions
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(floor)
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(ceil)
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(sin)
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(cos)
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(tan)
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(asin)
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(acos)
|
||||
KDBINDINGS_DECLARE_STD_FUNCTION(atan)
|
||||
|
||||
} // namespace KDBindings
|
||||
135
3rdparty/kdbindings/node_operators.h
vendored
Normal file
135
3rdparty/kdbindings/node_operators.h
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kdbindings/node.h>
|
||||
#include <kdbindings/make_node.h>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
// Helper macro to declare free standing unary operators for Property and Node
|
||||
|
||||
#define KDBINDINGS_DEFINE_UNARY_OP(OP) \
|
||||
template<typename... T> \
|
||||
inline auto operator OP(Property<T...> &arg) noexcept(noexcept(OP arg.get())) \
|
||||
->Private::Node<std::decay_t<decltype(OP arg.get())>> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&v) { return (OP v); }, arg); \
|
||||
} \
|
||||
\
|
||||
template<typename T> \
|
||||
inline auto operator OP(Private::Node<T> &&arg) noexcept(noexcept(OP arg.evaluate())) \
|
||||
->Private::Node<std::decay_t<decltype(OP arg.evaluate())>> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&v) { return (OP v); }, std::move(arg)); \
|
||||
}
|
||||
|
||||
KDBINDINGS_DEFINE_UNARY_OP(!)
|
||||
KDBINDINGS_DEFINE_UNARY_OP(~) // Bitwise not
|
||||
KDBINDINGS_DEFINE_UNARY_OP(+)
|
||||
KDBINDINGS_DEFINE_UNARY_OP(-)
|
||||
|
||||
// Helper macro to declare free standing binary operators for Property and Node.
|
||||
// The combinations we need are:
|
||||
//
|
||||
// operator op (Property<A> &a, B&& b) [Property, value]
|
||||
// operator op (A&& a, Property<B> &b) [value, Property]
|
||||
// operator op (Property<A> &a, Property<B> &b) [Property, Property]
|
||||
//
|
||||
// operator op (Node<A>&& a, B&& b) [Node value]
|
||||
// operator op (A&& a, Node<B>&& b) [value, Node]
|
||||
// operator op (Node<A>&& a, Node<B>&& b) [Node, Node]
|
||||
//
|
||||
// operator op (Property<A> &a, Node<B>&& b) [Property, Node]
|
||||
// operaotr op (Node<A>&& a, Property<B> &b) [Node, Property]
|
||||
|
||||
#define KDBINDINGS_DEFINE_BINARY_OP(OP) \
|
||||
template<typename B, typename... A> \
|
||||
inline auto operator OP(Property<A...> &a, B &&b) noexcept(noexcept(a.get() OP b)) \
|
||||
->std::enable_if_t<!Private::is_bindable<B>::value, \
|
||||
Private::Node<decltype(a.get() OP b)>> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, a, std::forward<B>(b)); \
|
||||
} \
|
||||
\
|
||||
template<typename A, typename... B> \
|
||||
inline auto operator OP(A &&a, Property<B...> &b) noexcept(noexcept(a OP b.get())) \
|
||||
->std::enable_if_t<!Private::is_bindable<A>::value, \
|
||||
Private::Node<decltype(a OP b.get())>> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::forward<A>(a), b); \
|
||||
} \
|
||||
\
|
||||
template<typename A, typename B> \
|
||||
inline auto operator OP(Property<A> &a, Property<B> &b) noexcept(noexcept(a.get() OP b.get())) \
|
||||
->Private::Node<decltype(a.get() OP b.get())> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, a, b); \
|
||||
} \
|
||||
\
|
||||
template<typename A, typename B> \
|
||||
inline auto operator OP(Private::Node<A> &&a, B &&b) noexcept(noexcept(a.evaluate() OP b)) \
|
||||
->std::enable_if_t<!Private::is_bindable<B>::value, \
|
||||
Private::Node<decltype(a.evaluate() OP b)>> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::move(a), std::forward<B>(b)); \
|
||||
} \
|
||||
\
|
||||
template<typename A, typename B> \
|
||||
inline auto operator OP(A &&a, Private::Node<B> &&b) noexcept(noexcept(a OP b.evaluate())) \
|
||||
->std::enable_if_t<!Private::is_bindable<A>::value, \
|
||||
Private::Node<decltype(a OP b.evaluate())>> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::forward<A>(a), std::move(b)); \
|
||||
} \
|
||||
\
|
||||
template<typename A, typename B> \
|
||||
inline auto operator OP(Private::Node<A> &&a, Private::Node<B> &&b) noexcept(noexcept(a.evaluate() OP b.evaluate())) \
|
||||
->Private::Node<decltype(a.evaluate() OP b.evaluate())> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::move(a), std::move(b)); \
|
||||
} \
|
||||
\
|
||||
template<typename B, typename A> \
|
||||
inline auto operator OP(Property<A> &a, Private::Node<B> &&b) noexcept(noexcept(a.get() OP b.evaluate())) \
|
||||
->Private::Node<decltype(a.get() OP b.evaluate())> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, a, std::move(b)); \
|
||||
} \
|
||||
\
|
||||
template<typename A, typename B> \
|
||||
inline auto operator OP(Private::Node<A> &&a, Property<B> &b) noexcept(noexcept(a.evaluate() OP b.get())) \
|
||||
->Private::Node<decltype(a.evaluate() OP b.get())> \
|
||||
{ \
|
||||
return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::move(a), b); \
|
||||
}
|
||||
|
||||
KDBINDINGS_DEFINE_BINARY_OP(*)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(/)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(%)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(+)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(-)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(<<)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(>>)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(<)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(<=)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(>)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(>=)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(==)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(!=)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(&)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(^)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(|)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(&&)
|
||||
KDBINDINGS_DEFINE_BINARY_OP(||)
|
||||
|
||||
} // namespace KDBindings
|
||||
446
3rdparty/kdbindings/property.h
vendored
Normal file
446
3rdparty/kdbindings/property.h
vendored
Normal file
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kdbindings/property_updater.h>
|
||||
#include <kdbindings/signal.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
/**
|
||||
* @brief A namespace containing parts of KDBindings that are not part of the public API.
|
||||
*
|
||||
* The contents of this namespace may only be accessed by the implementation of KDBindings, they
|
||||
* are not part of KDBindings public API and may be altered at any time and provide no guarantees
|
||||
* of any kind when used directly.
|
||||
**/
|
||||
namespace Private {
|
||||
|
||||
template<typename X, typename Y, typename = void>
|
||||
struct are_equality_comparable : std::false_type {
|
||||
};
|
||||
|
||||
template<typename X, typename Y>
|
||||
struct are_equality_comparable<X, Y,
|
||||
std::enable_if_t<
|
||||
std::is_same<
|
||||
std::decay_t<
|
||||
decltype(std::equal_to<>{}(std::declval<X>(), std::declval<Y>()))>,
|
||||
bool>::value>> : std::true_type {
|
||||
};
|
||||
|
||||
template<typename X, typename Y>
|
||||
constexpr bool are_equality_comparable_v = are_equality_comparable<X, Y>::value;
|
||||
|
||||
} // namespace Private
|
||||
|
||||
/**
|
||||
* A ReadOnlyProperty is thrown when trying to set the value of a Property
|
||||
* that has a PropertyUpdater associated with it.
|
||||
*
|
||||
* Most commonly because the property holds the result of a binding expression.
|
||||
*/
|
||||
struct ReadOnlyProperty : std::runtime_error {
|
||||
ReadOnlyProperty() = delete;
|
||||
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An instance of the KDBindings::equal_to struct is used to decide whether
|
||||
* two values of type T are equal in the context of data binding.
|
||||
*
|
||||
* If a new value is assigned to a Property and the existing value is equal_to
|
||||
* the existing value, the Property will not emit the Property::valueChanged or
|
||||
* Property::valueAboutToChange signals and not change the stored value.
|
||||
*
|
||||
* By default, all classes T that are equality comparable using std::equal_to
|
||||
* delegate to std::equal_to for equality comparison. All other instances are
|
||||
* assumed to never be equal.
|
||||
* Therefore, to change the equality behavior of a Property<T>, either:
|
||||
* - Implement operator== for T (std::equal_to uses operator== for equality comparison)
|
||||
* - Provide a template spezialization of KDBindings::equal_to and implement operator()()
|
||||
*/
|
||||
template<typename T>
|
||||
struct equal_to {
|
||||
/**
|
||||
* This implementation of operator()() is only enabled if std::equal_to can be
|
||||
* used to compare values of type T.
|
||||
* In this case, std::equal_to is used to decide whether values of type T are equal.
|
||||
*
|
||||
* @return bool - Whether the values are equal.
|
||||
*/
|
||||
auto operator()(const T &x, const T &y) const noexcept
|
||||
-> std::enable_if_t<Private::are_equality_comparable_v<T, T>, bool>
|
||||
{
|
||||
return std::equal_to<>{}(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* The fallback implementation of operator()() if the types are not equality comparable
|
||||
* using std::equal_to (i.e. no operator== implementation exists for this type).
|
||||
* In this case, two values of type T are assumed to never be equal.
|
||||
*
|
||||
* @return bool - Whether the values are equal - always false for this default implementation
|
||||
*/
|
||||
template<typename X, typename Y>
|
||||
auto operator()(const X &, const Y &) const noexcept
|
||||
-> std::enable_if_t<!Private::are_equality_comparable_v<X, Y>, bool>
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A property represents a value that can be part of or the result of data binding.
|
||||
*
|
||||
* Properties are at the basis of data binding.
|
||||
* They can contain a value of any type T.
|
||||
* The value can either represent the result of a data binding or a value that is used
|
||||
* in the calculation of a binding expression.
|
||||
*
|
||||
* If the value of a property is changed, either manually or because it is the result of a
|
||||
* binding expression, the Property will emit the valueAboutToChange(), and valueChanged() Signal.
|
||||
* If it is used as part of a binding expression, the expression will be marked
|
||||
* as dirty and (unless a custom BindingEvaluator is used) updated immediately.
|
||||
*
|
||||
* To create a property from a data binding expression, use the @ref makeBoundProperty or @ref makeBinding
|
||||
* functions in the @ref KDBindings namespace.
|
||||
*
|
||||
* Examples:
|
||||
* - @ref 04-simple-property/main.cpp
|
||||
* - @ref 05-property-bindings/main.cpp
|
||||
* - @ref 06-lazy-property-bindings/main.cpp
|
||||
*/
|
||||
template<typename T>
|
||||
class Property
|
||||
{
|
||||
public:
|
||||
typedef T valuetype;
|
||||
|
||||
/**
|
||||
* Properties are default constructable.
|
||||
*
|
||||
* The value of a default constructed property is then also default constructed.
|
||||
*/
|
||||
Property() = default;
|
||||
|
||||
/**
|
||||
* If a Property is destroyed, it emits the destroyed() Signal.
|
||||
*/
|
||||
~Property()
|
||||
{
|
||||
m_destroyed.emit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Property from the provided value.
|
||||
*/
|
||||
explicit Property(T value) noexcept(std::is_nothrow_move_constructible<T>::value)
|
||||
: m_value{ std::move(value) }
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties are not copyable.
|
||||
*/
|
||||
Property(Property<T> const &other) = delete;
|
||||
Property &operator=(Property<T> const &other) = delete;
|
||||
|
||||
/**
|
||||
* @brief Properties are movable.
|
||||
*
|
||||
* This will emit the moved() Signal of the property that is moving as well as
|
||||
* the property being moved into.
|
||||
* All data bindings that depend on this Property will update their references
|
||||
* to the newly move-constructed Property using this Signal.
|
||||
*/
|
||||
Property(Property<T> &&other) noexcept(std::is_nothrow_move_constructible<T>::value)
|
||||
: m_value(std::move(other.m_value))
|
||||
, m_valueAboutToChange(std::move(other.m_valueAboutToChange))
|
||||
, m_valueChanged(std::move(other.m_valueChanged))
|
||||
, m_destroyed(std::move(other.m_destroyed))
|
||||
, m_updater(std::move(other.m_updater))
|
||||
{
|
||||
// We do not move the m_moved signal so that objects interested in the moved-into
|
||||
// property can recreate any connections they need.
|
||||
|
||||
// If we have an updater, let it know how to update our internal value
|
||||
if (m_updater) {
|
||||
using namespace std::placeholders;
|
||||
m_updater->setUpdateFunction(
|
||||
std::bind(&Property<T>::setHelper, this, _1));
|
||||
}
|
||||
|
||||
// Emit the moved signals for the moved from and moved to properties
|
||||
m_moved.emit(*this);
|
||||
other.m_moved.emit(*this);
|
||||
m_moved = std::move(other.m_moved);
|
||||
}
|
||||
|
||||
/**
|
||||
* See: Property(Property<T> &&other)
|
||||
*/
|
||||
Property &operator=(Property<T> &&other) noexcept(std::is_nothrow_move_assignable<T>::value)
|
||||
{
|
||||
// We do not move the m_moved signal so that objects interested in the moved-into
|
||||
// property can recreate any connections they need.
|
||||
m_value = std::move(other.m_value);
|
||||
m_valueAboutToChange = std::move(other.m_valueAboutToChange);
|
||||
m_valueChanged = std::move(other.m_valueChanged);
|
||||
m_destroyed = std::move(other.m_destroyed);
|
||||
m_updater = std::move(other.m_updater);
|
||||
|
||||
// If we have an updater, let it know how to update our internal value
|
||||
if (m_updater) {
|
||||
using namespace std::placeholders;
|
||||
m_updater->setUpdateFunction(
|
||||
std::bind(&Property<T>::setHelper, this, _1));
|
||||
}
|
||||
|
||||
// Emit the moved signals for the moved from and moved to properties
|
||||
m_moved.emit(*this);
|
||||
other.m_moved.emit(*this);
|
||||
m_moved = std::move(other.m_moved);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a property that will be updated by the specified PropertyUpdater.
|
||||
*
|
||||
* This constructor is usually called by the creation of a data binding
|
||||
* and usually doesn't need to be called manually.
|
||||
*/
|
||||
template<typename UpdaterT>
|
||||
explicit Property(std::unique_ptr<UpdaterT> &&updater)
|
||||
{
|
||||
*this = std::move(updater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a Binding or other Updater to this Property.
|
||||
*
|
||||
* In comparison to the move assignment operator, this does NOT change any
|
||||
* of the existing Signal connections. They are all kept as-is.
|
||||
* Only the source of the update is changed.
|
||||
*
|
||||
* This will immediately set the value of this Property to the
|
||||
* result of the updater and will call the valueAboutToChange or valueChanged
|
||||
* Signals respectively if necessary.
|
||||
*/
|
||||
template<typename UpdaterT>
|
||||
Property &operator=(std::unique_ptr<UpdaterT> &&updater)
|
||||
{
|
||||
m_updater = std::move(updater);
|
||||
|
||||
// Let the updater know how to update our internal value
|
||||
using namespace std::placeholders;
|
||||
m_updater->setUpdateFunction(
|
||||
std::bind(&Property<T>::setHelper, this, _1));
|
||||
|
||||
// Now synchronise our value with whatever the updator has right now.
|
||||
setHelper(m_updater->get());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the binding from this Property
|
||||
*
|
||||
* If this Property has a binding, it will no longer update it.
|
||||
* Otherwise, this function does nothing.
|
||||
*
|
||||
* The value of the property does not change when it is reset.
|
||||
*/
|
||||
void reset()
|
||||
{
|
||||
m_updater.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Signal that will be emitted before the value is changed.
|
||||
*
|
||||
* The first emitted value is the current value of the Property.<br>
|
||||
* The second emitted value is the new value of the Property.
|
||||
*/
|
||||
Signal<const T &, const T &> &valueAboutToChange() const { return m_valueAboutToChange; }
|
||||
|
||||
/**
|
||||
* Returns a Signal that will be emitted after the value of the property changed.
|
||||
*
|
||||
* The emitted value is the current (new) value of the Property.
|
||||
*/
|
||||
Signal<const T &> &valueChanged() const { return m_valueChanged; }
|
||||
|
||||
/**
|
||||
* Returns a Signal that will be emitted when the Property is moved.
|
||||
*
|
||||
* The emitted value is a reference to the newly constructed Property that
|
||||
* this Property was moved into.
|
||||
*
|
||||
* The Signal will also be emitted if another Property is moved into
|
||||
* this property.
|
||||
*/
|
||||
Signal<Property<T> &> &moved() { return m_moved; }
|
||||
Signal<Property<T> &> const &moved() const { return m_moved; }
|
||||
|
||||
/**
|
||||
* Returns a Signal that will be emitted when this Property is destructed.
|
||||
*/
|
||||
Signal<> &destroyed() const { return m_destroyed; }
|
||||
|
||||
/**
|
||||
* Assign a new value to this Property.
|
||||
*
|
||||
* If the new value is equal_to the existing value, the value will not be
|
||||
* changed and no Signal will be emitted.
|
||||
*
|
||||
* Otherwise, the valueAboutToChange() Signal will be emitted before the value
|
||||
* of the Property is changed.
|
||||
* Then, the provided value will be assigned, and the valueChanged() Signal
|
||||
* will be emitted.
|
||||
*
|
||||
* @throw ReadOnlyProperty If the Property has a PropertyUpdater associated with it (i.e. it is
|
||||
* the result of a binding expression).
|
||||
*/
|
||||
void set(T value)
|
||||
{
|
||||
if (m_updater) {
|
||||
throw ReadOnlyProperty{
|
||||
"Cannot set value on a read-only property. This property likely holds the result of a binding expression."
|
||||
};
|
||||
}
|
||||
setHelper(std::move(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value represented by this Property.
|
||||
*/
|
||||
T const &get() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a new value to this Property.
|
||||
*
|
||||
* See: set().
|
||||
*/
|
||||
Property<T> &operator=(T const &rhs)
|
||||
{
|
||||
set(std::move(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value represented by this Property.
|
||||
*
|
||||
* See: get().
|
||||
*/
|
||||
T const &operator()() const
|
||||
{
|
||||
return Property<T>::get();
|
||||
}
|
||||
|
||||
private:
|
||||
void setHelper(T value)
|
||||
{
|
||||
if (equal_to<T>{}(value, m_value))
|
||||
return;
|
||||
|
||||
m_valueAboutToChange.emit(m_value, value);
|
||||
m_value = std::move(value);
|
||||
m_valueChanged.emit(m_value);
|
||||
}
|
||||
|
||||
T m_value;
|
||||
// the signals in a property are mutable, as a property
|
||||
// being "const" should mean that it's value or binding does
|
||||
// not change, not that nobody can listen to it anymore.
|
||||
mutable Signal<const T &, const T &> m_valueAboutToChange;
|
||||
mutable Signal<const T &> m_valueChanged; // By const ref so we can emit the signal for move-only types of T e.g. std::unique_ptr<int>
|
||||
Signal<Property<T> &> m_moved;
|
||||
mutable Signal<> m_destroyed;
|
||||
std::unique_ptr<PropertyUpdater<T>> m_updater;
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs the value of the Property onto an output stream.
|
||||
*/
|
||||
template<typename T>
|
||||
std::ostream &operator<<(std::ostream &stream, Property<T> const &property)
|
||||
{
|
||||
stream << property.get();
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a value of type T from the input stream and assigns it to
|
||||
* the Property using set().
|
||||
*/
|
||||
template<typename T>
|
||||
std::istream &operator>>(std::istream &stream, Property<T> &prop)
|
||||
{
|
||||
T temp;
|
||||
stream >> temp;
|
||||
prop.set(std::move(temp));
|
||||
return stream;
|
||||
}
|
||||
|
||||
namespace Private {
|
||||
|
||||
template<typename T>
|
||||
struct is_property_helper : std::false_type {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_property_helper<Property<T>> : std::true_type {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct is_property : is_property_helper<std::decay_t<T>> {
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
|
||||
/**
|
||||
* @example 04-simple-property/main.cpp
|
||||
*
|
||||
* An example of how to create a KDBindings::Property and use its valueChanged() KDBindings::Signal to receive notifications whenever the value of the KDBindigns::Property changes.
|
||||
*
|
||||
* The output of this example is:
|
||||
* ```
|
||||
* The new value is 42
|
||||
* The new value is 69
|
||||
* Property value is 69
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example 05-property-bindings/main.cpp
|
||||
*
|
||||
* An example of how to use makeBoundProperty() to create a KDBindings::Property that is automatically updated once any of its inputs change.
|
||||
*
|
||||
* The output of this example is:
|
||||
* ```
|
||||
* The initial size of the image = 1920000 bytes
|
||||
* The new size of the image = 4608000 bytes
|
||||
* The new size of the image = 8294400 bytes
|
||||
* ```
|
||||
*/
|
||||
|
||||
} // namespace KDBindings
|
||||
66
3rdparty/kdbindings/property_updater.h
vendored
Normal file
66
3rdparty/kdbindings/property_updater.h
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
/**
|
||||
* @brief A PropertyUpdater defines the interface used to update a Property, e.g. from a binding expression.
|
||||
*
|
||||
* An instance of this class (wrapped in a std::unique_ptr) can be passed to the Property constructor.
|
||||
* The Property will then become read-only, meaning an instance of ReadOnlyProperty will be thrown if the
|
||||
* Property's value is updated through any other means than through the PropertyUpdater.
|
||||
*
|
||||
* The Property constructor will pass a function to setUpdateFunction() for this purpose.
|
||||
* This function is then the only way to update the Property without encountering a ReadOnlyProperty error.
|
||||
*
|
||||
* The most typical use of PropertyUpdater is in instances of Binding, which are created by makeBoundProperty().
|
||||
*/
|
||||
template<typename T>
|
||||
class PropertyUpdater
|
||||
{
|
||||
public:
|
||||
/** A PropertyUpdater can be default constructed. */
|
||||
PropertyUpdater() = default;
|
||||
|
||||
/** A PropertyUpdater has a virtual destructor. */
|
||||
virtual ~PropertyUpdater() = default;
|
||||
|
||||
/** A PropertyUpdater can be copy constructed. */
|
||||
PropertyUpdater(PropertyUpdater const &other) = default;
|
||||
/** A PropertyUpdater can be copy assigned. */
|
||||
PropertyUpdater &operator=(PropertyUpdater const &other) = default;
|
||||
|
||||
/** A PropertyUpdater can be move constructed. */
|
||||
PropertyUpdater(PropertyUpdater &&other) = default;
|
||||
/** A PropertyUpdater can be move assigned. */
|
||||
PropertyUpdater &operator=(PropertyUpdater &&other) = default;
|
||||
|
||||
/**
|
||||
* The Property will call this function when it constructed and pass a std::function as argument that allows
|
||||
* the PropertyUpdater to update the Property value.
|
||||
*
|
||||
* A PropertyUpdater typically saves this function and calls it once the value it computes changes.
|
||||
*/
|
||||
virtual void setUpdateFunction(std::function<void(T &&)> const &updateFunction) = 0;
|
||||
|
||||
/**
|
||||
* The get() function must return the current value the PropertyUpdater wants to assign to the Property.
|
||||
*
|
||||
* It is called from the Property constructor.
|
||||
*/
|
||||
virtual T get() const = 0;
|
||||
};
|
||||
|
||||
} // namespace KDBindings
|
||||
605
3rdparty/kdbindings/signal.h
vendored
Normal file
605
3rdparty/kdbindings/signal.h
vendored
Normal file
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Sean Harmer <sean.harmer@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <kdbindings/genindex_array.h>
|
||||
#include <kdbindings/utils.h>
|
||||
|
||||
/**
|
||||
* @brief The main namespace of the KDBindings library.
|
||||
*
|
||||
* All public parts of KDBindings are members of this namespace.
|
||||
*/
|
||||
namespace KDBindings {
|
||||
|
||||
template<typename... Args>
|
||||
class Signal;
|
||||
|
||||
namespace Private {
|
||||
//
|
||||
// This class defines a virtual interface, that the Signal this ConnectionHandle refers
|
||||
// to must implement.
|
||||
// It allows ConnectionHandle to refer to this non-template class, which then dispatches
|
||||
// to the template implementation using virtual function calls.
|
||||
// It allows ConnectionHandle to be a non-template class.
|
||||
class SignalImplBase
|
||||
{
|
||||
public:
|
||||
SignalImplBase() = default;
|
||||
|
||||
virtual ~SignalImplBase() = default;
|
||||
|
||||
virtual void disconnect(const GenerationalIndex &id) = 0;
|
||||
virtual bool blockConnection(const GenerationalIndex &id, bool blocked) = 0;
|
||||
virtual bool isConnectionActive(const GenerationalIndex &id) const = 0;
|
||||
virtual bool isConnectionBlocked(const GenerationalIndex &id) const = 0;
|
||||
};
|
||||
|
||||
} // namespace Private
|
||||
|
||||
/**
|
||||
* @brief A ConnectionHandle represents the connection of a Signal
|
||||
* to a slot (i.e. a function that is called when the Signal is emitted).
|
||||
*
|
||||
* It is returned from a Signal when a connection is created and used to
|
||||
* manage the connection by disconnecting, (un)blocking it and checking its state.
|
||||
**/
|
||||
class ConnectionHandle
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* A ConnectionHandle can be default constructed.
|
||||
* In this case the ConnectionHandle will not reference any active connection (i.e. isActive() will return false),
|
||||
* and not belong to any Signal.
|
||||
**/
|
||||
ConnectionHandle() = default;
|
||||
|
||||
/**
|
||||
* A ConnectionHandle can be copied.
|
||||
**/
|
||||
ConnectionHandle(const ConnectionHandle &) = default;
|
||||
ConnectionHandle &operator=(const ConnectionHandle &) = default;
|
||||
|
||||
/**
|
||||
* A ConnectionHandle can be moved.
|
||||
**/
|
||||
ConnectionHandle(ConnectionHandle &&) = default;
|
||||
ConnectionHandle &operator=(ConnectionHandle &&) = default;
|
||||
|
||||
/**
|
||||
* Disconnect the slot.
|
||||
*
|
||||
* When this function is called, the function that was passed to Signal::connect
|
||||
* to create this ConnectionHandle will no longer be called when the Signal is emitted.
|
||||
*
|
||||
* If the ConnectionHandle is not active or the connection has already been disconnected,
|
||||
* nothing happens.
|
||||
*
|
||||
* After this call, the ConnectionHandle will be inactive (i.e. isActive() returns false)
|
||||
* and will no longer belong to any Signal (i.e. belongsTo returns false).
|
||||
**/
|
||||
void disconnect()
|
||||
{
|
||||
if (auto shared_impl = checkedLock()) {
|
||||
shared_impl->disconnect(m_id);
|
||||
}
|
||||
// ConnectionHandle is no longer active;
|
||||
m_signalImpl.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the connection of this ConnectionHandle is active.
|
||||
*
|
||||
* @return true if the ConnectionHandle refers to an active Signal
|
||||
* and the connection was not disconnected previously, false otherwise.
|
||||
**/
|
||||
bool isActive() const
|
||||
{
|
||||
return static_cast<bool>(checkedLock());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block state of the connection.
|
||||
* If a connection is blocked, emitting the Signal will no longer call this
|
||||
* connections slot, until the connection is unblocked.
|
||||
*
|
||||
* Behaves the same as calling Signal::blockConnection with this
|
||||
* ConnectionHandle as argument.
|
||||
*
|
||||
* To temporarily block a connection, consider using an instance of ConnectionBlocker,
|
||||
* which offers a RAII-style implementation that makes sure the connection is always
|
||||
* returned to its original state.
|
||||
*
|
||||
* @param blocked The new blocked state of the connection.
|
||||
* @return whether the connection was previously blocked.
|
||||
* @throw std::out_of_range Throws if the connection is not active (i.e. isActive() returns false).
|
||||
**/
|
||||
bool block(bool blocked)
|
||||
{
|
||||
if (auto shared_impl = checkedLock()) {
|
||||
return shared_impl->blockConnection(m_id, blocked);
|
||||
}
|
||||
throw std::out_of_range("Cannot block a non-active connection!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the connection is currently blocked.
|
||||
*
|
||||
* To change the blocked state of a connection, call ConnectionHandle::block.
|
||||
*
|
||||
* @return whether the connection is currently blocked.
|
||||
**/
|
||||
bool isBlocked() const
|
||||
{
|
||||
if (auto shared_impl = checkedLock()) {
|
||||
return shared_impl->isConnectionBlocked(m_id);
|
||||
}
|
||||
throw std::out_of_range("Cannot check whether a non-active connection is blocked!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this ConnectionHandle belongs to the given Signal.
|
||||
*
|
||||
* @return true if this ConnectionHandle refers to a connection within the given Signal
|
||||
**/
|
||||
template<typename... Args>
|
||||
bool belongsTo(const Signal<Args...> &signal) const
|
||||
{
|
||||
auto shared_impl = m_signalImpl.lock();
|
||||
return shared_impl && shared_impl == std::static_pointer_cast<Private::SignalImplBase>(signal.m_impl);
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename...>
|
||||
friend class Signal;
|
||||
|
||||
std::weak_ptr<Private::SignalImplBase> m_signalImpl;
|
||||
Private::GenerationalIndex m_id;
|
||||
|
||||
// private, so it is only available from Signal
|
||||
ConnectionHandle(std::weak_ptr<Private::SignalImplBase> signalImpl, Private::GenerationalIndex id)
|
||||
: m_signalImpl{ std::move(signalImpl) }, m_id{ std::move(id) }
|
||||
{
|
||||
}
|
||||
|
||||
// Checks that the weak_ptr can be locked and that the connection is
|
||||
// still active
|
||||
std::shared_ptr<Private::SignalImplBase> checkedLock() const
|
||||
{
|
||||
auto shared_impl = m_signalImpl.lock();
|
||||
if (shared_impl && shared_impl->isConnectionActive(m_id)) {
|
||||
return shared_impl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A Signal provides a mechanism for communication between objects.
|
||||
*
|
||||
* KDBindings::Signal recreates the <a href="https://doc.qt.io/qt-5/signalsandslots.html">Qt's Signals & Slots mechanism</a> in pure C++17.
|
||||
* A Signal can be used to notify any number of slots that a certain event has occurred.
|
||||
*
|
||||
* The slot can be almost any callable object, including member functions and lambdas.
|
||||
*
|
||||
* This connection happens in a type-safe manner, as a slot can only be connected to
|
||||
* a Signal when the arguments of the slot match the values the Signal emits.
|
||||
*
|
||||
* The Args type parameter pack describe which value types the Signal will emit.
|
||||
*
|
||||
* Examples:
|
||||
* - @ref 01-simple-connection/main.cpp
|
||||
* - @ref 02-signal-member/main.cpp
|
||||
* - @ref 03-member-arguments/main.cpp
|
||||
* - @ref 07-advanced-connections/main.cpp
|
||||
*/
|
||||
template<typename... Args>
|
||||
class Signal
|
||||
{
|
||||
static_assert(
|
||||
std::conjunction<std::negation<std::is_rvalue_reference<Args>>...>::value,
|
||||
"R-value references are not allowed as Signal parameters!");
|
||||
|
||||
// The Signal::Impl class exists, so Signals can be implemented in a PIMPL-like way.
|
||||
// This allows us to easily move Signals without losing their ConnectionHandles, as well as
|
||||
// making an unconnected Signal only sizeof(shared_ptr).
|
||||
class Impl : public Private::SignalImplBase
|
||||
{
|
||||
public:
|
||||
Impl() noexcept { }
|
||||
|
||||
~Impl() noexcept { }
|
||||
|
||||
// Signal::Impls are not copyable
|
||||
Impl(Impl const &other) = delete;
|
||||
Impl &operator=(Impl const &other) = delete;
|
||||
|
||||
// Signal::Impls are not moveable, this would break the ConnectionHandles
|
||||
Impl(Impl &&other) = delete;
|
||||
Impl &operator=(Impl &&other) = delete;
|
||||
|
||||
// Connects a std::function to the signal. The returned
|
||||
// value can be used to disconnect the function again.
|
||||
Private::GenerationalIndex connect(std::function<void(Args...)> const &slot)
|
||||
{
|
||||
return m_connections.insert({ slot });
|
||||
}
|
||||
|
||||
// Disconnects a previously connected function
|
||||
void disconnect(const Private::GenerationalIndex &id) override
|
||||
{
|
||||
m_connections.erase(id);
|
||||
}
|
||||
|
||||
// Disconnects all previously connected functions
|
||||
void disconnectAll()
|
||||
{
|
||||
m_connections.clear();
|
||||
}
|
||||
|
||||
bool blockConnection(const Private::GenerationalIndex &id, bool blocked) override
|
||||
{
|
||||
Connection *connection = m_connections.get(id);
|
||||
if (connection) {
|
||||
const bool wasBlocked = connection->blocked;
|
||||
connection->blocked = blocked;
|
||||
return wasBlocked;
|
||||
} else {
|
||||
throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
|
||||
}
|
||||
}
|
||||
|
||||
bool isConnectionActive(const Private::GenerationalIndex &id) const override
|
||||
{
|
||||
return m_connections.get(id);
|
||||
}
|
||||
|
||||
bool isConnectionBlocked(const Private::GenerationalIndex &id) const override
|
||||
{
|
||||
auto connection = m_connections.get(id);
|
||||
if (connection) {
|
||||
return connection->blocked;
|
||||
} else {
|
||||
throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
|
||||
}
|
||||
}
|
||||
|
||||
// Calls all connected functions
|
||||
void emit(Args... p) const
|
||||
{
|
||||
const auto numEntries = m_connections.entriesSize();
|
||||
|
||||
// This loop can tolerate signal handles being disconnected inside a slot,
|
||||
// but adding new connections to a signal inside a slot will still be undefined behaviour
|
||||
for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) {
|
||||
const auto index = m_connections.indexAtEntry(i);
|
||||
|
||||
if (index) {
|
||||
const auto con = m_connections.get(*index);
|
||||
|
||||
if (!con->blocked)
|
||||
con->slot(p...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Connection {
|
||||
std::function<void(Args...)> slot;
|
||||
bool blocked{ false };
|
||||
};
|
||||
mutable Private::GenerationalIndexArray<Connection> m_connections;
|
||||
};
|
||||
|
||||
public:
|
||||
/** Signals are default constructible */
|
||||
Signal() = default;
|
||||
|
||||
/**
|
||||
* Signals cannot be copied.
|
||||
**/
|
||||
Signal(const Signal &) = delete;
|
||||
Signal &operator=(Signal const &other) = delete;
|
||||
|
||||
/** Signals can be moved */
|
||||
Signal(Signal &&other) noexcept = default;
|
||||
Signal &operator=(Signal &&other) noexcept = default;
|
||||
|
||||
/**
|
||||
* A signal disconnects all slots when it is destructed
|
||||
*
|
||||
* Therefore, all active ConnectionHandles that belonged to this Signal
|
||||
* will no longer be active (i.e. ConnectionHandle::isActive will return false).
|
||||
*/
|
||||
~Signal()
|
||||
{
|
||||
disconnectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a std::function to the signal.
|
||||
*
|
||||
* When emit() is called on the Signal, the functions will be called with
|
||||
* the arguments provided to emit().
|
||||
*
|
||||
* @return An instance of ConnectionHandle, that can be used to disconnect
|
||||
* or temporarily block the connection.
|
||||
*/
|
||||
ConnectionHandle connect(std::function<void(Args...)> const &slot)
|
||||
{
|
||||
ensureImpl();
|
||||
|
||||
return ConnectionHandle{ m_impl, m_impl->connect(slot) };
|
||||
}
|
||||
|
||||
/**
|
||||
* A template overload of Signal::connect that makes it easier to connect arbitrary functions to this
|
||||
* Signal.
|
||||
* It connects a function to this Signal, binds any provided arguments to that function and discards
|
||||
* any values emitted by this Signal that aren't needed by the resulting function.
|
||||
*
|
||||
* This is especially useful for connecting member functions to signals.
|
||||
*
|
||||
* Examples:
|
||||
* @code
|
||||
* Signal<int> signal;
|
||||
* std::vector<int> numbers{ 1, 2, 3 };
|
||||
* bool emitted = false;
|
||||
*
|
||||
* // disambiguation necessary, as push_back is overloaded.
|
||||
* void (std::vector<int>::*push_back)(const int &) = &std::vector<int>::push_back;
|
||||
* signal.connect(push_back, &numbers);
|
||||
*
|
||||
* // this slot doesn't require the int argument, so it will be discarded.
|
||||
* signal.connect([&emitted]() { emitted = true; });
|
||||
*
|
||||
* signal.emit(4); // Will add 4 to the vector and set emitted to true
|
||||
* @endcode
|
||||
*
|
||||
* For more examples see the @ref 07-advanced-connections/main.cpp example.
|
||||
*
|
||||
* @return An instance of a Signal::ConnectionHandle that refers to this connection.
|
||||
* Warning: When connecting a member function you must use the returned ConnectionHandle
|
||||
* to disconnect when the object containing the slot goes out of scope!
|
||||
**/
|
||||
// The enable_if_t makes sure that this connect function specialization is only
|
||||
// available if we provide a function that cannot be otherwise converted to a
|
||||
// std::function<void(Args...)>, as it otherwise tries to take precedence
|
||||
// over the normal connect function.
|
||||
template<typename Func, typename... FuncArgs, typename = std::enable_if_t<std::disjunction_v<std::negation<std::is_convertible<Func, std::function<void(Args...)>>>, std::integral_constant<bool, sizeof...(FuncArgs) /*Also enable this function if we want to bind at least one argument*/>>>>
|
||||
ConnectionHandle connect(Func &&slot, FuncArgs &&...args)
|
||||
{
|
||||
std::function<void(Args...)> bound = Private::bind_first(std::forward<Func>(slot), std::forward<FuncArgs>(args)...);
|
||||
return connect(bound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a previously connected slot.
|
||||
*
|
||||
* After the slot was successfully disconnected, the ConnectionHandle will no
|
||||
* longer be active. (i.e. ConnectionHandle::isActive will return false).
|
||||
*
|
||||
* @throw std::out_of_range - If the ConnectionHandle does not belong to this
|
||||
* Signal (i.e. ConnectionHandle::belongsTo returns false).
|
||||
*/
|
||||
void disconnect(const ConnectionHandle &handle)
|
||||
{
|
||||
if (m_impl && handle.belongsTo(*this)) {
|
||||
m_impl->disconnect(handle.m_id);
|
||||
// TODO check if Impl is now empty and reset
|
||||
} else {
|
||||
throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect all previously connected functions.
|
||||
*
|
||||
* All currently active ConnectionHandles that belong to this Signal will no
|
||||
* longer be active afterwards. (i.e. ConnectionHandle::isActive will return false).
|
||||
*/
|
||||
void disconnectAll()
|
||||
{
|
||||
if (m_impl) {
|
||||
m_impl->disconnectAll();
|
||||
// Once all connections are disconnected, we can release ownership of the Impl.
|
||||
// This does not destroy the Signal itself, just the Impl object.
|
||||
// If another slot is connected, another Impl object will be constructed.
|
||||
m_impl.reset();
|
||||
}
|
||||
// If m_impl is nullptr, we don't have any connections to disconnect
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block state of the connection.
|
||||
* If a connection is blocked, emitting the Signal will no longer call this
|
||||
* connections slot, until the connection is unblocked.
|
||||
*
|
||||
* ConnectionHandle::block can be used as an alternative.
|
||||
*
|
||||
* To temporarily block a connection, consider using an instance of ConnectionBlocker,
|
||||
* which offers a RAII-style implementation that makes sure the connection is always
|
||||
* returned to its original state.
|
||||
*
|
||||
* @param blocked Whether the connection should be blocked from now on.
|
||||
* @param handle The ConnectionHandle to block.
|
||||
* @return Whether the connection was previously blocked.
|
||||
* @throw std::out_of_range - If the ConnectionHandle does not belong to this
|
||||
* Signal (i.e. ConnectionHandle::belongsTo returns false).
|
||||
*/
|
||||
bool blockConnection(const ConnectionHandle &handle, bool blocked)
|
||||
{
|
||||
if (m_impl && handle.belongsTo(*this)) {
|
||||
return m_impl->blockConnection(handle.m_id, blocked);
|
||||
} else {
|
||||
throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the connection is currently blocked.
|
||||
*
|
||||
* To change the blocked state of a connection, call blockConnection().
|
||||
*
|
||||
* @return Whether the connection is currently blocked
|
||||
* @throw std::out_of_range - If the ConnectionHandle does not belong to this
|
||||
* Signal (i.e. ConnectionHandle::belongsTo returns false).
|
||||
*/
|
||||
bool isConnectionBlocked(const ConnectionHandle &handle) const
|
||||
{
|
||||
assert(handle.belongsTo(*this));
|
||||
if (!m_impl) {
|
||||
throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!");
|
||||
}
|
||||
|
||||
return m_impl->isConnectionBlocked(handle.m_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the Signal, which causes all connected slots to be called,
|
||||
* as long as they are not blocked.
|
||||
*
|
||||
* The arguments provided to emit will be passed to each slot by copy,
|
||||
* therefore consider using (const) references as the Args to the Signal
|
||||
* wherever possible.
|
||||
*
|
||||
* Note: Slots may disconnect themselves during an emit, however it is
|
||||
* undefined whether a slot that is connected during the emit function
|
||||
* of the Signal will also be called during this emit, or only at the next
|
||||
* emit.
|
||||
*/
|
||||
void emit(Args... p) const
|
||||
{
|
||||
if (m_impl)
|
||||
m_impl->emit(p...);
|
||||
|
||||
// if m_impl is nullptr, we don't have any slots connected, don't bother emitting
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ConnectionHandle;
|
||||
|
||||
void ensureImpl()
|
||||
{
|
||||
if (!m_impl) {
|
||||
m_impl = std::make_shared<Impl>();
|
||||
}
|
||||
}
|
||||
|
||||
// shared_ptr is used here instead of unique_ptr, so ConnectionHandle instances can
|
||||
// use a weak_ptr to check if the Signal::Impl they reference is still alive.
|
||||
//
|
||||
// This makes Signals easily copyable in theory, but the semantics of this are unclear.
|
||||
// Copying could either simply copy the shared_ptr, which means the copy would share
|
||||
// the connections of the original, which is possibly unintuitive, or the Impl would
|
||||
// have to be copied as well.
|
||||
// This would however leave connections without handles to disconnect them.
|
||||
// So copying is forbidden for now.
|
||||
//
|
||||
// Think of this shared_ptr more like a unique_ptr with additional weak_ptr's
|
||||
// in ConnectionHandle that can check whether the Impl object is still alive.
|
||||
mutable std::shared_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A ConnectionBlocker is a convenient RAII-style mechanism for temporarily blocking a connection.
|
||||
*
|
||||
* When a ConnectionBlocker is constructed, it will block the connection.
|
||||
*
|
||||
* When it is destructed, it will return the connection to the blocked state it was in
|
||||
* before the ConnectionBlocker was constructed.
|
||||
*/
|
||||
class ConnectionBlocker
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructs a new ConnectionBlocker and blocks the connection this ConnectionHandle
|
||||
* refers to.
|
||||
*
|
||||
* @throw std::out_of_range If the connection is not active (i.e. ConnectionHandle::isActive() returns false).
|
||||
*/
|
||||
explicit ConnectionBlocker(const ConnectionHandle &handle)
|
||||
: m_handle{ handle }
|
||||
{
|
||||
m_wasBlocked = m_handle.block(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructs the ConnectionBlocker and returns the connection into the blocked state it was in
|
||||
* before the ConnectionBlocker was constructed.
|
||||
*/
|
||||
~ConnectionBlocker()
|
||||
{
|
||||
m_handle.block(m_wasBlocked);
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionHandle m_handle;
|
||||
bool m_wasBlocked{ false };
|
||||
};
|
||||
|
||||
/**
|
||||
* @example 01-simple-connection/main.cpp
|
||||
*
|
||||
* A simple example of how to create a KDBindings::Signal and connect a lambda to it.
|
||||
*
|
||||
* The output of this example is:
|
||||
* ```
|
||||
* The answer: 42
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example 02-signal-member/main.cpp
|
||||
*
|
||||
* An example of how to connect a member function to a KDBindings::Signal.
|
||||
*
|
||||
* The output of this example is:
|
||||
* ```
|
||||
* Hello World!
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example 03-member-arguments/main.cpp
|
||||
*
|
||||
* An example of how to connect a member function with arguments to a KDBindings::Signal.
|
||||
*
|
||||
* The output of this example is:
|
||||
* ```
|
||||
* Bob received: Have a nice day!
|
||||
* Alice received: Thank you!
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* @example 07-advanced-connections/main.cpp
|
||||
*
|
||||
* An example of how to use the KDBindings::Signal::connect() overloaded function for advanced slot connections.
|
||||
*
|
||||
* The output of this example is:
|
||||
* ```
|
||||
* Hello World!
|
||||
* Emitted value: 5
|
||||
* true
|
||||
* ```
|
||||
*/
|
||||
|
||||
} // namespace KDBindings
|
||||
169
3rdparty/kdbindings/utils.h
vendored
Normal file
169
3rdparty/kdbindings/utils.h
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
This file is part of KDBindings.
|
||||
|
||||
SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
Author: Leon Matthes <leon.matthes@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace KDBindings {
|
||||
|
||||
/**
|
||||
* The contents of this namespace may only be accessed by the implementation of KDBindings, they
|
||||
* are not part of KDBindings public API and may be altered at any time and provide no guarantees
|
||||
* of any kind when used directly.
|
||||
**/
|
||||
namespace Private {
|
||||
|
||||
// ------------------------ get_arity --------------------------
|
||||
// get_arity is a template function that returns the number of arguments
|
||||
// of (almost) any callable object.
|
||||
// The easiest way is to simply call get_arity<T>() for callable type T.
|
||||
// It needs to be constexpr in order so it can be used in template arguments.
|
||||
|
||||
// To overload get_arity, it needs a marker type, as C++ doesn't allow partial
|
||||
// function specialization.
|
||||
template<typename T>
|
||||
struct TypeMarker {
|
||||
constexpr TypeMarker() = default;
|
||||
};
|
||||
|
||||
// base implementation of get_arity refers to specialized implementations for each
|
||||
// type of callable object by using the overload for its specialized TypeMarker.
|
||||
template<typename T>
|
||||
constexpr size_t get_arity()
|
||||
{
|
||||
return get_arity(TypeMarker<std::decay_t<T>>{});
|
||||
}
|
||||
|
||||
// Syntactic sugar version of get_arity, allows to pass any callable object
|
||||
// to get_arity, instead of having to pass its decltype as a template argument.
|
||||
template<typename T>
|
||||
constexpr size_t get_arity(const T &)
|
||||
{
|
||||
return get_arity<T>();
|
||||
}
|
||||
|
||||
// The arity of a function pointer is simply its number of arguments.
|
||||
template<typename Return, typename... Arguments>
|
||||
constexpr size_t get_arity(TypeMarker<Return (*)(Arguments...)>)
|
||||
{
|
||||
return sizeof...(Arguments);
|
||||
}
|
||||
|
||||
template<typename Return, typename... Arguments>
|
||||
constexpr size_t get_arity(TypeMarker<Return (*)(Arguments...) noexcept>)
|
||||
{
|
||||
return sizeof...(Arguments);
|
||||
}
|
||||
|
||||
// The arity of a generic callable object is the arity of its operator() - 1, as the this
|
||||
// pointer is already known for such an object.
|
||||
template<typename T>
|
||||
constexpr size_t get_arity(TypeMarker<T>)
|
||||
{
|
||||
return get_arity(TypeMarker<decltype(&T::operator())>{}) - 1;
|
||||
}
|
||||
|
||||
// Macro to help define most combinations of possible member function qualifiers.
|
||||
// Add + 1 to sizeof...(Arguments) here as the "this" pointer is an implicit argument to any member function.
|
||||
#define KDBINDINGS_DEFINE_MEMBER_GET_ARITY(MODIFIERS) \
|
||||
template<typename Return, typename Class, typename... Arguments> \
|
||||
constexpr size_t get_arity(::KDBindings::Private::TypeMarker<Return (Class::*)(Arguments...) MODIFIERS>) \
|
||||
{ \
|
||||
return sizeof...(Arguments) + 1; \
|
||||
}
|
||||
|
||||
// Define the get_arity version without modifiers without using the macro.
|
||||
// MSVC otherwise complains about a call to the macro with too few arguments
|
||||
template<typename Return, typename Class, typename... Arguments>
|
||||
constexpr size_t get_arity(::KDBindings::Private::TypeMarker<Return (Class::*)(Arguments...)>)
|
||||
{
|
||||
return sizeof...(Arguments) + 1;
|
||||
}
|
||||
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&)
|
||||
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&)
|
||||
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&noexcept)
|
||||
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&noexcept)
|
||||
KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&noexcept)
|
||||
|
||||
// -------------------- placeholder and bind_first ---------------------
|
||||
// Inspired by https://gist.github.com/engelmarkus/fc1678adbed1b630584c90219f77eb48
|
||||
// A placeholder provides a way to construct something equivalent to a std::placeholders::_N
|
||||
// with N as a template argument.
|
||||
//
|
||||
// Note: As placeholders start at 1, therefore placeholder<0> is NOT a valid placeholder.
|
||||
template<int>
|
||||
struct placeholder {
|
||||
};
|
||||
|
||||
template<typename Func, typename... Args, std::size_t... Is>
|
||||
auto bind_first_helper(std::index_sequence<Is...>, Func &&fun, Args... args)
|
||||
{
|
||||
return std::bind(std::forward<Func>(fun), std::forward<Args>(args)..., placeholder<Is + 1>{}...);
|
||||
}
|
||||
|
||||
// bind_first binds the first arguments to the callable object (i.e. function) to the values provided by args.
|
||||
// The return value is a new function taking get_arity<Func> - sizeof...(Args) many arguments, with the first
|
||||
// sizeof...(Args) arguments bound to the values of args.
|
||||
// This is different to a call with std::bind(fun, args...), as the callable object created by std::bind would
|
||||
// in this case now take zero arguments, whilst bind_first still expects the remaining arguments to be provided
|
||||
//
|
||||
// For now, providing instances of std::placeholders in Args is not allowed, as the implications of this are
|
||||
// unclear if sizeof...(Args) != get_arity<Func>. The enable_if_t makes sure none of the Args value is a placeholder.
|
||||
//
|
||||
// In the future, we could provide another overload of this function that allows placeholders, as long as all arguments
|
||||
// are bound.
|
||||
template<
|
||||
typename Func,
|
||||
typename... Args,
|
||||
/*Disallow any placeholder arguments, they would mess with the number and ordering of required and bound arguments, and are, for now, unsupported*/
|
||||
typename = std::enable_if_t<std::conjunction_v<std::negation<std::is_placeholder<Args>>...>>>
|
||||
auto bind_first(Func &&fun, Args &&...args)
|
||||
{
|
||||
return bind_first_helper(std::make_index_sequence<get_arity<Func>() - sizeof...(Args)>{}, std::forward<Func>(fun), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // namespace Private
|
||||
|
||||
} // namespace KDBindings
|
||||
|
||||
namespace std {
|
||||
|
||||
// This allows a placeholder to be used as a replacement of a std::placeholders.
|
||||
template<int N>
|
||||
struct is_placeholder<KDBindings::Private::placeholder<N>>
|
||||
: integral_constant<int, N> {
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
@@ -122,6 +122,7 @@ target_include_directories(kddockwidgets_backend
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/backend_temp_includes> # TODO remove
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/backend_temp_includes/kddockwidgets>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty>
|
||||
)
|
||||
|
||||
#--------------------------------
|
||||
|
||||
Reference in New Issue
Block a user