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-2024 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
198template <typename Model>
199class [[deprecated("consider using AProperty and signal/slot connections instead")]] ADataBinding: public AObject {
200private:
201 using Observer = std::function<void(const Model& model, unsigned)>;
202 ADeque<Observer> mLinkObservers;
203
204 Model* mModel = nullptr;
205 bool mOwning = false;
206
207 void* mExcept = nullptr;
208
209public:
210
211 ADataBinding() = default;
212 explicit ADataBinding(const Model& m)
213 {
214 setModel(m);
215 }
216 explicit ADataBinding(Model* m)
217 {
218 setModel(m);
219 }
220
221 virtual ~ADataBinding() {
222 if (mOwning) {
223 delete mModel;
224 }
225 }
226
232 template<typename View, typename ModelField, typename SetterArg>
233 auto operator()(ModelField(Model::*field), void(View::*setterFunc)(SetterArg)) {
234 AUI_ASSERT(setterFunc != nullptr);
235 return operator()<View, ModelField>(field, (ASignal<ModelField>(View::*))nullptr, setterFunc);
236 }
237
254 template<typename ModelField, typename SetterLambda>
255 auto operator()(ModelField(Model::*field), SetterLambda setterLambda) {
256 using lambda_args = typename aui::lambda_info<SetterLambda>::args;
257 return aui::tuple_visitor<lambda_args>::for_each_all([&]<typename ViewReference, typename DataArgument>() {
258 static_assert(std::is_reference_v<ViewReference>, "View is expected to be a reference");
259 static_assert(aui::convertible_to<ModelField, DataArgument>, "lambda's argument is expected to be constructible from ModelField");
260 using View = std::decay_t<ViewReference>;
261 return operator()<View, ModelField>(field, (ASignal<ModelField>(View::*))nullptr, [setterLambda = std::move(setterLambda)](View* view, DataArgument dataArgument) {
262 std::invoke(setterLambda, *view, std::forward<DataArgument>(dataArgument));
263 });
264 });
265 }
266
273 template<typename View, typename ModelField, typename GetterRV, aui::invocable<View*, const ModelField&> Setter>
274 auto operator()(ModelField(Model::*field),
275 ASignal<GetterRV>(View::*getter),
276 Setter setter = (void(View::*)(const ModelField&))nullptr) {
277 return ADataBindingLinker<Model, View, std::decay_t<ModelField>, GetterRV, Setter>(this, getter, std::move(setter), field);
278 }
279
286 template<typename Data>
287 auto operator()(Data(Model::*field)) {
288 return ADataBindingLinker2<Model, Data, std::nullptr_t>(this, field, nullptr);
289 }
290
296 template<typename Data, aui::invocable<Data> Projection>
297 ADataBindingLinker2<Model, Data, Projection> operator()(Data(Model::*field), Projection projection) {
298 return ADataBindingLinker2<Model, Data, Projection>(this, field, std::move(projection));
299 }
300 const Model& getModel() const noexcept {
301 return *mModel;
302 }
303
304 Model const * operator->() const noexcept {
305 return &getModel();
306 }
307
308 Model& getEditableModel() {
309 return *mModel;
310 }
311 void setModel(const Model& model) {
312 if (mOwning) {
313 delete mModel;
314 }
315 mOwning = true;
316 mModel = new Model(model);
317 notifyUpdate();
318 }
319
320 void setModel(Model* model) {
321 if (mOwning) {
322 delete mModel;
323 }
324 mOwning = false;
325 mModel = model;
326 notifyUpdate();
327 }
328
329 const void* getExclusion() const {
330 return mExcept;
331 }
332
333 void notifyUpdate(void* except = nullptr, unsigned field = -1) {
334 mExcept = except;
335 for (auto& applier : mLinkObservers) {
336 applier(*mModel, field);
337 }
338 emit modelChanged;
339 }
340
341 template<typename ModelField>
342 void notifyUpdate(ModelField(Model::*field)) {
343 union converter {
344 unsigned i;
345 decltype(field) p;
346 } c;
347 c.p = field;
348 notifyUpdate(nullptr, c.i);
349 }
350
351 template<typename ModelField, aui::convertible_to<ModelField> U>
352 void setValue(ModelField(Model::*field), U&& value) {
353 mModel->*field = std::move(value);
354 notifyUpdate(field);
355 }
356
357 void addObserver(Observer applier) {
358 mLinkObservers << std::move(applier);
359 if (mModel) {
360 mLinkObservers.last()(*mModel, -1);
361 }
362 }
363
364 template<aui::invocable T>
365 void addObserver(T&& applier) {
366 addObserver([applier = std::forward<T>(applier)](const Model&, unsigned) {
367 applier();
368 });
369 }
370
371 template<typename ModelField, typename FieldObserver>
372 void addObserverNoInitialCall(ModelField(Model::*field), FieldObserver&& observer) {
373 mLinkObservers << [observer = std::forward<FieldObserver>(observer), field](const Model& model, unsigned index) {
374 union converter {
375 unsigned i;
376 decltype(field) p;
377 } c;
378 c.p = field;
379 if (c.i == index || index == -1) {
380 observer(model.*field);
381 }
382 };
383 }
384 template<typename ModelField, typename FieldObserver>
385 void addObserver(ModelField(Model::*field), FieldObserver&& observer) {
386 addObserverNoInitialCall(field, std::forward<FieldObserver>(observer));
387 if (mModel) {
388 mLinkObservers.last()(*mModel, -1);
389 }
390 }
391
392
393signals:
398};
399
400template<typename Klass1, typename View, typename Model, typename ModelField, typename GetterRV, typename SetterArg>
401_<Klass1> operator&&(const _<Klass1>& modelBinding, const ADataBindingLinker<Model, View, ModelField, GetterRV, SetterArg>& linker) {
402 union converter {
403 unsigned i;
404 decltype(linker.getField()) p;
405 };
406 if (linker.getGetter()) {
407 AObject::connect(modelBinding.get()->*(linker.getGetter()), linker.getBinder(), [modelBinding, linker](const GetterRV& data) {
408 AUI_ASSERTX(&linker.getBinder()->getEditableModel(), "please setModel for ADataBinding");
409 modelBinding->setSignalsEnabled(false);
410 linker.getBinder()->getEditableModel().*(linker.getField()) = data;
411 converter c;
412 c.p = linker.getField();
413 linker.getBinder()->notifyUpdate(modelBinding.get(), c.i);
414 modelBinding->setSignalsEnabled(true);
415 });
416 }
417
418 if constexpr (aui::convertible_to<decltype(linker.getSetterFunc()), bool>) {
419 if (!bool(linker.getSetterFunc())) {
420 return modelBinding;
421 }
422 }
423 linker.getBinder()->addObserver([modelBinding, linker](const Model& model, unsigned field) {
424 converter c;
425 c.p = linker.getField();
426 if (c.i == field || field == -1) {
427 if (modelBinding.get() != linker.getBinder()->getExclusion()) {
428 std::invoke(linker.getSetterFunc(), modelBinding.get(), model.*(linker.getField()));
429 }
430 }
431 });
432
433 return modelBinding;
434}
435
436
437namespace aui::detail {
438 template<typename ForcedClazz, typename Type>
439 struct pointer_to_member {
440 template<typename... Args>
441 static Type(ForcedClazz::*with_args(std::tuple<Args...>))(Args...) {
442 return nullptr;
443 }
444 };
445}
446
447template<typename View, typename Model, typename Data, typename Projection>
448_<View> operator&&(const _<View>& object, const ADataBindingLinker2<Model, Data, Projection>& linker) {
450
451 constexpr bool is_default_projection = std::is_same_v<Projection, std::nullptr_t>;
452 using projection_deduced = std::conditional_t<is_default_projection, aui::identity, Projection>;
453 static_assert(std::is_invocable_v<projection_deduced, Data>, "projection is expected to accept Data value from model");
454 using data_deduced = std::decay_t<std::invoke_result_t<projection_deduced, Data>>;
455
456 using getter = ASignal<Data>(View::*);
458
459 using setter_ret = typename setter::return_t;
460 using setter_args = typename setter::args;
461
462 using my_pointer_to_member = typename aui::detail::pointer_to_member<View, setter_ret>;
463
464 using pointer_to_setter = decltype(my_pointer_to_member::with_args(std::declval<setter_args>()));
465
466 if constexpr (is_default_projection) {
467 getter g = nullptr;
468 // getter is optional.
469 if constexpr (requires { ADataBindingDefault<View, Data>::getGetter(); }) {
470 if constexpr (requires { std::invoke(ADataBindingDefault<View, Data>::getGetter(), *object).changed; }) {
471 // AProperty.
472 g = (getter) &std::invoke(ADataBindingDefault<View, Data>::getGetter(), *object).changed;
473 } else {
474 g = (getter) &std::invoke(ADataBindingDefault<View, Data>::getGetter(), *object);
475 }
476 }
477 auto s = static_cast<pointer_to_setter>(ADataBindingDefault<View, Data>::getSetter());
478
479 AUI_ASSERTX(g != nullptr || s != nullptr, "ADataBindingDefault is not defined for View, Data");
480
481 object && (*linker.getBinder())(linker.getField(), g, s);
482 } else {
483 object && (*linker.getBinder())(linker.getField(), [projection = linker.getProjection()](View& v, const Data& d) {
484 auto s = static_cast<pointer_to_setter>(ADataBindingDefault<View, data_deduced>::getSetter());
485 std::invoke(s, &v, projection(d));
486 });
487 }
488 return object;
489}
490
491template<typename View>
492struct ADataBindingDefault<View, Visibility> {
493public:
498 static void setup(const _<View>& view) {}
499
500 static auto getSetter() {
501 return &AView::setVisibility;
502 }
503};
504
505template <typename Object, APropertyReadable Connectable>
506inline const _<Object>& operator&(const _<Object>& object, Connectable&& binding) {
508 typename AAnySignalOrPropertyTraits<std::decay_t<Connectable>>::args>::for_each_all([&]<typename... T>() {
509 using Binding = ADataBindingDefault<std::decay_t<Object>, std::decay_t<T>...>;
510 static_assert(
511 requires { { Binding::property(object) } -> AAnyProperty; } ||
512 requires { { Binding::property(object) } -> aui::derived_from<ASlotDefBase>; },
513 "ADataBindingDefault is required to have property() function to return any property or slot def; either "
514 "define proper ADataBindingDefault specialization or explicitly specify the destination property.");
515 static_assert(
516 requires { { Binding::setup(object) }; },
517 "ADataBindingDefault is required to have setup(const _<Object>&) function; either define proper "
518 "ADataBindingDefault specialization or explicitly specify the destination property.");
519 Binding::setup(object);
520 AObject::connect(binding, Binding::property(object));
521 });
522 return object;
523}
524
525template<typename Object, APropertyWritable Connectable>
526inline const _<Object>& operator&&(const _<Object>& object, Connectable&& binding) {
528 using Binding = ADataBindingDefault<std::decay_t<Object>, std::decay_t<T>...>;
529 static_assert(requires {
530 { Binding::property(object) } -> AAnyProperty;
531 }, "ADataBindingDefault is required to have property() function to return any property; either define proper "
532 "ADataBindingDefault specialization or explicitly specify the destination property.");
533 static_assert(
534 requires { { Binding::setup(object) }; },
535 "ADataBindingDefault is required to have setup(const _<Object>&) function; either define proper "
536 "ADataBindingDefault specialization or explicitly specify the destination property.");
537 Binding::setup(object);
538 AObject::biConnect(binding, Binding::property(object));
539 });
540 return object;
541}
542
543template <AAnyProperty Lhs, typename Destination>
544struct Binding {
545 Lhs sourceProperty;
546 Destination destinationPointerToMember;
547 explicit Binding(Lhs sourceProperty, Destination destinationPointerToMember)
548 : sourceProperty(sourceProperty), destinationPointerToMember(destinationPointerToMember) {}
549};
550
551template <AAnyProperty Property, typename Destination>
552inline decltype(auto) operator>(Property&& sourceProperty, Destination&& rhs) {
553 return Binding<Property, Destination>(std::forward<Property>(sourceProperty), std::forward<Destination>(rhs));
554}
555
556template <typename Object, APropertyReadable Property, typename Destination>
557inline const _<Object>& operator&(const _<Object>& object, Binding<Property, Destination>&& binding)
558 requires requires {
559 { binding.destinationPointerToMember } -> aui::invocable<Object&>;
560 { std::invoke(binding.destinationPointerToMember, *object) } -> AAnyProperty;
561 }
562{
563 AObject::connect(binding.sourceProperty, std::invoke(binding.destinationPointerToMember, *object));
564 return object;
565}
566
567template <typename Object, APropertyWritable Property, typename Destination>
568inline const _<Object>& operator&&(const _<Object>& object, Binding<Property, Destination>&& binding)
569 requires requires {
570 { binding.destinationPointerToMember } -> aui::invocable<Object&>;
571 { std::invoke(binding.destinationPointerToMember, *object) } -> AAnyProperty;
572 }
573{
574 AObject::biConnect(binding.sourceProperty, std::invoke(binding.destinationPointerToMember, *object));
575 return object;
576}
577
578
579template <typename Object, APropertyReadable Property, typename Destination>
580inline const _<Object>& operator&(const _<Object>& object, Binding<Property, Destination>&& binding)
581 requires requires {
582 { binding.destinationPointerToMember } -> aui::invocable<Object&, decltype(*binding.sourceProperty)>;
583 }
584{
586 binding.sourceProperty, object.get(),
587 [object = object.get(), wrapped = std::move(binding.destinationPointerToMember)](
588 const std::decay_t<decltype(*binding.sourceProperty)>& i) { std::invoke(wrapped, *object, i); });
589 return object;
590}
591
592template <typename Object, APropertyWritable Property, typename Destination>
593inline const _<Object>& operator&&(const _<Object>& object, Binding<Property, Destination>&& binding)
594 requires requires {
595 { binding.destinationPointerToMember } -> aui::invocable<Object&, decltype(*binding.sourceProperty)>;
596 }
597{
599 binding.sourceProperty, object.get(),
600 [object = object.get(), wrapped = std::move(binding.destinationPointerToMember)](
601 const std::decay_t<decltype(*binding.sourceProperty)>& i) { std::invoke(wrapped, *object, i); });
602 return object;
603}
Definition ADataBinding.h:114
Definition ADataBinding.h:85
Data binding implementation.
Definition ADataBinding.h:199
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:274
auto operator()(ModelField(Model::*field), void(View::*setterFunc)(SetterArg))
Create a connection to setter only.
Definition ADataBinding.h:233
auto operator()(ModelField(Model::*field), SetterLambda setterLambda)
Create a connection to specified lambda setter only.
Definition ADataBinding.h:255
emits modelChanged
Data in the model has changed.
Definition ADataBinding.h:397
auto operator()(Data(Model::*field))
Create a connection via ADataBindingDefault.
Definition ADataBinding.h:287
ADataBindingLinker2< Model, Data, Projection > operator()(Data(Model::*field), Projection projection)
Create a connection via ADataBindingDefault and projection (setter only).
Definition ADataBinding.h:297
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 ALayoutInflater.cpp:20
Definition ASignal.h:135
Base class of all UI objects.
Definition AView.h:78
An std::weak_ptr with AUI extensions.
Definition SharedPtrTypes.h:178
Definition concepts.h:223
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:135
ASignal< Args... > emits
A signal declaration.
Definition ASignal.h:348
#define emit
emits the specified signal in context of this object.
Definition AObject.h:310
static void connect(const Signal &signal, Object *object, Function &&function)
Connects signal to the slot of the specified object.
Definition AObject.h:65
#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:230
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:498
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:544
Definition members.h:19
Definition parameter_pack.h:76