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.

579 lines
20KB

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