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.

1644 lines
51KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../../Application/jucer_Headers.h"
  20. #include "jucer_PaintElementPath.h"
  21. #include "../Properties/jucer_PositionPropertyBase.h"
  22. #include "jucer_PaintElementUndoableAction.h"
  23. #include "../jucer_UtilityFunctions.h"
  24. //==============================================================================
  25. class ChangePointAction : public PaintElementUndoableAction <PaintElementPath>
  26. {
  27. public:
  28. ChangePointAction (PathPoint* const point,
  29. const int pointIndex,
  30. const PathPoint& newValue_)
  31. : PaintElementUndoableAction <PaintElementPath> (point->owner),
  32. index (pointIndex),
  33. newValue (newValue_),
  34. oldValue (*point)
  35. {
  36. }
  37. ChangePointAction (PathPoint* const point,
  38. const PathPoint& newValue_)
  39. : PaintElementUndoableAction <PaintElementPath> (point->owner),
  40. index (point->owner->indexOfPoint (point)),
  41. newValue (newValue_),
  42. oldValue (*point)
  43. {
  44. }
  45. bool perform()
  46. {
  47. return changeTo (newValue);
  48. }
  49. bool undo()
  50. {
  51. return changeTo (oldValue);
  52. }
  53. private:
  54. const int index;
  55. PathPoint newValue, oldValue;
  56. PathPoint* getPoint() const
  57. {
  58. PathPoint* p = getElement()->getPoint (index);
  59. jassert (p != nullptr);
  60. return p;
  61. }
  62. bool changeTo (const PathPoint& value) const
  63. {
  64. showCorrectTab();
  65. PaintElementPath* const path = getElement();
  66. jassert (path != nullptr);
  67. PathPoint* const p = path->getPoint (index);
  68. jassert (p != nullptr);
  69. const bool typeChanged = (p->type != value.type);
  70. *p = value;
  71. p->owner = path;
  72. if (typeChanged)
  73. path->pointListChanged();
  74. path->changed();
  75. return true;
  76. }
  77. };
  78. //==============================================================================
  79. class PathWindingModeProperty : public ChoicePropertyComponent,
  80. public ChangeListener
  81. {
  82. public:
  83. PathWindingModeProperty (PaintElementPath* const owner_)
  84. : ChoicePropertyComponent ("winding rule"),
  85. owner (owner_)
  86. {
  87. choices.add ("Non-zero winding");
  88. choices.add ("Even/odd winding");
  89. owner->getDocument()->addChangeListener (this);
  90. }
  91. ~PathWindingModeProperty()
  92. {
  93. owner->getDocument()->removeChangeListener (this);
  94. }
  95. void setIndex (int newIndex) { owner->setNonZeroWinding (newIndex == 0, true); }
  96. int getIndex() const { return owner->isNonZeroWinding() ? 0 : 1; }
  97. void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
  98. private:
  99. PaintElementPath* const owner;
  100. };
  101. //==============================================================================
  102. PaintElementPath::PaintElementPath (PaintRoutine* pr)
  103. : ColouredElement (pr, "Path", true, true),
  104. nonZeroWinding (true)
  105. {
  106. }
  107. PaintElementPath::~PaintElementPath()
  108. {
  109. }
  110. static int randomPos (int size)
  111. {
  112. return size / 4 + Random::getSystemRandom().nextInt (size / 4) - size / 8;
  113. }
  114. void PaintElementPath::setInitialBounds (int w, int h)
  115. {
  116. String s;
  117. int x = randomPos (w);
  118. int y = randomPos (h);
  119. s << "s "
  120. << x << " " << y << " l "
  121. << (x + 30) << " " << (y + 50) << " l "
  122. << (x - 30) << " " << (y + 50) << " x";
  123. restorePathFromString (s);
  124. }
  125. //==============================================================================
  126. int PaintElementPath::getBorderSize() const
  127. {
  128. return isStrokePresent ? 1 + roundFloatToInt (strokeType.stroke.getStrokeThickness())
  129. : 0;
  130. }
  131. Rectangle<int> PaintElementPath::getCurrentBounds (const Rectangle<int>& parentArea) const
  132. {
  133. updateStoredPath (getDocument()->getComponentLayout(), parentArea);
  134. Rectangle<float> r (path.getBounds());
  135. const int borderSize = getBorderSize();
  136. return Rectangle<int> ((int) r.getX() - borderSize,
  137. (int) r.getY() - borderSize,
  138. (int) r.getWidth() + borderSize * 2,
  139. (int) r.getHeight() + borderSize * 2);
  140. }
  141. void PaintElementPath::setCurrentBounds (const Rectangle<int>& b,
  142. const Rectangle<int>& parentArea,
  143. const bool /*undoable*/)
  144. {
  145. Rectangle<int> newBounds (b);
  146. newBounds.setSize (jmax (1, newBounds.getWidth()),
  147. jmax (1, newBounds.getHeight()));
  148. const Rectangle<int> current (getCurrentBounds (parentArea));
  149. if (newBounds != current)
  150. {
  151. const int borderSize = getBorderSize();
  152. const int dx = newBounds.getX() - current.getX();
  153. const int dy = newBounds.getY() - current.getY();
  154. const double scaleStartX = current.getX() + borderSize;
  155. const double scaleStartY = current.getY() + borderSize;
  156. const double scaleX = (newBounds.getWidth() - borderSize * 2) / (double) (current.getWidth() - borderSize * 2);
  157. const double scaleY = (newBounds.getHeight() - borderSize * 2) / (double) (current.getHeight() - borderSize * 2);
  158. for (int i = 0; i < points.size(); ++i)
  159. {
  160. PathPoint* const destPoint = points.getUnchecked(i);
  161. PathPoint p (*destPoint);
  162. for (int j = p.getNumPoints(); --j >= 0;)
  163. rescalePoint (p.pos[j], dx, dy,
  164. scaleX, scaleY,
  165. scaleStartX, scaleStartY,
  166. parentArea);
  167. perform (new ChangePointAction (destPoint, i, p), "Move path");
  168. }
  169. }
  170. }
  171. void PaintElementPath::rescalePoint (RelativePositionedRectangle& pos, int dx, int dy,
  172. double scaleX, double scaleY,
  173. double scaleStartX, double scaleStartY,
  174. const Rectangle<int>& parentArea) const
  175. {
  176. double x, y, w, h;
  177. pos.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
  178. x = (x - scaleStartX) * scaleX + scaleStartX + dx;
  179. y = (y - scaleStartY) * scaleY + scaleStartY + dy;
  180. pos.updateFrom (x, y, w, h, parentArea, getDocument()->getComponentLayout());
  181. }
  182. //==============================================================================
  183. static void drawArrow (Graphics& g, const Point<float> p1, const Point<float> p2)
  184. {
  185. g.drawArrow (Line<float> (p1.x, p1.y, (p1.x + p2.x) * 0.5f, (p1.y + p2.y) * 0.5f), 1.0f, 8.0f, 10.0f);
  186. g.drawLine (p1.x + (p2.x - p1.x) * 0.49f, p1.y + (p2.y - p1.y) * 0.49f, p2.x, p2.y);
  187. }
  188. void PaintElementPath::draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea)
  189. {
  190. updateStoredPath (layout, parentArea);
  191. path.setUsingNonZeroWinding (nonZeroWinding);
  192. fillType.setFillType (g, getDocument(), parentArea);
  193. g.fillPath (path);
  194. if (isStrokePresent)
  195. {
  196. strokeType.fill.setFillType (g, getDocument(), parentArea);
  197. g.strokePath (path, getStrokeType().stroke);
  198. }
  199. }
  200. void PaintElementPath::drawExtraEditorGraphics (Graphics& g, const Rectangle<int>& relativeTo)
  201. {
  202. ComponentLayout* layout = getDocument()->getComponentLayout();
  203. for (int i = 0; i < points.size(); ++i)
  204. {
  205. PathPoint* const p = points.getUnchecked (i);
  206. const int numPoints = p->getNumPoints();
  207. if (numPoints > 0)
  208. {
  209. if (owner->getSelectedPoints().isSelected (p))
  210. {
  211. g.setColour (Colours::red);
  212. Point<float> p1, p2;
  213. if (numPoints > 2)
  214. {
  215. p1 = p->pos[1].toXY (relativeTo, layout);
  216. p2 = p->pos[2].toXY (relativeTo, layout);
  217. drawArrow (g, p1, p2);
  218. }
  219. if (numPoints > 1)
  220. {
  221. p1 = p->pos[0].toXY (relativeTo, layout);
  222. p2 = p->pos[1].toXY (relativeTo, layout);
  223. drawArrow (g, p1, p2);
  224. }
  225. p2 = p->pos[0].toXY (relativeTo, layout);
  226. if (const PathPoint* const nextPoint = points [i - 1])
  227. {
  228. p1 = nextPoint->pos [nextPoint->getNumPoints() - 1].toXY (relativeTo, layout);
  229. drawArrow (g, p1, p2);
  230. }
  231. }
  232. }
  233. }
  234. }
  235. void PaintElementPath::resized()
  236. {
  237. ColouredElement::resized();
  238. }
  239. void PaintElementPath::parentSizeChanged()
  240. {
  241. repaint();
  242. }
  243. //==============================================================================
  244. void PaintElementPath::mouseDown (const MouseEvent& e)
  245. {
  246. if (e.mods.isPopupMenu() || ! owner->getSelectedElements().isSelected (this))
  247. mouseDownOnSegment = -1;
  248. else
  249. mouseDownOnSegment = findSegmentAtXY (getX() + e.x, getY() + e.y);
  250. if (points [mouseDownOnSegment] != nullptr)
  251. mouseDownSelectSegmentStatus = owner->getSelectedPoints().addToSelectionOnMouseDown (points [mouseDownOnSegment], e.mods);
  252. else
  253. ColouredElement::mouseDown (e);
  254. }
  255. void PaintElementPath::mouseDrag (const MouseEvent& e)
  256. {
  257. if (mouseDownOnSegment < 0)
  258. ColouredElement::mouseDrag (e);
  259. }
  260. void PaintElementPath::mouseUp (const MouseEvent& e)
  261. {
  262. if (points [mouseDownOnSegment] == 0)
  263. ColouredElement::mouseUp (e);
  264. else
  265. owner->getSelectedPoints().addToSelectionOnMouseUp (points [mouseDownOnSegment],
  266. e.mods, false, mouseDownSelectSegmentStatus);
  267. }
  268. //==============================================================================
  269. void PaintElementPath::changed()
  270. {
  271. ColouredElement::changed();
  272. lastPathBounds = Rectangle<int>();
  273. }
  274. void PaintElementPath::pointListChanged()
  275. {
  276. changed();
  277. siblingComponentsChanged();
  278. }
  279. //==============================================================================
  280. void PaintElementPath::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
  281. {
  282. if (multipleSelected)
  283. return;
  284. props.add (new PathWindingModeProperty (this));
  285. getColourSpecificProperties (props);
  286. }
  287. //==============================================================================
  288. static String positionToPairOfValues (const RelativePositionedRectangle& position,
  289. const ComponentLayout* layout)
  290. {
  291. String x, y, w, h;
  292. positionToCode (position, layout, x, y, w, h);
  293. return castToFloat (x) + ", " + castToFloat (y);
  294. }
  295. void PaintElementPath::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode)
  296. {
  297. if (fillType.isInvisible() && (strokeType.isInvisible() || ! isStrokePresent))
  298. return;
  299. const String pathVariable ("internalPath" + String (code.getUniqueSuffix()));
  300. const ComponentLayout* layout = code.document->getComponentLayout();
  301. code.privateMemberDeclarations
  302. << "Path " << pathVariable << ";\n";
  303. String r;
  304. bool somePointsAreRelative = false;
  305. if (! nonZeroWinding)
  306. r << pathVariable << ".setUsingNonZeroWinding (false);\n";
  307. for (auto* p : points)
  308. {
  309. switch (p->type)
  310. {
  311. case Path::Iterator::startNewSubPath:
  312. r << pathVariable << ".startNewSubPath (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
  313. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  314. break;
  315. case Path::Iterator::lineTo:
  316. r << pathVariable << ".lineTo (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
  317. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  318. break;
  319. case Path::Iterator::quadraticTo:
  320. r << pathVariable << ".quadraticTo (" << positionToPairOfValues (p->pos[0], layout)
  321. << ", " << positionToPairOfValues (p->pos[1], layout) << ");\n";
  322. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  323. somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
  324. break;
  325. case Path::Iterator::cubicTo:
  326. r << pathVariable << ".cubicTo (" << positionToPairOfValues (p->pos[0], layout)
  327. << ", " << positionToPairOfValues (p->pos[1], layout)
  328. << ", " << positionToPairOfValues (p->pos[2], layout) << ");\n";
  329. somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
  330. somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
  331. somePointsAreRelative = somePointsAreRelative || ! p->pos[2].rect.isPositionAbsolute();
  332. break;
  333. case Path::Iterator::closePath:
  334. r << pathVariable << ".closeSubPath();\n";
  335. break;
  336. default:
  337. jassertfalse;
  338. break;
  339. }
  340. }
  341. r << '\n';
  342. if (somePointsAreRelative)
  343. code.getCallbackCode (String(), "void", "resized()", false)
  344. << pathVariable << ".clear();\n" << r;
  345. else
  346. code.constructorCode << r;
  347. String s;
  348. s << "{\n"
  349. << " float x = 0, y = 0;\n";
  350. if (! fillType.isInvisible())
  351. s << " " << fillType.generateVariablesCode ("fill");
  352. if (isStrokePresent && ! strokeType.isInvisible())
  353. s << " " << strokeType.fill.generateVariablesCode ("stroke");
  354. s << " //[UserPaintCustomArguments] Customize the painting arguments here..\n"
  355. << customPaintCode
  356. << " //[/UserPaintCustomArguments]\n";
  357. RelativePositionedRectangle zero;
  358. if (! fillType.isInvisible())
  359. {
  360. s << " ";
  361. fillType.fillInGeneratedCode ("fill", zero, code, s);
  362. s << " g.fillPath (" << pathVariable << ", AffineTransform::translation(x, y));\n";
  363. }
  364. if (isStrokePresent && ! strokeType.isInvisible())
  365. {
  366. s << " ";
  367. strokeType.fill.fillInGeneratedCode ("stroke", zero, code, s);
  368. s << " g.strokePath (" << pathVariable << ", " << strokeType.getPathStrokeCode() << ", AffineTransform::translation(x, y));\n";
  369. }
  370. s << "}\n\n";
  371. paintMethodCode += s;
  372. }
  373. void PaintElementPath::applyCustomPaintSnippets (StringArray& snippets)
  374. {
  375. customPaintCode.clear();
  376. if (! snippets.isEmpty() && (! fillType.isInvisible() || (isStrokePresent && ! strokeType.isInvisible())))
  377. {
  378. customPaintCode = snippets[0];
  379. snippets.remove(0);
  380. }
  381. }
  382. //==============================================================================
  383. XmlElement* PaintElementPath::createXml() const
  384. {
  385. XmlElement* e = new XmlElement (getTagName());
  386. position.applyToXml (*e);
  387. addColourAttributes (e);
  388. e->setAttribute ("nonZeroWinding", nonZeroWinding);
  389. e->addTextElement (pathToString());
  390. return e;
  391. }
  392. bool PaintElementPath::loadFromXml (const XmlElement& xml)
  393. {
  394. if (xml.hasTagName (getTagName()))
  395. {
  396. position.restoreFromXml (xml, position);
  397. loadColourAttributes (xml);
  398. nonZeroWinding = xml.getBoolAttribute ("nonZeroWinding", true);
  399. restorePathFromString (xml.getAllSubText().trim());
  400. return true;
  401. }
  402. jassertfalse;
  403. return false;
  404. }
  405. //==============================================================================
  406. void PaintElementPath::createSiblingComponents()
  407. {
  408. ColouredElement::createSiblingComponents();
  409. for (int i = 0; i < points.size(); ++i)
  410. {
  411. switch (points.getUnchecked(i)->type)
  412. {
  413. case Path::Iterator::startNewSubPath:
  414. siblingComponents.add (new PathPointComponent (this, i, 0));
  415. break;
  416. case Path::Iterator::lineTo:
  417. siblingComponents.add (new PathPointComponent (this, i, 0));
  418. break;
  419. case Path::Iterator::quadraticTo:
  420. siblingComponents.add (new PathPointComponent (this, i, 0));
  421. siblingComponents.add (new PathPointComponent (this, i, 1));
  422. break;
  423. case Path::Iterator::cubicTo:
  424. siblingComponents.add (new PathPointComponent (this, i, 0));
  425. siblingComponents.add (new PathPointComponent (this, i, 1));
  426. siblingComponents.add (new PathPointComponent (this, i, 2));
  427. break;
  428. case Path::Iterator::closePath:
  429. break;
  430. default:
  431. jassertfalse; break;
  432. }
  433. }
  434. for (int i = 0; i < siblingComponents.size(); ++i)
  435. {
  436. getParentComponent()->addAndMakeVisible (siblingComponents.getUnchecked(i));
  437. siblingComponents.getUnchecked(i)->updatePosition();
  438. }
  439. }
  440. String PaintElementPath::pathToString() const
  441. {
  442. String s;
  443. for (int i = 0; i < points.size(); ++i)
  444. {
  445. const PathPoint* const p = points.getUnchecked(i);
  446. switch (p->type)
  447. {
  448. case Path::Iterator::startNewSubPath:
  449. s << "s " << p->pos[0].toString() << ' ';
  450. break;
  451. case Path::Iterator::lineTo:
  452. s << "l " << p->pos[0].toString() << ' ';
  453. break;
  454. case Path::Iterator::quadraticTo:
  455. s << "q " << p->pos[0].toString()
  456. << ' ' << p->pos[1].toString() << ' ';
  457. break;
  458. case Path::Iterator::cubicTo:
  459. s << "c " << p->pos[0].toString()
  460. << ' ' << p->pos[1].toString() << ' '
  461. << ' ' << p->pos[2].toString() << ' ';
  462. break;
  463. case Path::Iterator::closePath:
  464. s << "x ";
  465. break;
  466. default:
  467. jassertfalse; break;
  468. }
  469. }
  470. return s.trimEnd();
  471. }
  472. void PaintElementPath::restorePathFromString (const String& s)
  473. {
  474. points.clear();
  475. StringArray tokens;
  476. tokens.addTokens (s, false);
  477. tokens.trim();
  478. tokens.removeEmptyStrings();
  479. for (int i = 0; i < tokens.size(); ++i)
  480. {
  481. ScopedPointer<PathPoint> p (new PathPoint (this));
  482. if (tokens[i] == "s")
  483. {
  484. p->type = Path::Iterator::startNewSubPath;
  485. p->pos [0] = RelativePositionedRectangle();
  486. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  487. i += 2;
  488. }
  489. else if (tokens[i] == "l")
  490. {
  491. p->type = Path::Iterator::lineTo;
  492. p->pos [0] = RelativePositionedRectangle();
  493. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  494. i += 2;
  495. }
  496. else if (tokens[i] == "q")
  497. {
  498. p->type = Path::Iterator::quadraticTo;
  499. p->pos [0] = RelativePositionedRectangle();
  500. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  501. p->pos [1] = RelativePositionedRectangle();
  502. p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
  503. i += 4;
  504. }
  505. else if (tokens[i] == "c")
  506. {
  507. p->type = Path::Iterator::cubicTo;
  508. p->pos [0] = RelativePositionedRectangle();
  509. p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
  510. p->pos [1] = RelativePositionedRectangle();
  511. p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
  512. p->pos [2] = RelativePositionedRectangle();
  513. p->pos [2].rect = PositionedRectangle (tokens [i + 5] + " " + tokens [i + 6]);
  514. i += 6;
  515. }
  516. else if (tokens[i] == "x")
  517. {
  518. p->type = Path::Iterator::closePath;
  519. }
  520. else
  521. continue;
  522. points.add (p.release());
  523. }
  524. }
  525. void PaintElementPath::setToPath (const Path& newPath)
  526. {
  527. points.clear();
  528. Path::Iterator i (newPath);
  529. while (i.next())
  530. {
  531. ScopedPointer<PathPoint> p (new PathPoint (this));
  532. p->type = i.elementType;
  533. if (i.elementType == Path::Iterator::startNewSubPath)
  534. {
  535. p->pos [0].rect.setX (i.x1);
  536. p->pos [0].rect.setY (i.y1);
  537. }
  538. else if (i.elementType == Path::Iterator::lineTo)
  539. {
  540. p->pos [0].rect.setX (i.x1);
  541. p->pos [0].rect.setY (i.y1);
  542. }
  543. else if (i.elementType == Path::Iterator::quadraticTo)
  544. {
  545. p->pos [0].rect.setX (i.x1);
  546. p->pos [0].rect.setY (i.y1);
  547. p->pos [1].rect.setX (i.x2);
  548. p->pos [1].rect.setY (i.y2);
  549. }
  550. else if (i.elementType == Path::Iterator::cubicTo)
  551. {
  552. p->pos [0].rect.setX (i.x1);
  553. p->pos [0].rect.setY (i.y1);
  554. p->pos [1].rect.setX (i.x2);
  555. p->pos [1].rect.setY (i.y2);
  556. p->pos [2].rect.setX (i.x3);
  557. p->pos [2].rect.setY (i.y3);
  558. }
  559. else if (i.elementType == Path::Iterator::closePath)
  560. {
  561. }
  562. else
  563. {
  564. continue;
  565. }
  566. points.add (p.release());
  567. }
  568. }
  569. void PaintElementPath::updateStoredPath (const ComponentLayout* layout, const Rectangle<int>& relativeTo) const
  570. {
  571. if (lastPathBounds != relativeTo && ! relativeTo.isEmpty())
  572. {
  573. lastPathBounds = relativeTo;
  574. path.clear();
  575. for (int i = 0; i < points.size(); ++i)
  576. {
  577. const PathPoint* const p = points.getUnchecked(i);
  578. switch (p->type)
  579. {
  580. case Path::Iterator::startNewSubPath:
  581. path.startNewSubPath (p->pos[0].toXY (relativeTo, layout));
  582. break;
  583. case Path::Iterator::lineTo:
  584. path.lineTo (p->pos[0].toXY (relativeTo, layout));
  585. break;
  586. case Path::Iterator::quadraticTo:
  587. path.quadraticTo (p->pos[0].toXY (relativeTo, layout),
  588. p->pos[1].toXY (relativeTo, layout));
  589. break;
  590. case Path::Iterator::cubicTo:
  591. path.cubicTo (p->pos[0].toXY (relativeTo, layout),
  592. p->pos[1].toXY (relativeTo, layout),
  593. p->pos[2].toXY (relativeTo, layout));
  594. break;
  595. case Path::Iterator::closePath:
  596. path.closeSubPath();
  597. break;
  598. default:
  599. jassertfalse; break;
  600. }
  601. }
  602. }
  603. }
  604. //==============================================================================
  605. class ChangeWindingAction : public PaintElementUndoableAction <PaintElementPath>
  606. {
  607. public:
  608. ChangeWindingAction (PaintElementPath* const path, const bool newValue_)
  609. : PaintElementUndoableAction <PaintElementPath> (path),
  610. newValue (newValue_),
  611. oldValue (path->isNonZeroWinding())
  612. {
  613. }
  614. bool perform()
  615. {
  616. showCorrectTab();
  617. getElement()->setNonZeroWinding (newValue, false);
  618. return true;
  619. }
  620. bool undo()
  621. {
  622. showCorrectTab();
  623. getElement()->setNonZeroWinding (oldValue, false);
  624. return true;
  625. }
  626. private:
  627. bool newValue, oldValue;
  628. };
  629. void PaintElementPath::setNonZeroWinding (const bool nonZero, const bool undoable)
  630. {
  631. if (nonZero != nonZeroWinding)
  632. {
  633. if (undoable)
  634. {
  635. perform (new ChangeWindingAction (this, nonZero), "Change path winding rule");
  636. }
  637. else
  638. {
  639. nonZeroWinding = nonZero;
  640. changed();
  641. }
  642. }
  643. }
  644. bool PaintElementPath::isSubpathClosed (int index) const
  645. {
  646. for (int i = index + 1; i < points.size(); ++i)
  647. {
  648. if (points.getUnchecked (i)->type == Path::Iterator::closePath)
  649. return true;
  650. if (points.getUnchecked (i)->type == Path::Iterator::startNewSubPath)
  651. break;
  652. }
  653. return false;
  654. }
  655. //==============================================================================
  656. void PaintElementPath::setSubpathClosed (int index, const bool closed, const bool undoable)
  657. {
  658. if (closed != isSubpathClosed (index))
  659. {
  660. for (int i = index + 1; i < points.size(); ++i)
  661. {
  662. PathPoint* p = points.getUnchecked (i);
  663. if (p->type == Path::Iterator::closePath)
  664. {
  665. jassert (! closed);
  666. deletePoint (i, undoable);
  667. return;
  668. }
  669. if (p->type == Path::Iterator::startNewSubPath)
  670. {
  671. jassert (closed);
  672. PathPoint* pp = addPoint (i - 1, undoable);
  673. PathPoint p2 (*pp);
  674. p2.type = Path::Iterator::closePath;
  675. perform (new ChangePointAction (pp, p2), "Close subpath");
  676. return;
  677. }
  678. }
  679. jassert (closed);
  680. PathPoint* p = addPoint (points.size() - 1, undoable);
  681. PathPoint p2 (*p);
  682. p2.type = Path::Iterator::closePath;
  683. perform (new ChangePointAction (p, p2), "Close subpath");
  684. }
  685. }
  686. //==============================================================================
  687. class AddPointAction : public PaintElementUndoableAction <PaintElementPath>
  688. {
  689. public:
  690. AddPointAction (PaintElementPath* path, int pointIndexToAddItAfter_)
  691. : PaintElementUndoableAction <PaintElementPath> (path),
  692. indexAdded (-1),
  693. pointIndexToAddItAfter (pointIndexToAddItAfter_)
  694. {
  695. }
  696. bool perform()
  697. {
  698. showCorrectTab();
  699. PaintElementPath* const path = getElement();
  700. jassert (path != nullptr);
  701. PathPoint* const p = path->addPoint (pointIndexToAddItAfter, false);
  702. jassert (p != nullptr);
  703. indexAdded = path->indexOfPoint (p);
  704. jassert (indexAdded >= 0);
  705. return true;
  706. }
  707. bool undo()
  708. {
  709. showCorrectTab();
  710. PaintElementPath* const path = getElement();
  711. jassert (path != nullptr);
  712. path->deletePoint (indexAdded, false);
  713. return true;
  714. }
  715. int indexAdded;
  716. private:
  717. int pointIndexToAddItAfter;
  718. };
  719. PathPoint* PaintElementPath::addPoint (int pointIndexToAddItAfter, const bool undoable)
  720. {
  721. if (undoable)
  722. {
  723. AddPointAction* action = new AddPointAction (this, pointIndexToAddItAfter);
  724. perform (action, "Add path point");
  725. return points [action->indexAdded];
  726. }
  727. double x1 = 20.0, y1 = 20.0, x2, y2;
  728. ComponentLayout* layout = getDocument()->getComponentLayout();
  729. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  730. if (points [pointIndexToAddItAfter] != nullptr)
  731. points [pointIndexToAddItAfter]->pos [points [pointIndexToAddItAfter]->getNumPoints() - 1].getXY (x1, y1, area, layout);
  732. else if (points[0] != nullptr)
  733. points[0]->pos[0].getXY (x1, y1, area, layout);
  734. x2 = x1 + 50.0;
  735. y2 = y1 + 50.0;
  736. if (points [pointIndexToAddItAfter + 1] != nullptr)
  737. {
  738. if (points [pointIndexToAddItAfter + 1]->type == Path::Iterator::closePath
  739. || points [pointIndexToAddItAfter + 1]->type == Path::Iterator::startNewSubPath)
  740. {
  741. int i = pointIndexToAddItAfter;
  742. while (i > 0)
  743. if (points [--i]->type == Path::Iterator::startNewSubPath)
  744. break;
  745. if (i != pointIndexToAddItAfter)
  746. points [i]->pos[0].getXY (x2, y2, area, layout);
  747. }
  748. else
  749. {
  750. points [pointIndexToAddItAfter + 1]->pos[0].getXY (x2, y2, area, layout);
  751. }
  752. }
  753. else
  754. {
  755. int i = pointIndexToAddItAfter + 1;
  756. while (i > 0)
  757. if (points [--i]->type == Path::Iterator::startNewSubPath)
  758. break;
  759. points[i]->pos[0].getXY (x2, y2, area, layout);
  760. }
  761. PathPoint* const p = new PathPoint (this);
  762. p->type = Path::Iterator::lineTo;
  763. p->pos[0].rect.setX ((x1 + x2) * 0.5f);
  764. p->pos[0].rect.setY ((y1 + y2) * 0.5f);
  765. points.insert (pointIndexToAddItAfter + 1, p);
  766. pointListChanged();
  767. return p;
  768. }
  769. //==============================================================================
  770. class DeletePointAction : public PaintElementUndoableAction <PaintElementPath>
  771. {
  772. public:
  773. DeletePointAction (PaintElementPath* const path, const int indexToRemove_)
  774. : PaintElementUndoableAction <PaintElementPath> (path),
  775. indexToRemove (indexToRemove_),
  776. oldValue (*path->getPoint (indexToRemove))
  777. {
  778. }
  779. bool perform()
  780. {
  781. showCorrectTab();
  782. PaintElementPath* const path = getElement();
  783. jassert (path != nullptr);
  784. path->deletePoint (indexToRemove, false);
  785. return path != nullptr;
  786. }
  787. bool undo()
  788. {
  789. showCorrectTab();
  790. PaintElementPath* const path = getElement();
  791. jassert (path != nullptr);
  792. PathPoint* p = path->addPoint (indexToRemove - 1, false);
  793. *p = oldValue;
  794. return path != nullptr;
  795. }
  796. int indexToRemove;
  797. private:
  798. PathPoint oldValue;
  799. };
  800. void PaintElementPath::deletePoint (int pointIndex, const bool undoable)
  801. {
  802. if (undoable)
  803. {
  804. perform (new DeletePointAction (this, pointIndex), "Delete path point");
  805. }
  806. else
  807. {
  808. PathPoint* const p = points [pointIndex];
  809. if (p != nullptr && pointIndex > 0)
  810. {
  811. owner->getSelectedPoints().deselect (p);
  812. owner->getSelectedPoints().changed (true);
  813. points.remove (pointIndex);
  814. pointListChanged();
  815. }
  816. }
  817. }
  818. //==============================================================================
  819. bool PaintElementPath::getPoint (int index, int pointNumber, double& x, double& y, const Rectangle<int>& parentArea) const
  820. {
  821. const PathPoint* const p = points [index];
  822. if (p == nullptr)
  823. {
  824. x = y = 0;
  825. return false;
  826. }
  827. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  828. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  829. p->pos [pointNumber].getXY (x, y, parentArea, getDocument()->getComponentLayout());
  830. return true;
  831. }
  832. int PaintElementPath::findSegmentAtXY (int x, int y) const
  833. {
  834. double x1, y1, x2, y2, x3, y3, lastX = 0.0, lastY = 0.0, subPathStartX = 0.0, subPathStartY = 0.0;
  835. ComponentLayout* const layout = getDocument()->getComponentLayout();
  836. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  837. int subpathStartIndex = 0;
  838. float thickness = 10.0f;
  839. if (isStrokePresent)
  840. thickness = jmax (thickness, strokeType.stroke.getStrokeThickness());
  841. for (int i = 0; i < points.size(); ++i)
  842. {
  843. Path segmentPath;
  844. PathPoint* const p = points.getUnchecked (i);
  845. switch (p->type)
  846. {
  847. case Path::Iterator::startNewSubPath:
  848. p->pos[0].getXY (lastX, lastY, area, layout);
  849. subPathStartX = lastX;
  850. subPathStartY = lastY;
  851. subpathStartIndex = i;
  852. break;
  853. case Path::Iterator::lineTo:
  854. p->pos[0].getXY (x1, y1, area, layout);
  855. segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) x1, (float) y1), thickness);
  856. if (segmentPath.contains ((float) x, (float) y))
  857. return i;
  858. lastX = x1;
  859. lastY = y1;
  860. break;
  861. case Path::Iterator::quadraticTo:
  862. p->pos[0].getXY (x1, y1, area, layout);
  863. p->pos[1].getXY (x2, y2, area, layout);
  864. segmentPath.startNewSubPath ((float) lastX, (float) lastY);
  865. segmentPath.quadraticTo ((float) x1, (float) y1, (float) x2, (float) y2);
  866. PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
  867. if (segmentPath.contains ((float) x, (float) y))
  868. return i;
  869. lastX = x2;
  870. lastY = y2;
  871. break;
  872. case Path::Iterator::cubicTo:
  873. p->pos[0].getXY (x1, y1, area, layout);
  874. p->pos[1].getXY (x2, y2, area, layout);
  875. p->pos[2].getXY (x3, y3, area, layout);
  876. segmentPath.startNewSubPath ((float) lastX, (float) lastY);
  877. segmentPath.cubicTo ((float) x1, (float) y1, (float) x2, (float) y2, (float) x3, (float) y3);
  878. PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
  879. if (segmentPath.contains ((float) x, (float) y))
  880. return i;
  881. lastX = x3;
  882. lastY = y3;
  883. break;
  884. case Path::Iterator::closePath:
  885. segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) subPathStartX, (float) subPathStartY), thickness);
  886. if (segmentPath.contains ((float) x, (float) y))
  887. return subpathStartIndex;
  888. lastX = subPathStartX;
  889. lastY = subPathStartY;
  890. break;
  891. default:
  892. jassertfalse; break;
  893. }
  894. }
  895. return -1;
  896. }
  897. //==============================================================================
  898. void PaintElementPath::movePoint (int index, int pointNumber,
  899. double newX, double newY,
  900. const Rectangle<int>& parentArea,
  901. const bool undoable)
  902. {
  903. if (PathPoint* const p = points [index])
  904. {
  905. PathPoint newPoint (*p);
  906. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  907. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  908. RelativePositionedRectangle& pr = newPoint.pos [pointNumber];
  909. double x, y, w, h;
  910. pr.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
  911. pr.updateFrom (newX, newY, w, h, parentArea, getDocument()->getComponentLayout());
  912. if (undoable)
  913. {
  914. perform (new ChangePointAction (p, index, newPoint), "Move path point");
  915. }
  916. else
  917. {
  918. *p = newPoint;
  919. changed();
  920. }
  921. }
  922. }
  923. RelativePositionedRectangle PaintElementPath::getPoint (int index, int pointNumber) const
  924. {
  925. if (PathPoint* const p = points [index])
  926. {
  927. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  928. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  929. return p->pos [pointNumber];
  930. }
  931. jassertfalse;
  932. return RelativePositionedRectangle();
  933. }
  934. void PaintElementPath::setPoint (int index, int pointNumber, const RelativePositionedRectangle& newPos, const bool undoable)
  935. {
  936. if (PathPoint* const p = points [index])
  937. {
  938. PathPoint newPoint (*p);
  939. jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
  940. jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
  941. if (newPoint.pos [pointNumber] != newPos)
  942. {
  943. newPoint.pos [pointNumber] = newPos;
  944. if (undoable)
  945. {
  946. perform (new ChangePointAction (p, index, newPoint), "Change path point position");
  947. }
  948. else
  949. {
  950. *p = newPoint;
  951. changed();
  952. }
  953. }
  954. }
  955. else
  956. {
  957. jassertfalse;
  958. }
  959. }
  960. //==============================================================================
  961. class PathPointTypeProperty : public ChoicePropertyComponent,
  962. public ChangeListener
  963. {
  964. public:
  965. PathPointTypeProperty (PaintElementPath* const owner_,
  966. const int index_)
  967. : ChoicePropertyComponent ("point type"),
  968. owner (owner_),
  969. index (index_)
  970. {
  971. choices.add ("Start of sub-path");
  972. choices.add ("Line");
  973. choices.add ("Quadratic");
  974. choices.add ("Cubic");
  975. owner->getDocument()->addChangeListener (this);
  976. }
  977. ~PathPointTypeProperty()
  978. {
  979. owner->getDocument()->removeChangeListener (this);
  980. }
  981. void setIndex (int newIndex)
  982. {
  983. Path::Iterator::PathElementType type = Path::Iterator::startNewSubPath;
  984. switch (newIndex)
  985. {
  986. case 0: type = Path::Iterator::startNewSubPath; break;
  987. case 1: type = Path::Iterator::lineTo; break;
  988. case 2: type = Path::Iterator::quadraticTo; break;
  989. case 3: type = Path::Iterator::cubicTo; break;
  990. default: jassertfalse; break;
  991. }
  992. const Rectangle<int> area (((PaintRoutineEditor*) owner->getParentComponent())->getComponentArea());
  993. owner->getPoint (index)->changePointType (type, area, true);
  994. }
  995. int getIndex() const
  996. {
  997. const PathPoint* const p = owner->getPoint (index);
  998. jassert (p != nullptr);
  999. switch (p->type)
  1000. {
  1001. case Path::Iterator::startNewSubPath: return 0;
  1002. case Path::Iterator::lineTo: return 1;
  1003. case Path::Iterator::quadraticTo: return 2;
  1004. case Path::Iterator::cubicTo: return 3;
  1005. case Path::Iterator::closePath: break;
  1006. default: jassertfalse; break;
  1007. }
  1008. return 0;
  1009. }
  1010. void changeListenerCallback (ChangeBroadcaster*)
  1011. {
  1012. refresh();
  1013. }
  1014. private:
  1015. PaintElementPath* const owner;
  1016. const int index;
  1017. };
  1018. //==============================================================================
  1019. class PathPointPositionProperty : public PositionPropertyBase
  1020. {
  1021. public:
  1022. PathPointPositionProperty (PaintElementPath* const owner_,
  1023. const int index_, const int pointNumber_,
  1024. const String& name,
  1025. ComponentPositionDimension dimension_)
  1026. : PositionPropertyBase (owner_, name, dimension_, false, false,
  1027. owner_->getDocument()->getComponentLayout()),
  1028. owner (owner_),
  1029. index (index_),
  1030. pointNumber (pointNumber_)
  1031. {
  1032. owner->getDocument()->addChangeListener (this);
  1033. }
  1034. ~PathPointPositionProperty()
  1035. {
  1036. owner->getDocument()->removeChangeListener (this);
  1037. }
  1038. void setPosition (const RelativePositionedRectangle& newPos)
  1039. {
  1040. owner->setPoint (index, pointNumber, newPos, true);
  1041. }
  1042. RelativePositionedRectangle getPosition() const
  1043. {
  1044. return owner->getPoint (index, pointNumber);
  1045. }
  1046. private:
  1047. PaintElementPath* const owner;
  1048. const int index, pointNumber;
  1049. };
  1050. //==============================================================================
  1051. class PathPointClosedProperty : public ChoicePropertyComponent,
  1052. private ChangeListener
  1053. {
  1054. public:
  1055. PathPointClosedProperty (PaintElementPath* const owner_, const int index_)
  1056. : ChoicePropertyComponent ("openness"),
  1057. owner (owner_),
  1058. index (index_)
  1059. {
  1060. owner->getDocument()->addChangeListener (this);
  1061. choices.add ("Subpath is closed");
  1062. choices.add ("Subpath is open-ended");
  1063. }
  1064. ~PathPointClosedProperty()
  1065. {
  1066. owner->getDocument()->removeChangeListener (this);
  1067. }
  1068. void changeListenerCallback (ChangeBroadcaster*)
  1069. {
  1070. refresh();
  1071. }
  1072. void setIndex (int newIndex)
  1073. {
  1074. owner->setSubpathClosed (index, newIndex == 0, true);
  1075. }
  1076. int getIndex() const
  1077. {
  1078. return owner->isSubpathClosed (index) ? 0 : 1;
  1079. }
  1080. private:
  1081. PaintElementPath* const owner;
  1082. const int index;
  1083. };
  1084. //==============================================================================
  1085. class AddNewPointProperty : public ButtonPropertyComponent
  1086. {
  1087. public:
  1088. AddNewPointProperty (PaintElementPath* const owner_, const int index_)
  1089. : ButtonPropertyComponent ("new point", false),
  1090. owner (owner_),
  1091. index (index_)
  1092. {
  1093. }
  1094. void buttonClicked()
  1095. {
  1096. owner->addPoint (index, true);
  1097. }
  1098. String getButtonText() const { return "Add new point"; }
  1099. private:
  1100. PaintElementPath* const owner;
  1101. const int index;
  1102. };
  1103. //==============================================================================
  1104. PathPoint::PathPoint (PaintElementPath* const owner_)
  1105. : owner (owner_)
  1106. {
  1107. }
  1108. PathPoint::PathPoint (const PathPoint& other)
  1109. : owner (other.owner),
  1110. type (other.type)
  1111. {
  1112. pos [0] = other.pos [0];
  1113. pos [1] = other.pos [1];
  1114. pos [2] = other.pos [2];
  1115. }
  1116. PathPoint& PathPoint::operator= (const PathPoint& other)
  1117. {
  1118. owner = other.owner;
  1119. type = other.type;
  1120. pos [0] = other.pos [0];
  1121. pos [1] = other.pos [1];
  1122. pos [2] = other.pos [2];
  1123. return *this;
  1124. }
  1125. PathPoint::~PathPoint()
  1126. {
  1127. }
  1128. int PathPoint::getNumPoints() const
  1129. {
  1130. if (type == Path::Iterator::cubicTo) return 3;
  1131. if (type == Path::Iterator::quadraticTo) return 2;
  1132. if (type == Path::Iterator::closePath) return 0;
  1133. return 1;
  1134. }
  1135. PathPoint PathPoint::withChangedPointType (const Path::Iterator::PathElementType newType,
  1136. const Rectangle<int>& parentArea) const
  1137. {
  1138. PathPoint p (*this);
  1139. if (newType != p.type)
  1140. {
  1141. int oldNumPoints = getNumPoints();
  1142. p.type = newType;
  1143. int numPoints = p.getNumPoints();
  1144. if (numPoints != oldNumPoints)
  1145. {
  1146. double lastX, lastY;
  1147. double x, y, w, h;
  1148. p.pos [numPoints - 1] = p.pos [oldNumPoints - 1];
  1149. p.pos [numPoints - 1].getRectangleDouble (x, y, w, h, parentArea, owner->getDocument()->getComponentLayout());
  1150. const int index = owner->points.indexOf (this);
  1151. if (PathPoint* lastPoint = owner->points [index - 1])
  1152. {
  1153. lastPoint->pos [lastPoint->getNumPoints() - 1]
  1154. .getRectangleDouble (lastX, lastY, w, h, parentArea, owner->getDocument()->getComponentLayout());
  1155. }
  1156. else
  1157. {
  1158. jassertfalse;
  1159. lastX = x;
  1160. lastY = y;
  1161. }
  1162. for (int i = 0; i < numPoints - 1; ++i)
  1163. {
  1164. p.pos[i] = p.pos [numPoints - 1];
  1165. p.pos[i].updateFrom (lastX + (x - lastX) * (i + 1) / numPoints,
  1166. lastY + (y - lastY) * (i + 1) / numPoints,
  1167. w, h,
  1168. parentArea,
  1169. owner->getDocument()->getComponentLayout());
  1170. }
  1171. }
  1172. }
  1173. return p;
  1174. }
  1175. void PathPoint::changePointType (const Path::Iterator::PathElementType newType,
  1176. const Rectangle<int>& parentArea, const bool undoable)
  1177. {
  1178. if (newType != type)
  1179. {
  1180. if (undoable)
  1181. {
  1182. owner->perform (new ChangePointAction (this, withChangedPointType (newType, parentArea)),
  1183. "Change path point type");
  1184. }
  1185. else
  1186. {
  1187. *this = withChangedPointType (newType, parentArea);
  1188. owner->pointListChanged();
  1189. }
  1190. }
  1191. }
  1192. void PathPoint::getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected)
  1193. {
  1194. if (multipleSelected)
  1195. return;
  1196. auto index = owner->points.indexOf (this);
  1197. jassert (index >= 0);
  1198. switch (type)
  1199. {
  1200. case Path::Iterator::startNewSubPath:
  1201. props.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
  1202. props.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
  1203. props.add (new PathPointClosedProperty (owner, index));
  1204. props.add (new AddNewPointProperty (owner, index));
  1205. break;
  1206. case Path::Iterator::lineTo:
  1207. props.add (new PathPointTypeProperty (owner, index));
  1208. props.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
  1209. props.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
  1210. props.add (new AddNewPointProperty (owner, index));
  1211. break;
  1212. case Path::Iterator::quadraticTo:
  1213. props.add (new PathPointTypeProperty (owner, index));
  1214. props.add (new PathPointPositionProperty (owner, index, 0, "control pt x", PositionPropertyBase::componentX));
  1215. props.add (new PathPointPositionProperty (owner, index, 0, "control pt y", PositionPropertyBase::componentY));
  1216. props.add (new PathPointPositionProperty (owner, index, 1, "x", PositionPropertyBase::componentX));
  1217. props.add (new PathPointPositionProperty (owner, index, 1, "y", PositionPropertyBase::componentY));
  1218. props.add (new AddNewPointProperty (owner, index));
  1219. break;
  1220. case Path::Iterator::cubicTo:
  1221. props.add (new PathPointTypeProperty (owner, index));
  1222. props.add (new PathPointPositionProperty (owner, index, 0, "control pt1 x", PositionPropertyBase::componentX));
  1223. props.add (new PathPointPositionProperty (owner, index, 0, "control pt1 y", PositionPropertyBase::componentY));
  1224. props.add (new PathPointPositionProperty (owner, index, 1, "control pt2 x", PositionPropertyBase::componentX));
  1225. props.add (new PathPointPositionProperty (owner, index, 1, "control pt2 y", PositionPropertyBase::componentY));
  1226. props.add (new PathPointPositionProperty (owner, index, 2, "x", PositionPropertyBase::componentX));
  1227. props.add (new PathPointPositionProperty (owner, index, 2, "y", PositionPropertyBase::componentY));
  1228. props.add (new AddNewPointProperty (owner, index));
  1229. break;
  1230. case Path::Iterator::closePath:
  1231. break;
  1232. default:
  1233. jassertfalse;
  1234. break;
  1235. }
  1236. }
  1237. void PathPoint::deleteFromPath()
  1238. {
  1239. owner->deletePoint (owner->points.indexOf (this), true);
  1240. }
  1241. //==============================================================================
  1242. PathPointComponent::PathPointComponent (PaintElementPath* const path_,
  1243. const int index_,
  1244. const int pointNumber_)
  1245. : ElementSiblingComponent (path_),
  1246. path (path_),
  1247. routine (path_->getOwner()),
  1248. index (index_),
  1249. pointNumber (pointNumber_),
  1250. selected (false)
  1251. {
  1252. setSize (11, 11);
  1253. setRepaintsOnMouseActivity (true);
  1254. selected = routine->getSelectedPoints().isSelected (path_->points [index]);
  1255. routine->getSelectedPoints().addChangeListener (this);
  1256. }
  1257. PathPointComponent::~PathPointComponent()
  1258. {
  1259. routine->getSelectedPoints().removeChangeListener (this);
  1260. }
  1261. void PathPointComponent::updatePosition()
  1262. {
  1263. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  1264. jassert (getParentComponent() != nullptr);
  1265. double x, y;
  1266. path->getPoint (index, pointNumber, x, y, area);
  1267. setCentrePosition (roundToInt (x),
  1268. roundToInt (y));
  1269. }
  1270. void PathPointComponent::showPopupMenu()
  1271. {
  1272. }
  1273. void PathPointComponent::paint (Graphics& g)
  1274. {
  1275. if (isMouseOverOrDragging())
  1276. g.fillAll (Colours::red);
  1277. if (selected)
  1278. {
  1279. g.setColour (Colours::red);
  1280. g.drawRect (getLocalBounds());
  1281. }
  1282. g.setColour (Colours::white);
  1283. g.fillRect (getWidth() / 2 - 3, getHeight() / 2 - 3, 7, 7);
  1284. g.setColour (Colours::black);
  1285. if (pointNumber < path->getPoint (index)->getNumPoints() - 1)
  1286. g.drawRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
  1287. else
  1288. g.fillRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
  1289. }
  1290. void PathPointComponent::mouseDown (const MouseEvent& e)
  1291. {
  1292. dragging = false;
  1293. if (e.mods.isPopupMenu())
  1294. {
  1295. showPopupMenu();
  1296. return; // this may be deleted now..
  1297. }
  1298. dragX = getX() + getWidth() / 2;
  1299. dragY = getY() + getHeight() / 2;
  1300. mouseDownSelectStatus = routine->getSelectedPoints().addToSelectionOnMouseDown (path->points [index], e.mods);
  1301. owner->getDocument()->beginTransaction();
  1302. }
  1303. void PathPointComponent::mouseDrag (const MouseEvent& e)
  1304. {
  1305. if (! e.mods.isPopupMenu())
  1306. {
  1307. if (selected && ! dragging)
  1308. dragging = e.mouseWasDraggedSinceMouseDown();
  1309. if (dragging)
  1310. {
  1311. const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
  1312. int x = dragX + e.getDistanceFromDragStartX() - area.getX();
  1313. int y = dragY + e.getDistanceFromDragStartY() - area.getY();
  1314. if (JucerDocument* const document = owner->getDocument())
  1315. {
  1316. x = document->snapPosition (x);
  1317. y = document->snapPosition (y);
  1318. }
  1319. owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  1320. path->movePoint (index, pointNumber, x + area.getX(), y + area.getY(), area, true);
  1321. }
  1322. }
  1323. }
  1324. void PathPointComponent::mouseUp (const MouseEvent& e)
  1325. {
  1326. routine->getSelectedPoints().addToSelectionOnMouseUp (path->points [index],
  1327. e.mods, dragging,
  1328. mouseDownSelectStatus);
  1329. }
  1330. void PathPointComponent::changeListenerCallback (ChangeBroadcaster* source)
  1331. {
  1332. ElementSiblingComponent::changeListenerCallback (source);
  1333. const bool nowSelected = routine->getSelectedPoints().isSelected (path->points [index]);
  1334. if (nowSelected != selected)
  1335. {
  1336. selected = nowSelected;
  1337. repaint();
  1338. if (Component* parent = getParentComponent())
  1339. parent->repaint();
  1340. }
  1341. }