Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
7.3KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #ifndef JUCE_ANIMATEDPOSITION_H_INCLUDED
  18. #define JUCE_ANIMATEDPOSITION_H_INCLUDED
  19. //==============================================================================
  20. /**
  21. Models a 1-dimensional position that can be dragged around by the user, and which
  22. will then continue moving with a customisable physics behaviour when released.
  23. This is useful for things like scrollable views or objects that can be dragged and
  24. thrown around with the mouse/touch, and by writing your own behaviour class, you can
  25. customise the trajectory that it follows when released.
  26. The class uses its own Timer to continuously change its value when a drag ends, and
  27. Listener objects can be registered to receive callbacks whenever the value changes.
  28. The value is stored as a double, and can be used to represent whatever units you need.
  29. The template parameter Behaviour must be a class that implements various methods to
  30. return the physics of the value's movement - you can use the classes provided for this
  31. in the AnimatedPositionBehaviours namespace, or write your own custom behaviour.
  32. @see AnimatedPositionBehaviours::ContinuousWithMomentum,
  33. AnimatedPositionBehaviours::SnapToPageBoundaries
  34. */
  35. template <typename Behaviour>
  36. class AnimatedPosition : private Timer
  37. {
  38. public:
  39. AnimatedPosition()
  40. : position(), grabbedPos(), releaseVelocity(),
  41. range (-std::numeric_limits<double>::max(),
  42. std::numeric_limits<double>::max())
  43. {
  44. }
  45. /** Sets a range within which the value will be constrained. */
  46. void setLimits (Range<double> newRange)
  47. {
  48. range = newRange;
  49. }
  50. //==============================================================================
  51. /** Called to indicate that the object is now being controlled by a
  52. mouse-drag or similar operation.
  53. After calling this method, you should make calls to the drag() method
  54. each time the mouse drags the position around, and always be sure to
  55. finish with a call to endDrag() when the mouse is released, which allows
  56. the position to continue moving freely according to the specified behaviour.
  57. */
  58. void beginDrag()
  59. {
  60. grabbedPos = position;
  61. releaseVelocity = 0;
  62. stopTimer();
  63. }
  64. /** Called during a mouse-drag operation, to indicate that the mouse has moved.
  65. The delta is the difference between the position when beginDrag() was called
  66. and the new position that's required.
  67. */
  68. void drag (double deltaFromStartOfDrag)
  69. {
  70. moveTo (grabbedPos + deltaFromStartOfDrag);
  71. }
  72. /** Called after beginDrag() and drag() to indicate that the drag operation has
  73. now finished.
  74. */
  75. void endDrag()
  76. {
  77. startTimer (1000 / 60);
  78. }
  79. /** Called outside of a drag operation to cause a nudge in the specified direction.
  80. This is intended for use by e.g. mouse-wheel events.
  81. */
  82. void nudge (double deltaFromCurrentPosition)
  83. {
  84. startTimer (100);
  85. moveTo (position + deltaFromCurrentPosition);
  86. }
  87. //==============================================================================
  88. /** Returns the current position. */
  89. double getPosition() const noexcept
  90. {
  91. return position;
  92. }
  93. /** Explicitly sets the position and stops any further movement.
  94. This will cause a synchronous call to any listeners if the position actually
  95. changes.
  96. */
  97. void setPosition (double newPosition)
  98. {
  99. stopTimer();
  100. setPositionAndSendChange (newPosition);
  101. }
  102. //==============================================================================
  103. /** Implement this class if you need to receive callbacks when the value of
  104. an AnimatedPosition changes.
  105. @see AnimatedPosition::addListener, AnimatedPosition::removeListener
  106. */
  107. class Listener
  108. {
  109. public:
  110. virtual ~Listener() {}
  111. /** Called synchronously when an AnimatedPosition changes. */
  112. virtual void positionChanged (AnimatedPosition&, double newPosition) = 0;
  113. };
  114. /** Adds a listener to be called when the value changes. */
  115. void addListener (Listener* listener) { listeners.add (listener); }
  116. /** Removes a previously-registered listener. */
  117. void removeListener (Listener* listener) { listeners.remove (listener); }
  118. //==============================================================================
  119. /** The behaviour object.
  120. This is public to let you tweak any parameters that it provides.
  121. */
  122. Behaviour behaviour;
  123. private:
  124. //==============================================================================
  125. double position, grabbedPos, releaseVelocity;
  126. Range<double> range;
  127. Time lastUpdate, lastDrag;
  128. ListenerList<Listener> listeners;
  129. static double getSpeed (const Time last, double lastPos,
  130. const Time now, double newPos)
  131. {
  132. const double elapsedSecs = jmax (0.005, (now - last).inSeconds());
  133. const double v = (newPos - lastPos) / elapsedSecs;
  134. return std::abs (v) > 0.2 ? v : 0.0;
  135. }
  136. void moveTo (double newPos)
  137. {
  138. const Time now (Time::getCurrentTime());
  139. releaseVelocity = getSpeed (lastDrag, position, now, newPos);
  140. behaviour.releasedWithVelocity (newPos, releaseVelocity);
  141. lastDrag = now;
  142. setPositionAndSendChange (newPos);
  143. }
  144. void setPositionAndSendChange (double newPosition)
  145. {
  146. newPosition = range.clipValue (newPosition);
  147. if (position != newPosition)
  148. {
  149. position = newPosition;
  150. listeners.call (&Listener::positionChanged, *this, newPosition);
  151. }
  152. }
  153. void timerCallback() override
  154. {
  155. const Time now = Time::getCurrentTime();
  156. const double elapsed = jlimit (0.001, 0.020, (now - lastUpdate).inSeconds());
  157. lastUpdate = now;
  158. const double newPos = behaviour.getNextPosition (position, elapsed);
  159. if (behaviour.isStopped (newPos))
  160. stopTimer();
  161. else
  162. startTimer (1000 / 60);
  163. setPositionAndSendChange (newPos);
  164. }
  165. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedPosition)
  166. };
  167. #endif // JUCE_ANIMATEDPOSITION_H_INCLUDED