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.

1662 lines
52KB

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