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 "cello_update_source.h"
23
24namespace cello
25{
26
27class Object;
28
29class ValueBase : public UpdateSource
30{
31public:
35 juce::Identifier getId () const { return id; }
36
37protected:
45 ValueBase (const juce::Identifier& id_)
46 : id { id_ }
47 {
48 }
49
50protected:
52 const juce::Identifier id;
53};
54
73template <typename T> class Value : public ValueBase
74{
75public:
83 Value (Object& data, const juce::Identifier& id_, T initVal = {})
84 : ValueBase { id_ }
85 , object { data }
86 {
87 // if the object doesn't have this value yet, add it and set it
88 // to the initial value. This will happen as part of initializing a
89 // new Object, but may also happen if new values are added to an existing
90 // type.
91 if (!object.hasattr (id))
92 object.setattr<T> (id, initVal);
93 }
94
103 Value& operator= (const T& val)
104 {
105 set (val);
106 return *this;
107 }
108
116 void set (const T& val)
117 {
118 if (onSet != nullptr)
119 doSet (onSet (val));
120 else
121 doSet (val);
122 }
123
129 operator T () const { return get (); }
130
136 T get () const
137 {
138 if (onGet != nullptr)
139 return onGet (doGet ());
140 return doGet ();
141 }
142
158 class Cached
159 {
160 public:
161 Cached (Value<T>& val)
162 : value { val }
163 , cachedValue { static_cast<T> (value) }
164 {
165 // when the underlying value changes, cache it here so it can
166 // be used without needing to look it up, go through validation, etc.
167 value.onPropertyChange ([this] (juce::Identifier /*id*/) { cachedValue = static_cast<T> (value); });
168 }
169
170 ~Cached () { value.onPropertyChange (nullptr); }
171
172 operator T () const { return cachedValue; }
173
174 private:
175 Value<T>& value;
176 T cachedValue;
177 };
178
183 Cached getCached () { return Cached (*this); }
184
192 using ValidatePropertyFn = std::function<T (const T&)>;
193
198
205
211 void excludeListener (juce::ValueTree::Listener* listener) { excludedListener = listener; }
212
219 void onPropertyChange (PropertyUpdateFn callback) { object.onPropertyChange (getId (), callback); }
220
221private:
222 void doSet (const T& val)
223 {
224 juce::ValueTree tree { object };
225
226 // check if this call should change the current value.
227 if (notEqualTo (val))
228 {
229 // check if this value or our parent object have a listener to exclude
230 // from updates.
231 auto* excluded = (excludedListener != nullptr) ? excludedListener : object.getExcludedListener ();
232 const auto asVar { juce::VariantConverter<T>::toVar (val) };
233 if (excluded)
234 tree.setPropertyExcludingListener (excluded, id, asVar, object.getUndoManager ());
235 else
236 tree.setProperty (id, asVar, object.getUndoManager ());
237 }
238 else
239 {
240 // check if we or our parent object want us to always send
241 // a property change callback for this value.
242 if (shouldForceUpdate () || object.shouldForceUpdate ())
243 tree.sendPropertyChangeMessage (id);
244 }
245 }
246
247 T doGet () const
248 {
249 juce::ValueTree tree { object };
250 return juce::VariantConverter<T>::fromVar (tree.getProperty (id));
251 }
252
260 bool notEqualTo (const T& newValue)
261 {
262 if constexpr (std::is_floating_point_v<T>)
263 return std::fabs (newValue - doGet ()) > epsilon;
264 else
265 return (newValue != doGet ());
266 }
267
268public:
271 static inline float epsilon { 0.001f };
272
273private:
275 Object& object;
276
278 juce::ValueTree::Listener* excludedListener { nullptr };
279};
280
281template <typename T, // the actual type
282 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
283Value<T>& operator+= (Value<T>& val, const T& rhs)
284{
285 const auto current { static_cast<T> (val) };
286 val = current + rhs;
287 return val;
288}
289
290template <typename T, // the actual type
291 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
292Value<T>& operator-= (Value<T>& val, const T& rhs)
293{
294 const auto current { static_cast<T> (val) };
295 val = current - rhs;
296 return val;
297}
298
299template <typename T, // the actual type
300 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
301Value<T>& operator*= (Value<T>& val, const T& rhs)
302{
303 const auto current { static_cast<T> (val) };
304 val = current * rhs;
305 return val;
306}
307
308template <typename T, // the actual type
309 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
310Value<T>& operator/= (Value<T>& val, const T& rhs)
311{
312 jassert (rhs != 0);
313 const auto current { static_cast<T> (val) };
314 val = current / rhs;
315 return val;
316}
317
326template <typename T, // the actual type
327 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
328T operator++ (Value<T>& val)
329{
330 const auto newVal { static_cast<T> (val) + static_cast<int> (1) };
331 val.set (newVal);
332 return newVal;
333}
334
347template <typename T, // the actual type
348 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
349T operator++ (Value<T>& val, int)
350{
351 const auto original { static_cast<T> (val) };
352 val.set (original + static_cast<T> (1));
353 return original;
354}
355
364template <typename T, // the actual type
365 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
366T operator-- (Value<T>& val)
367{
368 const auto newVal { static_cast<T> (val) - static_cast<T> (1) };
369 val.set (newVal);
370 return newVal;
371}
372
385template <typename T, // the actual type
386 typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
387T operator-- (Value<T>& val, int)
388{
389 const auto original { static_cast<T> (val) };
390 val.set (original - static_cast<T> (1));
391 return original;
392}
393
394} // namespace cello
395
401#define MAKE_VALUE_MEMBER(type, name, init) \
402 cello::Value<type> name \
403 { \
404 *this, #name, init \
405 }
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:159
Definition cello_value.h:30
const juce::Identifier id
identifier of this value/property.
Definition cello_value.h:52
juce::Identifier getId() const
Definition cello_value.h:35
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:45
A class to abstract away the issues around storing and retrieving a value from a ValueTree....
Definition cello_value.h:74
void onPropertyChange(PropertyUpdateFn callback)
Register (or clear) a callback function to execute when this value changes.
Definition cello_value.h:219
static float epsilon
Definition cello_value.h:271
T get() const
Get the current value of this property from the tree.
Definition cello_value.h:136
void excludeListener(juce::ValueTree::Listener *listener)
A listener to exclude from property change updates.
Definition cello_value.h:211
Value(Object &data, const juce::Identifier &id_, T initVal={})
Construct a new Value object.
Definition cello_value.h:83
Value & operator=(const T &val)
Assign a new value, setting it in the underlying tree and perhaps notifying listeners.
Definition cello_value.h:103
void set(const T &val)
Set property value in the tree. If the onSet validator function has been configured,...
Definition cello_value.h:116
std::function< T(const T &)> ValidatePropertyFn
We define the signature of a 'validator' function that can validate/modify/replace values as your app...
Definition cello_value.h:192
ValidatePropertyFn onSet
validator function called before setting this Value.
Definition cello_value.h:197
ValidatePropertyFn onGet
validator function called when retrieving this Value. This function is called with the current stored...
Definition cello_value.h:204
Cached getCached()
Definition cello_value.h:183