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.

930 lines
30KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For the technical preview this file cannot be licensed commercially.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #include "../../Application/jucer_Headers.h"
  14. #include "jucer_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. if (! isPositiveAndBelow (newIndex, numElementsInArray (joints)))
  225. {
  226. jassertfalse;
  227. return;
  228. }
  229. listener.owner->setStrokeType (PathStrokeType (listener.owner->getStrokeType().stroke.getStrokeThickness(),
  230. joints [newIndex],
  231. listener.owner->getStrokeType().stroke.getEndStyle()),
  232. true);
  233. }
  234. int getIndex() const
  235. {
  236. switch (listener.owner->getStrokeType().stroke.getJointStyle())
  237. {
  238. case PathStrokeType::mitered: return 0;
  239. case PathStrokeType::curved: return 1;
  240. case PathStrokeType::beveled: return 2;
  241. default: jassertfalse; break;
  242. }
  243. return 0;
  244. }
  245. ElementListener<ColouredElement> listener;
  246. };
  247. //==============================================================================
  248. class StrokeEndCapProperty : public ChoicePropertyComponent
  249. {
  250. public:
  251. explicit StrokeEndCapProperty (ColouredElement* const owner_)
  252. : ChoicePropertyComponent ("end-cap style"),
  253. listener (owner_)
  254. {
  255. listener.setPropertyToRefresh (*this);
  256. choices.add ("butt");
  257. choices.add ("square");
  258. choices.add ("round");
  259. }
  260. void setIndex (int newIndex)
  261. {
  262. const PathStrokeType::EndCapStyle ends[] = { PathStrokeType::butt,
  263. PathStrokeType::square,
  264. PathStrokeType::rounded };
  265. if (! isPositiveAndBelow (newIndex, numElementsInArray (ends)))
  266. {
  267. jassertfalse;
  268. return;
  269. }
  270. listener.owner->setStrokeType (PathStrokeType (listener.owner->getStrokeType().stroke.getStrokeThickness(),
  271. listener.owner->getStrokeType().stroke.getJointStyle(),
  272. ends [newIndex]),
  273. true);
  274. }
  275. int getIndex() const
  276. {
  277. switch (listener.owner->getStrokeType().stroke.getEndStyle())
  278. {
  279. case PathStrokeType::butt: return 0;
  280. case PathStrokeType::square: return 1;
  281. case PathStrokeType::rounded: return 2;
  282. default: jassertfalse; break;
  283. }
  284. return 0;
  285. }
  286. ElementListener<ColouredElement> listener;
  287. };
  288. //==============================================================================
  289. class ImageBrushResourceProperty : public ImageResourceProperty <ColouredElement>
  290. {
  291. public:
  292. ImageBrushResourceProperty (ColouredElement* const e, const bool isForStroke_)
  293. : ImageResourceProperty <ColouredElement> (e, isForStroke_ ? "stroke image"
  294. : "fill image"),
  295. isForStroke (isForStroke_)
  296. {
  297. }
  298. //==============================================================================
  299. void setResource (const String& newName)
  300. {
  301. if (element != nullptr)
  302. {
  303. if (isForStroke)
  304. {
  305. JucerFillType type (element->getStrokeType().fill);
  306. type.imageResourceName = newName;
  307. element->setStrokeFill (type, true);
  308. }
  309. else
  310. {
  311. JucerFillType type (element->getFillType());
  312. type.imageResourceName = newName;
  313. element->setFillType (type, true);
  314. }
  315. }
  316. }
  317. String getResource() const
  318. {
  319. if (element == nullptr)
  320. return {};
  321. if (isForStroke)
  322. return element->getStrokeType().fill.imageResourceName;
  323. return element->getFillType().imageResourceName;
  324. }
  325. private:
  326. bool isForStroke;
  327. };
  328. //==============================================================================
  329. class ImageBrushPositionProperty : public PositionPropertyBase
  330. {
  331. public:
  332. ImageBrushPositionProperty (ColouredElement* const owner_,
  333. const String& name,
  334. ComponentPositionDimension dimension_,
  335. const bool isForStroke_)
  336. : PositionPropertyBase (owner_, name, dimension_, false, false,
  337. owner_->getDocument()->getComponentLayout()),
  338. listener (owner_),
  339. isForStroke (isForStroke_)
  340. {
  341. listener.setPropertyToRefresh (*this);
  342. }
  343. void setPosition (const RelativePositionedRectangle& newPos)
  344. {
  345. if (isForStroke)
  346. {
  347. JucerFillType type (listener.owner->getStrokeType().fill);
  348. type.imageAnchor = newPos;
  349. listener.owner->setStrokeFill (type, true);
  350. }
  351. else
  352. {
  353. JucerFillType type (listener.owner->getFillType());
  354. type.imageAnchor = newPos;
  355. listener.owner->setFillType (type, true);
  356. }
  357. }
  358. RelativePositionedRectangle getPosition() const
  359. {
  360. if (isForStroke)
  361. return listener.owner->getStrokeType().fill.imageAnchor;
  362. return listener.owner->getFillType().imageAnchor;
  363. }
  364. private:
  365. ElementListener<ColouredElement> listener;
  366. const bool isForStroke;
  367. };
  368. //==============================================================================
  369. class ImageBrushOpacityProperty : public SliderPropertyComponent
  370. {
  371. public:
  372. ImageBrushOpacityProperty (ColouredElement* const e, const bool isForStroke_)
  373. : SliderPropertyComponent ("opacity", 0.0, 1.0, 0.001),
  374. listener (e),
  375. isForStroke (isForStroke_)
  376. {
  377. listener.setPropertyToRefresh (*this);
  378. }
  379. void setValue (double newValue)
  380. {
  381. if (listener.owner != nullptr)
  382. {
  383. listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
  384. if (isForStroke)
  385. {
  386. JucerFillType type (listener.owner->getStrokeType().fill);
  387. type.imageOpacity = newValue;
  388. listener.owner->setStrokeFill (type, true);
  389. }
  390. else
  391. {
  392. JucerFillType type (listener.owner->getFillType());
  393. type.imageOpacity = newValue;
  394. listener.owner->setFillType (type, true);
  395. }
  396. }
  397. }
  398. double getValue() const
  399. {
  400. if (listener.owner == nullptr)
  401. return 0;
  402. if (isForStroke)
  403. return listener.owner->getStrokeType().fill.imageOpacity;
  404. return listener.owner->getFillType().imageOpacity;
  405. }
  406. private:
  407. ElementListener<ColouredElement> listener;
  408. bool isForStroke;
  409. };
  410. //==============================================================================
  411. ColouredElement::ColouredElement (PaintRoutine* owner_,
  412. const String& name,
  413. const bool showOutline_,
  414. const bool showJointAndEnd_)
  415. : PaintElement (owner_, name),
  416. isStrokePresent (false),
  417. showOutline (showOutline_),
  418. showJointAndEnd (showJointAndEnd_)
  419. {
  420. }
  421. ColouredElement::~ColouredElement()
  422. {
  423. }
  424. //==============================================================================
  425. void ColouredElement::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
  426. {
  427. PaintElement::getEditableProperties (props, multipleSelected);
  428. if (! multipleSelected)
  429. getColourSpecificProperties (props);
  430. }
  431. void ColouredElement::getColourSpecificProperties (Array <PropertyComponent*>& props)
  432. {
  433. props.add (new ElementFillModeProperty (this, false));
  434. switch (getFillType().mode)
  435. {
  436. case JucerFillType::solidColour:
  437. props.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, false));
  438. break;
  439. case JucerFillType::linearGradient:
  440. case JucerFillType::radialGradient:
  441. props.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, false));
  442. props.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, false));
  443. props.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, false));
  444. props.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, false));
  445. props.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, false));
  446. props.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, false));
  447. break;
  448. case JucerFillType::imageBrush:
  449. props.add (new ImageBrushResourceProperty (this, false));
  450. props.add (new ImageBrushPositionProperty (this, "anchor x", PositionPropertyBase::componentX, false));
  451. props.add (new ImageBrushPositionProperty (this, "anchor y", PositionPropertyBase::componentY, false));
  452. props.add (new ImageBrushOpacityProperty (this, false));
  453. break;
  454. default:
  455. jassertfalse;
  456. break;
  457. }
  458. if (showOutline)
  459. {
  460. props.add (new EnableStrokeProperty (this));
  461. if (isStrokePresent)
  462. {
  463. props.add (new StrokeThicknessProperty (this));
  464. if (showJointAndEnd)
  465. {
  466. props.add (new StrokeJointProperty (this));
  467. props.add (new StrokeEndCapProperty (this));
  468. }
  469. props.add (new ElementFillModeProperty (this, true));
  470. switch (getStrokeType().fill.mode)
  471. {
  472. case JucerFillType::solidColour:
  473. props.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, true));
  474. break;
  475. case JucerFillType::linearGradient:
  476. case JucerFillType::radialGradient:
  477. props.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, true));
  478. props.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, true));
  479. props.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, true));
  480. props.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, true));
  481. props.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, true));
  482. props.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, true));
  483. break;
  484. case JucerFillType::imageBrush:
  485. props.add (new ImageBrushResourceProperty (this, true));
  486. props.add (new ImageBrushPositionProperty (this, "stroke anchor x", PositionPropertyBase::componentX, true));
  487. props.add (new ImageBrushPositionProperty (this, "stroke anchor y", PositionPropertyBase::componentY, true));
  488. props.add (new ImageBrushOpacityProperty (this, true));
  489. break;
  490. default:
  491. jassertfalse;
  492. break;
  493. }
  494. }
  495. }
  496. }
  497. //==============================================================================
  498. const JucerFillType& ColouredElement::getFillType() noexcept
  499. {
  500. return fillType;
  501. }
  502. class FillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  503. {
  504. public:
  505. FillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
  506. : PaintElementUndoableAction <ColouredElement> (element),
  507. newState (newState_)
  508. {
  509. oldState = element->getFillType();
  510. }
  511. bool perform()
  512. {
  513. showCorrectTab();
  514. getElement()->setFillType (newState, false);
  515. return true;
  516. }
  517. bool undo()
  518. {
  519. showCorrectTab();
  520. getElement()->setFillType (oldState, false);
  521. return true;
  522. }
  523. private:
  524. JucerFillType newState, oldState;
  525. };
  526. void ColouredElement::setFillType (const JucerFillType& newType, const bool undoable)
  527. {
  528. if (fillType != newType)
  529. {
  530. if (undoable)
  531. {
  532. perform (new FillTypeChangeAction (this, newType),
  533. "Change fill type");
  534. }
  535. else
  536. {
  537. repaint();
  538. if (fillType.mode != newType.mode)
  539. {
  540. owner->getSelectedElements().changed();
  541. siblingComponentsChanged();
  542. }
  543. fillType = newType;
  544. changed();
  545. }
  546. }
  547. }
  548. //==============================================================================
  549. bool ColouredElement::isStrokeEnabled() const noexcept
  550. {
  551. return isStrokePresent && showOutline;
  552. }
  553. class StrokeEnableChangeAction : public PaintElementUndoableAction <ColouredElement>
  554. {
  555. public:
  556. StrokeEnableChangeAction (ColouredElement* const element, const bool newState_)
  557. : PaintElementUndoableAction <ColouredElement> (element),
  558. newState (newState_)
  559. {
  560. oldState = element->isStrokeEnabled();
  561. }
  562. bool perform()
  563. {
  564. showCorrectTab();
  565. getElement()->enableStroke (newState, false);
  566. return true;
  567. }
  568. bool undo()
  569. {
  570. showCorrectTab();
  571. getElement()->enableStroke (oldState, false);
  572. return true;
  573. }
  574. private:
  575. bool newState, oldState;
  576. };
  577. void ColouredElement::enableStroke (bool enable, const bool undoable)
  578. {
  579. enable = enable && showOutline;
  580. if (isStrokePresent != enable)
  581. {
  582. if (undoable)
  583. {
  584. perform (new StrokeEnableChangeAction (this, enable),
  585. "Change stroke mode");
  586. }
  587. else
  588. {
  589. repaint();
  590. isStrokePresent = enable;
  591. siblingComponentsChanged();
  592. owner->changed();
  593. owner->getSelectedElements().changed();
  594. }
  595. }
  596. }
  597. //==============================================================================
  598. const StrokeType& ColouredElement::getStrokeType() noexcept
  599. {
  600. return strokeType;
  601. }
  602. class StrokeTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  603. {
  604. public:
  605. StrokeTypeChangeAction (ColouredElement* const element, const PathStrokeType& newState_)
  606. : PaintElementUndoableAction <ColouredElement> (element),
  607. newState (newState_),
  608. oldState (element->getStrokeType().stroke)
  609. {
  610. }
  611. bool perform()
  612. {
  613. showCorrectTab();
  614. getElement()->setStrokeType (newState, false);
  615. return true;
  616. }
  617. bool undo()
  618. {
  619. showCorrectTab();
  620. getElement()->setStrokeType (oldState, false);
  621. return true;
  622. }
  623. private:
  624. PathStrokeType newState, oldState;
  625. };
  626. void ColouredElement::setStrokeType (const PathStrokeType& newType, const bool undoable)
  627. {
  628. if (strokeType.stroke != newType)
  629. {
  630. if (undoable)
  631. {
  632. perform (new StrokeTypeChangeAction (this, newType),
  633. "Change stroke type");
  634. }
  635. else
  636. {
  637. repaint();
  638. strokeType.stroke = newType;
  639. changed();
  640. }
  641. }
  642. }
  643. class StrokeFillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
  644. {
  645. public:
  646. StrokeFillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
  647. : PaintElementUndoableAction <ColouredElement> (element),
  648. newState (newState_)
  649. {
  650. oldState = element->getStrokeType().fill;
  651. }
  652. bool perform()
  653. {
  654. showCorrectTab();
  655. getElement()->setStrokeFill (newState, false);
  656. return true;
  657. }
  658. bool undo()
  659. {
  660. showCorrectTab();
  661. getElement()->setStrokeFill (oldState, false);
  662. return true;
  663. }
  664. private:
  665. JucerFillType newState, oldState;
  666. };
  667. void ColouredElement::setStrokeFill (const JucerFillType& newType, const bool undoable)
  668. {
  669. if (strokeType.fill != newType)
  670. {
  671. if (undoable)
  672. {
  673. perform (new StrokeFillTypeChangeAction (this, newType),
  674. "Change stroke fill type");
  675. }
  676. else
  677. {
  678. repaint();
  679. if (strokeType.fill.mode != newType.mode)
  680. {
  681. siblingComponentsChanged();
  682. owner->getSelectedElements().changed();
  683. }
  684. strokeType.fill = newType;
  685. changed();
  686. }
  687. }
  688. }
  689. //==============================================================================
  690. void ColouredElement::createSiblingComponents()
  691. {
  692. {
  693. GradientPointComponent* g1 = new GradientPointComponent (this, false, true);
  694. siblingComponents.add (g1);
  695. GradientPointComponent* g2 = new GradientPointComponent (this, false, false);
  696. siblingComponents.add (g2);
  697. getParentComponent()->addAndMakeVisible (g1);
  698. getParentComponent()->addAndMakeVisible (g2);
  699. g1->updatePosition();
  700. g2->updatePosition();
  701. }
  702. if (isStrokePresent && showOutline)
  703. {
  704. GradientPointComponent* g1 = new GradientPointComponent (this, true, true);
  705. siblingComponents.add (g1);
  706. GradientPointComponent* g2 = new GradientPointComponent (this, true, false);
  707. siblingComponents.add (g2);
  708. getParentComponent()->addAndMakeVisible (g1);
  709. getParentComponent()->addAndMakeVisible (g2);
  710. g1->updatePosition();
  711. g2->updatePosition();
  712. }
  713. }
  714. Rectangle<int> ColouredElement::getCurrentBounds (const Rectangle<int>& parentArea) const
  715. {
  716. int borderSize = 0;
  717. if (isStrokePresent)
  718. borderSize = (int) strokeType.stroke.getStrokeThickness() / 2 + 1;
  719. return position.getRectangle (parentArea, getDocument()->getComponentLayout())
  720. .expanded (borderSize);
  721. }
  722. void ColouredElement::setCurrentBounds (const Rectangle<int>& newBounds,
  723. const Rectangle<int>& parentArea,
  724. const bool undoable)
  725. {
  726. Rectangle<int> r (newBounds);
  727. if (isStrokePresent)
  728. {
  729. r = r.expanded (-((int) strokeType.stroke.getStrokeThickness() / 2 + 1));
  730. r.setSize (jmax (1, r.getWidth()), jmax (1, r.getHeight()));
  731. }
  732. RelativePositionedRectangle pr (position);
  733. pr.updateFrom (r.getX() - parentArea.getX(),
  734. r.getY() - parentArea.getY(),
  735. r.getWidth(), r.getHeight(),
  736. Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
  737. getDocument()->getComponentLayout());
  738. setPosition (pr, undoable);
  739. updateBounds (parentArea);
  740. }
  741. //==============================================================================
  742. void ColouredElement::addColourAttributes (XmlElement* const e) const
  743. {
  744. e->setAttribute ("fill", fillType.toString());
  745. e->setAttribute ("hasStroke", isStrokePresent);
  746. if (isStrokePresent && showOutline)
  747. {
  748. e->setAttribute ("stroke", strokeType.toString());
  749. e->setAttribute ("strokeColour", strokeType.fill.toString());
  750. }
  751. }
  752. bool ColouredElement::loadColourAttributes (const XmlElement& xml)
  753. {
  754. fillType.restoreFromString (xml.getStringAttribute ("fill", String()));
  755. isStrokePresent = showOutline && xml.getBoolAttribute ("hasStroke", false);
  756. strokeType.restoreFromString (xml.getStringAttribute ("stroke", String()));
  757. strokeType.fill.restoreFromString (xml.getStringAttribute ("strokeColour", String()));
  758. return true;
  759. }
  760. //==============================================================================
  761. void ColouredElement::convertToNewPathElement (const Path& path)
  762. {
  763. if (! path.isEmpty())
  764. {
  765. PaintElementPath newElement (getOwner());
  766. newElement.setToPath (path);
  767. newElement.setFillType (fillType, false);
  768. newElement.enableStroke (isStrokeEnabled(), false);
  769. newElement.setStrokeType (getStrokeType().stroke, false);
  770. newElement.setStrokeFill (getStrokeType().fill, false);
  771. std::unique_ptr<XmlElement> xml (newElement.createXml());
  772. PaintElement* e = getOwner()->addElementFromXml (*xml, getOwner()->indexOfElement (this), true);
  773. getOwner()->getSelectedElements().selectOnly (e);
  774. getOwner()->removeElement (this, true);
  775. }
  776. }