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.

937 lines
30KB

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