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.

1670 lines
52KB

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