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.

470 lines
17KB

  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. #pragma once
  19. #include "../UI/jucer_PaintRoutineEditor.h"
  20. #include "../UI/jucer_ComponentLayoutEditor.h"
  21. //==============================================================================
  22. /**
  23. Base class for a property that edits the x, y, w, or h of a PositionedRectangle.
  24. */
  25. class PositionPropertyBase : public PropertyComponent,
  26. protected ChangeListener
  27. {
  28. public:
  29. enum ComponentPositionDimension
  30. {
  31. componentX = 0,
  32. componentY = 1,
  33. componentWidth = 2,
  34. componentHeight = 3
  35. };
  36. PositionPropertyBase (Component* comp,
  37. const String& name,
  38. ComponentPositionDimension dimension_,
  39. const bool includeAnchorOptions_,
  40. const bool allowRelativeOptions_,
  41. ComponentLayout* layout_)
  42. : PropertyComponent (name),
  43. layout (layout_),
  44. button ("mode"),
  45. component (comp),
  46. dimension (dimension_),
  47. includeAnchorOptions (includeAnchorOptions_),
  48. allowRelativeOptions (allowRelativeOptions_)
  49. {
  50. addAndMakeVisible (button);
  51. button.setTriggeredOnMouseDown (true);
  52. button.setConnectedEdges (TextButton::ConnectedOnLeft | TextButton::ConnectedOnRight);
  53. button.onClick = [this]
  54. {
  55. SafePointer<PositionPropertyBase> safeThis { this };
  56. showMenu (layout, [safeThis] (bool shouldRefresh)
  57. {
  58. if (safeThis == nullptr)
  59. return;
  60. if (shouldRefresh)
  61. safeThis->refresh(); // (to clear the text editor if it's got focus)
  62. });
  63. };
  64. textEditor.reset (new PositionPropLabel (*this));
  65. addAndMakeVisible (textEditor.get());
  66. }
  67. String getText() const
  68. {
  69. RelativePositionedRectangle rpr (getPosition());
  70. PositionedRectangle& p = rpr.rect;
  71. String s;
  72. switch (dimension)
  73. {
  74. case componentX:
  75. if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
  76. s << valueToString (p.getX() * 100.0) << '%';
  77. else
  78. s << valueToString (p.getX());
  79. break;
  80. case componentY:
  81. if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
  82. s << valueToString (p.getY() * 100.0) << '%';
  83. else
  84. s << valueToString (p.getY());
  85. break;
  86. case componentWidth:
  87. if (p.getWidthMode() == PositionedRectangle::proportionalSize)
  88. s << valueToString (p.getWidth() * 100.0) << '%';
  89. else
  90. s << valueToString (p.getWidth());
  91. break;
  92. case componentHeight:
  93. if (p.getHeightMode() == PositionedRectangle::proportionalSize)
  94. s << valueToString (p.getHeight() * 100.0) << '%';
  95. else
  96. s << valueToString (p.getHeight());
  97. break;
  98. default:
  99. jassertfalse;
  100. break;
  101. };
  102. return s;
  103. }
  104. static String valueToString (const double n)
  105. {
  106. return String (roundToInt (n * 1000.0) / 1000.0);
  107. }
  108. void setText (const String& newText)
  109. {
  110. RelativePositionedRectangle rpr (getPosition());
  111. PositionedRectangle p (rpr.rect);
  112. const double value = newText.getDoubleValue();
  113. switch (dimension)
  114. {
  115. case componentX:
  116. if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
  117. p.setX (value / 100.0);
  118. else
  119. p.setX (value);
  120. break;
  121. case componentY:
  122. if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
  123. p.setY (value / 100.0);
  124. else
  125. p.setY (value);
  126. break;
  127. case componentWidth:
  128. if (p.getWidthMode() == PositionedRectangle::proportionalSize)
  129. p.setWidth (value / 100.0);
  130. else
  131. p.setWidth (value);
  132. break;
  133. case componentHeight:
  134. if (p.getHeightMode() == PositionedRectangle::proportionalSize)
  135. p.setHeight (value / 100.0);
  136. else
  137. p.setHeight (value);
  138. break;
  139. default:
  140. jassertfalse;
  141. break;
  142. };
  143. if (p != rpr.rect)
  144. {
  145. rpr.rect = p;
  146. setPosition (rpr);
  147. }
  148. }
  149. void changeListenerCallback (ChangeBroadcaster*)
  150. {
  151. refresh();
  152. }
  153. void showMenu (ComponentLayout* compLayout, std::function<void (bool)> callback)
  154. {
  155. RelativePositionedRectangle rpr (getPosition());
  156. PositionedRectangle p (rpr.rect);
  157. PositionedRectangle::AnchorPoint xAnchor = p.getAnchorPointX();
  158. PositionedRectangle::AnchorPoint yAnchor = p.getAnchorPointY();
  159. PositionedRectangle::PositionMode xMode = p.getPositionModeX();
  160. PositionedRectangle::PositionMode yMode = p.getPositionModeY();
  161. PositionedRectangle::SizeMode sizeW = p.getWidthMode();
  162. PositionedRectangle::SizeMode sizeH = p.getHeightMode();
  163. String relCompName ("parent");
  164. if (Component* const relComp = compLayout != nullptr ? compLayout->getComponentRelativePosTarget (component, (int) dimension)
  165. : nullptr)
  166. relCompName = compLayout->getComponentMemberVariableName (relComp);
  167. jassert (relCompName.isNotEmpty());
  168. PopupMenu m;
  169. if (dimension == componentX || dimension == componentY)
  170. {
  171. const PositionedRectangle::PositionMode posMode = (dimension == componentX) ? xMode : yMode;
  172. m.addItem (10, ((dimension == componentX) ? "Absolute distance from left of "
  173. : "Absolute distance from top of ") + relCompName,
  174. true, posMode == PositionedRectangle::absoluteFromParentTopLeft);
  175. m.addItem (11, ((dimension == componentX) ? "Absolute distance from right of "
  176. : "Absolute distance from bottom of ") + relCompName,
  177. true, posMode == PositionedRectangle::absoluteFromParentBottomRight);
  178. m.addItem (12, "Absolute distance from centre of " + relCompName,
  179. true, posMode == PositionedRectangle::absoluteFromParentCentre);
  180. m.addItem (13, ((dimension == componentX) ? "Percentage of width of "
  181. : "Percentage of height of ") + relCompName,
  182. true, posMode == PositionedRectangle::proportionOfParentSize);
  183. m.addSeparator();
  184. if (includeAnchorOptions)
  185. {
  186. const PositionedRectangle::AnchorPoint anchor = (dimension == componentX) ? xAnchor : yAnchor;
  187. m.addItem (14, (dimension == componentX) ? "Anchored at left of component"
  188. : "Anchored at top of component",
  189. true, anchor == PositionedRectangle::anchorAtLeftOrTop);
  190. m.addItem (15, "Anchored at centre of component", true, anchor == PositionedRectangle::anchorAtCentre);
  191. m.addItem (16, (dimension == componentX) ? "Anchored at right of component"
  192. : "Anchored at bottom of component",
  193. true, anchor == PositionedRectangle::anchorAtRightOrBottom);
  194. }
  195. }
  196. else
  197. {
  198. const PositionedRectangle::SizeMode sizeMode = (dimension == componentWidth) ? sizeW : sizeH;
  199. m.addItem (20, (dimension == componentWidth) ? "Absolute width"
  200. : "Absolute height",
  201. true, sizeMode == PositionedRectangle::absoluteSize);
  202. m.addItem (21, ((dimension == componentWidth) ? "Percentage of width of "
  203. : "Percentage of height of ") + relCompName,
  204. true, sizeMode == PositionedRectangle::proportionalSize);
  205. m.addItem (22, ((dimension == componentWidth) ? "Subtracted from width of "
  206. : "Subtracted from height of ") + relCompName,
  207. true, sizeMode == PositionedRectangle::parentSizeMinusAbsolute);
  208. }
  209. if (allowRelativeOptions && compLayout != nullptr)
  210. {
  211. m.addSeparator();
  212. m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension));
  213. }
  214. m.showMenuAsync (PopupMenu::Options().withTargetComponent (&button),
  215. [compLayout, callback, xAnchor, yAnchor, xMode, yMode, sizeW, sizeH, p, rpr,
  216. ref = SafePointer<PositionPropertyBase> { this }] (int menuResult) mutable
  217. {
  218. if (menuResult == 0 || ref == nullptr)
  219. {
  220. callback (false);
  221. return;
  222. }
  223. switch (menuResult)
  224. {
  225. case 10:
  226. if (ref->dimension == componentX)
  227. xMode = PositionedRectangle::absoluteFromParentTopLeft;
  228. else
  229. yMode = PositionedRectangle::absoluteFromParentTopLeft;
  230. break;
  231. case 11:
  232. if (ref->dimension == componentX)
  233. xMode = PositionedRectangle::absoluteFromParentBottomRight;
  234. else
  235. yMode = PositionedRectangle::absoluteFromParentBottomRight;
  236. break;
  237. case 12:
  238. if (ref->dimension == componentX)
  239. xMode = PositionedRectangle::absoluteFromParentCentre;
  240. else
  241. yMode = PositionedRectangle::absoluteFromParentCentre;
  242. break;
  243. case 13:
  244. if (ref->dimension == componentX)
  245. xMode = PositionedRectangle::proportionOfParentSize;
  246. else
  247. yMode = PositionedRectangle::proportionOfParentSize;
  248. break;
  249. case 14:
  250. if (ref->dimension == componentX)
  251. xAnchor = PositionedRectangle::anchorAtLeftOrTop;
  252. else
  253. yAnchor = PositionedRectangle::anchorAtLeftOrTop;
  254. break;
  255. case 15:
  256. if (ref->dimension == componentX)
  257. xAnchor = PositionedRectangle::anchorAtCentre;
  258. else
  259. yAnchor = PositionedRectangle::anchorAtCentre;
  260. break;
  261. case 16:
  262. if (ref->dimension == componentX)
  263. xAnchor = PositionedRectangle::anchorAtRightOrBottom;
  264. else
  265. yAnchor = PositionedRectangle::anchorAtRightOrBottom;
  266. break;
  267. case 20:
  268. if (ref->dimension == componentWidth)
  269. sizeW = PositionedRectangle::absoluteSize;
  270. else
  271. sizeH = PositionedRectangle::absoluteSize;
  272. break;
  273. case 21:
  274. if (ref->dimension == componentWidth)
  275. sizeW = PositionedRectangle::proportionalSize;
  276. else
  277. sizeH = PositionedRectangle::proportionalSize;
  278. break;
  279. case 22:
  280. if (ref->dimension == componentWidth)
  281. sizeW = PositionedRectangle::parentSizeMinusAbsolute;
  282. else
  283. sizeH = PositionedRectangle::parentSizeMinusAbsolute;
  284. break;
  285. default:
  286. if (ref->allowRelativeOptions && compLayout != nullptr)
  287. compLayout->processRelativeTargetMenuResult (ref->component, (int) ref->dimension, menuResult);
  288. break;
  289. }
  290. const auto parentArea = [&]() -> Rectangle<int>
  291. {
  292. if (ref->component->findParentComponentOfClass<ComponentLayoutEditor>() != nullptr)
  293. return { ref->component->getParentWidth(), ref->component->getParentHeight() };
  294. if (auto pre = dynamic_cast<PaintRoutineEditor*> (ref->component->getParentComponent()))
  295. return pre->getComponentArea();
  296. jassertfalse;
  297. return {};
  298. }();
  299. int x, xw, y, yh, w, h;
  300. rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);
  301. PositionedRectangle xyRect (p);
  302. PositionedRectangle whRect (p);
  303. xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
  304. Rectangle<int> (x, y, xw, yh));
  305. whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
  306. Rectangle<int> (x, y, w, h));
  307. p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
  308. Rectangle<int> (x, y, xw, yh));
  309. p.setX (xyRect.getX());
  310. p.setY (xyRect.getY());
  311. p.setWidth (whRect.getWidth());
  312. p.setHeight (whRect.getHeight());
  313. if (p != rpr.rect)
  314. {
  315. rpr.rect = p;
  316. ref->setPosition (rpr);
  317. }
  318. callback (true);
  319. });
  320. }
  321. void resized()
  322. {
  323. const Rectangle<int> r (getLookAndFeel().getPropertyComponentContentPosition (*this));
  324. button.changeWidthToFitText (r.getHeight());
  325. button.setTopRightPosition (r.getRight(), r.getY());
  326. textEditor->setBounds (r.getX(), r.getY(), button.getX() - r.getX(), r.getHeight());
  327. }
  328. void refresh()
  329. {
  330. textEditor->setText (getText(), dontSendNotification);
  331. }
  332. void textWasEdited()
  333. {
  334. const String newText (textEditor->getText());
  335. if (getText() != newText)
  336. setText (newText);
  337. }
  338. //==============================================================================
  339. virtual void setPosition (const RelativePositionedRectangle& newPos) = 0;
  340. virtual RelativePositionedRectangle getPosition() const = 0;
  341. protected:
  342. class PositionPropLabel : public Label
  343. {
  344. public:
  345. PositionPropLabel (PositionPropertyBase& owner_)
  346. : Label (String(), String()),
  347. owner (owner_)
  348. {
  349. setEditable (true, true, false);
  350. updateLookAndFeel();
  351. }
  352. TextEditor* createEditorComponent() override
  353. {
  354. TextEditor* ed = Label::createEditorComponent();
  355. ed->setInputRestrictions (14, "0123456789.-%");
  356. return ed;
  357. }
  358. void textWasEdited() override
  359. {
  360. owner.textWasEdited();
  361. }
  362. void lookAndFeelChanged() override
  363. {
  364. updateLookAndFeel();
  365. }
  366. private:
  367. void updateLookAndFeel()
  368. {
  369. setColour (backgroundColourId, findColour (widgetBackgroundColourId));
  370. setColour (textColourId, findColour (widgetTextColourId));
  371. }
  372. PositionPropertyBase& owner;
  373. };
  374. ComponentLayout* layout;
  375. std::unique_ptr<PositionPropLabel> textEditor;
  376. TextButton button;
  377. Component* component;
  378. ComponentPositionDimension dimension;
  379. const bool includeAnchorOptions, allowRelativeOptions;
  380. };