1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 //==============================================================================
30 /**
31     Models a 1-dimensional position that can be dragged around by the user, and which
32     will then continue moving with a customisable physics behaviour when released.
33 
34     This is useful for things like scrollable views or objects that can be dragged and
35     thrown around with the mouse/touch, and by writing your own behaviour class, you can
36     customise the trajectory that it follows when released.
37 
38     The class uses its own Timer to continuously change its value when a drag ends, and
39     Listener objects can be registered to receive callbacks whenever the value changes.
40 
41     The value is stored as a double, and can be used to represent whatever units you need.
42 
43     The template parameter Behaviour must be a class that implements various methods to
44     return the physics of the value's movement - you can use the classes provided for this
45     in the AnimatedPositionBehaviours namespace, or write your own custom behaviour.
46 
47     @see AnimatedPositionBehaviours::ContinuousWithMomentum,
48          AnimatedPositionBehaviours::SnapToPageBoundaries
49 
50     @tags{GUI}
51 */
52 template <typename Behaviour>
53 class AnimatedPosition  : private Timer
54 {
55 public:
AnimatedPosition()56     AnimatedPosition()
57         :  range (-std::numeric_limits<double>::max(),
58                    std::numeric_limits<double>::max())
59     {
60     }
61 
62     /** Sets a range within which the value will be constrained. */
setLimits(Range<double> newRange)63     void setLimits (Range<double> newRange) noexcept
64     {
65         range = newRange;
66     }
67 
68     //==============================================================================
69     /** Called to indicate that the object is now being controlled by a
70         mouse-drag or similar operation.
71 
72         After calling this method, you should make calls to the drag() method
73         each time the mouse drags the position around, and always be sure to
74         finish with a call to endDrag() when the mouse is released, which allows
75         the position to continue moving freely according to the specified behaviour.
76     */
beginDrag()77     void beginDrag()
78     {
79         grabbedPos = position;
80         releaseVelocity = 0;
81         stopTimer();
82     }
83 
84     /** Called during a mouse-drag operation, to indicate that the mouse has moved.
85         The delta is the difference between the position when beginDrag() was called
86         and the new position that's required.
87     */
drag(double deltaFromStartOfDrag)88     void drag (double deltaFromStartOfDrag)
89     {
90         moveTo (grabbedPos + deltaFromStartOfDrag);
91     }
92 
93     /** Called after beginDrag() and drag() to indicate that the drag operation has
94         now finished.
95     */
endDrag()96     void endDrag()
97     {
98         startTimerHz (60);
99     }
100 
101     /** Called outside of a drag operation to cause a nudge in the specified direction.
102         This is intended for use by e.g. mouse-wheel events.
103     */
nudge(double deltaFromCurrentPosition)104     void nudge (double deltaFromCurrentPosition)
105     {
106         startTimerHz (10);
107         moveTo (position + deltaFromCurrentPosition);
108     }
109 
110     //==============================================================================
111     /** Returns the current position. */
getPosition()112     double getPosition() const noexcept
113     {
114         return position;
115     }
116 
117     /** Explicitly sets the position and stops any further movement.
118         This will cause a synchronous call to any listeners if the position actually
119         changes.
120     */
setPosition(double newPosition)121     void setPosition (double newPosition)
122     {
123         stopTimer();
124         setPositionAndSendChange (newPosition);
125     }
126 
127     //==============================================================================
128     /** Implement this class if you need to receive callbacks when the value of
129         an AnimatedPosition changes.
130         @see AnimatedPosition::addListener, AnimatedPosition::removeListener
131     */
132     class Listener
133     {
134     public:
135         virtual ~Listener() = default;
136 
137         /** Called synchronously when an AnimatedPosition changes. */
138         virtual void positionChanged (AnimatedPosition&, double newPosition) = 0;
139     };
140 
141     /** Adds a listener to be called when the value changes. */
addListener(Listener * listener)142     void addListener (Listener* listener)       { listeners.add (listener); }
143 
144     /** Removes a previously-registered listener. */
removeListener(Listener * listener)145     void removeListener (Listener* listener)    { listeners.remove (listener); }
146 
147     //==============================================================================
148     /** The behaviour object.
149         This is public to let you tweak any parameters that it provides.
150     */
151     Behaviour behaviour;
152 
153 private:
154     //==============================================================================
155     double position = 0.0, grabbedPos = 0.0, releaseVelocity = 0.0;
156     Range<double> range;
157     Time lastUpdate, lastDrag;
158     ListenerList<Listener> listeners;
159 
getSpeed(const Time last,double lastPos,const Time now,double newPos)160     static double getSpeed (const Time last, double lastPos,
161                             const Time now, double newPos)
162     {
163         auto elapsedSecs = jmax (0.005, (now - last).inSeconds());
164         auto v = (newPos - lastPos) / elapsedSecs;
165         return std::abs (v) > 0.2 ? v : 0.0;
166     }
167 
moveTo(double newPos)168     void moveTo (double newPos)
169     {
170         auto now = Time::getCurrentTime();
171         releaseVelocity = getSpeed (lastDrag, position, now, newPos);
172         behaviour.releasedWithVelocity (newPos, releaseVelocity);
173         lastDrag = now;
174 
175         setPositionAndSendChange (newPos);
176     }
177 
setPositionAndSendChange(double newPosition)178     void setPositionAndSendChange (double newPosition)
179     {
180         newPosition = range.clipValue (newPosition);
181 
182         if (position != newPosition)
183         {
184             position = newPosition;
185             listeners.call ([this, newPosition] (Listener& l) { l.positionChanged (*this, newPosition); });
186         }
187     }
188 
timerCallback()189     void timerCallback() override
190     {
191         auto now = Time::getCurrentTime();
192         auto elapsed = jlimit (0.001, 0.020, (now - lastUpdate).inSeconds());
193         lastUpdate = now;
194         auto newPos = behaviour.getNextPosition (position, elapsed);
195 
196         if (behaviour.isStopped (newPos))
197             stopTimer();
198         else
199             startTimerHz (60);
200 
201         setPositionAndSendChange (newPos);
202     }
203 
204     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedPosition)
205 };
206 
207 } // namespace juce
208