Add KDBindings to 3rdparty

To replace Qt in the backend.
This commit is contained in:
Sergio Martins
2022-02-12 11:02:32 +00:00
parent b5003f8fb8
commit d4bcc96f2f
12 changed files with 2646 additions and 0 deletions

298
3rdparty/kdbindings/binding.h vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -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>
)
#--------------------------------