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.

571 lines
20KB

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