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