neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
property.hpp
Go to the documentation of this file.
1// property.hpp
2/*
3 neogfx C++ App/Game Engine
4 Copyright (c) 2018, 2020 Leigh Johnston. All Rights Reserved.
5
6 This program is free software: you can redistribute it and / or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#pragma once
21
22#include <neogfx/neogfx.hpp>
23#include <string>
28
29#ifdef _MSC_VER
30#pragma warning (push)
31#pragma warning (disable: 4702 ) // unreachable code
32#endif
33
34namespace neogfx
35{
36 template <typename T, typename Category, class Context, typename Calculator>
37 class property;
38
39 template <typename T, typename Category, class Context, typename Calculator>
41 {
42 public:
44 typedef T value_type;
45 public:
46 property_transition(i_animator& aAnimator, property_type& aProperty, easing aEasingFunction, double aDuration, bool aEnabled = true) :
47 transition{ aAnimator, aEasingFunction, aDuration, aEnabled },
48 iProperty{ aProperty }
49 {
50 }
51 public:
52 void start_if()
53 {
54 if (iProperty.iPreviousValue != std::nullopt)
55 {
56 bool alreadyActive = active();
57 sync();
58 reset(true, disable_when_finished(), !alreadyActive);
59 neolib::scoped_flag sf{ iUpdatingProperty };
60 iProperty = *iFrom;
61 }
62 }
63 bool started() const
64 {
65 return iFrom != std::nullopt;
66 }
67 value_type const& from() const
68 {
69 return iFrom.value();
70 }
71 value_type const& to() const
72 {
73 return iTo.value();
74 }
75 value_type const& mix() const
76 {
77 return iMix.value();
78 }
79 bool updating_property() const
80 {
81 return iUpdatingProperty;
82 }
83 public:
85 {
86 return !finished() && enabled() && !paused();
87 }
88 void apply() final
89 {
90 if (!can_apply())
91 throw cannot_apply();
92 if (!animation_finished())
93 {
94 auto const mixValue = mix_value();
95 iMix = neogfx::mix(iFrom, iTo, mixValue);
96 neolib::scoped_flag sf{ iUpdatingProperty };
97 iProperty = mix();
98 }
99 else
100 {
101 iMix = (easing_function() != easing::Zero ? *iTo : *iFrom);
102 neolib::scoped_flag sf{ iUpdatingProperty };
103 iProperty = mix();
104 clear();
106 disable();
107 }
108 }
109 bool finished() const final
110 {
111 return iFrom == std::nullopt;
112 }
113 public:
114 void clear() final
115 {
116 iFrom = std::nullopt;
117 iTo = std::nullopt;
118 iMix = std::nullopt;
119 }
120 void sync(bool aIgnorePrevious = false) final
121 {
122 iFrom = aIgnorePrevious ? iProperty.iValue : iProperty.iPreviousValue;
123 iTo = iProperty.iValue;
124 }
125 private:
126 property_type& iProperty;
127 std::optional<value_type> iFrom;
128 std::optional<value_type> iTo;
129 std::optional<value_type> iMix;
130 bool iUpdatingProperty = false;
131 };
132
133 template <typename T, typename Category, class Context, typename Calculator = T(*)()>
134 class property : public i_property, public neolib::lifetime<>
135 {
137 template <typename, typename, class, typename>
139 public:
140 typedef T value_type;
141 typedef Context context_type;
143 public:
144 define_declared_event(PropertyChanged, property_changed, const property_variant&)
146 define_event(Changed, changed, value_type const&)
147 define_event(ChangedFromTo, changed_from_to, value_type const&, value_type const&)
148 public:
149 struct invalid_type : std::logic_error { invalid_type() : std::logic_error("neogfx::property::invalid_type") {} };
150 public:
151 typedef Category category_type;
152 typedef Calculator calculator_function_type;
153 public:
154 template <typename ParentType>
156 {
157 public:
158 typedef ParentType parent_type;
159 public:
160 optional_proxy(parent_type& aParent) : iParent{ aParent }
161 {
162 }
163 public:
164 template <typename SFINAE = neolib::optional_t<T>>
165 operator const typename std::enable_if<neolib::is_optional_v<T>, SFINAE>::type&() const
166 {
167 return *iParent.value();
168 }
169 template <typename T2, typename SFINAE = optional_proxy<parent_type>>
170 typename std::enable_if<!std::is_const<parent_type>::value, SFINAE>::type& operator=(const T2& aValue)
171 {
172 iParent.assign(aValue);
173 return *this;
174 }
175 template <typename SFINAE = const neolib::optional_t<T>*>
176 const typename std::enable_if<neolib::is_optional_v<T>, SFINAE>::type operator->() const
177 {
178 return &*iParent.value();
179 }
180 private:
181 parent_type& iParent;
182 };
183 public:
184 property(i_property_owner& aOwner, std::string const& aName) : iOwner{ aOwner }, iName{ aName }, iValue{}
185 {
186 aOwner.properties().register_property(*this);
187 }
188 property(i_property_owner& aOwner, std::string const& aName, calculator_function_type aCalculator) : iOwner{ aOwner }, iName{ aName }, iCalculator{ aCalculator }, iValue{}
189 {
190 aOwner.properties().register_property(*this);
191 }
192 property(i_property_owner& aOwner, std::string const& aName, const T& aValue) : iOwner{ aOwner }, iName{ aName }, iValue { aValue }
193 {
194 aOwner.properties().register_property(*this);
195 }
196 property(i_property_owner& aOwner, std::string const& aName, calculator_function_type aCalculator, const T& aValue) : iOwner{ aOwner }, iName{ aName }, iCalculator{ aCalculator }, iValue{ aValue }
197 {
198 aOwner.properties().register_property(*this);
199 }
200 public:
201 property_variant get(const i_property& aProperty) const final
202 {
203 return get_as_variant();
204 }
205 public:
206 i_property_owner& owner() const final
207 {
208 return iOwner;
209 }
210 public:
211 const string& name() const final
212 {
213 return iName;
214 }
215 const std::type_info& type() const final
216 {
217 return typeid(value_type);
218 }
219 const std::type_info& category() const final
220 {
221 return typeid(category_type);
222 }
223 bool optional() const final
224 {
226 }
228 {
229 if constexpr (neolib::is_optional_v<T>)
230 {
231 if (value() != std::nullopt)
232 return *value();
233 else
234 return neolib::none;
235 }
236 else
237 return value();
238 }
239 void set_from_variant(const property_variant& aValue) final
240 {
241 std::visit([this](auto&& arg)
242 {
243 if constexpr (std::is_same_v<std::decay_t<decltype(arg)>, std::monostate>)
244 *this = value_type{};
245 else
246 *this = std::forward<decltype(arg)>(arg);
247 }, aValue);
248 }
249 bool read_only() const final
250 {
251 return iReadOnly;
252 }
253 void set_read_only(bool aReadOnly) final
254 {
255 iReadOnly = aReadOnly;
256 }
257 bool transition_set() const final
258 {
259 return iTransition != nullptr;
260 }
262 {
263 if (iTransition != nullptr)
264 return *iTransition;
265 throw std::logic_error( "neogfx::property: no transition!" );
266 }
267 void set_transition(i_animator& aAnimator, easing aEasingFunction, double aDuration, bool aEnabled = true) final
268 {
269 iTransition = std::make_unique<transition_type>(aAnimator, *this, aEasingFunction, aDuration, aEnabled);
270 }
271 void clear_transition() final
272 {
273 iTransition = nullptr;
274 }
275 bool transition_suppressed() const final
276 {
277 return iTransitionSuppressed;
278 }
279 void suppress_transition(bool aSuppress) final
280 {
281 iTransitionSuppressed = aSuppress;
282 if (aSuppress)
283 {
284 if (transition_set() && transition().started())
285 assign(effective_value(), true, true);
286 }
287 }
288 bool has_delegate() const final
289 {
290 return iDelegate != nullptr;
291 }
292 i_property_delegate const& delegate() const final
293 {
294 if (has_delegate())
295 return *iDelegate;
296 throw no_delegate();
297 }
299 {
300 return const_cast<i_property_delegate&>(to_const(*this).delegate());
301 }
302 void set_delegate(i_property_delegate& aDelegate) final
303 {
304 iDelegate = &aDelegate;
305 }
306 void unset_delegate() final
307 {
308 iDelegate = nullptr;
309 }
310 public:
311 value_type const& value() const
312 {
313 if (has_delegate())
314 {
315 std::visit([this](auto&& arg)
316 {
317 typedef std::decay_t<decltype(arg)> try_type;
318 if constexpr (std::is_same_v<try_type, value_type>)
319 iValue = arg;
320 else if constexpr (std::is_same_v<try_type, custom_type>)
321 {
324 else
326 }
327 else if constexpr (std::is_same_v<try_type, neolib::none_t> || std::is_same_v<try_type, std::monostate>)
328 {
330 iValue = {};
331 else
332 iValue = std::nullopt;
333 }
334 else
335 {
336 // [[unreachable]]
337 throw invalid_type();
338 }
339 }, delegate().get(*this));
340 }
341 return iValue;
342 }
344 {
345 if (!transition_set() || !transition().started())
346 return value();
347 else
348 return transition().to();
349 }
350 template <typename T2>
351 self_type& assign(T2&& aValue, bool aOwnerNotify = true, bool aDiscardPreviousValue = false)
352 {
353 typedef std::decay_t<decltype(aValue)> try_type;
354 if constexpr (std::is_same_v<try_type, value_type> || std::is_same_v<neolib::optional<try_type>, value_type>)
355 return do_assign(std::forward<T2>(aValue), aOwnerNotify, aDiscardPreviousValue);
356 else if constexpr (std::is_same_v<try_type, custom_type>)
357 return do_assign(neolib::any_cast<value_type>(std::forward<T2>(aValue)), aOwnerNotify, aDiscardPreviousValue);
358 else if constexpr (std::is_same_v<try_type, neolib::none_t> || std::is_same_v<try_type, std::monostate>)
359 return do_assign(value_type{}, aOwnerNotify, aDiscardPreviousValue);
360 else if constexpr (std::is_arithmetic_v<value_type> && std::is_convertible_v<try_type, value_type> && std::is_integral_v<try_type> == std::is_integral_v<value_type>)
361 return do_assign(static_cast<value_type>(std::forward<T2>(aValue)), aOwnerNotify, aDiscardPreviousValue);
362 else
363 {
364 // [[unreachable]]
365 (void)aValue;
366 (void)aOwnerNotify;
367 throw invalid_type();
368 }
369 }
370 template <typename T2>
371 self_type& operator=(T2&& aValue)
372 {
373 return assign(std::forward<T2>(aValue));
374 }
375 operator value_type const&() const
376 {
377 return value();
378 }
379 template <typename SFINAE = optional_proxy<const self_type>>
380 const typename std::enable_if<neolib::is_optional_v<T>, SFINAE>::type operator*() const
381 {
382 return optional_proxy<const self_type>{ *this };
383 }
384 template <typename SFINAE = optional_proxy<self_type>>
385 typename std::enable_if<neolib::is_optional_v<T>, SFINAE>::type operator*()
386 {
387 return optional_proxy<self_type>{ *this };
388 }
389 template <typename SFINAE = optional_proxy<const self_type>>
390 const typename std::enable_if<neolib::is_optional_v<T>, SFINAE>::type operator->() const
391 {
392 return optional_proxy<const self_type>{ *this };
393 }
394 template <typename SFINAE = optional_proxy<self_type>>
395 typename std::enable_if<neolib::is_optional_v<T>, SFINAE>::type operator->()
396 {
397 return optional_proxy<self_type>{ *this };
398 }
399 template <typename T>
400 bool operator==(const T& aRhs) const
401 {
402 return value() == aRhs;
403 }
404 template <typename T>
405 bool operator!=(const T& aRhs) const
406 {
407 return value() != aRhs;
408 }
409 template <typename T>
410 bool operator==(const neolib::optional<T>& aRhs) const
411 {
412 return value() == aRhs;
413 }
414 template <typename T>
415 bool operator!=(const neolib::optional<T>& aRhs) const
416 {
417 return value() != aRhs;
418 }
419 protected:
420 const void* data() const final
421 {
422 if (!has_delegate())
423 return &value();
424 else
425 return delegate().data();
426 }
427 void* data() final
428 {
429 if (!has_delegate())
430 return &mutable_value();
431 else
432 return delegate().data();
433 }
434 void*const* calculator_function() const final
435 {
436 // why? because we have to type-erase to support plugins and std::function can't be passed across a plugin boundary.
437 if (iCalculator != nullptr)
438 return reinterpret_cast<void*const*>(&iCalculator);
439 throw no_calculator();
440 }
441 private:
442 value_type& mutable_value()
443 {
444 return const_cast<value_type&>(to_const(*this).value());
445 }
446 template <typename T2>
447 self_type& do_assign(T2&& aValue, bool aOwnerNotify = true, bool aDiscardPreviousValue = false)
448 {
449 if (read_only())
450 return *this;
451
453 !transition().updating_property() &&
454 transition().started() && aValue == effective_value())
455 return *this;
456
457 if (mutable_value() != aValue)
458 {
459 if (!transition_set())
460 {
461 iPreviousValue = !aDiscardPreviousValue ? value() : aValue;
462 mutable_value() = aValue;
463 }
464 else if (transition().updating_property())
465 {
466 mutable_value() = aValue;
467 }
468 else
469 {
470 iPreviousValue = !aDiscardPreviousValue ? effective_value() : aValue;
471 mutable_value() = aValue;
473 transition().start_if();
474 else
475 transition().clear();
476 }
477 update(aOwnerNotify);
478 }
479
480 return *this;
481 }
482 void update(bool aOwnerNotify = true)
483 {
484 destroyed_flag destroyed{ *this };
485
486 if (aOwnerNotify)
487 iOwner.property_changed(*this);
488 if (destroyed)
489 return;
490
491 bool const discardChanged = event_consumed(PropertyChanged.trigger(get_as_variant()));
492 if (destroyed)
493 return;
494
495 bool discardChangedFromTo = false;
496 if constexpr (!neolib::is_optional_v<T>)
497 discardChangedFromTo = event_consumed(PropertyChangedFromTo.trigger(property_variant{ *iPreviousValue }, get_as_variant()));
498 else
499 discardChangedFromTo = event_consumed(PropertyChangedFromTo.trigger(*iPreviousValue != std::nullopt ? property_variant{ **iPreviousValue } : property_variant{ neolib::none }, get_as_variant()));
500 if (destroyed)
501 return;
502
503 if (!discardChanged && event_consumed(Changed.trigger(value())))
504 return;
505 if (destroyed)
506 return;
507
508 if (!discardChangedFromTo && event_consumed(ChangedFromTo.trigger(*iPreviousValue, value())))
509 return;
510 if (destroyed)
511 return;
512 }
513 private:
514 i_property_owner& iOwner;
515 string iName;
516 calculator_function_type iCalculator;
517 mutable value_type iValue;
518 std::optional<value_type> iPreviousValue;
519 bool iReadOnly = false;
520 std::unique_ptr<transition_type> iTransition;
521 bool iTransitionSuppressed = false;
522 i_property_delegate* iDelegate = nullptr;
523 };
524
525 namespace property_category
526 {
527 struct soft_geometry {};
528 struct hard_geometry {};
529 struct font {};
530 struct color {};
531 struct other_appearance {};
532 struct interaction {};
533 struct other {};
534 };
535
536 #define define_property( category, type, name, calculator, ... ) neogfx::property<type, category, property_context_type, decltype(&property_context_type::##calculator)> name = { *this, #name ##s, &property_context_type::##calculator, __VA_ARGS__ };
537}
538
539#ifdef _MSC_VER
540#pragma warning (pop)
541#endif
virtual void register_property(i_property &aProperty)=0
virtual property_variant get(const i_property &aProperty) const =0
virtual const void * data() const =0
virtual const i_properties & properties() const =0
virtual void property_changed(i_property &aProperty)=0
std::enable_if<!std::is_const< parent_type >::value, SFINAE >::type & operator=(const T2 &aValue)
Definition property.hpp:170
const std::enable_if< neolib::is_optional_v< T >, SFINAE >::type operator->() const
Definition property.hpp:176
optional_proxy(parent_type &aParent)
Definition property.hpp:160
bool updating_property() const
Definition property.hpp:79
bool property_transition::can_apply() const final
Definition property.hpp:84
value_type const & mix() const
Definition property.hpp:75
bool finished() const final
Definition property.hpp:109
void sync(bool aIgnorePrevious=false) final
Definition property.hpp:120
property_transition(i_animator &aAnimator, property_type &aProperty, easing aEasingFunction, double aDuration, bool aEnabled=true)
Definition property.hpp:46
property< T, Category, Context, Calculator > property_type
Definition property.hpp:43
value_type const & from() const
Definition property.hpp:67
value_type const & to() const
Definition property.hpp:71
std::enable_if< neolib::is_optional_v< T >, SFINAE >::type operator->()
Definition property.hpp:395
void clear_transition() final
Definition property.hpp:271
const std::enable_if< neolib::is_optional_v< T >, SFINAE >::type operator*() const
Definition property.hpp:380
void * data() final
Definition property.hpp:427
bool optional() const final
Definition property.hpp:223
property(i_property_owner &aOwner, std::string const &aName, calculator_function_type aCalculator)
Definition property.hpp:188
property_variant get(const i_property &aProperty) const final
Definition property.hpp:201
const string & name() const final
Definition property.hpp:211
self_type & assign(T2 &&aValue, bool aOwnerNotify=true, bool aDiscardPreviousValue=false)
Definition property.hpp:351
const std::type_info & type() const final
Definition property.hpp:215
bool transition_suppressed() const final
Definition property.hpp:275
bool transition_set() const final
Definition property.hpp:257
void set_transition(i_animator &aAnimator, easing aEasingFunction, double aDuration, bool aEnabled=true) final
Definition property.hpp:267
i_property_delegate & delegate() final
Definition property.hpp:298
self_type & operator=(T2 &&aValue)
Definition property.hpp:371
Category category_type
Definition property.hpp:151
const void * data() const final
Definition property.hpp:420
bool operator!=(const neolib::optional< T > &aRhs) const
Definition property.hpp:415
const std::type_info & category() const final
Definition property.hpp:219
property(i_property_owner &aOwner, std::string const &aName)
Definition property.hpp:184
i_property_delegate const & delegate() const final
Definition property.hpp:292
void set_delegate(i_property_delegate &aDelegate) final
Definition property.hpp:302
value_type const & effective_value() const
Definition property.hpp:343
void unset_delegate() final
Definition property.hpp:306
Calculator calculator_function_type
Definition property.hpp:152
bool operator==(const neolib::optional< T > &aRhs) const
Definition property.hpp:410
property_transition< T, Category, Context, Calculator > transition_type
Definition property.hpp:142
bool operator!=(const T &aRhs) const
Definition property.hpp:405
property(i_property_owner &aOwner, std::string const &aName, const T &aValue)
Definition property.hpp:192
transition_type & transition() const final
Definition property.hpp:261
define_declared_event(PropertyChanged, property_changed, const property_variant &) define_declared_event(PropertyChangedFromTo
value_type const & value() const
Definition property.hpp:311
property_variant get_as_variant() const final
Definition property.hpp:227
std::enable_if< neolib::is_optional_v< T >, SFINAE >::type operator*()
Definition property.hpp:385
property(i_property_owner &aOwner, std::string const &aName, calculator_function_type aCalculator, const T &aValue)
Definition property.hpp:196
void *const * calculator_function() const final
Definition property.hpp:434
void suppress_transition(bool aSuppress) final
Definition property.hpp:279
Context context_type
Definition property.hpp:141
bool read_only() const final
Definition property.hpp:249
void set_read_only(bool aReadOnly) final
Definition property.hpp:253
i_property_owner & owner() const final
Definition property.hpp:206
bool has_delegate() const final
Definition property.hpp:288
const std::enable_if< neolib::is_optional_v< T >, SFINAE >::type operator->() const
Definition property.hpp:390
void set_from_variant(const property_variant &aValue) final
Definition property.hpp:239
const property_variant const property_variant changed_from_to
Definition property.hpp:147
bool operator==(const T &aRhs) const
Definition property.hpp:400
bool animation_finished() const override
easing easing_function() const override
bool active() const override
bool enabled() const override
double mix_value() const override
void disable() override
bool disable_when_finished() const override
bool paused() const override
void reset(bool aEnable=true, bool aDisableWhenFinished=false, bool aResetStartTime=true) override
neolib::destroyed_flag destroyed_flag
Definition lifetime.hpp:87
neolib::variant< void *, bool, char, int32_t, uint32_t, int64_t, uint64_t, float, double, string, mat33, size, point, rect, box_areas, custom_type > property_variant
point mix(const point &aLhs, const point &aRhs, double aMixValue)
sRGB_color color
Definition color.hpp:1067
ref_ptr< ConcreteType > make_ref(Args &&... args)
bool event_consumed(trigger_result aTriggerResult)
Definition i_event.hpp:115
auto destroyed(Object &aObject, const Handler aHandler)
Definition i_object.hpp:73
const none_t none
Definition variant.hpp:111
Definition plf_hive.h:79
#define define_event(name, declName,...)
Definition event.hpp:200
#define define_declared_event(name, declName,...)
Definition event.hpp:195