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_DrawablePath.cpp 20KB

7 years ago
9 years ago
9 years ago
9 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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. DrawablePath::DrawablePath()
  22. {
  23. }
  24. DrawablePath::DrawablePath (const DrawablePath& other)
  25. : DrawableShape (other)
  26. {
  27. if (other.relativePath != nullptr)
  28. setPath (*other.relativePath);
  29. else
  30. setPath (other.path);
  31. }
  32. DrawablePath::~DrawablePath()
  33. {
  34. }
  35. Drawable* DrawablePath::createCopy() const
  36. {
  37. return new DrawablePath (*this);
  38. }
  39. //==============================================================================
  40. void DrawablePath::setPath (const Path& newPath)
  41. {
  42. path = newPath;
  43. pathChanged();
  44. }
  45. const Path& DrawablePath::getPath() const
  46. {
  47. return path;
  48. }
  49. const Path& DrawablePath::getStrokePath() const
  50. {
  51. return strokePath;
  52. }
  53. void DrawablePath::applyRelativePath (const RelativePointPath& newRelativePath, Expression::Scope* scope)
  54. {
  55. Path newPath;
  56. newRelativePath.createPath (newPath, scope);
  57. if (path != newPath)
  58. {
  59. path.swapWithPath (newPath);
  60. pathChanged();
  61. }
  62. }
  63. //==============================================================================
  64. class DrawablePath::RelativePositioner : public RelativeCoordinatePositionerBase
  65. {
  66. public:
  67. RelativePositioner (DrawablePath& comp)
  68. : RelativeCoordinatePositionerBase (comp),
  69. owner (comp)
  70. {
  71. }
  72. bool registerCoordinates() override
  73. {
  74. bool ok = true;
  75. jassert (owner.relativePath != nullptr);
  76. const RelativePointPath& relPath = *owner.relativePath;
  77. for (int i = 0; i < relPath.elements.size(); ++i)
  78. {
  79. RelativePointPath::ElementBase* const e = relPath.elements.getUnchecked(i);
  80. int numPoints;
  81. RelativePoint* const points = e->getControlPoints (numPoints);
  82. for (int j = numPoints; --j >= 0;)
  83. ok = addPoint (points[j]) && ok;
  84. }
  85. return ok;
  86. }
  87. void applyToComponentBounds() override
  88. {
  89. jassert (owner.relativePath != nullptr);
  90. ComponentScope scope (getComponent());
  91. owner.applyRelativePath (*owner.relativePath, &scope);
  92. }
  93. void applyNewBounds (const Rectangle<int>&) override
  94. {
  95. jassertfalse; // drawables can't be resized directly!
  96. }
  97. private:
  98. DrawablePath& owner;
  99. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner)
  100. };
  101. void DrawablePath::setPath (const RelativePointPath& newRelativePath)
  102. {
  103. if (newRelativePath.containsAnyDynamicPoints())
  104. {
  105. if (relativePath == nullptr || newRelativePath != *relativePath)
  106. {
  107. relativePath = new RelativePointPath (newRelativePath);
  108. RelativePositioner* const p = new RelativePositioner (*this);
  109. setPositioner (p);
  110. p->apply();
  111. }
  112. }
  113. else
  114. {
  115. relativePath = nullptr;
  116. applyRelativePath (newRelativePath, nullptr);
  117. }
  118. }
  119. //==============================================================================
  120. const Identifier DrawablePath::valueTreeType ("Path");
  121. const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding");
  122. const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1");
  123. const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2");
  124. const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3");
  125. //==============================================================================
  126. DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
  127. : FillAndStrokeState (state_)
  128. {
  129. jassert (state.hasType (valueTreeType));
  130. }
  131. ValueTree DrawablePath::ValueTreeWrapper::getPathState()
  132. {
  133. return state.getOrCreateChildWithName (path, nullptr);
  134. }
  135. bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const
  136. {
  137. return state [nonZeroWinding];
  138. }
  139. void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager)
  140. {
  141. state.setProperty (nonZeroWinding, b, undoManager);
  142. }
  143. void DrawablePath::ValueTreeWrapper::readFrom (const RelativePointPath& p, UndoManager* undoManager)
  144. {
  145. setUsesNonZeroWinding (p.usesNonZeroWinding, undoManager);
  146. ValueTree pathTree (getPathState());
  147. pathTree.removeAllChildren (undoManager);
  148. for (int i = 0; i < p.elements.size(); ++i)
  149. pathTree.addChild (p.elements.getUnchecked(i)->createTree(), -1, undoManager);
  150. }
  151. void DrawablePath::ValueTreeWrapper::writeTo (RelativePointPath& p) const
  152. {
  153. p.usesNonZeroWinding = usesNonZeroWinding();
  154. RelativePoint points[3];
  155. const ValueTree pathTree (state.getChildWithName (path));
  156. const int num = pathTree.getNumChildren();
  157. for (int i = 0; i < num; ++i)
  158. {
  159. const Element e (pathTree.getChild(i));
  160. const int numCps = e.getNumControlPoints();
  161. for (int j = 0; j < numCps; ++j)
  162. points[j] = e.getControlPoint (j);
  163. RelativePointPath::ElementBase* newElement = nullptr;
  164. const Identifier t (e.getType());
  165. if (t == Element::startSubPathElement) newElement = new RelativePointPath::StartSubPath (points[0]);
  166. else if (t == Element::closeSubPathElement) newElement = new RelativePointPath::CloseSubPath();
  167. else if (t == Element::lineToElement) newElement = new RelativePointPath::LineTo (points[0]);
  168. else if (t == Element::quadraticToElement) newElement = new RelativePointPath::QuadraticTo (points[0], points[1]);
  169. else if (t == Element::cubicToElement) newElement = new RelativePointPath::CubicTo (points[0], points[1], points[2]);
  170. else jassertfalse;
  171. p.addElement (newElement);
  172. }
  173. }
  174. //==============================================================================
  175. const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode");
  176. const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move");
  177. const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close");
  178. const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line");
  179. const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad");
  180. const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic");
  181. const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner";
  182. const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round";
  183. const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm";
  184. DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_)
  185. : state (state_)
  186. {
  187. }
  188. DrawablePath::ValueTreeWrapper::Element::~Element()
  189. {
  190. }
  191. DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const
  192. {
  193. return ValueTreeWrapper (state.getParent().getParent());
  194. }
  195. DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const
  196. {
  197. return Element (state.getSibling (-1));
  198. }
  199. int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const noexcept
  200. {
  201. const Identifier i (state.getType());
  202. if (i == startSubPathElement || i == lineToElement) return 1;
  203. if (i == quadraticToElement) return 2;
  204. if (i == cubicToElement) return 3;
  205. return 0;
  206. }
  207. RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const
  208. {
  209. jassert (index >= 0 && index < getNumControlPoints());
  210. return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString());
  211. }
  212. Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager)
  213. {
  214. jassert (index >= 0 && index < getNumControlPoints());
  215. return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager);
  216. }
  217. void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager)
  218. {
  219. jassert (index >= 0 && index < getNumControlPoints());
  220. state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager);
  221. }
  222. RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const
  223. {
  224. const Identifier i (state.getType());
  225. if (i == startSubPathElement)
  226. return getControlPoint (0);
  227. jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement);
  228. return getPreviousElement().getEndPoint();
  229. }
  230. RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const
  231. {
  232. const Identifier i (state.getType());
  233. if (i == startSubPathElement || i == lineToElement) return getControlPoint (0);
  234. if (i == quadraticToElement) return getControlPoint (1);
  235. if (i == cubicToElement) return getControlPoint (2);
  236. jassert (i == closeSubPathElement);
  237. return RelativePoint();
  238. }
  239. float DrawablePath::ValueTreeWrapper::Element::getLength (Expression::Scope* scope) const
  240. {
  241. const Identifier i (state.getType());
  242. if (i == lineToElement || i == closeSubPathElement)
  243. return getEndPoint().resolve (scope).getDistanceFrom (getStartPoint().resolve (scope));
  244. if (i == cubicToElement)
  245. {
  246. Path p;
  247. p.startNewSubPath (getStartPoint().resolve (scope));
  248. p.cubicTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope), getControlPoint (2).resolve (scope));
  249. return p.getLength();
  250. }
  251. if (i == quadraticToElement)
  252. {
  253. Path p;
  254. p.startNewSubPath (getStartPoint().resolve (scope));
  255. p.quadraticTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope));
  256. return p.getLength();
  257. }
  258. jassert (i == startSubPathElement);
  259. return 0;
  260. }
  261. String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const
  262. {
  263. return state [mode].toString();
  264. }
  265. void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager)
  266. {
  267. if (state.hasType (cubicToElement))
  268. state.setProperty (mode, newMode, undoManager);
  269. }
  270. void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager)
  271. {
  272. const Identifier i (state.getType());
  273. if (i == quadraticToElement || i == cubicToElement)
  274. {
  275. ValueTree newState (lineToElement);
  276. Element e (newState);
  277. e.setControlPoint (0, getEndPoint(), undoManager);
  278. state = newState;
  279. }
  280. }
  281. void DrawablePath::ValueTreeWrapper::Element::convertToCubic (Expression::Scope* scope, UndoManager* undoManager)
  282. {
  283. const Identifier i (state.getType());
  284. if (i == lineToElement || i == quadraticToElement)
  285. {
  286. ValueTree newState (cubicToElement);
  287. Element e (newState);
  288. const RelativePoint start (getStartPoint());
  289. const RelativePoint end (getEndPoint());
  290. const Point<float> startResolved (start.resolve (scope));
  291. const Point<float> endResolved (end.resolve (scope));
  292. e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager);
  293. e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager);
  294. e.setControlPoint (2, end, undoManager);
  295. state = newState;
  296. }
  297. }
  298. void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager)
  299. {
  300. const Identifier i (state.getType());
  301. if (i != startSubPathElement)
  302. {
  303. ValueTree newState (startSubPathElement);
  304. Element e (newState);
  305. e.setControlPoint (0, getEndPoint(), undoManager);
  306. state = newState;
  307. }
  308. }
  309. namespace DrawablePathHelpers
  310. {
  311. static Point<float> findCubicSubdivisionPoint (float proportion, const Point<float> points[4])
  312. {
  313. const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
  314. mid2 (points[1] + (points[2] - points[1]) * proportion),
  315. mid3 (points[2] + (points[3] - points[2]) * proportion);
  316. const Point<float> newCp1 (mid1 + (mid2 - mid1) * proportion),
  317. newCp2 (mid2 + (mid3 - mid2) * proportion);
  318. return newCp1 + (newCp2 - newCp1) * proportion;
  319. }
  320. static Point<float> findQuadraticSubdivisionPoint (float proportion, const Point<float> points[3])
  321. {
  322. const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
  323. mid2 (points[1] + (points[2] - points[1]) * proportion);
  324. return mid1 + (mid2 - mid1) * proportion;
  325. }
  326. }
  327. float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (Point<float> targetPoint, Expression::Scope* scope) const
  328. {
  329. using namespace DrawablePathHelpers;
  330. const Identifier pointType (state.getType());
  331. float bestProp = 0;
  332. if (pointType == cubicToElement)
  333. {
  334. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
  335. const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) };
  336. float bestDistance = std::numeric_limits<float>::max();
  337. for (int i = 110; --i >= 0;)
  338. {
  339. float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
  340. const Point<float> centre (findCubicSubdivisionPoint (prop, points));
  341. const float distance = centre.getDistanceFrom (targetPoint);
  342. if (distance < bestDistance)
  343. {
  344. bestProp = prop;
  345. bestDistance = distance;
  346. }
  347. }
  348. }
  349. else if (pointType == quadraticToElement)
  350. {
  351. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
  352. const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) };
  353. float bestDistance = std::numeric_limits<float>::max();
  354. for (int i = 110; --i >= 0;)
  355. {
  356. float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
  357. const Point<float> centre (findQuadraticSubdivisionPoint ((float) prop, points));
  358. const float distance = centre.getDistanceFrom (targetPoint);
  359. if (distance < bestDistance)
  360. {
  361. bestProp = prop;
  362. bestDistance = distance;
  363. }
  364. }
  365. }
  366. else if (pointType == lineToElement)
  367. {
  368. RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
  369. const Line<float> line (rp1.resolve (scope), rp2.resolve (scope));
  370. bestProp = line.findNearestProportionalPositionTo (targetPoint);
  371. }
  372. return bestProp;
  373. }
  374. ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (Point<float> targetPoint, Expression::Scope* scope, UndoManager* undoManager)
  375. {
  376. ValueTree newTree;
  377. const Identifier pointType (state.getType());
  378. if (pointType == cubicToElement)
  379. {
  380. float bestProp = findProportionAlongLine (targetPoint, scope);
  381. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
  382. const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) };
  383. const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
  384. mid2 (points[1] + (points[2] - points[1]) * bestProp),
  385. mid3 (points[2] + (points[3] - points[2]) * bestProp);
  386. const Point<float> newCp1 (mid1 + (mid2 - mid1) * bestProp),
  387. newCp2 (mid2 + (mid3 - mid2) * bestProp);
  388. const Point<float> newCentre (newCp1 + (newCp2 - newCp1) * bestProp);
  389. setControlPoint (0, mid1, undoManager);
  390. setControlPoint (1, newCp1, undoManager);
  391. setControlPoint (2, newCentre, undoManager);
  392. setModeOfEndPoint (roundedMode, undoManager);
  393. Element newElement (newTree = ValueTree (cubicToElement));
  394. newElement.setControlPoint (0, newCp2, nullptr);
  395. newElement.setControlPoint (1, mid3, nullptr);
  396. newElement.setControlPoint (2, rp4, nullptr);
  397. state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
  398. }
  399. else if (pointType == quadraticToElement)
  400. {
  401. float bestProp = findProportionAlongLine (targetPoint, scope);
  402. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
  403. const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) };
  404. const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
  405. mid2 (points[1] + (points[2] - points[1]) * bestProp);
  406. const Point<float> newCentre (mid1 + (mid2 - mid1) * bestProp);
  407. setControlPoint (0, mid1, undoManager);
  408. setControlPoint (1, newCentre, undoManager);
  409. setModeOfEndPoint (roundedMode, undoManager);
  410. Element newElement (newTree = ValueTree (quadraticToElement));
  411. newElement.setControlPoint (0, mid2, nullptr);
  412. newElement.setControlPoint (1, rp3, nullptr);
  413. state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
  414. }
  415. else if (pointType == lineToElement)
  416. {
  417. RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
  418. const Line<float> line (rp1.resolve (scope), rp2.resolve (scope));
  419. const Point<float> newPoint (line.findNearestPointTo (targetPoint));
  420. setControlPoint (0, newPoint, undoManager);
  421. Element newElement (newTree = ValueTree (lineToElement));
  422. newElement.setControlPoint (0, rp2, nullptr);
  423. state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
  424. }
  425. else if (pointType == closeSubPathElement)
  426. {
  427. }
  428. return newTree;
  429. }
  430. void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager)
  431. {
  432. state.getParent().removeChild (state, undoManager);
  433. }
  434. //==============================================================================
  435. void DrawablePath::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
  436. {
  437. ValueTreeWrapper v (tree);
  438. setComponentID (v.getID());
  439. refreshFillTypes (v, builder.getImageProvider());
  440. setStrokeType (v.getStrokeType());
  441. RelativePointPath newRelativePath;
  442. v.writeTo (newRelativePath);
  443. setPath (newRelativePath);
  444. }
  445. ValueTree DrawablePath::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
  446. {
  447. ValueTree tree (valueTreeType);
  448. ValueTreeWrapper v (tree);
  449. v.setID (getComponentID());
  450. writeTo (v, imageProvider, nullptr);
  451. if (relativePath != nullptr)
  452. v.readFrom (*relativePath, nullptr);
  453. else
  454. v.readFrom (RelativePointPath (path), nullptr);
  455. return tree;
  456. }
  457. } // namespace juce