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.

572 lines
20KB

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