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.

450 lines
16KB

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