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.

juce_ComponentAnimator.cpp 11KB

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