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:
85 Value (Object& data, const juce::Identifier& id_, T initVal = {})
86 : ValueBase { id_ }
87 , object { data }
88 {
89 // if the object doesn't have this value yet, add it and set it
90 // to the initial value. This will happen as part of initializing a
91 // new Object, but may also happen if new values are added to an existing
92 // type.
93 if (!object.hasattr (id))
94 object.setattr<T> (id, initVal);
95 }
96
105 Value& operator= (const T& val)
106 {
107 set (val);
108 return *this;
109 }
110
118 void set (const T& val)
119 {
120 if (onSet != nullptr)
121 {
122 const auto validated { onSet (val) };
123 if (validated.has_value ())
124 doSet (validated.value ());
125 }
126 else
127 doSet (val);
128 }
129
135 operator T () const { return get (); }
136
142 T get () const
143 {
144 if (onGet != nullptr)
145 return onGet (doGet ());
146 return doGet ();
147 }
148
164 class Cached
165 {
166 public:
167 Cached (Value<T>& val)
168 : value { val }
169 , cachedValue { static_cast<T> (value) }
170 {
171 // when the underlying value changes, cache it here so it can
172 // be used without needing to look it up, go through validation, etc.
173 value.onPropertyChange ([this] (const juce::Identifier& /*id*/) { cachedValue = static_cast<T> (value); });
174 }
175
176 ~Cached () { value.onPropertyChange (nullptr); }
177
183 T get () const { return cachedValue; }
184
190 operator T () const { return get (); }
191
192 private:
193 Value<T>& value;
194 T cachedValue;
195 };
196
201 Cached getCached () { return Cached (*this); }
202
207 using ValidateGetFn = std::function<T (const T&)>;
208
217 using ValidateSetFn = std::function<std::optional<T> (const T&)>;
218
223
230
236 void excludeListener (juce::ValueTree::Listener* listener) { excludedListener = listener; }
237
244 void onPropertyChange (PropertyUpdateFn callback) { object.onPropertyChange (getId (), callback); }
245
246private:
247 void doSet (const T& val)
248 {
249 juce::ValueTree tree { object };
250
251 // check if this call should change the current value.
252 if (notEqualTo (val))
253 {
254 // check if this value or our parent object have a listener to exclude
255 // from updates.
256 auto* excluded = (excludedListener != nullptr) ? excludedListener : object.getExcludedListener ();
257 const auto asVar { juce::VariantConverter<T>::toVar (val) };
258 if (excluded)
259 tree.setPropertyExcludingListener (excluded, id, asVar, object.getUndoManager ());
260 else
261 tree.setProperty (id, asVar, object.getUndoManager ());
262 }
263 else
264 {
265 // check if we or our parent object want us to always send
266 // a property change callback for this value.
267 if (shouldForceUpdate () || object.shouldForceUpdate ())
268 tree.sendPropertyChangeMessage (id);
269 }
270 }
271
272 T doGet () const
273 {
274 juce::ValueTree tree { object };
275 return juce::VariantConverter<T>::fromVar (tree.getProperty (id));
276 }
277
285 bool notEqualTo (const T& newValue)
286 {
287 if constexpr (std::is_floating_point_v<T>)
288 return std::fabs (newValue - doGet ()) > epsilon;
289 else
290 return (newValue != doGet ());
291 }
292
293public:
296 static inline float epsilon { 0.001f };
297
298private:
300 Object& object;
301
303 juce::ValueTree::Listener* excludedListener { nullptr };
304};
305
306template <typename T, // the actual type
307 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
308Value<T>& operator+= (Value<T>& val, const T& rhs)
309{
310 val = val.get () + rhs;
311 return val;
312}
313
314template <typename T, // the actual type
315 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
316Value<T>& operator-= (Value<T>& val, const T& rhs)
317{
318 val = val.get () - rhs;
319 return val;
320}
321
322template <typename T, // the actual type
323 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
324Value<T>& operator*= (Value<T>& val, const T& rhs)
325{
326 val = val.get () * rhs;
327 return val;
328}
329
330template <typename T, // the actual type
331 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
332Value<T>& operator/= (Value<T>& val, const T& rhs)
333{
334 jassert (rhs != 0);
335 val = val.get () / rhs;
336 return val;
337}
338
347template <typename T, // the actual type
348 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
349T operator++ (Value<T>& val)
350{
351 const auto newVal { val.get () + static_cast<int> (1) };
352 val = newVal;
353 return newVal;
354}
355
368template <typename T, // the actual type
369 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
370T operator++ (Value<T>& val, int)
371{
372 const auto original { val.get () };
373 val.set (original + static_cast<T> (1));
374 return original;
375}
376
385template <typename T, // the actual type
386 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
387T operator-- (Value<T>& val)
388{
389 const auto newVal { val.get () - static_cast<T> (1) };
390 val.set (newVal);
391 return newVal;
392}
393
406template <typename T, // the actual type
407 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
408T operator-- (Value<T>& val, int)
409{
410 const auto original { val.get () };
411 val.set (original - static_cast<T> (1));
412 return original;
413}
414
415} // namespace cello
416
422// clang-format off
423#define MAKE_VALUE_MEMBER(type, name, init) \
424 static const inline juce::Identifier name##Id { #name }; \
425 cello::Value<type> name { *this, name##Id, init }
426// clang-format on
427
428// clang-format off
436#define CACHED_VALUE(name, value) decltype(value.getCached()) name { value }
437// 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:528
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:165
T get() const
retrieve the current value of this cached object.
Definition cello_value.h:183
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
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:207
void onPropertyChange(PropertyUpdateFn callback)
Register (or clear) a callback function to execute when this value changes.
Definition cello_value.h:244
static float epsilon
Definition cello_value.h:296
T get() const
Get the current value of this property from the tree.
Definition cello_value.h:142
void excludeListener(juce::ValueTree::Listener *listener)
A listener to exclude from property change updates.
Definition cello_value.h:236
Value(Object &data, const juce::Identifier &id_, T initVal={})
Construct a new Value object.
Definition cello_value.h:85
Value & operator=(const T &val)
Assign a new value, setting it in the underlying tree and perhaps notifying listeners.
Definition cello_value.h:105
void set(const T &val)
Set property value in the tree. If the onSet validator function has been configured,...
Definition cello_value.h:118
ValidateGetFn onGet
validator function called when retrieving this Value. This function is called with the current stored...
Definition cello_value.h:229
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:217
Cached getCached()
Definition cello_value.h:201
ValidateSetFn onSet
validator function called before setting this Value.
Definition cello_value.h:222