The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

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