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.

493 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 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. #include "../../../core/juce_StandardHeader.h"
  19. BEGIN_JUCE_NAMESPACE
  20. #include "juce_DrawablePath.h"
  21. #include "juce_DrawableComposite.h"
  22. //==============================================================================
  23. DrawablePath::DrawablePath()
  24. {
  25. }
  26. DrawablePath::DrawablePath (const DrawablePath& other)
  27. : DrawableShape (other)
  28. {
  29. if (other.relativePath != 0)
  30. relativePath = new RelativePointPath (*other.relativePath);
  31. else
  32. setPath (other.path);
  33. }
  34. DrawablePath::~DrawablePath()
  35. {
  36. }
  37. Drawable* DrawablePath::createCopy() const
  38. {
  39. return new DrawablePath (*this);
  40. }
  41. //==============================================================================
  42. void DrawablePath::setPath (const Path& newPath)
  43. {
  44. path = newPath;
  45. pathChanged();
  46. }
  47. const Path& DrawablePath::getPath() const
  48. {
  49. return path;
  50. }
  51. const Path& DrawablePath::getStrokePath() const
  52. {
  53. return strokePath;
  54. }
  55. bool DrawablePath::rebuildPath (Path& path) const
  56. {
  57. if (relativePath != 0)
  58. {
  59. path.clear();
  60. relativePath->createPath (path, getParent());
  61. return true;
  62. }
  63. return false;
  64. }
  65. //==============================================================================
  66. const Identifier DrawablePath::valueTreeType ("Path");
  67. const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding");
  68. const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1");
  69. const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2");
  70. const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3");
  71. //==============================================================================
  72. DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
  73. : FillAndStrokeState (state_)
  74. {
  75. jassert (state.hasType (valueTreeType));
  76. }
  77. ValueTree DrawablePath::ValueTreeWrapper::getPathState()
  78. {
  79. return state.getOrCreateChildWithName (path, 0);
  80. }
  81. bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const
  82. {
  83. return state [nonZeroWinding];
  84. }
  85. void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager)
  86. {
  87. state.setProperty (nonZeroWinding, b, undoManager);
  88. }
  89. //==============================================================================
  90. const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode");
  91. const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move");
  92. const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close");
  93. const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line");
  94. const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad");
  95. const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic");
  96. const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner";
  97. const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round";
  98. const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm";
  99. DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_)
  100. : state (state_)
  101. {
  102. }
  103. DrawablePath::ValueTreeWrapper::Element::~Element()
  104. {
  105. }
  106. DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const
  107. {
  108. return ValueTreeWrapper (state.getParent().getParent());
  109. }
  110. DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const
  111. {
  112. return Element (state.getSibling (-1));
  113. }
  114. int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const throw()
  115. {
  116. const Identifier i (state.getType());
  117. if (i == startSubPathElement || i == lineToElement) return 1;
  118. if (i == quadraticToElement) return 2;
  119. if (i == cubicToElement) return 3;
  120. return 0;
  121. }
  122. const RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const
  123. {
  124. jassert (index >= 0 && index < getNumControlPoints());
  125. return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString());
  126. }
  127. Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager) const
  128. {
  129. jassert (index >= 0 && index < getNumControlPoints());
  130. return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager);
  131. }
  132. void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager)
  133. {
  134. jassert (index >= 0 && index < getNumControlPoints());
  135. state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager);
  136. }
  137. const RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const
  138. {
  139. const Identifier i (state.getType());
  140. if (i == startSubPathElement)
  141. return getControlPoint (0);
  142. jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement);
  143. return getPreviousElement().getEndPoint();
  144. }
  145. const RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const
  146. {
  147. const Identifier i (state.getType());
  148. if (i == startSubPathElement || i == lineToElement) return getControlPoint (0);
  149. if (i == quadraticToElement) return getControlPoint (1);
  150. if (i == cubicToElement) return getControlPoint (2);
  151. jassert (i == closeSubPathElement);
  152. return RelativePoint();
  153. }
  154. float DrawablePath::ValueTreeWrapper::Element::getLength (Expression::EvaluationContext* nameFinder) const
  155. {
  156. const Identifier i (state.getType());
  157. if (i == lineToElement || i == closeSubPathElement)
  158. return getEndPoint().resolve (nameFinder).getDistanceFrom (getStartPoint().resolve (nameFinder));
  159. if (i == cubicToElement)
  160. {
  161. Path p;
  162. p.startNewSubPath (getStartPoint().resolve (nameFinder));
  163. p.cubicTo (getControlPoint (0).resolve (nameFinder), getControlPoint (1).resolve (nameFinder), getControlPoint (2).resolve (nameFinder));
  164. return p.getLength();
  165. }
  166. if (i == quadraticToElement)
  167. {
  168. Path p;
  169. p.startNewSubPath (getStartPoint().resolve (nameFinder));
  170. p.quadraticTo (getControlPoint (0).resolve (nameFinder), getControlPoint (1).resolve (nameFinder));
  171. return p.getLength();
  172. }
  173. jassert (i == startSubPathElement);
  174. return 0;
  175. }
  176. const String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const
  177. {
  178. return state [mode].toString();
  179. }
  180. void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager)
  181. {
  182. if (state.hasType (cubicToElement))
  183. state.setProperty (mode, newMode, undoManager);
  184. }
  185. void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager)
  186. {
  187. const Identifier i (state.getType());
  188. if (i == quadraticToElement || i == cubicToElement)
  189. {
  190. ValueTree newState (lineToElement);
  191. Element e (newState);
  192. e.setControlPoint (0, getEndPoint(), undoManager);
  193. state = newState;
  194. }
  195. }
  196. void DrawablePath::ValueTreeWrapper::Element::convertToCubic (Expression::EvaluationContext* nameFinder, UndoManager* undoManager)
  197. {
  198. const Identifier i (state.getType());
  199. if (i == lineToElement || i == quadraticToElement)
  200. {
  201. ValueTree newState (cubicToElement);
  202. Element e (newState);
  203. const RelativePoint start (getStartPoint());
  204. const RelativePoint end (getEndPoint());
  205. const Point<float> startResolved (start.resolve (nameFinder));
  206. const Point<float> endResolved (end.resolve (nameFinder));
  207. e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager);
  208. e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager);
  209. e.setControlPoint (2, end, undoManager);
  210. state = newState;
  211. }
  212. }
  213. void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager)
  214. {
  215. const Identifier i (state.getType());
  216. if (i != startSubPathElement)
  217. {
  218. ValueTree newState (startSubPathElement);
  219. Element e (newState);
  220. e.setControlPoint (0, getEndPoint(), undoManager);
  221. state = newState;
  222. }
  223. }
  224. namespace DrawablePathHelpers
  225. {
  226. const Point<float> findCubicSubdivisionPoint (float proportion, const Point<float> points[4])
  227. {
  228. const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
  229. mid2 (points[1] + (points[2] - points[1]) * proportion),
  230. mid3 (points[2] + (points[3] - points[2]) * proportion);
  231. const Point<float> newCp1 (mid1 + (mid2 - mid1) * proportion),
  232. newCp2 (mid2 + (mid3 - mid2) * proportion);
  233. return newCp1 + (newCp2 - newCp1) * proportion;
  234. }
  235. const Point<float> findQuadraticSubdivisionPoint (float proportion, const Point<float> points[3])
  236. {
  237. const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
  238. mid2 (points[1] + (points[2] - points[1]) * proportion);
  239. return mid1 + (mid2 - mid1) * proportion;
  240. }
  241. }
  242. float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (const Point<float>& targetPoint, Expression::EvaluationContext* nameFinder) const
  243. {
  244. using namespace DrawablePathHelpers;
  245. const Identifier i (state.getType());
  246. float bestProp = 0;
  247. if (i == cubicToElement)
  248. {
  249. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
  250. const Point<float> points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder), rp4.resolve (nameFinder) };
  251. float bestDistance = std::numeric_limits<float>::max();
  252. for (int i = 110; --i >= 0;)
  253. {
  254. float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
  255. const Point<float> centre (findCubicSubdivisionPoint (prop, points));
  256. const float distance = centre.getDistanceFrom (targetPoint);
  257. if (distance < bestDistance)
  258. {
  259. bestProp = prop;
  260. bestDistance = distance;
  261. }
  262. }
  263. }
  264. else if (i == quadraticToElement)
  265. {
  266. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
  267. const Point<float> points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder) };
  268. float bestDistance = std::numeric_limits<float>::max();
  269. for (int i = 110; --i >= 0;)
  270. {
  271. float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
  272. const Point<float> centre (findQuadraticSubdivisionPoint ((float) prop, points));
  273. const float distance = centre.getDistanceFrom (targetPoint);
  274. if (distance < bestDistance)
  275. {
  276. bestProp = prop;
  277. bestDistance = distance;
  278. }
  279. }
  280. }
  281. else if (i == lineToElement)
  282. {
  283. RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
  284. const Line<float> line (rp1.resolve (nameFinder), rp2.resolve (nameFinder));
  285. bestProp = line.findNearestProportionalPositionTo (targetPoint);
  286. }
  287. return bestProp;
  288. }
  289. ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (const Point<float>& targetPoint, Expression::EvaluationContext* nameFinder, UndoManager* undoManager)
  290. {
  291. ValueTree newTree;
  292. const Identifier i (state.getType());
  293. if (i == cubicToElement)
  294. {
  295. float bestProp = findProportionAlongLine (targetPoint, nameFinder);
  296. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
  297. const Point<float> points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder), rp4.resolve (nameFinder) };
  298. const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
  299. mid2 (points[1] + (points[2] - points[1]) * bestProp),
  300. mid3 (points[2] + (points[3] - points[2]) * bestProp);
  301. const Point<float> newCp1 (mid1 + (mid2 - mid1) * bestProp),
  302. newCp2 (mid2 + (mid3 - mid2) * bestProp);
  303. const Point<float> newCentre (newCp1 + (newCp2 - newCp1) * bestProp);
  304. setControlPoint (0, mid1, undoManager);
  305. setControlPoint (1, newCp1, undoManager);
  306. setControlPoint (2, newCentre, undoManager);
  307. setModeOfEndPoint (roundedMode, undoManager);
  308. Element newElement (newTree = ValueTree (cubicToElement));
  309. newElement.setControlPoint (0, newCp2, 0);
  310. newElement.setControlPoint (1, mid3, 0);
  311. newElement.setControlPoint (2, rp4, 0);
  312. state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
  313. }
  314. else if (i == quadraticToElement)
  315. {
  316. float bestProp = findProportionAlongLine (targetPoint, nameFinder);
  317. RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
  318. const Point<float> points[] = { rp1.resolve (nameFinder), rp2.resolve (nameFinder), rp3.resolve (nameFinder) };
  319. const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
  320. mid2 (points[1] + (points[2] - points[1]) * bestProp);
  321. const Point<float> newCentre (mid1 + (mid2 - mid1) * bestProp);
  322. setControlPoint (0, mid1, undoManager);
  323. setControlPoint (1, newCentre, undoManager);
  324. setModeOfEndPoint (roundedMode, undoManager);
  325. Element newElement (newTree = ValueTree (quadraticToElement));
  326. newElement.setControlPoint (0, mid2, 0);
  327. newElement.setControlPoint (1, rp3, 0);
  328. state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
  329. }
  330. else if (i == lineToElement)
  331. {
  332. RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
  333. const Line<float> line (rp1.resolve (nameFinder), rp2.resolve (nameFinder));
  334. const Point<float> newPoint (line.findNearestPointTo (targetPoint));
  335. setControlPoint (0, newPoint, undoManager);
  336. Element newElement (newTree = ValueTree (lineToElement));
  337. newElement.setControlPoint (0, rp2, 0);
  338. state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
  339. }
  340. else if (i == closeSubPathElement)
  341. {
  342. }
  343. return newTree;
  344. }
  345. void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager)
  346. {
  347. state.getParent().removeChild (state, undoManager);
  348. }
  349. //==============================================================================
  350. void DrawablePath::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider)
  351. {
  352. ValueTreeWrapper v (tree);
  353. setName (v.getID());
  354. if (refreshFillTypes (v, getParent(), imageProvider))
  355. repaint();
  356. ScopedPointer<RelativePointPath> newRelativePath (new RelativePointPath (tree));
  357. Path newPath;
  358. newRelativePath->createPath (newPath, getParent());
  359. if (! newRelativePath->containsAnyDynamicPoints())
  360. newRelativePath = 0;
  361. const PathStrokeType newStroke (v.getStrokeType());
  362. if (strokeType != newStroke || path != newPath)
  363. {
  364. path.swapWithPath (newPath);
  365. strokeType = newStroke;
  366. pathChanged();
  367. }
  368. relativePath = newRelativePath;
  369. }
  370. const ValueTree DrawablePath::createValueTree (ImageProvider* imageProvider) const
  371. {
  372. ValueTree tree (valueTreeType);
  373. ValueTreeWrapper v (tree);
  374. v.setID (getName(), 0);
  375. writeTo (v, imageProvider, 0);
  376. if (relativePath != 0)
  377. {
  378. relativePath->writeTo (tree, 0);
  379. }
  380. else
  381. {
  382. RelativePointPath rp (path);
  383. rp.writeTo (tree, 0);
  384. }
  385. return tree;
  386. }
  387. END_JUCE_NAMESPACE