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.

346 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. class ComponentAnimator::AnimationTask
  22. {
  23. public:
  24. AnimationTask (Component* c) noexcept : component (c) {}
  25. void reset (const Rectangle<int>& finalBounds,
  26. float finalAlpha,
  27. int millisecondsToSpendMoving,
  28. bool useProxyComponent,
  29. double startSpd, double endSpd)
  30. {
  31. msElapsed = 0;
  32. msTotal = jmax (1, millisecondsToSpendMoving);
  33. lastProgress = 0;
  34. destination = finalBounds;
  35. destAlpha = finalAlpha;
  36. isMoving = (finalBounds != component->getBounds());
  37. isChangingAlpha = (finalAlpha != component->getAlpha());
  38. left = component->getX();
  39. top = component->getY();
  40. right = component->getRight();
  41. bottom = component->getBottom();
  42. alpha = component->getAlpha();
  43. const double invTotalDistance = 4.0 / (startSpd + endSpd + 2.0);
  44. startSpeed = jmax (0.0, startSpd * invTotalDistance);
  45. midSpeed = invTotalDistance;
  46. endSpeed = jmax (0.0, endSpd * invTotalDistance);
  47. if (useProxyComponent)
  48. proxy = new ProxyComponent (*component);
  49. else
  50. proxy = nullptr;
  51. component->setVisible (! useProxyComponent);
  52. }
  53. bool useTimeslice (const int elapsed)
  54. {
  55. if (auto* c = proxy != nullptr ? static_cast<Component*> (proxy)
  56. : static_cast<Component*> (component))
  57. {
  58. msElapsed += elapsed;
  59. double newProgress = msElapsed / (double) msTotal;
  60. if (newProgress >= 0 && newProgress < 1.0)
  61. {
  62. const WeakReference<AnimationTask> weakRef (this);
  63. newProgress = timeToDistance (newProgress);
  64. const double delta = (newProgress - lastProgress) / (1.0 - lastProgress);
  65. jassert (newProgress >= lastProgress);
  66. lastProgress = newProgress;
  67. if (delta < 1.0)
  68. {
  69. bool stillBusy = false;
  70. if (isMoving)
  71. {
  72. left += (destination.getX() - left) * delta;
  73. top += (destination.getY() - top) * delta;
  74. right += (destination.getRight() - right) * delta;
  75. bottom += (destination.getBottom() - bottom) * delta;
  76. const Rectangle<int> newBounds (roundToInt (left),
  77. roundToInt (top),
  78. roundToInt (right - left),
  79. roundToInt (bottom - top));
  80. if (newBounds != destination)
  81. {
  82. c->setBounds (newBounds);
  83. stillBusy = true;
  84. }
  85. }
  86. // Check whether the animation was cancelled/deleted during
  87. // a callback during the setBounds method
  88. if (weakRef.wasObjectDeleted())
  89. return false;
  90. if (isChangingAlpha)
  91. {
  92. alpha += (destAlpha - alpha) * delta;
  93. c->setAlpha ((float) alpha);
  94. stillBusy = true;
  95. }
  96. if (stillBusy)
  97. return true;
  98. }
  99. }
  100. }
  101. moveToFinalDestination();
  102. return false;
  103. }
  104. void moveToFinalDestination()
  105. {
  106. if (component != nullptr)
  107. {
  108. const WeakReference<AnimationTask> weakRef (this);
  109. component->setAlpha ((float) destAlpha);
  110. component->setBounds (destination);
  111. if (! weakRef.wasObjectDeleted())
  112. if (proxy != nullptr)
  113. component->setVisible (destAlpha > 0);
  114. }
  115. }
  116. //==============================================================================
  117. struct ProxyComponent : public Component
  118. {
  119. ProxyComponent (Component& c)
  120. {
  121. setWantsKeyboardFocus (false);
  122. setBounds (c.getBounds());
  123. setTransform (c.getTransform());
  124. setAlpha (c.getAlpha());
  125. setInterceptsMouseClicks (false, false);
  126. if (auto* parent = c.getParentComponent())
  127. parent->addAndMakeVisible (this);
  128. else if (c.isOnDesktop() && c.getPeer() != nullptr)
  129. addToDesktop (c.getPeer()->getStyleFlags() | ComponentPeer::windowIgnoresKeyPresses);
  130. else
  131. jassertfalse; // seem to be trying to animate a component that's not visible..
  132. auto scale = (float) Desktop::getInstance().getDisplays()
  133. .getDisplayContaining (getScreenBounds().getCentre()).scale;
  134. image = c.createComponentSnapshot (c.getLocalBounds(), false, scale);
  135. setVisible (true);
  136. toBehind (&c);
  137. }
  138. void paint (Graphics& g) override
  139. {
  140. g.setOpacity (1.0f);
  141. g.drawImageTransformed (image, AffineTransform::scale (getWidth() / (float) image.getWidth(),
  142. getHeight() / (float) image.getHeight()), false);
  143. }
  144. private:
  145. Image image;
  146. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProxyComponent)
  147. };
  148. WeakReference<Component> component;
  149. ScopedPointer<Component> proxy;
  150. Rectangle<int> destination;
  151. double destAlpha;
  152. int msElapsed, msTotal;
  153. double startSpeed, midSpeed, endSpeed, lastProgress;
  154. double left, top, right, bottom, alpha;
  155. bool isMoving, isChangingAlpha;
  156. private:
  157. double timeToDistance (const double time) const noexcept
  158. {
  159. return (time < 0.5) ? time * (startSpeed + time * (midSpeed - startSpeed))
  160. : 0.5 * (startSpeed + 0.5 * (midSpeed - startSpeed))
  161. + (time - 0.5) * (midSpeed + (time - 0.5) * (endSpeed - midSpeed));
  162. }
  163. JUCE_DECLARE_WEAK_REFERENCEABLE (AnimationTask)
  164. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimationTask)
  165. };
  166. //==============================================================================
  167. ComponentAnimator::ComponentAnimator() : lastTime (0) {}
  168. ComponentAnimator::~ComponentAnimator() {}
  169. //==============================================================================
  170. ComponentAnimator::AnimationTask* ComponentAnimator::findTaskFor (Component* const component) const noexcept
  171. {
  172. for (int i = tasks.size(); --i >= 0;)
  173. if (component == tasks.getUnchecked(i)->component.get())
  174. return tasks.getUnchecked(i);
  175. return nullptr;
  176. }
  177. void ComponentAnimator::animateComponent (Component* const component,
  178. const Rectangle<int>& finalBounds,
  179. const float finalAlpha,
  180. const int millisecondsToSpendMoving,
  181. const bool useProxyComponent,
  182. const double startSpeed,
  183. const double endSpeed)
  184. {
  185. // the speeds must be 0 or greater!
  186. jassert (startSpeed >= 0 && endSpeed >= 0);
  187. if (component != nullptr)
  188. {
  189. auto* at = findTaskFor (component);
  190. if (at == nullptr)
  191. {
  192. at = new AnimationTask (component);
  193. tasks.add (at);
  194. sendChangeMessage();
  195. }
  196. at->reset (finalBounds, finalAlpha, millisecondsToSpendMoving,
  197. useProxyComponent, startSpeed, endSpeed);
  198. if (! isTimerRunning())
  199. {
  200. lastTime = Time::getMillisecondCounter();
  201. startTimerHz (50);
  202. }
  203. }
  204. }
  205. void ComponentAnimator::fadeOut (Component* component, int millisecondsToTake)
  206. {
  207. if (component != nullptr)
  208. {
  209. if (component->isShowing() && millisecondsToTake > 0)
  210. animateComponent (component, component->getBounds(), 0.0f, millisecondsToTake, true, 1.0, 1.0);
  211. component->setVisible (false);
  212. }
  213. }
  214. void ComponentAnimator::fadeIn (Component* component, int millisecondsToTake)
  215. {
  216. if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f))
  217. {
  218. component->setAlpha (0.0f);
  219. component->setVisible (true);
  220. animateComponent (component, component->getBounds(), 1.0f, millisecondsToTake, false, 1.0, 1.0);
  221. }
  222. }
  223. void ComponentAnimator::cancelAllAnimations (const bool moveComponentsToTheirFinalPositions)
  224. {
  225. if (tasks.size() > 0)
  226. {
  227. if (moveComponentsToTheirFinalPositions)
  228. for (int i = tasks.size(); --i >= 0;)
  229. tasks.getUnchecked(i)->moveToFinalDestination();
  230. tasks.clear();
  231. sendChangeMessage();
  232. }
  233. }
  234. void ComponentAnimator::cancelAnimation (Component* const component,
  235. const bool moveComponentToItsFinalPosition)
  236. {
  237. if (auto* at = findTaskFor (component))
  238. {
  239. if (moveComponentToItsFinalPosition)
  240. at->moveToFinalDestination();
  241. tasks.removeObject (at);
  242. sendChangeMessage();
  243. }
  244. }
  245. Rectangle<int> ComponentAnimator::getComponentDestination (Component* const component)
  246. {
  247. jassert (component != nullptr);
  248. if (auto* at = findTaskFor (component))
  249. return at->destination;
  250. return component->getBounds();
  251. }
  252. bool ComponentAnimator::isAnimating (Component* component) const noexcept
  253. {
  254. return findTaskFor (component) != nullptr;
  255. }
  256. bool ComponentAnimator::isAnimating() const noexcept
  257. {
  258. return tasks.size() != 0;
  259. }
  260. void ComponentAnimator::timerCallback()
  261. {
  262. auto timeNow = Time::getMillisecondCounter();
  263. if (lastTime == 0)
  264. lastTime = timeNow;
  265. auto elapsed = (int) (timeNow - lastTime);
  266. for (auto* task : Array<AnimationTask*> (tasks.begin(), tasks.size()))
  267. {
  268. if (tasks.contains (task) && ! task->useTimeslice (elapsed))
  269. {
  270. tasks.removeObject (task);
  271. sendChangeMessage();
  272. }
  273. }
  274. lastTime = timeNow;
  275. if (tasks.size() == 0)
  276. stopTimer();
  277. }
  278. } // namespace juce