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.

929 lines
30KB

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