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.

412 lines
13KB

  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_ButtonDocument.h"
  20. #include "../jucer_UtilityFunctions.h"
  21. //==============================================================================
  22. static const int normalOff = 0;
  23. static const int overOff = 1;
  24. static const int downOff = 2;
  25. static const int normalOn = 3;
  26. static const int overOn = 4;
  27. static const int downOn = 5;
  28. static const int background = 6;
  29. //==============================================================================
  30. ButtonDocument::ButtonDocument (SourceCodeDocument* c)
  31. : JucerDocument (c)
  32. {
  33. paintStatesEnabled [normalOff] = true;
  34. paintStatesEnabled [overOff] = true;
  35. paintStatesEnabled [downOff] = true;
  36. paintStatesEnabled [normalOn] = false;
  37. paintStatesEnabled [overOn] = false;
  38. paintStatesEnabled [downOn] = false;
  39. paintStatesEnabled [background] = false;
  40. parentClasses = "public juce::Button";
  41. for (int i = 7; --i >= 0;)
  42. {
  43. paintRoutines[i].reset (new PaintRoutine());
  44. paintRoutines[i]->setDocument (this);
  45. paintRoutines[i]->setBackgroundColour (Colours::transparentBlack);
  46. }
  47. }
  48. ButtonDocument::~ButtonDocument()
  49. {
  50. }
  51. static const char* const stateNames[] =
  52. {
  53. "normal", "over", "down",
  54. "normal on", "over on", "down on",
  55. "common background"
  56. };
  57. static int stateNameToIndex (const String& name)
  58. {
  59. for (int i = 7; --i >= 0;)
  60. if (name.equalsIgnoreCase (stateNames[i]))
  61. return i;
  62. jassertfalse;
  63. return normalOff;
  64. }
  65. int ButtonDocument::getNumPaintRoutines() const
  66. {
  67. int n = 0;
  68. for (int i = 7; --i >= 0;)
  69. if (paintStatesEnabled [i])
  70. ++n;
  71. return n;
  72. }
  73. StringArray ButtonDocument::getPaintRoutineNames() const
  74. {
  75. StringArray s;
  76. for (int i = 0; i < 7; ++i)
  77. if (paintStatesEnabled [i])
  78. s.add (stateNames [i]);
  79. return s;
  80. }
  81. PaintRoutine* ButtonDocument::getPaintRoutine (const int index) const
  82. {
  83. int n = 0;
  84. for (int i = 0; i < 7; ++i)
  85. {
  86. if (paintStatesEnabled [i])
  87. {
  88. if (index == n)
  89. return paintRoutines[i].get();
  90. ++n;
  91. }
  92. }
  93. jassertfalse;
  94. return {};
  95. }
  96. void ButtonDocument::setStatePaintRoutineEnabled (const int index, bool b)
  97. {
  98. jassert (index > 0 && index < 7);
  99. if (paintStatesEnabled [index] != b)
  100. {
  101. paintStatesEnabled [index] = b;
  102. changed();
  103. }
  104. }
  105. bool ButtonDocument::isStatePaintRoutineEnabled (const int index) const
  106. {
  107. return paintStatesEnabled [index];
  108. }
  109. int ButtonDocument::chooseBestEnabledPaintRoutine (int paintRoutineWanted) const
  110. {
  111. switch (paintRoutineWanted)
  112. {
  113. case normalOff: return normalOff;
  114. case overOff: return paintStatesEnabled [overOff] ? overOff : normalOff;
  115. case downOff: return paintStatesEnabled [downOff] ? downOff : chooseBestEnabledPaintRoutine (overOff);
  116. case normalOn: return paintStatesEnabled [normalOn] ? normalOn : normalOff;
  117. case overOn: return paintStatesEnabled [overOn] ? overOn : (paintStatesEnabled [normalOn] ? normalOn : chooseBestEnabledPaintRoutine (overOff));
  118. case downOn: return paintStatesEnabled [downOn] ? downOn : ((paintStatesEnabled [overOn] || paintStatesEnabled [normalOn])
  119. ? chooseBestEnabledPaintRoutine (overOn)
  120. : chooseBestEnabledPaintRoutine (downOff));
  121. default: jassertfalse; break;
  122. }
  123. return normalOff;
  124. }
  125. //==============================================================================
  126. String ButtonDocument::getTypeName() const
  127. {
  128. return "Button";
  129. }
  130. JucerDocument* ButtonDocument::createCopy()
  131. {
  132. auto newOne = new ButtonDocument (cpp);
  133. newOne->resources = resources;
  134. newOne->loadFromXml (*createXml());
  135. return newOne;
  136. }
  137. std::unique_ptr<XmlElement> ButtonDocument::createXml() const
  138. {
  139. auto doc = JucerDocument::createXml();
  140. for (int i = 0; i < 7; ++i)
  141. {
  142. auto e = paintRoutines[i]->createXml();
  143. e->setAttribute ("buttonState", stateNames [i]);
  144. e->setAttribute ("enabled", paintStatesEnabled [i]);
  145. doc->addChildElement (e);
  146. }
  147. return doc;
  148. }
  149. bool ButtonDocument::loadFromXml (const XmlElement& xml)
  150. {
  151. if (JucerDocument::loadFromXml (xml))
  152. {
  153. for (int i = 7; --i >= 0;)
  154. paintStatesEnabled [i] = false;
  155. for (auto* e : xml.getChildWithTagNameIterator (PaintRoutine::xmlTagName))
  156. {
  157. const int stateIndex = stateNameToIndex (e->getStringAttribute ("buttonState"));
  158. paintRoutines [stateIndex]->loadFromXml (*e);
  159. paintStatesEnabled [stateIndex] = e->getBoolAttribute ("enabled", stateIndex < normalOn);
  160. }
  161. changed();
  162. getUndoManager().clearUndoHistory();
  163. return true;
  164. }
  165. return false;
  166. }
  167. void ButtonDocument::getOptionalMethods (StringArray& baseClasses,
  168. StringArray& returnValues,
  169. StringArray& methods,
  170. StringArray& initialContents) const
  171. {
  172. JucerDocument::getOptionalMethods (baseClasses, returnValues, methods, initialContents);
  173. addMethod ("juce::Button", "void", "clicked()", "", baseClasses, returnValues, methods, initialContents);
  174. addMethod ("juce::Button", "void", "buttonStateChanged()", "", baseClasses, returnValues, methods, initialContents);
  175. }
  176. //==============================================================================
  177. class ButtonStatePaintEnabledProperty : public BooleanPropertyComponent,
  178. private ChangeListener
  179. {
  180. public:
  181. ButtonStatePaintEnabledProperty (const String& name, ButtonDocument& doc, const int stateMethod_)
  182. : BooleanPropertyComponent (name, "enabled", "disabled"),
  183. document (doc),
  184. stateMethod (stateMethod_)
  185. {
  186. document.addChangeListener (this);
  187. }
  188. ~ButtonStatePaintEnabledProperty()
  189. {
  190. document.removeChangeListener (this);
  191. }
  192. void setState (bool newState)
  193. {
  194. document.setStatePaintRoutineEnabled (stateMethod, newState);
  195. }
  196. bool getState() const
  197. {
  198. return document.isStatePaintRoutineEnabled (stateMethod);
  199. }
  200. private:
  201. void changeListenerCallback (ChangeBroadcaster*)
  202. {
  203. refresh();
  204. }
  205. ButtonDocument& document;
  206. const int stateMethod;
  207. };
  208. void ButtonDocument::addExtraClassProperties (PropertyPanel& panel)
  209. {
  210. Array <PropertyComponent*> props;
  211. for (int i = 1; i < 7; ++i)
  212. props.add (new ButtonStatePaintEnabledProperty (stateNames[i], *this, i));
  213. panel.addSection ("Button paint routines", props);
  214. }
  215. //==============================================================================
  216. class ButtonTestComponent : public Button
  217. {
  218. public:
  219. ButtonTestComponent (ButtonDocument* const doc, const bool fillBackground)
  220. : Button (String()),
  221. document (doc),
  222. alwaysFillBackground (fillBackground)
  223. {
  224. setClickingTogglesState (true);
  225. }
  226. void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
  227. {
  228. if (document->paintStatesEnabled [background])
  229. {
  230. document->paintRoutines [background]->fillWithBackground (g, alwaysFillBackground);
  231. document->paintRoutines [background]->drawElements (g, getLocalBounds());
  232. }
  233. const int stateIndex
  234. = getToggleState()
  235. ? (isButtonDown ? document->chooseBestEnabledPaintRoutine (downOn)
  236. : (isMouseOverButton ? document->chooseBestEnabledPaintRoutine (overOn)
  237. : document->chooseBestEnabledPaintRoutine (normalOn)))
  238. : (isButtonDown ? document->chooseBestEnabledPaintRoutine (downOff)
  239. : (isMouseOverButton ? document->chooseBestEnabledPaintRoutine (overOff)
  240. : normalOff));
  241. document->paintRoutines [stateIndex]->fillWithBackground (g, ! document->paintStatesEnabled [background]);
  242. document->paintRoutines [stateIndex]->drawElements (g, getLocalBounds());
  243. }
  244. private:
  245. ButtonDocument* const document;
  246. const bool alwaysFillBackground;
  247. };
  248. Component* ButtonDocument::createTestComponent (const bool alwaysFillBackground)
  249. {
  250. return new ButtonTestComponent (this, alwaysFillBackground);
  251. }
  252. //==============================================================================
  253. void ButtonDocument::fillInGeneratedCode (GeneratedCode& code) const
  254. {
  255. JucerDocument::fillInGeneratedCode (code);
  256. code.parentClassInitialiser = "Button (" + quotedString (code.componentName, false) + ")";
  257. code.removeCallback ("void", "paint (Graphics& g)");
  258. }
  259. void ButtonDocument::fillInPaintCode (GeneratedCode& code) const
  260. {
  261. jassert (paintStatesEnabled [normalOff]);
  262. String paintCode [7];
  263. for (int i = 0; i < 7; ++i)
  264. if (paintStatesEnabled [i])
  265. paintRoutines[i]->fillInGeneratedCode (code, paintCode [i]);
  266. String& s = code.getCallbackCode ("public juce::Button",
  267. "void",
  268. "paintButton (juce::Graphics& g, bool isMouseOverButton, bool isButtonDown)",
  269. false);
  270. int numPaintRoutines = getNumPaintRoutines();
  271. if (paintStatesEnabled [background])
  272. {
  273. s << paintCode [background] << "\n";
  274. --numPaintRoutines;
  275. }
  276. if (numPaintRoutines == 1)
  277. {
  278. s << paintCode [normalOff];
  279. }
  280. else if (numPaintRoutines == downOff && (paintStatesEnabled [overOff] || paintStatesEnabled [downOff] || paintStatesEnabled [normalOn]))
  281. {
  282. if (paintStatesEnabled [normalOn])
  283. {
  284. s << "if (getToggleState())\n{\n "
  285. << CodeHelpers::indent (paintCode [normalOn], 4, false).trimEnd();
  286. }
  287. else if (paintStatesEnabled [overOff])
  288. {
  289. s << "if (isButtonDown || isMouseOverButton)\n{\n "
  290. << CodeHelpers::indent (paintCode [overOff], 4, false).trimEnd();
  291. }
  292. else
  293. {
  294. s << "if (isButtonDown)\n{\n "
  295. << CodeHelpers::indent (paintCode [downOff], 4, false).trimEnd();
  296. }
  297. s << "\n}\nelse\n{\n "
  298. << CodeHelpers::indent (paintCode [normalOff], 4, false).trimEnd()
  299. << "\n}\n";
  300. }
  301. else if (numPaintRoutines == normalOn && paintStatesEnabled [overOff] && paintStatesEnabled [downOff])
  302. {
  303. s << "if (isButtonDown)\n{\n "
  304. << CodeHelpers::indent (paintCode [downOff], 4, false).trimEnd()
  305. << "\n}\nelse if (isMouseOverButton)\n{\n "
  306. << CodeHelpers::indent (paintCode [overOff], 4, false).trimEnd()
  307. << "\n}\nelse\n{\n "
  308. << CodeHelpers::indent (paintCode [normalOff], 4, false).trimEnd()
  309. << "\n}\n";
  310. }
  311. else
  312. {
  313. if (paintStatesEnabled [normalOn] || paintStatesEnabled [overOn] || paintStatesEnabled [downOn])
  314. {
  315. s << "switch (getToggleState() ? (isButtonDown ? "
  316. << chooseBestEnabledPaintRoutine (downOn) << " : (isMouseOverButton ? "
  317. << chooseBestEnabledPaintRoutine (overOn) << " : "
  318. << chooseBestEnabledPaintRoutine (normalOn) << "))\n : (isButtonDown ? "
  319. << chooseBestEnabledPaintRoutine (downOff) << " : (isMouseOverButton ? "
  320. << chooseBestEnabledPaintRoutine (overOff) << " : 0)))\n{\n";
  321. }
  322. else
  323. {
  324. s << "switch (isButtonDown ? " << chooseBestEnabledPaintRoutine (downOff)
  325. << " : (isMouseOverButton ? " << chooseBestEnabledPaintRoutine (overOff)
  326. << " : 0))\n{\n";
  327. }
  328. for (int i = 0; i < 6; ++i)
  329. {
  330. if (paintStatesEnabled [i])
  331. {
  332. s << "case " << i << ":\n {\n "
  333. << CodeHelpers::indent (paintCode [i], 8, false).trimEnd()
  334. << "\n break;\n }\n\n";
  335. }
  336. }
  337. s << "default:\n break;\n}\n";
  338. }
  339. }