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.

922 lines
30KB

  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_ColouredElement.h"
  15. #include "jucer_GradientPointComponent.h"
  16. #include "../Properties/jucer_PositionPropertyBase.h"
  17. #include "../Properties/jucer_ColourPropertyComponent.h"
  18. #include "jucer_PaintElementUndoableAction.h"
  19. #include "jucer_PaintElementPath.h"
  20. #include "jucer_ImageResourceProperty.h"
  21. //==============================================================================
  22. class ElementFillModeProperty : public ChoicePropertyComponent
  23. {
  24. public:
  25. ElementFillModeProperty (ColouredElement* const e, const bool isForStroke_)
  26. : ChoicePropertyComponent ("fill mode"), listener (e),
  27. isForStroke (isForStroke_)
  28. {
  29. listener.setPropertyToRefresh (*this);
  30. choices.add ("Solid Colour");
  31. choices.add ("Linear Gradient");
  32. choices.add ("Radial Gradient");
  33. choices.add ("Image Brush");
  34. }
  35. void setIndex (int newIndex)
  36. {
  37. JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
  38. : listener.owner->getFillType());
  39. switch (newIndex)
  40. {
  41. case 0: fill.mode = JucerFillType::solidColour; break;
  42. case 1: fill.mode = JucerFillType::linearGradient; break;
  43. case 2: fill.mode = JucerFillType::radialGradient; break;
  44. case 3: fill.mode = JucerFillType::imageBrush; break;
  45. default: jassertfalse; break;
  46. }
  47. if (! isForStroke)
  48. listener.owner->setFillType (fill, true);
  49. else
  50. listener.owner->setStrokeFill (fill, true);
  51. }
  52. int getIndex() const
  53. {
  54. switch (isForStroke ? listener.owner->getStrokeType().fill.mode
  55. : listener.owner->getFillType().mode)
  56. {
  57. case JucerFillType::solidColour: return 0;
  58. case JucerFillType::linearGradient: return 1;
  59. case JucerFillType::radialGradient: return 2;
  60. case JucerFillType::imageBrush: return 3;
  61. default: jassertfalse; break;
  62. }
  63. return 0;
  64. }
  65. private:
  66. ElementListener<ColouredElement> listener;
  67. const bool isForStroke;
  68. };
  69. //==============================================================================
  70. class ElementFillColourProperty : public JucerColourPropertyComponent
  71. {
  72. public:
  73. enum ColourType
  74. {
  75. solidColour,
  76. gradientColour1,
  77. gradientColour2
  78. };
  79. ElementFillColourProperty (const String& name,
  80. ColouredElement* const owner_,
  81. const ColourType type_,
  82. const bool isForStroke_)
  83. : JucerColourPropertyComponent (name, false),
  84. listener (owner_),
  85. type (type_),
  86. isForStroke (isForStroke_)
  87. {
  88. listener.setPropertyToRefresh (*this);
  89. }
  90. void setColour (Colour newColour) override
  91. {
  92. listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  93. JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
  94. : listener.owner->getFillType());
  95. switch (type)
  96. {
  97. case solidColour: fill.colour = newColour; break;
  98. case gradientColour1: fill.gradCol1 = newColour; break;
  99. case gradientColour2: fill.gradCol2 = newColour; break;
  100. default: jassertfalse; break;
  101. }
  102. if (! isForStroke)
  103. listener.owner->setFillType (fill, true);
  104. else
  105. listener.owner->setStrokeFill (fill, true);
  106. }
  107. Colour getColour() const override
  108. {
  109. const JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
  110. : listener.owner->getFillType());
  111. switch (type)
  112. {
  113. case solidColour: return fill.colour; break;
  114. case gradientColour1: return fill.gradCol1; break;
  115. case gradientColour2: return fill.gradCol2; break;
  116. default: jassertfalse; break;
  117. }
  118. return Colours::black;
  119. }
  120. void resetToDefault() override
  121. {
  122. jassertfalse; // option shouldn't be visible
  123. }
  124. private:
  125. ElementListener<ColouredElement> listener;
  126. const ColourType type;
  127. const bool isForStroke;
  128. };
  129. //==============================================================================
  130. class ElementFillPositionProperty : public PositionPropertyBase
  131. {
  132. public:
  133. ElementFillPositionProperty (ColouredElement* const owner_,
  134. const String& name,
  135. ComponentPositionDimension dimension_,
  136. const bool isStart_,
  137. const bool isForStroke_)
  138. : PositionPropertyBase (owner_, name, dimension_, false, false,
  139. owner_->getDocument()->getComponentLayout()),
  140. listener (owner_),
  141. isStart (isStart_),
  142. isForStroke (isForStroke_)
  143. {
  144. listener.setPropertyToRefresh (*this);
  145. }
  146. void setPosition (const RelativePositionedRectangle& newPos)
  147. {
  148. JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
  149. : listener.owner->getFillType());
  150. if (isStart)
  151. fill.gradPos1 = newPos;
  152. else
  153. fill.gradPos2 = newPos;
  154. if (! isForStroke)
  155. listener.owner->setFillType (fill, true);
  156. else
  157. listener.owner->setStrokeFill (fill, true);
  158. }
  159. RelativePositionedRectangle getPosition() const
  160. {
  161. const JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
  162. : listener.owner->getFillType());
  163. return isStart ? fill.gradPos1
  164. : fill.gradPos2;
  165. }
  166. private:
  167. ElementListener<ColouredElement> listener;
  168. const bool isStart, isForStroke;
  169. };
  170. //==============================================================================
  171. class EnableStrokeProperty : public BooleanPropertyComponent
  172. {
  173. public:
  174. explicit EnableStrokeProperty (ColouredElement* const owner_)
  175. : BooleanPropertyComponent ("outline", "Outline enabled", "No outline"),
  176. listener (owner_)
  177. {
  178. listener.setPropertyToRefresh (*this);
  179. }
  180. //==============================================================================
  181. void setState (bool newState) { listener.owner->enableStroke (newState, true); }
  182. bool getState() const { return listener.owner->isStrokeEnabled(); }
  183. ElementListener<ColouredElement> listener;
  184. };
  185. //==============================================================================
  186. class StrokeThicknessProperty : public SliderPropertyComponent
  187. {
  188. public:
  189. explicit StrokeThicknessProperty (ColouredElement* const owner_)
  190. : SliderPropertyComponent ("outline thickness", 0.1, 200.0, 0.1, 0.3),
  191. listener (owner_)
  192. {
  193. listener.setPropertyToRefresh (*this);
  194. }
  195. void setValue (double newValue)
  196. {
  197. listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  198. listener.owner->setStrokeType (PathStrokeType ((float) newValue,
  199. listener.owner->getStrokeType().stroke.getJointStyle(),
  200. listener.owner->getStrokeType().stroke.getEndStyle()),
  201. true);
  202. }
  203. double getValue() const { return listener.owner->getStrokeType().stroke.getStrokeThickness(); }
  204. ElementListener<ColouredElement> listener;
  205. };
  206. //==============================================================================
  207. class StrokeJointProperty : public ChoicePropertyComponent
  208. {
  209. public:
  210. explicit StrokeJointProperty (ColouredElement* const owner_)
  211. : ChoicePropertyComponent ("joint style"),
  212. listener (owner_)
  213. {
  214. listener.setPropertyToRefresh (*this);
  215. choices.add ("mitered");
  216. choices.add ("curved");
  217. choices.add ("beveled");
  218. }
  219. void setIndex (int newIndex)
  220. {
  221. const PathStrokeType::JointStyle joints[] = { PathStrokeType::mitered,
  222. PathStrokeType::curved,
  223. PathStrokeType::beveled };
  224. jassert (newIndex >= 0 && newIndex < 3);
  225. listener.owner->setStrokeType (PathStrokeType (listener.owner->getStrokeType().stroke.getStrokeThickness(),
  226. joints [newIndex],
  227. listener.owner->getStrokeType().stroke.getEndStyle()),
  228. true);
  229. }
  230. int getIndex() const
  231. {
  232. switch (listener.owner->getStrokeType().stroke.getJointStyle())
  233. {
  234. case PathStrokeType::mitered: return 0;
  235. case PathStrokeType::curved: return 1;
  236. case PathStrokeType::beveled: return 2;
  237. default: jassertfalse; break;
  238. }
  239. return 0;
  240. }
  241. ElementListener<ColouredElement> listener;
  242. };
  243. //==============================================================================
  244. class StrokeEndCapProperty : public ChoicePropertyComponent
  245. {
  246. public:
  247. explicit StrokeEndCapProperty (ColouredElement* const owner_)
  248. : ChoicePropertyComponent ("end-cap style"),
  249. listener (owner_)
  250. {
  251. listener.setPropertyToRefresh (*this);
  252. choices.add ("butt");
  253. choices.add ("square");
  254. choices.add ("round");
  255. }
  256. void setIndex (int newIndex)
  257. {
  258. const PathStrokeType::EndCapStyle ends[] = { PathStrokeType::butt,
  259. PathStrokeType::square,
  260. PathStrokeType::rounded };
  261. jassert (newIndex >= 0 && newIndex < 3);
  262. listener.owner->setStrokeType (PathStrokeType (listener.owner->getStrokeType().stroke.getStrokeThickness(),
  263. listener.owner->getStrokeType().stroke.getJointStyle(),
  264. ends [newIndex]),
  265. true);
  266. }
  267. int getIndex() const
  268. {
  269. switch (listener.owner->getStrokeType().stroke.getEndStyle())
  270. {
  271. case PathStrokeType::butt: return 0;
  272. case PathStrokeType::square: return 1;
  273. case PathStrokeType::rounded: return 2;
  274. default: jassertfalse; break;
  275. }
  276. return 0;
  277. }
  278. ElementListener<ColouredElement> listener;
  279. };
  280. //==============================================================================
  281. class ImageBrushResourceProperty : public ImageResourceProperty <ColouredElement>
  282. {
  283. public:
  284. ImageBrushResourceProperty (ColouredElement* const e, const bool isForStroke_)
  285. : ImageResourceProperty <ColouredElement> (e, isForStroke_ ? "stroke image"
  286. : "fill image"),
  287. isForStroke (isForStroke_)
  288. {
  289. }
  290. //==============================================================================
  291. void setResource (const String& newName)
  292. {
  293. if (element != nullptr)
  294. {
  295. if (isForStroke)
  296. {
  297. JucerFillType type (element->getStrokeType().fill);
  298. type.imageResourceName = newName;
  299. element->setStrokeFill (type, true);
  300. }
  301. else
  302. {
  303. JucerFillType type (element->getFillType());
  304. type.imageResourceName = newName;
  305. element->setFillType (type, true);
  306. }
  307. }
  308. }
  309. String getResource() const
  310. {
  311. if (element == nullptr)
  312. return {};
  313. if (isForStroke)
  314. return element->getStrokeType().fill.imageResourceName;
  315. return element->getFillType().imageResourceName;
  316. }
  317. private:
  318. bool isForStroke;
  319. };
  320. //==============================================================================
  321. class ImageBrushPositionProperty : public PositionPropertyBase
  322. {
  323. public:
  324. ImageBrushPositionProperty (ColouredElement* const owner_,
  325. const String& name,
  326. ComponentPositionDimension dimension_,
  327. const bool isForStroke_)
  328. : PositionPropertyBase (owner_, name, dimension_, false, false,
  329. owner_->getDocument()->getComponentLayout()),
  330. listener (owner_),
  331. isForStroke (isForStroke_)
  332. {
  333. listener.setPropertyToRefresh (*this);
  334. }
  335. void setPosition (const RelativePositionedRectangle& newPos)
  336. {
  337. if (isForStroke)
  338. {
  339. JucerFillType type (listener.owner->getStrokeType().fill);
  340. type.imageAnchor = newPos;
  341. listener.owner->setStrokeFill (type, true);
  342. }
  343. else
  344. {
  345. JucerFillType type (listener.owner->getFillType());
  346. type.imageAnchor = newPos;
  347. listener.owner->setFillType (type, true);
  348. }
  349. }
  350. RelativePositionedRectangle getPosition() const
  351. {
  352. if (isForStroke)
  353. return listener.owner->getStrokeType().fill.imageAnchor;
  354. return listener.owner->getFillType().imageAnchor;
  355. }
  356. private:
  357. ElementListener<ColouredElement> listener;
  358. const bool isForStroke;
  359. };
  360. //==============================================================================
  361. class ImageBrushOpacityProperty : public SliderPropertyComponent
  362. {
  363. public:
  364. ImageBrushOpacityProperty (ColouredElement* const e, const bool isForStroke_)
  365. : SliderPropertyComponent ("opacity", 0.0, 1.0, 0.001),
  366. listener (e),
  367. isForStroke (isForStroke_)
  368. {
  369. listener.setPropertyToRefresh (*this);
  370. }
  371. void setValue (double newValue)
  372. {
  373. if (listener.owner != nullptr)
  374. {
  375. listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  376. if (isForStroke)
  377. {
  378. JucerFillType type (listener.owner->getStrokeType().fill);
  379. type.imageOpacity = newValue;
  380. listener.owner->setStrokeFill (type, true);
  381. }
  382. else
  383. {
  384. JucerFillType type (listener.owner->getFillType());
  385. type.imageOpacity = newValue;
  386. listener.owner->setFillType (type, true);
  387. }
  388. }
  389. }
  390. double getValue() const
  391. {
  392. if (listener.owner == nullptr)
  393. return 0;
  394. if (isForStroke)
  395. return listener.owner->getStrokeType().fill.imageOpacity;
  396. return listener.owner->getFillType().imageOpacity;
  397. }
  398. private:
  399. ElementListener<ColouredElement> listener;
  400. bool isForStroke;
  401. };
  402. //==============================================================================
  403. ColouredElement::ColouredElement (PaintRoutine* owner_,
  404. const String& name,
  405. const bool showOutline_,
  406. const bool showJointAndEnd_)
  407. : PaintElement (owner_, name),
  408. isStrokePresent (false),
  409. showOutline (showOutline_),
  410. showJointAndEnd (showJointAndEnd_)
  411. {
  412. }
  413. ColouredElement::~ColouredElement()
  414. {
  415. }
  416. //==============================================================================
  417. void ColouredElement::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
  418. {
  419. PaintElement::getEditableProperties (props, multipleSelected);
  420. if (! multipleSelected)
  421. getColourSpecificProperties (props);
  422. }
  423. void ColouredElement::getColourSpecificProperties (Array <PropertyComponent*>& props)
  424. {
  425. props.add (new ElementFillModeProperty (this, false));
  426. switch (getFillType().mode)
  427. {
  428. case JucerFillType::solidColour:
  429. props.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, false));
  430. break;
  431. case JucerFillType::linearGradient:
  432. case JucerFillType::radialGradient:
  433. props.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, false));
  434. props.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, false));
  435. props.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, false));
  436. props.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, false));
  437. props.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, false));
  438. props.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, false));
  439. break;
  440. case JucerFillType::imageBrush:
  441. props.add (new ImageBrushResourceProperty (this, false));
  442. props.add (new ImageBrushPositionProperty (this, "anchor x", PositionPropertyBase::componentX, false));
  443. props.add (new ImageBrushPositionProperty (this, "anchor y", PositionPropertyBase::componentY, false));
  444. props.add (new ImageBrushOpacityProperty (this, false));
  445. break;
  446. default:
  447. jassertfalse;
  448. break;
  449. }
  450. if (showOutline)
  451. {
  452. props.add (new EnableStrokeProperty (this));
  453. if (isStrokePresent)
  454. {
  455. props.add (new StrokeThicknessProperty (this));
  456. if (showJointAndEnd)
  457. {
  458. props.add (new StrokeJointProperty (this));
  459. props.add (new StrokeEndCapProperty (this));
  460. }
  461. props.add (new ElementFillModeProperty (this, true));
  462. switch (getStrokeType().fill.mode)
  463. {
  464. case JucerFillType::solidColour:
  465. props.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, true));
  466. break;
  467. case JucerFillType::linearGradient:
  468. case JucerFillType::radialGradient:
  469. props.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, true));
  470. props.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, true));
  471. props.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, true));
  472. props.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, true));
  473. props.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, true));
  474. props.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, true));
  475. break;
  476. case JucerFillType::imageBrush:
  477. props.add (new ImageBrushResourceProperty (this, true));
  478. props.add (new ImageBrushPositionProperty (this, "stroke anchor x", PositionPropertyBase::componentX, true));
  479. props.add (new ImageBrushPositionProperty (this, "stroke anchor y", PositionPropertyBase::componentY, true));
  480. props.add (new ImageBrushOpacityProperty (this, true));
  481. break;
  482. default:
  483. jassertfalse;
  484. break;
  485. }
  486. }
  487. }
  488. }
  489. //==============================================================================
  490. const JucerFillType& ColouredElement::getFillType() noexcept
  491. {
  492. return fillType;
  493. }
  494. class FillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  495. {
  496. public:
  497. FillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
  498. : PaintElementUndoableAction <ColouredElement> (element),
  499. newState (newState_)
  500. {
  501. oldState = element->getFillType();
  502. }
  503. bool perform()
  504. {
  505. showCorrectTab();
  506. getElement()->setFillType (newState, false);
  507. return true;
  508. }
  509. bool undo()
  510. {
  511. showCorrectTab();
  512. getElement()->setFillType (oldState, false);
  513. return true;
  514. }
  515. private:
  516. JucerFillType newState, oldState;
  517. };
  518. void ColouredElement::setFillType (const JucerFillType& newType, const bool undoable)
  519. {
  520. if (fillType != newType)
  521. {
  522. if (undoable)
  523. {
  524. perform (new FillTypeChangeAction (this, newType),
  525. "Change fill type");
  526. }
  527. else
  528. {
  529. repaint();
  530. if (fillType.mode != newType.mode)
  531. {
  532. owner->getSelectedElements().changed();
  533. siblingComponentsChanged();
  534. }
  535. fillType = newType;
  536. changed();
  537. }
  538. }
  539. }
  540. //==============================================================================
  541. bool ColouredElement::isStrokeEnabled() const noexcept
  542. {
  543. return isStrokePresent && showOutline;
  544. }
  545. class StrokeEnableChangeAction : public PaintElementUndoableAction <ColouredElement>
  546. {
  547. public:
  548. StrokeEnableChangeAction (ColouredElement* const element, const bool newState_)
  549. : PaintElementUndoableAction <ColouredElement> (element),
  550. newState (newState_)
  551. {
  552. oldState = element->isStrokeEnabled();
  553. }
  554. bool perform()
  555. {
  556. showCorrectTab();
  557. getElement()->enableStroke (newState, false);
  558. return true;
  559. }
  560. bool undo()
  561. {
  562. showCorrectTab();
  563. getElement()->enableStroke (oldState, false);
  564. return true;
  565. }
  566. private:
  567. bool newState, oldState;
  568. };
  569. void ColouredElement::enableStroke (bool enable, const bool undoable)
  570. {
  571. enable = enable && showOutline;
  572. if (isStrokePresent != enable)
  573. {
  574. if (undoable)
  575. {
  576. perform (new StrokeEnableChangeAction (this, enable),
  577. "Change stroke mode");
  578. }
  579. else
  580. {
  581. repaint();
  582. isStrokePresent = enable;
  583. siblingComponentsChanged();
  584. owner->changed();
  585. owner->getSelectedElements().changed();
  586. }
  587. }
  588. }
  589. //==============================================================================
  590. const StrokeType& ColouredElement::getStrokeType() noexcept
  591. {
  592. return strokeType;
  593. }
  594. class StrokeTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  595. {
  596. public:
  597. StrokeTypeChangeAction (ColouredElement* const element, const PathStrokeType& newState_)
  598. : PaintElementUndoableAction <ColouredElement> (element),
  599. newState (newState_),
  600. oldState (element->getStrokeType().stroke)
  601. {
  602. }
  603. bool perform()
  604. {
  605. showCorrectTab();
  606. getElement()->setStrokeType (newState, false);
  607. return true;
  608. }
  609. bool undo()
  610. {
  611. showCorrectTab();
  612. getElement()->setStrokeType (oldState, false);
  613. return true;
  614. }
  615. private:
  616. PathStrokeType newState, oldState;
  617. };
  618. void ColouredElement::setStrokeType (const PathStrokeType& newType, const bool undoable)
  619. {
  620. if (strokeType.stroke != newType)
  621. {
  622. if (undoable)
  623. {
  624. perform (new StrokeTypeChangeAction (this, newType),
  625. "Change stroke type");
  626. }
  627. else
  628. {
  629. repaint();
  630. strokeType.stroke = newType;
  631. changed();
  632. }
  633. }
  634. }
  635. class StrokeFillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  636. {
  637. public:
  638. StrokeFillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
  639. : PaintElementUndoableAction <ColouredElement> (element),
  640. newState (newState_)
  641. {
  642. oldState = element->getStrokeType().fill;
  643. }
  644. bool perform()
  645. {
  646. showCorrectTab();
  647. getElement()->setStrokeFill (newState, false);
  648. return true;
  649. }
  650. bool undo()
  651. {
  652. showCorrectTab();
  653. getElement()->setStrokeFill (oldState, false);
  654. return true;
  655. }
  656. private:
  657. JucerFillType newState, oldState;
  658. };
  659. void ColouredElement::setStrokeFill (const JucerFillType& newType, const bool undoable)
  660. {
  661. if (strokeType.fill != newType)
  662. {
  663. if (undoable)
  664. {
  665. perform (new StrokeFillTypeChangeAction (this, newType),
  666. "Change stroke fill type");
  667. }
  668. else
  669. {
  670. repaint();
  671. if (strokeType.fill.mode != newType.mode)
  672. {
  673. siblingComponentsChanged();
  674. owner->getSelectedElements().changed();
  675. }
  676. strokeType.fill = newType;
  677. changed();
  678. }
  679. }
  680. }
  681. //==============================================================================
  682. void ColouredElement::createSiblingComponents()
  683. {
  684. {
  685. GradientPointComponent* g1 = new GradientPointComponent (this, false, true);
  686. siblingComponents.add (g1);
  687. GradientPointComponent* g2 = new GradientPointComponent (this, false, false);
  688. siblingComponents.add (g2);
  689. getParentComponent()->addAndMakeVisible (g1);
  690. getParentComponent()->addAndMakeVisible (g2);
  691. g1->updatePosition();
  692. g2->updatePosition();
  693. }
  694. if (isStrokePresent && showOutline)
  695. {
  696. GradientPointComponent* g1 = new GradientPointComponent (this, true, true);
  697. siblingComponents.add (g1);
  698. GradientPointComponent* g2 = new GradientPointComponent (this, true, false);
  699. siblingComponents.add (g2);
  700. getParentComponent()->addAndMakeVisible (g1);
  701. getParentComponent()->addAndMakeVisible (g2);
  702. g1->updatePosition();
  703. g2->updatePosition();
  704. }
  705. }
  706. Rectangle<int> ColouredElement::getCurrentBounds (const Rectangle<int>& parentArea) const
  707. {
  708. int borderSize = 0;
  709. if (isStrokePresent)
  710. borderSize = (int) strokeType.stroke.getStrokeThickness() / 2 + 1;
  711. return position.getRectangle (parentArea, getDocument()->getComponentLayout())
  712. .expanded (borderSize);
  713. }
  714. void ColouredElement::setCurrentBounds (const Rectangle<int>& newBounds,
  715. const Rectangle<int>& parentArea,
  716. const bool undoable)
  717. {
  718. Rectangle<int> r (newBounds);
  719. if (isStrokePresent)
  720. {
  721. r = r.expanded (-((int) strokeType.stroke.getStrokeThickness() / 2 + 1));
  722. r.setSize (jmax (1, r.getWidth()), jmax (1, r.getHeight()));
  723. }
  724. RelativePositionedRectangle pr (position);
  725. pr.updateFrom (r.getX() - parentArea.getX(),
  726. r.getY() - parentArea.getY(),
  727. r.getWidth(), r.getHeight(),
  728. Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
  729. getDocument()->getComponentLayout());
  730. setPosition (pr, undoable);
  731. updateBounds (parentArea);
  732. }
  733. //==============================================================================
  734. void ColouredElement::addColourAttributes (XmlElement* const e) const
  735. {
  736. e->setAttribute ("fill", fillType.toString());
  737. e->setAttribute ("hasStroke", isStrokePresent);
  738. if (isStrokePresent && showOutline)
  739. {
  740. e->setAttribute ("stroke", strokeType.toString());
  741. e->setAttribute ("strokeColour", strokeType.fill.toString());
  742. }
  743. }
  744. bool ColouredElement::loadColourAttributes (const XmlElement& xml)
  745. {
  746. fillType.restoreFromString (xml.getStringAttribute ("fill", String()));
  747. isStrokePresent = showOutline && xml.getBoolAttribute ("hasStroke", false);
  748. strokeType.restoreFromString (xml.getStringAttribute ("stroke", String()));
  749. strokeType.fill.restoreFromString (xml.getStringAttribute ("strokeColour", String()));
  750. return true;
  751. }
  752. //==============================================================================
  753. void ColouredElement::convertToNewPathElement (const Path& path)
  754. {
  755. if (! path.isEmpty())
  756. {
  757. PaintElementPath newElement (getOwner());
  758. newElement.setToPath (path);
  759. newElement.setFillType (fillType, false);
  760. newElement.enableStroke (isStrokeEnabled(), false);
  761. newElement.setStrokeType (getStrokeType().stroke, false);
  762. newElement.setStrokeFill (getStrokeType().fill, false);
  763. std::unique_ptr<XmlElement> xml (newElement.createXml());
  764. PaintElement* e = getOwner()->addElementFromXml (*xml, getOwner()->indexOfElement (this), true);
  765. getOwner()->getSelectedElements().selectOnly (e);
  766. getOwner()->removeElement (this, true);
  767. }
  768. }