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.

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