AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
AProperty.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/Common/ASignal.h>
15#include <AUI/Common/APropertyPrecomputed.h>
16
17namespace aui::detail::property {
18
19template<typename Projection, typename Source>
20concept ProjectionBidirectional = requires (Projection&& projectionBidirectional, Source&& source) {
21 // projection must accept SOURCE type.
22 { projectionBidirectional } -> aui::invocable<const Source&>;
23
24 // projection must be able to accept DESTINATION type to perform the opposite conversion.
25 { projectionBidirectional } -> aui::invocable<const std::invoke_result_t<Projection, const Source&>&>;
26
27 // projection's SOURCE type must be distinguishable from DESTINATION type.
28 requires not aui::same_as<std::decay_t<decltype(projectionBidirectional(source))>, std::decay_t<Source>>;
29};
30
31template <typename Property> // can't use AAnyProperty here, as concept would depend on itself
32auto makeAssignment(Property&& property) { // note the rvalue reference template argument here:
33 // pass your property as std::move(*this) if your
34 // property-compliant struct is temporary! otherwise you'll
35 // spend your weekend on debugging segfaults :)
36 using Underlying = std::decay_t<decltype(*property)>;
37 struct Invocable {
38 Property property;
39 void operator()(const Underlying& value) const {
40 const_cast<Property&>(property) = std::move(value);
41 };
42 } i = { std::forward<Property>(property) };
43
44 return ASlotDef<decltype(property.boundObject()), decltype(i)> {
45 .boundObject = property.boundObject(),
46 .invocable = std::move(i),
47 };
48}
49
50template<typename Property, typename Projection>
51auto makeReadonlyProjection(Property&& property, Projection&& projection) {
52 using Underlying = std::decay_t<decltype(*property)>;
53 auto signalProjected = property.changed.projected(projection);
54 using Signal = decltype(signalProjected);
55 using ProjectionResult = std::invoke_result_t<Projection, Underlying>;
56 struct PropertyReadProjection {
57 protected:
58 Property wrappedProperty;
59 Projection projection;
60
61 public:
62 Signal changed;
63 PropertyReadProjection(Property wrappedProperty, Projection&& projection, Signal changed)
64 : wrappedProperty(wrappedProperty), projection(std::move(projection)), changed(changed) {}
65 using Underlying = ProjectionResult;
66
67 [[nodiscard]]
68 auto boundObject() const {
69 return wrappedProperty.boundObject();
70 }
71
72 [[nodiscard]]
73 Underlying value() const {
74 return std::invoke(projection, wrappedProperty.value());
75 }
76
77 [[nodiscard]]
78 Underlying operator*() const noexcept {
79 return value();
80 }
81
82 [[nodiscard]] operator Underlying() const { return value(); }
83
84 };
85 static_assert(APropertyReadable<PropertyReadProjection>, "PropertyReadProjection must conform with APropertyReadable");
86 return PropertyReadProjection(std::forward<Property>(property), std::forward<Projection>(projection), std::move(signalProjected));
87}
88
89template<typename PropertyReadProjection, typename ProjectionWrite>
90struct PropertyReadWriteProjection: PropertyReadProjection {
91 ProjectionWrite projectionWrite;
92 using Underlying = typename PropertyReadProjection::Underlying;
93 explicit PropertyReadWriteProjection(PropertyReadProjection&& read, ProjectionWrite&& projectionWrite)
94 : PropertyReadProjection(std::move(read)), projectionWrite(std::move(projectionWrite)) {}
95
96 template <aui::convertible_to<Underlying> U>
97 PropertyReadWriteProjection& operator=(U&& value) noexcept {
98 this->wrappedProperty = std::invoke(projectionWrite, std::forward<U>(value));
99 return *this;
100 }
101private:
102 friend class API_AUI_CORE ::AObject;
103
107 [[nodiscard]]
108 auto assignment() noexcept {
109 return aui::detail::property::makeAssignment(std::move(*this));
110 }
111};
112
113template<typename Property, aui::not_overloaded_lambda ProjectionRead, aui::not_overloaded_lambda ProjectionWrite>
114auto makeBidirectionalProjection(Property&& property, ProjectionRead&& projectionRead, ProjectionWrite&& projectionWrite) {
115 auto readProjected =
116 makeReadonlyProjection(std::forward<Property>(property), std::forward<ProjectionRead>(projectionRead));
117 using PropertyReadProjection = decltype(readProjected);
118 PropertyReadWriteProjection result(std::move(readProjected), std::forward<ProjectionWrite>(projectionWrite));
119// static_assert(APropertyWritable<decltype(result)>, "PropertyReadWriteProjection must conform with APropertyWriteable");
120 return result;
121}
122
123template<typename Property, ProjectionBidirectional<typename std::decay_t<Property>::Underlying> Projection>
124auto makeBidirectionalProjection(Property&& property, Projection&& projection) {
125 // we must define non-overloaded lambdas for makeBidirectionalProjection overload.
126 using Source = std::decay_t<typename std::decay_t<Property>::Underlying>;
127 using Destination = std::decay_t<std::invoke_result_t<Projection, const Source&>>;
128 return makeBidirectionalProjection(
129 std::forward<Property>(property),
130 [projection](const Source& s) -> Destination { return std::invoke(projection, s); },
131 [projection](const Destination& d) -> Source { return std::invoke(projection, d); });
132}
133}
134
155template <typename T>
156struct AProperty: AObjectBase {
157 using Underlying = T;
158
159 T raw;
160 emits<T> changed;
161
162 AProperty()
164 = default;
165
166 template <aui::convertible_to<T> U>
167 AProperty(U&& value) noexcept : raw(std::forward<U>(value)) {}
168
169 AObjectBase* boundObject() {
170 return this;
171 }
172
173 template <aui::convertible_to<T> U>
174 AProperty& operator=(U&& value) noexcept {
175 static constexpr auto IS_COMPARABLE = requires { this->raw == value; };
176 if constexpr (IS_COMPARABLE) {
177 if (this->raw == value) [[unlikely]] {
178 return *this;
179 }
180 }
181 this->raw = std::forward<U>(value);
182 emit changed(this->raw);
183 return *this;
184 }
185
186 template <ASignalInvokable SignalInvokable>
187 void operator^(SignalInvokable&& t) {
188 t.invokeSignal(nullptr);
189 }
190
191 [[nodiscard]]
192 const T& value() const noexcept {
193 aui::property_precomputed::addDependency(changed);
194 return raw;
195 }
196
197 [[nodiscard]]
198 T& value() noexcept {
199 aui::property_precomputed::addDependency(changed);
200 return raw;
201 }
202
203 [[nodiscard]] operator const T&() const noexcept { return value(); }
204
205 [[nodiscard]]
206 const T* operator->() const noexcept {
207 return &value();
208 }
209
210 [[nodiscard]]
211 const T& operator*() const noexcept {
212 return value();
213 }
214
215 [[nodiscard]]
216 T& operator*() noexcept {
217 return value();
218 }
219
223 template<aui::invocable<const T&> Projection>
224 [[nodiscard]]
225 auto readProjected(Projection&& projection) noexcept {
226 return aui::detail::property::makeReadonlyProjection(*this, std::forward<Projection>(projection));
227 }
228
232 template<aui::invocable<const T&> ProjectionRead,
233 aui::invocable<const std::invoke_result_t<ProjectionRead, T>&> ProjectionWrite>
234 [[nodiscard]]
235 auto biProjected(ProjectionRead&& projectionRead, ProjectionWrite&& projectionWrite) noexcept {
236 return aui::detail::property::makeBidirectionalProjection(*this,
237 std::forward<ProjectionRead>(projectionRead),
238 std::forward<ProjectionWrite>(projectionWrite));
239 }
240
244 template<aui::detail::property::ProjectionBidirectional<T> Projection>
245 [[nodiscard]]
246 auto biProjected(Projection&& projectionBidirectional) noexcept {
247 return aui::detail::property::makeBidirectionalProjection(*this, projectionBidirectional);
248 }
249
250private:
251 friend class AObject;
255 [[nodiscard]]
256 auto assignment() noexcept {
257 return aui::detail::property::makeAssignment(*this);
258 }
259};
260static_assert(AAnyProperty<AProperty<int>>, "AProperty does not conform AAnyProperty concept");
261
305template <
307 typename SignalArg>
308struct APropertyDef {
312 const M* base;
313 using Model = M;
314
318 Getter get;
319
335 Setter set;
336 using GetterReturnT = decltype(std::invoke(get, base));
337 using Underlying = std::decay_t<GetterReturnT>;
338
343 static_assert(aui::same_as<Underlying , std::decay_t<SignalArg>>, "different getter result and signal arg?");
344
345 // this ctor effectively prohibits designated initialization, i.e., this one is not possible:
346 //
347 // auto size() const {
348 // return APropertyDef {
349 // .base = this,
350 // .get = &AView::mSize,
351 // .set = &AView::setSize,
352 // .changed = mSizeChanged,
353 // };
354 // }
355 //
356 // deduction in designated initializers is relatively recent feature.
357 APropertyDef(const M* base, Getter get, Setter set, const emits<SignalArg>& changed)
358 : base(base), get(std::move(get)), set(std::move(set)), changed(changed) {}
359
360 template <aui::convertible_to<Underlying> U>
361 APropertyDef& operator=(U&& u) {
362 std::invoke(set, *const_cast<Model*>(base), std::forward<U>(u));
363 return *this;
364 }
365
366 [[nodiscard]]
367 GetterReturnT value() const noexcept {
368 return std::invoke(get, base);
369 }
370
371 [[nodiscard]]
372 GetterReturnT operator*() const noexcept {
373 return std::invoke(get, base);
374 }
375
376 [[nodiscard]]
377 const Underlying* operator->() const noexcept {
378 return &std::invoke(get, base);
379 }
380
381 [[nodiscard]] operator GetterReturnT() const noexcept { return std::invoke(get, base); }
382
383 [[nodiscard]]
384 M* boundObject() const {
385 return const_cast<M*>(base);
386 }
387
391 template <aui::invocable<const Underlying&> Projection>
392 [[nodiscard]]
393 auto readProjected(Projection&& projection) noexcept {
394 return aui::detail::property::makeReadonlyProjection(std::move(*this), std::forward<Projection>(projection));
395 }
396
400 template <
403 [[nodiscard]]
404 auto biProjected(ProjectionRead&& projectionRead, ProjectionWrite&& projectionWrite) noexcept {
405 return aui::detail::property::makeBidirectionalProjection(
406 std::move(*this), std::forward<ProjectionRead>(projectionRead),
407 std::forward<ProjectionWrite>(projectionWrite));
408 }
409
413 template <aui::detail::property::ProjectionBidirectional<Underlying> Projection>
414 [[nodiscard]]
415 auto biProjected(Projection&& projectionBidirectional) noexcept {
416 return aui::detail::property::makeBidirectionalProjection(std::move(*this), projectionBidirectional);
417 };
418
419private:
420 friend class AObject;
424 [[nodiscard]]
425 auto assignment() noexcept {
426 return aui::detail::property::makeAssignment(std::move(*this));
427 }
428};
429
430// binary operations for properties.
431template<AAnyProperty Lhs, typename Rhs>
432[[nodiscard]]
433inline auto operator==(const Lhs& lhs, Rhs&& rhs) {
434 return *lhs == std::forward<Rhs>(rhs);
435}
436
437template<AAnyProperty Lhs, typename Rhs>
438[[nodiscard]]
439inline auto operator!=(const Lhs& lhs, Rhs&& rhs) {
440 return *lhs != std::forward<Rhs>(rhs);
441}
442
443template<AAnyProperty Lhs, typename Rhs>
444[[nodiscard]]
445inline auto operator+(const Lhs& lhs, Rhs&& rhs) {
446 return *lhs + std::forward<Rhs>(rhs);
447}
448
449template<AAnyProperty Lhs, typename Rhs>
450inline decltype(auto) operator+=(Lhs& lhs, Rhs&& rhs) {
451 *lhs += std::forward<Rhs>(rhs);
452 return lhs;
453}
454
455template<AAnyProperty Lhs, typename Rhs>
456inline decltype(auto) operator+=(Lhs&& lhs, Rhs&& rhs) {
457 return lhs = *lhs + std::forward<Rhs>(rhs);
458}
459
460template<AAnyProperty Lhs, typename Rhs>
461[[nodiscard]]
462inline auto operator-(const Lhs& lhs, Rhs&& rhs) {
463 return *lhs - std::forward<Rhs>(rhs);
464}
465
466// simple check above operators work.
467static_assert(requires { AProperty<int>() + 1; });
468
469
470/*
471// UNCOMMENT THIS to test biProjected
472static_assert(requires (AProperty<int>& intProperty) {
473 { intProperty.biProjected(aui::lambda_overloaded {
474 [](int) -> AString { return ""; },
475 [](const AString&) -> int { return 0; },
476 }).value() } -> aui::convertible_to<AString>;
477
478 { intProperty.biProjected(aui::lambda_overloaded {
479 [](int) -> AString { return ""; },
480 [](const AString&) -> int { return 0; },
481 }) = "AString" };
482
483
484 { intProperty.biProjected(aui::lambda_overloaded {
485 [](int) -> AString { return ""; },
486 [](const AString&) -> int { return 0; },
487 }) };
488
489 { intProperty.biProjected(aui::lambda_overloaded {
490 [](int) -> AString { return ""; },
491 [](const AString&) -> int { return 0; },
492 }).assignment() } -> aui::invocable<AString>;
493});
494*/
495
496template <APropertyReadable T> struct fmt::formatter<T> {
497 template<typename ParseContext>
498 constexpr auto parse(ParseContext& ctx)
499 {
500 return ctx.begin();
501 }
502
503 auto format(T& c, format_context& ctx) const {
504 return fmt::format_to(ctx.out(), "{}", *c);
505 }
506};
A base object class.
Definition AObject.h:39
Definition concepts.h:223
Definition concepts.h:107
Invokable concept.
Definition concepts.h:37
Definition concepts.h:70
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
void API_AUI_XML read(const _< IInputStream > &is, const _< IXmlDocumentVisitor > &visitor)
Parses xml from the input stream to the IXmlDocumentVisitor.
Definition AXml.cpp:19
Property implementation to use with custom getter/setter.
Definition AProperty.h:308
const M * base
AObject which this property belongs to.
Definition AProperty.h:312
Getter get
Getter. Can be pointer-to-member(function or field) or lambda.
Definition AProperty.h:318
Setter set
Setter. Can be pointer-to-member(function or field) or lambda.
Definition AProperty.h:335
auto biProjected(ProjectionRead &&projectionRead, ProjectionWrite &&projectionWrite) noexcept
Makes a bidirectional projection of this property.
Definition AProperty.h:404
const emits< SignalArg > & changed
Reference to underlying signal emitting on value changes.
Definition AProperty.h:342
auto biProjected(Projection &&projectionBidirectional) noexcept
Makes a bidirectional projection of this property (by a single aui::lambda_overloaded).
Definition AProperty.h:415
auto readProjected(Projection &&projection) noexcept
Makes a readonly projection of this property.
Definition AProperty.h:393
Basic easy-to-use property implementation containing T.
Definition AProperty.h:156
auto biProjected(Projection &&projectionBidirectional) noexcept
Makes a bidirectional projection of this property (by a single aui::lambda_overloaded).
Definition AProperty.h:246
auto readProjected(Projection &&projection) noexcept
Makes a readonly projection of this property.
Definition AProperty.h:225
auto biProjected(ProjectionRead &&projectionRead, ProjectionWrite &&projectionWrite) noexcept
Makes a bidirectional projection of this property.
Definition AProperty.h:235