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.

1643 lines
51KB

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