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.

350 lines
11KB

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