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.

507 lines
19KB

  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. struct BlankCanvas : public AnimatedContent
  20. {
  21. String getName() const override { return "Blank Canvas"; }
  22. void reset() override {}
  23. void handleTouch (Point<float>) override {}
  24. void generateCanvas (Graphics&, SharedCanvasDescription&, Rectangle<float>) override {}
  25. };
  26. //==============================================================================
  27. struct GridLines : public AnimatedContent
  28. {
  29. String getName() const override { return "Grid Lines"; }
  30. void reset() override {}
  31. void handleTouch (Point<float>) override {}
  32. void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float>) override
  33. {
  34. auto limits = canvas.getLimits();
  35. float lineThickness = 0.1f;
  36. g.setColour (Colours::blue);
  37. g.drawRect (canvas.getLimits(), lineThickness);
  38. for (float y = limits.getY(); y < limits.getBottom(); y += 2.0f)
  39. g.drawLine (limits.getX(), y, limits.getRight(), y, lineThickness);
  40. for (float x = limits.getX(); x < limits.getRight(); x += 2.0f)
  41. g.drawLine (x, limits.getY(), x, limits.getBottom(), lineThickness);
  42. g.setColour (Colours::darkred);
  43. g.drawLine (limits.getX(), limits.getCentreY(), limits.getRight(), limits.getCentreY(), lineThickness);
  44. g.drawLine (limits.getCentreX(), limits.getY(), limits.getCentreX(), limits.getBottom(), lineThickness);
  45. g.setColour (Colours::lightgrey);
  46. g.drawLine (limits.getX(), limits.getY(), limits.getRight(), limits.getBottom(), lineThickness);
  47. g.drawLine (limits.getX(), limits.getBottom(), limits.getRight(), limits.getY(), lineThickness);
  48. }
  49. };
  50. //==============================================================================
  51. struct BackgroundLogo : public AnimatedContent
  52. {
  53. BackgroundLogo()
  54. {
  55. static const char logoData[] = R"blahblah(
  56. <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
  57. viewBox="0 0 239.2 239.2" enable-background="new 0 0 239.2 239.2" xml:space="preserve">
  58. <path fill="#6CC04A" d="M118.8,201.3c-44.6,0-81-36.3-81-81s36.3-81,81-81s81,36.3,81,81S163.4,201.3,118.8,201.3z M118.8,44.8c-41.7,0-75.6,33.9-75.6,75.6s33.9,75.6,75.6,75.6s75.6-33.9,75.6-75.6S160.4,44.8,118.8,44.8z"/>
  59. <path fill="#3B5CAD" d="M182.6,117.6c1.4,0,2.7-0.5,3.7-1.5c1.1-1.1,1.6-2.5,1.4-4c-1.5-12.7-6.5-24.7-14.4-34.8c-1-1.2-2.3-1.9-3.8-1.9c-1.3,0-2.6,0.5-3.6,1.5l-39,39c-0.6,0.6-0.2,1.6,0.7,1.6L182.6,117.6z"/>
  60. <path fill="#E73E51" d="M169.5,165.2L169.5,165.2c1.5,0,2.8-0.7,3.8-1.9c7.9-10.1,12.9-22.1,14.4-34.8c0.2-1.5-0.3-2.9-1.4-4c-1-1-2.3-1.5-3.7-1.5l-55,0c-0.9,0-1.3,1-0.7,1.6l39,39C166.9,164.7,168.2,165.2,169.5,165.2z"/>
  61. <path fill="#E67E3C" d="M122.9,188L122.9,188c1,1,2.5,1.5,4,1.3c12.7-1.5,24.8-6.5,34.8-14.4c1.2-0.9,1.8-2.3,1.9-3.8c0-1.4-0.6-2.7-1.6-3.7l-38.9-38.9c-0.6-0.6-1.6-0.2-1.6,0.7l0,55.2C121.4,185.8,122,187,122.9,188z"/>
  62. <path fill="#F0E049" d="M68,75.4c-1.5,0-2.8,0.7-3.8,1.9c-7.9,10.1-12.9,22.1-14.4,34.8c-0.2,1.5,0.3,2.9,1.4,4c1,1,2.3,1.5,3.7,1.5l55,0c0.9,0,1.3-1,0.7-1.6l-39-39C70.6,76,69.3,75.4,68,75.4z"/>
  63. <path fill="#D5D755" d="M114.6,52.7c-1-1-2.5-1.5-4-1.3c-12.7,1.5-24.8,6.5-34.8,14.4c-1.2,0.9-1.8,2.3-1.9,3.8c0,1.4,0.6,2.7,1.6,3.7l38.9,38.9c0.6,0.6,1.6,0.2,1.6-0.7l0-55.2C116.1,54.9,115.5,53.6,114.6,52.7z"/>
  64. <path fill="#9CB6D3" d="M163.7,69.6c0-1.5-0.7-2.8-1.9-3.8c-10.1-7.9-22.1-12.9-34.8-14.4c-1.5-0.2-2.9,0.3-4,1.4c-1,1-1.5,2.3-1.5,3.7l0,55c0,0.9,1,1.3,1.6,0.7l39-39C163.1,72.1,163.7,70.9,163.7,69.6z"/>
  65. <path fill="#F5BD47" d="M109.9,123l-55,0c-1.4,0-2.7,0.5-3.7,1.5c-1.1,1.1-1.6,2.5-1.4,4c1.5,12.7,6.5,24.7,14.4,34.8c1,1.2,2.3,1.9,3.8,1.9c1.3,0,2.6-0.5,3.5-1.5c0,0,0,0,0,0l39-39C111.2,124,110.8,123,109.9,123z"/>
  66. <path fill="#F19F53" d="M114.4,128.5l-38.9,38.9c-1,1-1.6,2.3-1.6,3.7c0,1.5,0.7,2.9,1.9,3.8c10,7.9,22.1,12.9,34.8,14.4c1.6,0.2,3-0.3,4-1.3c0.9-0.9,1.4-2.2,1.4-3.6c0,0,0,0,0,0l0-55.2C116.1,128.3,115,127.9,114.4,128.5z"/>
  67. </svg>
  68. )blahblah";
  69. ScopedPointer<XmlElement> svg (XmlDocument::parse (logoData));
  70. logo = Drawable::createFromSVG (*svg);
  71. }
  72. String getName() const override { return "Background Image"; }
  73. void reset() override {}
  74. void handleTouch (Point<float>) override {}
  75. void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float> activeArea) override
  76. {
  77. logo->drawWithin (g, canvas.getLimits().reduced (3.0f), RectanglePlacement (RectanglePlacement::centred), 0.6f);
  78. }
  79. ScopedPointer<Drawable> logo;
  80. };
  81. //==============================================================================
  82. struct FlockDemo : public BackgroundLogo
  83. {
  84. String getName() const override { return "Flock"; }
  85. void setNumBirds (int numBirds)
  86. {
  87. BackgroundLogo::reset();
  88. birds.clear();
  89. for (int i = numBirds; --i >= 0;)
  90. birds.add ({});
  91. centreOfGravity = {};
  92. lastGravityMove = {};
  93. fakeMouseTouchLengthToRun = 0;
  94. fakeMouseTouchPosition = {};
  95. fakeMouseTouchVelocity = {};
  96. }
  97. void reset() override
  98. {
  99. BackgroundLogo::reset();
  100. setNumBirds (100);
  101. }
  102. void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float> activeArea) override
  103. {
  104. BackgroundLogo::generateCanvas (g, canvas, activeArea);
  105. if (Time::getCurrentTime() > lastGravityMove + RelativeTime::seconds (0.5))
  106. {
  107. if (fakeMouseTouchLengthToRun > 0)
  108. {
  109. --fakeMouseTouchLengthToRun;
  110. fakeMouseTouchPosition += fakeMouseTouchVelocity;
  111. centreOfGravity = fakeMouseTouchPosition;
  112. }
  113. else
  114. {
  115. centreOfGravity = {};
  116. if (rng.nextInt (300) == 2 && canvas.clients.size() > 0)
  117. {
  118. fakeMouseTouchLengthToRun = 50;
  119. fakeMouseTouchPosition = canvas.clients.getReference (rng.nextInt (canvas.clients.size())).centre;
  120. fakeMouseTouchVelocity = { rng.nextFloat() * 0.3f - 0.15f,
  121. rng.nextFloat() * 0.3f - 0.15f };
  122. }
  123. }
  124. }
  125. g.setColour (Colours::white.withAlpha (0.2f));
  126. if (! centreOfGravity.isOrigin())
  127. g.fillEllipse (centreOfGravity.getX() - 1.0f, centreOfGravity.getY() - 1.0f, 2.0f, 2.0f);
  128. for (int i = 0; i < birds.size(); ++i)
  129. for (int j = i + 1; j < birds.size(); ++j)
  130. attractBirds (birds.getReference(i), birds.getReference(j));
  131. for (auto& b : birds)
  132. {
  133. if (! centreOfGravity.isOrigin())
  134. b.move (centreOfGravity, 0.4f);
  135. b.update();
  136. b.draw (g);
  137. b.bounceOffEdges (canvas.getLimits().expanded (1.0f));
  138. }
  139. for (int i = rings.size(); --i >= 0;)
  140. {
  141. if (rings.getReference(i).update())
  142. rings.getReference(i).draw (g);
  143. else
  144. rings.remove (i);
  145. }
  146. }
  147. bool isRingNear (Point<float> p) const
  148. {
  149. for (auto& r : rings)
  150. if (r.centre.getDistanceFrom (p) < 1.0f)
  151. return true;
  152. return false;
  153. }
  154. void handleTouch (Point<float> position) override
  155. {
  156. lastGravityMove = Time::getCurrentTime();
  157. centreOfGravity = position;
  158. fakeMouseTouchLengthToRun = 0;
  159. if (! isRingNear (position))
  160. rings.add ({ position, 1.0f, 0.5f });
  161. }
  162. //==============================================================================
  163. struct Bird
  164. {
  165. Bird()
  166. {
  167. Random rng;
  168. pos.x = rng.nextFloat() * 10.0f - 5.0f;
  169. pos.y = rng.nextFloat() * 10.0f - 5.0f;
  170. velocity.x = rng.nextFloat() * 0.001f;
  171. velocity.y = rng.nextFloat() * 0.001f;
  172. colour = Colour::fromHSV (rng.nextFloat(), 0.2f, 0.9f, rng.nextFloat() * 0.4f + 0.2f);
  173. shape.addTriangle (0.0f, 0.0f, -0.3f, 1.0f, 0.3f, 1.0f);
  174. shape = shape.createPathWithRoundedCorners (0.2f);
  175. shape.applyTransform (AffineTransform::scale (rng.nextFloat() + 1.0f));
  176. }
  177. Point<float> pos, velocity, acc;
  178. Colour colour;
  179. Path shape;
  180. void move (Point<float> target, float strength)
  181. {
  182. auto r = target - pos;
  183. float rSquared = jmax (0.1f, (r.x * r.x) + (r.y * r.y));
  184. if (rSquared > 1.0f)
  185. velocity += (r * strength / rSquared);
  186. acc = {};
  187. }
  188. void accelerate (Point<float> acceleration)
  189. {
  190. acc += acceleration;
  191. }
  192. void bounceOffEdges (Rectangle<float> limits)
  193. {
  194. if (pos.x < limits.getX()) { velocity.x = std::abs (velocity.x); acc = {}; }
  195. if (pos.x > limits.getRight()) { velocity.x = -std::abs (velocity.x); acc = {}; }
  196. if (pos.y < limits.getY()) { velocity.y = std::abs (velocity.y); acc = {}; }
  197. if (pos.y > limits.getBottom()) { velocity.y = -std::abs (velocity.y); acc = {}; }
  198. }
  199. void update()
  200. {
  201. velocity += acc;
  202. float length = velocity.getDistanceFromOrigin();
  203. const float maxSpeed = 0.5f;
  204. if (length > maxSpeed)
  205. velocity = getVectorWithLength (velocity, maxSpeed);
  206. pos += velocity;
  207. }
  208. void draw (Graphics& g)
  209. {
  210. g.setColour (colour);
  211. g.fillPath (shape, AffineTransform::rotation (Point<float>().getAngleToPoint (velocity)).translated (pos));
  212. }
  213. };
  214. static Point<float> getVectorWithLength (Point<float> v, float newLength)
  215. {
  216. return v * (newLength / v.getDistanceFromOrigin());
  217. }
  218. static void attractBirds (Bird& b1, Bird& b2)
  219. {
  220. auto delta = b1.pos - b2.pos;
  221. const float zoneRadius = 10.0f;
  222. const float low = 0.4f;
  223. const float high = 0.65f;
  224. const float strength = 0.01f;
  225. const float distanceSquared = (delta.x * delta.x) * (delta.y * delta.y);
  226. if (distanceSquared < zoneRadius * zoneRadius && distanceSquared > 0.01f)
  227. {
  228. float proportion = distanceSquared / (zoneRadius * zoneRadius);
  229. if (proportion < low)
  230. {
  231. const float F = (low / proportion - 1.0f) * strength * 0.003f;
  232. delta = getVectorWithLength (delta, F);
  233. b1.accelerate (delta);
  234. b2.accelerate (-delta);
  235. }
  236. else if (proportion < high)
  237. {
  238. const float regionSize = high - low;
  239. const float adjustedProportion = (proportion - low) / regionSize;
  240. const float F = (0.5f - cos (adjustedProportion * float_Pi * 2.0f) * 0.5f + 0.5f) * strength;
  241. b1.accelerate (getVectorWithLength (b2.velocity, F));
  242. b2.accelerate (getVectorWithLength (b1.velocity, F));
  243. }
  244. else
  245. {
  246. const float regionSize = 1.0f - high;
  247. const float adjustedProportion = (proportion - high) / regionSize;
  248. const float F = (0.5f - cos (adjustedProportion * float_Pi * 2.0f) * 0.5f + 0.5f) * strength;
  249. delta = getVectorWithLength (delta, F);
  250. b1.accelerate (-delta);
  251. b2.accelerate (delta);
  252. }
  253. }
  254. }
  255. Random rng;
  256. Array<Bird> birds;
  257. Point<float> centreOfGravity;
  258. Time lastGravityMove;
  259. int fakeMouseTouchLengthToRun = 0;
  260. Point<float> fakeMouseTouchPosition, fakeMouseTouchVelocity;
  261. //==============================================================================
  262. struct Ring
  263. {
  264. Point<float> centre;
  265. float diameter, opacity;
  266. bool update()
  267. {
  268. diameter += 0.7f;
  269. opacity -= 0.01f;
  270. return opacity > 0;
  271. }
  272. void draw (Graphics& g)
  273. {
  274. const float thickness = 0.2f;
  275. auto r = Rectangle<float> (diameter, diameter).withCentre (centre);
  276. Path p;
  277. p.addEllipse (r);
  278. p.addEllipse (r.reduced (thickness));
  279. p.setUsingNonZeroWinding (false);
  280. g.setColour (Colours::white.withAlpha (opacity));
  281. g.fillPath (p);
  282. }
  283. };
  284. Array<Ring> rings;
  285. };
  286. //==============================================================================
  287. struct FlockWithText : public FlockDemo
  288. {
  289. FlockWithText()
  290. {
  291. messages.add ("JUCE is our cross-platform C++ framework\n\n"
  292. "In this demo, the same C++ app is running natively on NUMDEVICES devices,\n"
  293. "which are sharing their graphic state via the network");
  294. messages.add ("No other libraries were needed to create this demo.\n"
  295. "JUCE provides thousands of classes for cross-platform GUI,\n"
  296. "audio, networking, data-structures and many other common tasks");
  297. messages.add ("As well as a code library, JUCE provides tools for managing\n"
  298. "cross-platform projects that are built with Xcode,\n"
  299. "Visual Studio, Android Studio, GCC and other compilers");
  300. messages.add ("JUCE can be used to build desktop or mobile apps, and also\n"
  301. "audio plug-ins in the VST2, VST3, AudioUnit, AAX and RTAS formats");
  302. }
  303. String getName() const override { return "Flock with text"; }
  304. void reset() override
  305. {
  306. FlockDemo::reset();
  307. currentMessage = 0;
  308. currentMessageStart = {};
  309. clientIndex = 0;
  310. }
  311. void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float> activeArea) override
  312. {
  313. FlockDemo::generateCanvas (g, canvas, activeArea);
  314. const float textSize = 0.5f; // inches
  315. const float textBlockWidth = 20.0f; // inches
  316. tick();
  317. Graphics::ScopedSaveState ss (g);
  318. const float scale = 20.0f; // scaled to allow the fonts to use more reasonable sizes
  319. g.addTransform (AffineTransform::scale (1.0f / scale));
  320. String text = String (messages[currentMessage]).replace ("NUMDEVICES", String (canvas.clients.size()));
  321. AttributedString as;
  322. as.append (text, Font (textSize * scale), Colour (0x80ffffff).withMultipliedAlpha (alpha));
  323. as.setJustification (Justification::centred);
  324. auto centre = canvas.clients[clientIndex % canvas.clients.size()].centre * scale;
  325. as.draw (g, Rectangle<float> (textBlockWidth * scale, textBlockWidth * scale).withCentre (centre));
  326. }
  327. void tick()
  328. {
  329. const double displayTimeSeconds = 5.0;
  330. const double fadeTimeSeconds = 1.0;
  331. Time now = Time::getCurrentTime();
  332. const double secondsSinceStart = (now - currentMessageStart).inSeconds();
  333. if (secondsSinceStart > displayTimeSeconds)
  334. {
  335. currentMessageStart = now;
  336. currentMessage = (currentMessage + 1) % messages.size();
  337. ++clientIndex;
  338. alpha = 0;
  339. }
  340. else if (secondsSinceStart > displayTimeSeconds - fadeTimeSeconds)
  341. {
  342. alpha = (float) jlimit (0.0, 1.0, (displayTimeSeconds - secondsSinceStart) / fadeTimeSeconds);
  343. }
  344. else if (secondsSinceStart < fadeTimeSeconds)
  345. {
  346. alpha = (float) jlimit (0.0, 1.0, secondsSinceStart / fadeTimeSeconds);
  347. }
  348. }
  349. StringArray messages;
  350. int currentMessage = 0, clientIndex = 0;
  351. float alpha = 0;
  352. Point<float> centre;
  353. Time currentMessageStart;
  354. };
  355. //==============================================================================
  356. struct SmallFlock : public FlockDemo
  357. {
  358. String getName() const override { return "Small Flock"; }
  359. void reset() override
  360. {
  361. setNumBirds (20);
  362. }
  363. };
  364. //==============================================================================
  365. struct BigFlock : public FlockDemo
  366. {
  367. String getName() const override { return "Big Flock"; }
  368. void reset() override
  369. {
  370. setNumBirds (200);
  371. }
  372. };
  373. //==============================================================================
  374. template <int numHorizontalLogos>
  375. struct MultiLogo : public BackgroundLogo
  376. {
  377. String getName() const override { return "Multi-Logo " + String ((int) numHorizontalLogos); }
  378. void generateCanvas (Graphics& g, SharedCanvasDescription& canvas, Rectangle<float> activeArea) override
  379. {
  380. float indent = 0.5f;
  381. float logoSize = canvas.getLimits().getWidth() / numHorizontalLogos;
  382. auto limits = canvas.getLimits();
  383. for (float x = limits.getX(); x < limits.getRight(); x += logoSize)
  384. {
  385. for (float y = limits.getY(); y < limits.getBottom(); y += logoSize)
  386. {
  387. logo->drawWithin (g, Rectangle<float> (x, y, logoSize, logoSize).reduced (indent),
  388. RectanglePlacement (RectanglePlacement::centred), 0.5f);
  389. }
  390. }
  391. }
  392. };
  393. //==============================================================================
  394. void createAllDemos (OwnedArray<AnimatedContent>& demos)
  395. {
  396. demos.add (new FlockDemo());
  397. demos.add (new FlockWithText());
  398. demos.add (new SmallFlock());
  399. demos.add (new BigFlock());
  400. demos.add (new BackgroundLogo());
  401. demos.add (new MultiLogo<5>());
  402. demos.add (new MultiLogo<10>());
  403. demos.add (new GridLines());
  404. demos.add (new BlankCanvas());
  405. }