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.

1636 lines
51KB

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