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.

689 lines
22KB

  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 "../../Application/jucer_Application.h"
  20. #include "../jucer_PaintRoutine.h"
  21. #include "../jucer_UtilityFunctions.h"
  22. #include "../UI/jucer_JucerCommandIDs.h"
  23. #include "../UI/jucer_PaintRoutineEditor.h"
  24. #include "../Properties/jucer_PositionPropertyBase.h"
  25. #include "jucer_ElementSiblingComponent.h"
  26. #include "jucer_PaintElementUndoableAction.h"
  27. //==============================================================================
  28. PaintElement::PaintElement (PaintRoutine* owner_,
  29. const String& typeName_)
  30. : borderThickness (4),
  31. owner (owner_),
  32. typeName (typeName_),
  33. selected (false),
  34. dragging (false),
  35. originalAspectRatio (1.0)
  36. {
  37. setRepaintsOnMouseActivity (true);
  38. position.rect.setWidth (100);
  39. position.rect.setHeight (100);
  40. setMinimumOnscreenAmounts (0, 0, 0, 0);
  41. setSizeLimits (borderThickness * 2 + 1, borderThickness * 2 + 1, 8192, 8192);
  42. border.reset (new ResizableBorderComponent (this, this));
  43. addChildComponent (border.get());
  44. border->setBorderThickness (BorderSize<int> (borderThickness));
  45. if (owner != nullptr)
  46. owner->getSelectedElements().addChangeListener (this);
  47. selfChangeListenerList.addChangeListener (this);
  48. siblingComponentsChanged();
  49. }
  50. PaintElement::~PaintElement()
  51. {
  52. siblingComponents.clear();
  53. if (owner != nullptr)
  54. {
  55. owner->getSelectedElements().deselect (this);
  56. owner->getSelectedElements().removeChangeListener (this);
  57. }
  58. }
  59. //==============================================================================
  60. void PaintElement::setInitialBounds (int parentWidth, int parentHeight)
  61. {
  62. RelativePositionedRectangle pr (getPosition());
  63. pr.rect.setX (parentWidth / 4 + Random::getSystemRandom().nextInt (parentWidth / 4) - parentWidth / 8);
  64. pr.rect.setY (parentHeight / 3 + Random::getSystemRandom().nextInt (parentHeight / 4) - parentHeight / 8);
  65. setPosition (pr, false);
  66. }
  67. //==============================================================================
  68. const RelativePositionedRectangle& PaintElement::getPosition() const
  69. {
  70. return position;
  71. }
  72. class PaintElementMoveAction : public PaintElementUndoableAction <PaintElement>
  73. {
  74. public:
  75. PaintElementMoveAction (PaintElement* const element, const RelativePositionedRectangle& newState_)
  76. : PaintElementUndoableAction <PaintElement> (element),
  77. newState (newState_),
  78. oldState (element->getPosition())
  79. {
  80. }
  81. bool perform()
  82. {
  83. showCorrectTab();
  84. getElement()->setPosition (newState, false);
  85. return true;
  86. }
  87. bool undo()
  88. {
  89. showCorrectTab();
  90. getElement()->setPosition (oldState, false);
  91. return true;
  92. }
  93. RelativePositionedRectangle newState, oldState;
  94. };
  95. class ChangePaintElementBoundsAction : public PaintElementUndoableAction <PaintElement>
  96. {
  97. public:
  98. ChangePaintElementBoundsAction (PaintElement* const element, const Rectangle<int>& bounds)
  99. : PaintElementUndoableAction <PaintElement> (element),
  100. newBounds (bounds),
  101. oldBounds (element->getBounds())
  102. {
  103. }
  104. bool perform()
  105. {
  106. showCorrectTab();
  107. getElement()->setBounds (newBounds);
  108. return true;
  109. }
  110. bool undo()
  111. {
  112. showCorrectTab();
  113. getElement()->setBounds (oldBounds);
  114. return true;
  115. }
  116. private:
  117. Rectangle<int> newBounds, oldBounds;
  118. };
  119. class ChangePaintElementBoundsAndPropertiesAction : public PaintElementUndoableAction <PaintElement>
  120. {
  121. public:
  122. ChangePaintElementBoundsAndPropertiesAction (PaintElement* const element, const Rectangle<int>& bounds,
  123. const NamedValueSet& props)
  124. : PaintElementUndoableAction <PaintElement> (element),
  125. newBounds (bounds),
  126. oldBounds (element->getBounds()),
  127. newProps (props),
  128. oldProps (element->getProperties())
  129. {
  130. }
  131. bool perform()
  132. {
  133. showCorrectTab();
  134. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
  135. getElement()->setCurrentBounds (newBounds, pe->getComponentArea(), false);
  136. getElement()->getProperties() = newProps;
  137. return true;
  138. }
  139. bool undo()
  140. {
  141. showCorrectTab();
  142. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
  143. getElement()->setCurrentBounds (oldBounds, pe->getComponentArea(), false);
  144. getElement()->getProperties() = oldProps;
  145. return true;
  146. }
  147. private:
  148. Rectangle<int> newBounds, oldBounds;
  149. NamedValueSet newProps, oldProps;
  150. };
  151. void PaintElement::setPosition (const RelativePositionedRectangle& newPosition, const bool undoable)
  152. {
  153. if (position != newPosition)
  154. {
  155. if (undoable)
  156. {
  157. perform (new PaintElementMoveAction (this, newPosition),
  158. "Move " + getTypeName());
  159. }
  160. else
  161. {
  162. position = newPosition;
  163. if (owner != nullptr)
  164. owner->changed();
  165. }
  166. }
  167. }
  168. void PaintElement::setPaintElementBounds (const Rectangle<int>& newBounds, const bool undoable)
  169. {
  170. if (getBounds() != newBounds)
  171. {
  172. if (undoable)
  173. {
  174. perform (new ChangePaintElementBoundsAction (this, newBounds), "Change paint element bounds");
  175. }
  176. else
  177. {
  178. setBounds (newBounds);
  179. changed();
  180. }
  181. }
  182. }
  183. void PaintElement::setPaintElementBoundsAndProperties (PaintElement* elementToPosition, const Rectangle<int>& newBounds,
  184. PaintElement* referenceElement, const bool undoable)
  185. {
  186. auto props = NamedValueSet (elementToPosition->getProperties());
  187. auto rect = elementToPosition->getPosition().rect;
  188. auto referenceElementPosition = referenceElement->getPosition();
  189. auto referenceElementRect = referenceElementPosition.rect;
  190. rect.setModes (referenceElementRect.getAnchorPointX(), referenceElementRect.getPositionModeX(),
  191. referenceElementRect.getAnchorPointY(), referenceElementRect.getPositionModeY(),
  192. referenceElementRect.getWidthMode(), referenceElementRect.getHeightMode(),
  193. elementToPosition->getBounds());
  194. props.set ("pos", rect.toString());
  195. props.set ("relativeToX", String::toHexString (referenceElementPosition.relativeToX));
  196. props.set ("relativeToY", String::toHexString (referenceElementPosition.relativeToY));
  197. props.set ("relativeToW", String::toHexString (referenceElementPosition.relativeToW));
  198. props.set ("relativeToH", String::toHexString (referenceElementPosition.relativeToH));
  199. if (elementToPosition->getBounds() != newBounds || elementToPosition->getProperties() != props)
  200. {
  201. if (undoable)
  202. {
  203. perform (new ChangePaintElementBoundsAndPropertiesAction (elementToPosition, newBounds, props),
  204. "Change paint element bounds");
  205. }
  206. else
  207. {
  208. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (elementToPosition->getParentComponent()))
  209. elementToPosition->setCurrentBounds (newBounds, pe->getComponentArea(), false);
  210. elementToPosition->getProperties() = props;
  211. owner->changed();
  212. }
  213. }
  214. }
  215. //==============================================================================
  216. Rectangle<int> PaintElement::getCurrentBounds (const Rectangle<int>& parentArea) const
  217. {
  218. return position.getRectangle (parentArea, getDocument()->getComponentLayout());
  219. }
  220. void PaintElement::setCurrentBounds (const Rectangle<int>& newBounds,
  221. const Rectangle<int>& parentArea,
  222. const bool undoable)
  223. {
  224. RelativePositionedRectangle pr (position);
  225. pr.updateFrom (newBounds.getX() - parentArea.getX(),
  226. newBounds.getY() - parentArea.getY(),
  227. jmax (1, newBounds.getWidth()),
  228. jmax (1, newBounds.getHeight()),
  229. Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
  230. getDocument()->getComponentLayout());
  231. setPosition (pr, undoable);
  232. updateBounds (parentArea);
  233. }
  234. void PaintElement::updateBounds (const Rectangle<int>& parentArea)
  235. {
  236. if (! parentArea.isEmpty())
  237. {
  238. setBounds (getCurrentBounds (parentArea)
  239. .expanded (borderThickness,
  240. borderThickness));
  241. for (int i = siblingComponents.size(); --i >= 0;)
  242. siblingComponents.getUnchecked (i)->updatePosition();
  243. }
  244. }
  245. //==============================================================================
  246. class ElementPositionProperty : public PositionPropertyBase
  247. {
  248. public:
  249. ElementPositionProperty (PaintElement* e, const String& name,
  250. ComponentPositionDimension dimension_)
  251. : PositionPropertyBase (e, name, dimension_, true, false,
  252. e->getDocument()->getComponentLayout()),
  253. listener (e),
  254. element (e)
  255. {
  256. listener.setPropertyToRefresh (*this);
  257. }
  258. void setPosition (const RelativePositionedRectangle& newPos)
  259. {
  260. if (element->getOwner()->getSelectedElements().getNumSelected() > 1)
  261. positionOtherSelectedElements (getPosition(), newPos);
  262. listener.owner->setPosition (newPos, true);
  263. }
  264. RelativePositionedRectangle getPosition() const
  265. {
  266. return listener.owner->getPosition();
  267. }
  268. private:
  269. ElementListener<PaintElement> listener;
  270. PaintElement* element;
  271. void positionOtherSelectedElements (const RelativePositionedRectangle& oldPos, const RelativePositionedRectangle& newPos)
  272. {
  273. for (auto* s : element->getOwner()->getSelectedElements())
  274. {
  275. if (s != element)
  276. {
  277. auto currentPos = s->getPosition();
  278. auto diff = 0.0;
  279. if (dimension == ComponentPositionDimension::componentX)
  280. {
  281. diff = newPos.rect.getX() - oldPos.rect.getX();
  282. currentPos.rect.setX (currentPos.rect.getX() + diff);
  283. }
  284. else if (dimension == ComponentPositionDimension::componentY)
  285. {
  286. diff = newPos.rect.getY() - oldPos.rect.getY();
  287. currentPos.rect.setY (currentPos.rect.getY() + diff);
  288. }
  289. else if (dimension == ComponentPositionDimension::componentWidth)
  290. {
  291. diff = newPos.rect.getWidth() - oldPos.rect.getWidth();
  292. currentPos.rect.setWidth (currentPos.rect.getWidth() + diff);
  293. }
  294. else if (dimension == ComponentPositionDimension::componentHeight)
  295. {
  296. diff = newPos.rect.getHeight() - oldPos.rect.getHeight();
  297. currentPos.rect.setHeight (currentPos.rect.getHeight() + diff);
  298. }
  299. s->setPosition (currentPos, true);
  300. }
  301. }
  302. }
  303. };
  304. //==============================================================================
  305. void PaintElement::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
  306. {
  307. ignoreUnused (multipleSelected);
  308. props.add (new ElementPositionProperty (this, "x", PositionPropertyBase::componentX));
  309. props.add (new ElementPositionProperty (this, "y", PositionPropertyBase::componentY));
  310. props.add (new ElementPositionProperty (this, "width", PositionPropertyBase::componentWidth));
  311. props.add (new ElementPositionProperty (this, "height", PositionPropertyBase::componentHeight));
  312. }
  313. //==============================================================================
  314. JucerDocument* PaintElement::getDocument() const
  315. {
  316. return owner->getDocument();
  317. }
  318. void PaintElement::changed()
  319. {
  320. repaint();
  321. owner->changed();
  322. }
  323. bool PaintElement::perform (UndoableAction* action, const String& actionName)
  324. {
  325. return owner->perform (action, actionName);
  326. }
  327. void PaintElement::parentHierarchyChanged()
  328. {
  329. updateSiblingComps();
  330. }
  331. //==============================================================================
  332. void PaintElement::drawExtraEditorGraphics (Graphics&, const Rectangle<int>& /*relativeTo*/)
  333. {
  334. }
  335. void PaintElement::paint (Graphics& g)
  336. {
  337. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  338. {
  339. auto area = pe->getComponentArea();
  340. g.saveState();
  341. g.setOrigin (area.getPosition() - Component::getPosition());
  342. area.setPosition (0, 0);
  343. g.saveState();
  344. g.reduceClipRegion (0, 0, area.getWidth(), area.getHeight());
  345. draw (g, getDocument()->getComponentLayout(), area);
  346. g.restoreState();
  347. drawExtraEditorGraphics (g, area);
  348. g.restoreState();
  349. if (selected)
  350. {
  351. const BorderSize<int> borderSize (border->getBorderThickness());
  352. auto baseColour = findColour (defaultHighlightColourId);
  353. drawResizableBorder (g, getWidth(), getHeight(), borderSize,
  354. (isMouseOverOrDragging() || border->isMouseOverOrDragging()),
  355. baseColour.withAlpha (owner->getSelectedElements().getSelectedItem (0) == this ? 1.0f : 0.3f));
  356. }
  357. else if (isMouseOverOrDragging())
  358. {
  359. drawMouseOverCorners (g, getWidth(), getHeight());
  360. }
  361. }
  362. }
  363. void PaintElement::resized()
  364. {
  365. border->setBounds (getLocalBounds());
  366. }
  367. void PaintElement::mouseDown (const MouseEvent& e)
  368. {
  369. dragging = false;
  370. if (owner != nullptr)
  371. {
  372. owner->getSelectedPoints().deselectAll();
  373. mouseDownSelectStatus = owner->getSelectedElements().addToSelectionOnMouseDown (this, e.mods);
  374. }
  375. if (e.mods.isPopupMenu())
  376. {
  377. showPopupMenu();
  378. return; // this may be deleted now..
  379. }
  380. }
  381. void PaintElement::mouseDrag (const MouseEvent& e)
  382. {
  383. if (! e.mods.isPopupMenu())
  384. {
  385. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  386. {
  387. auto area = pe->getComponentArea();
  388. if (selected && ! dragging)
  389. {
  390. dragging = e.mouseWasDraggedSinceMouseDown();
  391. if (dragging)
  392. owner->startDragging (area);
  393. }
  394. if (dragging)
  395. owner->dragSelectedComps (e.getDistanceFromDragStartX(),
  396. e.getDistanceFromDragStartY(),
  397. area);
  398. }
  399. }
  400. }
  401. void PaintElement::mouseUp (const MouseEvent& e)
  402. {
  403. if (owner != nullptr)
  404. {
  405. if (dragging)
  406. owner->endDragging();
  407. if (owner != nullptr)
  408. owner->getSelectedElements().addToSelectionOnMouseUp (this, e.mods, dragging, mouseDownSelectStatus);
  409. }
  410. }
  411. void PaintElement::resizeStart()
  412. {
  413. if (getHeight() > 0)
  414. originalAspectRatio = getWidth() / (double) getHeight();
  415. else
  416. originalAspectRatio = 1.0;
  417. }
  418. void PaintElement::resizeEnd()
  419. {
  420. }
  421. void PaintElement::checkBounds (Rectangle<int>& b,
  422. const Rectangle<int>& previousBounds,
  423. const Rectangle<int>& limits,
  424. const bool isStretchingTop,
  425. const bool isStretchingLeft,
  426. const bool isStretchingBottom,
  427. const bool isStretchingRight)
  428. {
  429. if (ModifierKeys::currentModifiers.isShiftDown())
  430. setFixedAspectRatio (originalAspectRatio);
  431. else
  432. setFixedAspectRatio (0.0);
  433. ComponentBoundsConstrainer::checkBounds (b, previousBounds, limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
  434. if (auto* document = getDocument())
  435. {
  436. if (document->isSnapActive (true))
  437. {
  438. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  439. {
  440. auto area = pe->getComponentArea();
  441. int x = b.getX();
  442. int y = b.getY();
  443. int w = b.getWidth();
  444. int h = b.getHeight();
  445. x += borderThickness - area.getX();
  446. y += borderThickness - area.getY();
  447. w -= borderThickness * 2;
  448. h -= borderThickness * 2;
  449. int right = x + w;
  450. int bottom = y + h;
  451. if (isStretchingRight)
  452. right = document->snapPosition (right);
  453. if (isStretchingBottom)
  454. bottom = document->snapPosition (bottom);
  455. if (isStretchingLeft)
  456. x = document->snapPosition (x);
  457. if (isStretchingTop)
  458. y = document->snapPosition (y);
  459. w = (right - x) + borderThickness * 2;
  460. h = (bottom - y) + borderThickness * 2;
  461. x -= borderThickness - area.getX();
  462. y -= borderThickness - area.getY();
  463. b = { x, y, w, h };
  464. }
  465. }
  466. }
  467. }
  468. void PaintElement::applyBoundsToComponent (Component&, Rectangle<int> newBounds)
  469. {
  470. if (getBounds() != newBounds)
  471. {
  472. getDocument()->getUndoManager().undoCurrentTransactionOnly();
  473. auto dX = newBounds.getX() - getX();
  474. auto dY = newBounds.getY() - getY();
  475. auto dW = newBounds.getWidth() - getWidth();
  476. auto dH = newBounds.getHeight() - getHeight();
  477. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  478. setCurrentBounds (newBounds.expanded (-borderThickness, -borderThickness),
  479. pe->getComponentArea(), true);
  480. if (owner->getSelectedElements().getNumSelected() > 1)
  481. {
  482. for (auto selectedElement : owner->getSelectedElements())
  483. {
  484. if (selectedElement != nullptr && selectedElement != this)
  485. {
  486. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (selectedElement->getParentComponent()))
  487. {
  488. Rectangle<int> r { selectedElement->getX() + dX, selectedElement->getY() + dY,
  489. selectedElement->getWidth() + dW, selectedElement->getHeight() + dH };
  490. selectedElement->setCurrentBounds (r.expanded (-borderThickness, -borderThickness),
  491. pe->getComponentArea(), true);
  492. }
  493. }
  494. }
  495. }
  496. }
  497. }
  498. Rectangle<int> PaintElement::getCurrentAbsoluteBounds() const
  499. {
  500. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  501. return position.getRectangle (pe->getComponentArea(), getDocument()->getComponentLayout());
  502. return {};
  503. }
  504. void PaintElement::getCurrentAbsoluteBoundsDouble (double& x, double& y, double& w, double& h) const
  505. {
  506. if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
  507. position.getRectangleDouble (x, y, w, h, pe->getComponentArea(), getDocument()->getComponentLayout());
  508. }
  509. void PaintElement::changeListenerCallback (ChangeBroadcaster*)
  510. {
  511. const bool nowSelected = owner != nullptr && owner->getSelectedElements().isSelected (this);
  512. if (selected != nowSelected)
  513. {
  514. selected = nowSelected;
  515. border->setVisible (nowSelected);
  516. repaint();
  517. selectionChanged (nowSelected);
  518. }
  519. updateSiblingComps();
  520. }
  521. void PaintElement::selectionChanged (const bool /*isSelected*/)
  522. {
  523. }
  524. void PaintElement::createSiblingComponents()
  525. {
  526. }
  527. void PaintElement::siblingComponentsChanged()
  528. {
  529. siblingComponents.clear();
  530. selfChangeListenerList.sendChangeMessage();
  531. }
  532. void PaintElement::updateSiblingComps()
  533. {
  534. if (selected && getParentComponent() != nullptr && owner->getSelectedElements().getNumSelected() == 1)
  535. {
  536. if (siblingComponents.size() == 0)
  537. createSiblingComponents();
  538. for (int i = siblingComponents.size(); --i >= 0;)
  539. siblingComponents.getUnchecked (i)->updatePosition();
  540. }
  541. else
  542. {
  543. siblingComponents.clear();
  544. }
  545. }
  546. void PaintElement::showPopupMenu()
  547. {
  548. auto* commandManager = &ProjucerApplication::getCommandManager();
  549. PopupMenu m;
  550. m.addCommandItem (commandManager, JucerCommandIDs::toFront);
  551. m.addCommandItem (commandManager, JucerCommandIDs::toBack);
  552. m.addSeparator();
  553. if (owner != nullptr && owner->getSelectedElements().getNumSelected() > 1)
  554. {
  555. m.addCommandItem (commandManager, JucerCommandIDs::alignTop);
  556. m.addCommandItem (commandManager, JucerCommandIDs::alignRight);
  557. m.addCommandItem (commandManager, JucerCommandIDs::alignBottom);
  558. m.addCommandItem (commandManager, JucerCommandIDs::alignLeft);
  559. m.addSeparator();
  560. }
  561. m.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
  562. m.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
  563. m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
  564. m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  565. m.showMenuAsync ({});
  566. }