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