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.

334 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. class ComponentAnimator::AnimationTask
  19. {
  20. public:
  21. AnimationTask (Component* const comp)
  22. : component (comp)
  23. {
  24. }
  25. void reset (const Rectangle<int>& finalBounds,
  26. float finalAlpha,
  27. int millisecondsToSpendMoving,
  28. bool useProxyComponent,
  29. double startSpeed_, double endSpeed_)
  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 / (startSpeed_ + endSpeed_ + 2.0);
  44. startSpeed = jmax (0.0, startSpeed_ * invTotalDistance);
  45. midSpeed = invTotalDistance;
  46. endSpeed = jmax (0.0, endSpeed_ * 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 (Component* const 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. newProgress = timeToDistance (newProgress);
  63. const double delta = (newProgress - lastProgress) / (1.0 - lastProgress);
  64. jassert (newProgress >= lastProgress);
  65. lastProgress = newProgress;
  66. if (delta < 1.0)
  67. {
  68. bool stillBusy = false;
  69. if (isMoving)
  70. {
  71. left += (destination.getX() - left) * delta;
  72. top += (destination.getY() - top) * delta;
  73. right += (destination.getRight() - right) * delta;
  74. bottom += (destination.getBottom() - bottom) * delta;
  75. const Rectangle<int> newBounds (roundToInt (left),
  76. roundToInt (top),
  77. roundToInt (right - left),
  78. roundToInt (bottom - top));
  79. if (newBounds != destination)
  80. {
  81. c->setBounds (newBounds);
  82. stillBusy = true;
  83. }
  84. }
  85. if (isChangingAlpha)
  86. {
  87. alpha += (destAlpha - alpha) * delta;
  88. c->setAlpha ((float) alpha);
  89. stillBusy = true;
  90. }
  91. if (stillBusy)
  92. return true;
  93. }
  94. }
  95. }
  96. moveToFinalDestination();
  97. return false;
  98. }
  99. void moveToFinalDestination()
  100. {
  101. if (component != nullptr)
  102. {
  103. component->setAlpha ((float) destAlpha);
  104. component->setBounds (destination);
  105. if (proxy != nullptr)
  106. component->setVisible (destAlpha > 0);
  107. }
  108. }
  109. //==============================================================================
  110. class ProxyComponent : public Component
  111. {
  112. public:
  113. ProxyComponent (Component& c)
  114. : image (c.createComponentSnapshot (c.getLocalBounds()))
  115. {
  116. setBounds (c.getBounds());
  117. setTransform (c.getTransform());
  118. setAlpha (c.getAlpha());
  119. setInterceptsMouseClicks (false, false);
  120. if (Component* const parent = c.getParentComponent())
  121. parent->addAndMakeVisible (this);
  122. else if (c.isOnDesktop() && c.getPeer() != nullptr)
  123. addToDesktop (c.getPeer()->getStyleFlags() | ComponentPeer::windowIgnoresKeyPresses);
  124. else
  125. jassertfalse; // seem to be trying to animate a component that's not visible..
  126. setVisible (true);
  127. toBehind (&c);
  128. }
  129. void paint (Graphics& g)
  130. {
  131. g.setOpacity (1.0f);
  132. g.drawImage (image, 0, 0, getWidth(), getHeight(),
  133. 0, 0, image.getWidth(), image.getHeight());
  134. }
  135. private:
  136. Image image;
  137. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProxyComponent);
  138. };
  139. WeakReference<Component> component;
  140. ScopedPointer<Component> proxy;
  141. Rectangle<int> destination;
  142. double destAlpha;
  143. int msElapsed, msTotal;
  144. double startSpeed, midSpeed, endSpeed, lastProgress;
  145. double left, top, right, bottom, alpha;
  146. bool isMoving, isChangingAlpha;
  147. private:
  148. double timeToDistance (const double time) const noexcept
  149. {
  150. return (time < 0.5) ? time * (startSpeed + time * (midSpeed - startSpeed))
  151. : 0.5 * (startSpeed + 0.5 * (midSpeed - startSpeed))
  152. + (time - 0.5) * (midSpeed + (time - 0.5) * (endSpeed - midSpeed));
  153. }
  154. };
  155. //==============================================================================
  156. ComponentAnimator::ComponentAnimator()
  157. : lastTime (0)
  158. {
  159. }
  160. ComponentAnimator::~ComponentAnimator()
  161. {
  162. }
  163. //==============================================================================
  164. ComponentAnimator::AnimationTask* ComponentAnimator::findTaskFor (Component* const component) const noexcept
  165. {
  166. for (int i = tasks.size(); --i >= 0;)
  167. if (component == tasks.getUnchecked(i)->component.get())
  168. return tasks.getUnchecked(i);
  169. return nullptr;
  170. }
  171. void ComponentAnimator::animateComponent (Component* const component,
  172. const Rectangle<int>& finalBounds,
  173. const float finalAlpha,
  174. const int millisecondsToSpendMoving,
  175. const bool useProxyComponent,
  176. const double startSpeed,
  177. const double endSpeed)
  178. {
  179. // the speeds must be 0 or greater!
  180. jassert (startSpeed >= 0 && endSpeed >= 0)
  181. if (component != nullptr)
  182. {
  183. AnimationTask* at = findTaskFor (component);
  184. if (at == nullptr)
  185. {
  186. at = new AnimationTask (component);
  187. tasks.add (at);
  188. sendChangeMessage();
  189. }
  190. at->reset (finalBounds, finalAlpha, millisecondsToSpendMoving,
  191. useProxyComponent, startSpeed, endSpeed);
  192. if (! isTimerRunning())
  193. {
  194. lastTime = Time::getMillisecondCounter();
  195. startTimer (1000 / 50);
  196. }
  197. }
  198. }
  199. void ComponentAnimator::fadeOut (Component* component, int millisecondsToTake)
  200. {
  201. if (component != nullptr)
  202. {
  203. if (component->isShowing() && millisecondsToTake > 0)
  204. animateComponent (component, component->getBounds(), 0.0f, millisecondsToTake, true, 1.0, 1.0);
  205. component->setVisible (false);
  206. }
  207. }
  208. void ComponentAnimator::fadeIn (Component* component, int millisecondsToTake)
  209. {
  210. if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f))
  211. {
  212. component->setAlpha (0.0f);
  213. component->setVisible (true);
  214. animateComponent (component, component->getBounds(), 1.0f, millisecondsToTake, false, 1.0, 1.0);
  215. }
  216. }
  217. void ComponentAnimator::cancelAllAnimations (const bool moveComponentsToTheirFinalPositions)
  218. {
  219. if (tasks.size() > 0)
  220. {
  221. if (moveComponentsToTheirFinalPositions)
  222. for (int i = tasks.size(); --i >= 0;)
  223. tasks.getUnchecked(i)->moveToFinalDestination();
  224. tasks.clear();
  225. sendChangeMessage();
  226. }
  227. }
  228. void ComponentAnimator::cancelAnimation (Component* const component,
  229. const bool moveComponentToItsFinalPosition)
  230. {
  231. if (AnimationTask* const at = findTaskFor (component))
  232. {
  233. if (moveComponentToItsFinalPosition)
  234. at->moveToFinalDestination();
  235. tasks.removeObject (at);
  236. sendChangeMessage();
  237. }
  238. }
  239. Rectangle<int> ComponentAnimator::getComponentDestination (Component* const component)
  240. {
  241. jassert (component != nullptr);
  242. if (AnimationTask* const at = findTaskFor (component))
  243. return at->destination;
  244. return component->getBounds();
  245. }
  246. bool ComponentAnimator::isAnimating (Component* component) const noexcept
  247. {
  248. return findTaskFor (component) != nullptr;
  249. }
  250. bool ComponentAnimator::isAnimating() const noexcept
  251. {
  252. return tasks.size() != 0;
  253. }
  254. void ComponentAnimator::timerCallback()
  255. {
  256. const uint32 timeNow = Time::getMillisecondCounter();
  257. if (lastTime == 0 || lastTime == timeNow)
  258. lastTime = timeNow;
  259. const int elapsed = (int) (timeNow - lastTime);
  260. for (int i = tasks.size(); --i >= 0;)
  261. {
  262. if (! tasks.getUnchecked(i)->useTimeslice (elapsed))
  263. {
  264. tasks.remove (i);
  265. sendChangeMessage();
  266. }
  267. }
  268. lastTime = timeNow;
  269. if (tasks.size() == 0)
  270. stopTimer();
  271. }