AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
ADataBinding.h
    1/*
    2 * AUI Framework - Declarative UI toolkit for modern C++20
    3 * Copyright (C) 2020-2025 Alex2772 and Contributors
    4 *
    5 * SPDX-License-Identifier: MPL-2.0
    6 *
    7 * This Source Code Form is subject to the terms of the Mozilla Public
    8 * License, v. 2.0. If a copy of the MPL was not distributed with this
    9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   10 */
   11
   12#pragma once
   13
   14#include "AUI/Traits/concepts.h"
   15#include "ALayoutInflater.h"
   16#include <functional>
   17#include <type_traits>
   18#include <AUI/Common/SharedPtr.h>
   19#include <AUI/Common/ASignal.h>
   20#include <AUI/Traits/members.h>
   21#include <AUI/View/AViewContainer.h>
   22#include <AUI/Reflect/members.h>
   23
   24
   37template<typename View, typename FieldType>
   39public:
   44    static void setup(const _<View>& view) {}
   45
   50    static auto property(const _<View>& view) {}
   51
   55    [[deprecated("ADataBinding is deprecated. Please use Property System to bind values")]]
   56    static ASignal<FieldType>(View::*getGetter()) {
   57        return nullptr;
   58    }
   59
   63    [[deprecated("ADataBinding is deprecated. Please use Property System to bind values")]]
   64    static void(View::*getSetter())(const FieldType& v) {
   65        return nullptr;
   66    }
   67};
   68
   69template<aui::derived_from<AViewContainer> Container>
   70struct ADataBindingDefault<Container, _<AView>> {
   71    static void setup(const _<AViewContainer>& container) {}
   72    static auto property(const _<AViewContainer>& container) {
   73        return ASlotDef {
   74            container.get(),
   75            [&container = *container](const _<AView>& viewToInflate) {
   76                ALayoutInflater::inflate(container, viewToInflate);
   77            },
   78        };
   79    }
   80};
   81
   82template <typename Model>
   83class ADataBinding;
   84
   85template<typename Model, typename View, typename ModelField, typename Getter, aui::invocable<View*, const ModelField&> Setter>
   86class ADataBindingLinker {
   87private:
   88    ADataBinding<Model>* mBinder;
   89    Setter setter;
   90    std::decay_t<ModelField>(Model::*mField);
   91    ASignal<std::decay_t<Getter>>(View::*mGetter);
   92
   93public:
   94    ADataBindingLinker(ADataBinding<Model>* binder, ASignal<std::decay_t<Getter>>(View::*getter), Setter setter,
   95                       std::decay_t<ModelField>(Model::*field)):
   96            mBinder(binder), mGetter(getter), setter(std::move(setter)), mField(field) {}
   97
   98    ADataBinding<Model>* getBinder() const {
   99        return mBinder;
  100    }
  101
  102    auto getSetterFunc() const {
  103        return setter;
  104    }
  105
  106    auto getField() const {
  107        return mField;
  108    }
  109
  110    auto getGetter() const {
  111        return mGetter;
  112    }
  113};
  114template<typename Model, typename Data, typename Projection>
  115class ADataBindingLinker2 {
  116private:
  117    ADataBinding<Model>* mBinder;
  118    Data(Model::*mField);
  119    Projection mProjection;
  120
  121public:
  122    ADataBindingLinker2(ADataBinding<Model>* binder, Data(Model::*field), Projection projection = nullptr) : mBinder(binder), mField(field), mProjection(std::move(projection)) {}
  123
  124    ADataBinding<Model>* getBinder() const {
  125        return mBinder;
  126    }
  127
  128    auto getField() const {
  129        return mField;
  130    }
  131
  132    Projection getProjection() const {
  133        return mProjection;
  134    }
  135};
  136
  200template <typename Model>
  201class [[deprecated("consider using AProperty and signal/slot connections instead")]] ADataBinding: public AObject {
  202private:
  203    using Observer = std::function<void(const Model& model, unsigned)>;
  204    ADeque<Observer> mLinkObservers;
  205
  206    Model* mModel = nullptr;
  207    bool mOwning = false;
  208
  209    void* mExcept = nullptr;
  210
  211public:
  212
  213    ADataBinding() = default;
  214    explicit ADataBinding(const Model& m)
  215    {
  216        setModel(m);
  217    }
  218    explicit ADataBinding(Model* m)
  219    {
  220        setModel(m);
  221    }
  222
  223    virtual ~ADataBinding() {
  224        if (mOwning) {
  225            delete mModel;
  226        }
  227    }
  228
  234    template<typename View, typename ModelField, typename SetterArg>
  235    auto operator()(ModelField(Model::*field), void(View::*setterFunc)(SetterArg)) {
  236        AUI_ASSERT(setterFunc != nullptr);
  237        return operator()<View, ModelField>(field, (ASignal<ModelField>(View::*))nullptr, setterFunc);
  238    }
  239
  256    template<typename ModelField, typename SetterLambda>
  257    auto operator()(ModelField(Model::*field), SetterLambda setterLambda) {
  258        using lambda_args = typename aui::lambda_info<SetterLambda>::args;
  259        return aui::tuple_visitor<lambda_args>::for_each_all([&]<typename ViewReference, typename DataArgument>() {
  260            static_assert(std::is_reference_v<ViewReference>, "View is expected to be a reference");
  261            static_assert(aui::convertible_to<ModelField, DataArgument>, "lambda's argument is expected to be constructible from ModelField");
  262            using View = std::decay_t<ViewReference>;
  263            return operator()<View, ModelField>(field, (ASignal<ModelField>(View::*))nullptr, [setterLambda = std::move(setterLambda)](View* view, DataArgument dataArgument) {
  264                std::invoke(setterLambda, *view, std::forward<DataArgument>(dataArgument));
  265            });
  266        });
  267    }
  268
  275    template<typename View, typename ModelField, typename GetterRV, aui::invocable<View*, const ModelField&> Setter>
  276    auto operator()(ModelField(Model::*field),
  277                    ASignal<GetterRV>(View::*getter),
  278                    Setter setter = (void(View::*)(const ModelField&))nullptr) {
  279        return ADataBindingLinker<Model, View, std::decay_t<ModelField>, GetterRV, Setter>(this, getter, std::move(setter), field);
  280    }
  281
  288    template<typename Data>
  289    auto operator()(Data(Model::*field)) {
  290        return ADataBindingLinker2<Model, Data, std::nullptr_t>(this, field, nullptr);
  291    }
  292
  298    template<typename Data, aui::invocable<Data> Projection>
  299    ADataBindingLinker2<Model, Data, Projection> operator()(Data(Model::*field), Projection projection) {
  300        return ADataBindingLinker2<Model, Data, Projection>(this, field, std::move(projection));
  301    }
  302    const Model& getModel() const noexcept {
  303        return *mModel;
  304    }
  305
  306    Model const * operator->() const noexcept {
  307        return &getModel();
  308    }
  309
  310    Model& getEditableModel() {
  311        return *mModel;
  312    }
  313    void setModel(const Model& model) {
  314        if (mOwning) {
  315            delete mModel;
  316        }
  317        mOwning = true;
  318        mModel = new Model(model);
  319        notifyUpdate();
  320    }
  321
  322    void setModel(Model* model) {
  323        if (mOwning) {
  324            delete mModel;
  325        }
  326        mOwning = false;
  327        mModel = model;
  328        notifyUpdate();
  329    }
  330
  331    const void* getExclusion() const {
  332        return mExcept;
  333    }
  334
  335    void notifyUpdate(void* except = nullptr, unsigned field = -1) {
  336        mExcept = except;
  337        for (auto& applier : mLinkObservers) {
  338            applier(*mModel, field);
  339        }
  340        emit modelChanged;
  341    }
  342
  343    template<typename ModelField>
  344    void notifyUpdate(ModelField(Model::*field)) {
  345        union converter {
  346            unsigned i;
  347            decltype(field) p;
  348        } c;
  349        c.p = field;
  350        notifyUpdate(nullptr, c.i);
  351    }
  352
  353    template<typename ModelField, aui::convertible_to<ModelField> U>
  354    void setValue(ModelField(Model::*field), U&& value) {
  355        mModel->*field = std::move(value);
  356        notifyUpdate(field);
  357    }
  358
  359    void addObserver(Observer applier) {
  360        mLinkObservers << std::move(applier);
  361        if (mModel) {
  362            mLinkObservers.last()(*mModel, -1);
  363        }
  364    }
  365
  366    template<aui::invocable T>
  367    void addObserver(T&& applier) {
  368        addObserver([applier = std::forward<T>(applier)](const Model&, unsigned) {
  369            applier();
  370        });
  371    }
  372
  373    template<typename ModelField, typename FieldObserver>
  374    void addObserverNoInitialCall(ModelField(Model::*field), FieldObserver&& observer) {
  375        mLinkObservers << [observer = std::forward<FieldObserver>(observer), field](const Model& model, unsigned index) {
  376            union converter {
  377                unsigned i;
  378                decltype(field) p;
  379            } c;
  380            c.p = field;
  381            if (c.i == index || index == -1) {
  382                observer(model.*field);
  383            }
  384        };
  385    }
  386    template<typename ModelField, typename FieldObserver>
  387    void addObserver(ModelField(Model::*field), FieldObserver&& observer) {
  388        addObserverNoInitialCall(field, std::forward<FieldObserver>(observer));
  389        if (mModel) {
  390            mLinkObservers.last()(*mModel, -1);
  391        }
  392    }
  393
  394
  395signals:
  400};
  401
  402template<typename Klass1, typename View, typename Model, typename ModelField, typename GetterRV, typename SetterArg>
  403_<Klass1> operator&&(const _<Klass1>& modelBinding, const ADataBindingLinker<Model, View, ModelField, GetterRV, SetterArg>& linker) {
  404    union converter {
  405        unsigned i;
  406        decltype(linker.getField()) p;
  407    };
  408    if (linker.getGetter()) {
  409        AObject::connect(modelBinding.get()->*(linker.getGetter()), linker.getBinder(), [modelBinding, linker](const GetterRV& data) {
  410            AUI_ASSERTX(&linker.getBinder()->getEditableModel(), "please setModel for ADataBinding");
  411            modelBinding->setSignalsEnabled(false);
  412            linker.getBinder()->getEditableModel().*(linker.getField()) = data;
  413            converter c;
  414            c.p = linker.getField();
  415            linker.getBinder()->notifyUpdate(modelBinding.get(), c.i);
  416            modelBinding->setSignalsEnabled(true);
  417        });
  418    }
  419
  420    if constexpr (aui::convertible_to<decltype(linker.getSetterFunc()), bool>) {
  421        if (!bool(linker.getSetterFunc())) {
  422            return modelBinding;
  423        }
  424    }
  425    linker.getBinder()->addObserver([modelBinding, linker](const Model& model, unsigned field) {
  426        converter c;
  427        c.p = linker.getField();
  428        if (c.i == field || field == -1) {
  429            if (modelBinding.get() != linker.getBinder()->getExclusion()) {
  430                std::invoke(linker.getSetterFunc(), modelBinding.get(), model.*(linker.getField()));
  431            }
  432        }
  433    });
  434
  435    return modelBinding;
  436}
  437
  438
  439namespace aui::detail {
  440    template<typename ForcedClazz, typename Type>
  441    struct pointer_to_member {
  442        template<typename... Args>
  443        static Type(ForcedClazz::*with_args(std::tuple<Args...>))(Args...) {
  444            return nullptr;
  445        }
  446    };
  447}
  448
  449template<typename View, typename Model, typename Data, typename Projection>
  450_<View> operator&&(const _<View>& object, const ADataBindingLinker2<Model, Data, Projection>& linker) {
  452
  453    constexpr bool is_default_projection = std::is_same_v<Projection, std::nullptr_t>;
  454    using projection_deduced = std::conditional_t<is_default_projection, aui::identity, Projection>;
  455    static_assert(std::is_invocable_v<projection_deduced, Data>, "projection is expected to accept Data value from model");
  456    using data_deduced = std::decay_t<std::invoke_result_t<projection_deduced, Data>>;
  457
  458    using getter = ASignal<Data>(View::*);
  460
  461    using setter_ret = typename setter::return_t;
  462    using setter_args = typename setter::args;
  463
  464    using my_pointer_to_member = typename aui::detail::pointer_to_member<View, setter_ret>;
  465
  466    using pointer_to_setter = decltype(my_pointer_to_member::with_args(std::declval<setter_args>()));
  467
  468    if constexpr (is_default_projection) {
  469        getter g = nullptr;
  470        // getter is optional.
  471        if constexpr (requires { ADataBindingDefault<View, Data>::getGetter(); }) {
  472            if constexpr (requires { std::invoke(ADataBindingDefault<View, Data>::getGetter(), *object).changed; }) {
  473                // AProperty.
  474                g = (getter) &std::invoke(ADataBindingDefault<View, Data>::getGetter(), *object).changed; // BROKEN -- https://github.com/aui-framework/aui/discussions/442
  475            } else {
  476                g = (getter) &std::invoke(ADataBindingDefault<View, Data>::getGetter(), *object); // BROKEN -- https://github.com/aui-framework/aui/discussions/442
  477            }
  478        }
  479        auto s = static_cast<pointer_to_setter>(ADataBindingDefault<View, Data>::getSetter());
  480
  481        AUI_ASSERTX(g != nullptr || s != nullptr, "ADataBindingDefault is not defined for View, Data");
  482
  483        object && (*linker.getBinder())(linker.getField(), g, s);
  484    } else {
  485        object && (*linker.getBinder())(linker.getField(), [projection = linker.getProjection()](View& v, const Data& d) {
  486            auto s = static_cast<pointer_to_setter>(ADataBindingDefault<View, data_deduced>::getSetter());
  487            std::invoke(s, &v, projection(d));
  488        });
  489    }
  490    return object;
  491}
  492
  493template<typename View>
  494struct ADataBindingDefault<View, Visibility> {
  495public:
  500    static void setup(const _<View>& view) {}
  501
  502    static auto getSetter() {
  503        return &AView::setVisibility;
  504    }
  505};
  506
  507template <typename Object, APropertyReadable Connectable>
  508inline const _<Object>& operator&(const _<Object>& object, Connectable&& binding) {
  509    aui::tuple_visitor<
  510        typename AAnySignalOrPropertyTraits<std::decay_t<Connectable>>::args>::for_each_all([&]<typename... T>() {
  511        using Binding = ADataBindingDefault<std::decay_t<Object>, std::decay_t<T>...>;
  512        static_assert(
  513            requires { { Binding::property(object) } -> AAnyProperty; } ||
  514                requires { { Binding::property(object) } -> aui::derived_from<ASlotDefBase>; },
  515            "ADataBindingDefault is required to have property() function to return any property or slot def; either "
  516            "define proper ADataBindingDefault specialization or explicitly specify the destination property.");
  517        static_assert(
  518            requires { { Binding::setup(object) }; },
  519            "ADataBindingDefault is required to have setup(const _<Object>&) function; either define proper "
  520            "ADataBindingDefault specialization or explicitly specify the destination property.");
  521        Binding::setup(object);
  522        AObject::connect(binding, Binding::property(object));
  523    });
  524    return object;
  525}
  526
  527template<typename Object, APropertyWritable Connectable>
  528inline const _<Object>& operator&&(const _<Object>& object, Connectable&& binding) {
  529    aui::tuple_visitor<typename AAnySignalOrPropertyTraits<std::decay_t<Connectable>>::args>::for_each_all([&]<typename... T>() {
  530      using Binding = ADataBindingDefault<std::decay_t<Object>, std::decay_t<T>...>;
  531      static_assert(requires {
  532          { Binding::property(object) } -> AAnyProperty;
  533      }, "ADataBindingDefault is required to have property() function to return any property; either define proper "
  534         "ADataBindingDefault specialization or explicitly specify the destination property.");
  535      static_assert(
  536          requires { { Binding::setup(object) }; },
  537          "ADataBindingDefault is required to have setup(const _<Object>&) function; either define proper "
  538          "ADataBindingDefault specialization or explicitly specify the destination property.");
  539      Binding::setup(object);
  540      AObject::biConnect(binding, Binding::property(object));
  541    });
  542    return object;
  543}
  544
  545template <AAnyProperty Lhs, typename Destination>
  546struct Binding {
  547    Lhs sourceProperty;
  548    Destination destinationPointerToMember;
  549    explicit Binding(Lhs sourceProperty, Destination destinationPointerToMember)
  550      : sourceProperty(sourceProperty), destinationPointerToMember(destinationPointerToMember) {}
  551};
  552
  553template <AAnyProperty Property, typename Destination>
  554requires aui::reflect::pointer_to_member<Destination> || std::is_class_v<Destination>
  555inline decltype(auto) operator>(Property&& sourceProperty, Destination&& rhs) {
  556    return Binding<Property, Destination>(std::forward<Property>(sourceProperty), std::forward<Destination>(rhs));
  557}
  558
  559template <typename Object, APropertyReadable Property, typename Destination>
  560inline const _<Object>& operator&(const _<Object>& object, Binding<Property, Destination>&& binding)
  561    requires requires {
  562        { binding.destinationPointerToMember } -> aui::invocable<Object&>;
  563        { std::invoke(binding.destinationPointerToMember, *object) } -> AAnyProperty;
  564    }
  565{
  566    AObject::connect(binding.sourceProperty, std::invoke(binding.destinationPointerToMember, *object));
  567    return object;
  568}
  569
  570template <typename Object, APropertyWritable Property, typename Destination>
  571inline const _<Object>& operator&&(const _<Object>& object, Binding<Property, Destination>&& binding)
  572    requires requires {
  573        { binding.destinationPointerToMember } -> aui::invocable<Object&>;
  574        { std::invoke(binding.destinationPointerToMember, *object) } -> AAnyProperty;
  575    }
  576{
  577    AObject::biConnect(binding.sourceProperty, std::invoke(binding.destinationPointerToMember, *object));
  578    return object;
  579}
  580
  581
  582template <typename Object, APropertyReadable Property, typename Destination>
  583inline const _<Object>& operator&(const _<Object>& object, Binding<Property, Destination>&& binding)
  584    requires requires {
  585        { binding.destinationPointerToMember } -> aui::invocable<Object&, decltype(*binding.sourceProperty)>;
  586    }
  587{
  588    AObject::connect(
  589        binding.sourceProperty, object.get(),
  590        [object = object.get(), wrapped = std::move(binding.destinationPointerToMember)](
  591            const std::decay_t<decltype(*binding.sourceProperty)>& i) { std::invoke(wrapped, *object, i); });
  592    return object;
  593}
  594
  595template <typename Object, APropertyWritable Property, typename Destination>
  596inline const _<Object>& operator&&(const _<Object>& object, Binding<Property, Destination>&& binding)
  597    requires requires {
  598        { binding.destinationPointerToMember } -> aui::invocable<Object&, decltype(*binding.sourceProperty)>;
  599    }
  600{
  601    AObject::biConnect(
  602        binding.sourceProperty, object.get(),
  603        [object = object.get(), wrapped = std::move(binding.destinationPointerToMember)](
  604            const std::decay_t<decltype(*binding.sourceProperty)>& i) { std::invoke(wrapped, *object, i); });
  605    return object;
  606}
Definition ADataBinding.h:115
Definition ADataBinding.h:86
Data binding implementation.
Definition ADataBinding.h:201
auto operator()(ModelField(Model::*field), ASignal< GetterRV >(View::*getter), Setter setter=(void(View::*)(const ModelField &)) nullptr)
Create a connection to specified pointer-to-member-field signal and pointer-to-member-function setter...
Definition ADataBinding.h:276
auto operator()(ModelField(Model::*field), void(View::*setterFunc)(SetterArg))
Create a connection to setter only.
Definition ADataBinding.h:235
auto operator()(ModelField(Model::*field), SetterLambda setterLambda)
Create a connection to specified lambda setter only.
Definition ADataBinding.h:257
emits modelChanged
Data in the model has changed.
Definition ADataBinding.h:399
auto operator()(Data(Model::*field))
Create a connection via ADataBindingDefault.
Definition ADataBinding.h:289
ADataBindingLinker2< Model, Data, Projection > operator()(Data(Model::*field), Projection projection)
Create a connection via ADataBindingDefault and projection (setter only).
Definition ADataBinding.h:299
A std::deque with AUI extensions.
Definition ADeque.h:27
StoredType & last() noexcept
Definition ADeque.h:164
static void inflate(aui::no_escape< AViewContainer > root, const _< AView > &view)
Wraps view with root using Stacked layout and expanding.
Definition ASignal.h:148
Base class of all UI objects.
Definition AView.h:78
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:179
Definition concepts.h:226
Definition concepts.h:42
Definition concepts.h:25
Invokable concept.
Definition concepts.h:37
Concept of a pointer-to-member.
Definition members.h:124
class_of c
Selects views that are of the specified classes.
Definition class_of.h:84
static void biConnect(PropertySource &&propertySource, PropertyDestination &&propertyDestination)
Connects source property to the destination property and opposite (bidirectionally).
Definition AObject.h:156
static decltype(auto) connect(const Signal &signal, Object *object, Function &&function)
Connects signal to the slot of the specified object.
Definition AObject.h:86
ASignal< Args... > emits
A signal declaration.
Definition ASignal.h:577
#define emit
emits the specified signal in context of this object.
Definition AObject.h:344
#define AUI_ASSERT(condition)
Asserts that the passed condition evaluates to true.
Definition Assert.h:55
#define AUI_ASSERTX(condition, what)
Asserts that the passed condition evaluates to true. Adds extra message string.
Definition Assert.h:74
Definition concepts.h:233
Defines how View handles properties of FieldType type.
Definition ADataBinding.h:38
static void(View::*)(const FieldType &v) getSetter()
Returns setter for ADataBinding (deprecated)
Definition ADataBinding.h:64
static void setup(const _< View > &view)
Called then view linked with field.
Definition ADataBinding.h:44
static void setup(const _< View > &view)
Definition ADataBinding.h:500
static auto property(const _< View > &view)
Returns property definition for FieldType.
Definition ADataBinding.h:50
static ASignal< FieldType >View::* getGetter()
Returns getter for ADataBinding (deprecated)
Definition ADataBinding.h:56
Definition concepts.h:188
Definition ADataBinding.h:546
Pointer to member type (not value) introspection.
Definition members.h:48
Definition parameter_pack.h:76