cello
JUCE ValueTrees for Humans
Loading...
Searching...
No Matches
cello_value.h
1/*
2 Copyright (c) 2023 Brett g Porter
3 Permission is hereby granted, free of charge, to any person obtaining a copy
4 of this software and associated documentation files (the "Software"), to deal
5 in the Software without restriction, including without limitation the rights
6 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 copies of the Software, and to permit persons to whom the Software is
8 furnished to do so, subject to the following conditions:
9 The above copyright notice and this permission notice shall be included in all
10 copies or substantial portions of the Software.
11 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17 SOFTWARE.
18*/
19
20#pragma once
21
22#include <optional>
23
24#include "cello_update_source.h"
25
26namespace cello
27{
28
29class Object;
30
31class ValueBase : public UpdateSource
32{
33public:
37 juce::Identifier getId () const { return id; }
38
39protected:
47 ValueBase (const juce::Identifier& id_)
48 : id { id_ }
49 {
50 }
51
52protected:
54 const juce::Identifier id;
55};
56
75template <typename T> class Value : public ValueBase
76{
77public:
82 using ValidateGetFn = std::function<T (const T&)>;
83
92 using ValidateSetFn = std::function<std::optional<T> (const T&)>;
93
101 Value (Object& data, const juce::Identifier& id_, T initVal = {}, ValidateGetFn getFn = nullptr,
102 ValidateSetFn setFn = nullptr)
103 : ValueBase { id_ }
104 , onSet { setFn }
105 , onGet { getFn }
106 , object { data }
107 {
108 // if the object doesn't have this value yet, add it and set it
109 // to the initial value. This will happen as part of initializing a
110 // new Object, but may also happen if new values are added to an existing
111 // type.
112 if (!object.hasattr (id))
113 object.setattr<T> (id, initVal);
114 }
115
124 Value& operator= (const T& val)
125 {
126 set (val);
127 return *this;
128 }
129
137 void set (const T& val)
138 {
139 if (onSet != nullptr)
140 {
141 const auto validated { onSet (val) };
142 if (validated.has_value ())
143 doSet (validated.value ());
144 }
145 else
146 doSet (val);
147 }
148
154 operator T () const { return get (); }
155
161 T get () const
162 {
163 if (onGet != nullptr)
164 return onGet (doGet ());
165 return doGet ();
166 }
167
183 class Cached
184 {
185 public:
186 Cached (Value<T>& val)
187 : value { val }
188 , cachedValue { static_cast<T> (value) }
189 {
190 // when the underlying value changes, cache it here so it can
191 // be used without needing to look it up, go through validation, etc.
192 value.onPropertyChange ([this] (const juce::Identifier& /*id*/) { cachedValue = static_cast<T> (value); });
193 }
194
195 ~Cached () { value.onPropertyChange (nullptr); }
196
202 T get () const { return cachedValue; }
203
209 operator T () const { return get (); }
210
211 private:
212 Value<T>& value;
213 T cachedValue;
214 };
215
220 Cached getCached () { return Cached (*this); }
221
226
233
239 void excludeListener (juce::ValueTree::Listener* listener) { excludedListener = listener; }
240
247 void onPropertyChange (PropertyUpdateFn callback) { object.onPropertyChange (getId (), callback); }
248
249private:
250 void doSet (const T& val)
251 {
252 juce::ValueTree tree { object };
253
254 // check if this call should change the current value.
255 if (notEqualTo (val))
256 {
257 // check if this value or our parent object have a listener to exclude
258 // from updates.
259 auto* excluded = (excludedListener != nullptr) ? excludedListener : object.getExcludedListener ();
260 const auto asVar { juce::VariantConverter<T>::toVar (val) };
261 if (excluded)
262 tree.setPropertyExcludingListener (excluded, id, asVar, object.getUndoManager ());
263 else
264 tree.setProperty (id, asVar, object.getUndoManager ());
265 }
266 else
267 {
268 // check if we or our parent object want us to always send
269 // a property change callback for this value.
270 if (shouldForceUpdate () || object.shouldForceUpdate ())
271 tree.sendPropertyChangeMessage (id);
272 }
273 }
274
275 T doGet () const
276 {
277 juce::ValueTree tree { object };
278 return juce::VariantConverter<T>::fromVar (tree.getProperty (id));
279 }
280
288 bool notEqualTo (const T& newValue)
289 {
290 if constexpr (std::is_floating_point_v<T>)
291 return std::fabs (newValue - doGet ()) > epsilon;
292 else
293 return (newValue != doGet ());
294 }
295
296public:
299 static inline float epsilon { 0.001f };
300
301private:
303 Object& object;
304
306 juce::ValueTree::Listener* excludedListener { nullptr };
307};
308
309template <typename T, // the actual type
310 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
311Value<T>& operator+= (Value<T>& val, const T& rhs)
312{
313 val = val.get () + rhs;
314 return val;
315}
316
317template <typename T, // the actual type
318 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
319Value<T>& operator-= (Value<T>& val, const T& rhs)
320{
321 val = val.get () - rhs;
322 return val;
323}
324
325template <typename T, // the actual type
326 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
327Value<T>& operator*= (Value<T>& val, const T& rhs)
328{
329 val = val.get () * rhs;
330 return val;
331}
332
333template <typename T, // the actual type
334 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
335Value<T>& operator/= (Value<T>& val, const T& rhs)
336{
337 jassert (rhs != 0);
338 val = val.get () / rhs;
339 return val;
340}
341
350template <typename T, // the actual type
351 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
352T operator++ (Value<T>& val)
353{
354 const auto newVal { val.get () + static_cast<int> (1) };
355 val = newVal;
356 return newVal;
357}
358
371template <typename T, // the actual type
372 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
373T operator++ (Value<T>& val, int)
374{
375 const auto original { val.get () };
376 val.set (original + static_cast<T> (1));
377 return original;
378}
379
388template <typename T, // the actual type
389 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
390T operator-- (Value<T>& val)
391{
392 const auto newVal { val.get () - static_cast<T> (1) };
393 val.set (newVal);
394 return newVal;
395}
396
409template <typename T, // the actual type
410 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
411T operator-- (Value<T>& val, int)
412{
413 const auto original { val.get () };
414 val.set (original - static_cast<T> (1));
415 return original;
416}
417
418} // namespace cello
419
425// clang-format off
426#define MAKE_VALUE_MEMBER(type, name, init) \
427 static const inline juce::Identifier name##Id { #name }; \
428 cello::Value<type> name { *this, name##Id, init }
429
430// ...also pass in a ValidateGetFn callable at construction time.
431#define MAKE_VALUE_MEMBER_GET(type, name, init, getFn) \
432 static const inline juce::Identifier name##Id { #name }; \
433 cello::Value<type> name { *this, name##Id, init, getFn }
434
435// ...pass in Validation functions for get and set.
436#define MAKE_VALUE_MEMBER_GET_SET(type, name, init, getFn, setFn) \
437 static const inline juce::Identifier name##Id { #name }; \
438 cello::Value<type> name { *this, name##Id, init, getFn, setFn }
439
440// clang-format on
441
442// clang-format off
450#define CACHED_VALUE(name, value) decltype(value.getCached()) name { value }
451// clang-format on
Definition cello_object.h:34
Object & setattr(const juce::Identifier &attr, const T &attrVal)
Set a new value for the specified attribute/property. We return a reference to this object so that se...
Definition cello_object.h:537
bool shouldForceUpdate() const
Definition cello_update_source.h:44
A utility class to maintain the last known value of a cello::Value object – each call that fetches fr...
Definition cello_value.h:184
T get() const
retrieve the current value of this cached object.
Definition cello_value.h:202
Definition cello_value.h:32
const juce::Identifier id
identifier of this value/property.
Definition cello_value.h:54
juce::Identifier getId() const
Definition cello_value.h:37
ValueBase(const juce::Identifier &id_)
ctor is protected – you can't create an object of type ValueBase directly, this only exists so we hav...
Definition cello_value.h:47
A class to abstract away the issues around storing and retrieving a value from a ValueTree....
Definition cello_value.h:76
Value(Object &data, const juce::Identifier &id_, T initVal={}, ValidateGetFn getFn=nullptr, ValidateSetFn setFn=nullptr)
Construct a new Value object.
Definition cello_value.h:101
std::function< T(const T &)> ValidateGetFn
An optional validator function that can be used to modify the value when it's retrieved.
Definition cello_value.h:82
void onPropertyChange(PropertyUpdateFn callback)
Register (or clear) a callback function to execute when this value changes.
Definition cello_value.h:247
static float epsilon
Definition cello_value.h:299
T get() const
Get the current value of this property from the tree.
Definition cello_value.h:161
void excludeListener(juce::ValueTree::Listener *listener)
A listener to exclude from property change updates.
Definition cello_value.h:239
Value & operator=(const T &val)
Assign a new value, setting it in the underlying tree and perhaps notifying listeners.
Definition cello_value.h:124
void set(const T &val)
Set property value in the tree. If the onSet validator function has been configured,...
Definition cello_value.h:137
ValidateGetFn onGet
validator function called when retrieving this Value. This function is called with the current stored...
Definition cello_value.h:232
std::function< std::optional< T >(const T &)> ValidateSetFn
an optional validator function that can be used to modify the value when it's set....
Definition cello_value.h:92
Cached getCached()
Definition cello_value.h:220
ValidateSetFn onSet
validator function called before setting this Value.
Definition cello_value.h:225