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.

1623 lines
62KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: WidgetsDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Showcases various widgets.
  24. dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
  25. juce_gui_basics, juce_gui_extra
  26. exporters: xcode_mac, vs2022, linux_make, androidstudio, xcode_iphone
  27. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  28. type: Component
  29. mainClass: WidgetsDemo
  30. useLocalCopy: 1
  31. END_JUCE_PIP_METADATA
  32. *******************************************************************************/
  33. #pragma once
  34. #ifndef PIP_DEMO_UTILITIES_INCLUDED
  35. #include "../Assets/DemoUtilities.h"
  36. #endif
  37. //==============================================================================
  38. static void showBubbleMessage (Component& targetComponent, const String& textToShow,
  39. std::unique_ptr<BubbleMessageComponent>& bmc,
  40. bool isRunningComponentTransformDemo);
  41. //==============================================================================
  42. /** To demonstrate how sliders can have custom snapping applied to their values,
  43. this simple class snaps the value to 50 if it comes near.
  44. */
  45. struct SnappingSlider final : public Slider
  46. {
  47. double snapValue (double attemptedValue, DragMode dragMode) override
  48. {
  49. if (dragMode == notDragging)
  50. return attemptedValue; // if they're entering the value in the text-box, don't mess with it.
  51. if (attemptedValue > 40 && attemptedValue < 60)
  52. return 50.0;
  53. return attemptedValue;
  54. }
  55. };
  56. /** A TextButton that pops up a colour chooser to change its colours. */
  57. class ColourChangeButton final : public TextButton,
  58. public ChangeListener
  59. {
  60. public:
  61. ColourChangeButton()
  62. : TextButton ("Click to change colour...")
  63. {
  64. setSize (10, 24);
  65. changeWidthToFitText();
  66. }
  67. void clicked() override
  68. {
  69. auto colourSelector = std::make_unique<ColourSelector> (ColourSelector::showAlphaChannel
  70. | ColourSelector::showColourAtTop
  71. | ColourSelector::editableColour
  72. | ColourSelector::showSliders
  73. | ColourSelector::showColourspace);
  74. colourSelector->setName ("background");
  75. colourSelector->setCurrentColour (findColour (TextButton::buttonColourId));
  76. colourSelector->addChangeListener (this);
  77. colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  78. colourSelector->setSize (300, 400);
  79. CallOutBox::launchAsynchronously (std::move (colourSelector), getScreenBounds(), nullptr);
  80. }
  81. using TextButton::clicked;
  82. void changeListenerCallback (ChangeBroadcaster* source) override
  83. {
  84. if (auto* cs = dynamic_cast<ColourSelector*> (source))
  85. setColour (TextButton::buttonColourId, cs->getCurrentColour());
  86. }
  87. };
  88. //==============================================================================
  89. struct SlidersPage final : public Component
  90. {
  91. SlidersPage()
  92. {
  93. Rectangle<int> layoutArea { 20, 20, 580, 430 };
  94. auto sliderArea = layoutArea.removeFromTop (320);
  95. auto* s = createSlider (false);
  96. s->setSliderStyle (Slider::LinearVertical);
  97. s->setTextBoxStyle (Slider::TextBoxBelow, false, 100, 20);
  98. s->setBounds (sliderArea.removeFromLeft (70));
  99. s->setDoubleClickReturnValue (true, 50.0); // double-clicking this slider will set it to 50.0
  100. s->setTextValueSuffix (" units");
  101. s = createSlider (false);
  102. s->setSliderStyle (Slider::LinearVertical);
  103. s->setVelocityBasedMode (true);
  104. s->setSkewFactor (0.5);
  105. s->setTextBoxStyle (Slider::TextBoxAbove, true, 100, 20);
  106. s->setBounds (sliderArea.removeFromLeft (70));
  107. s->setTextValueSuffix (" rels");
  108. sliderArea.removeFromLeft (20);
  109. auto horizontalSliderArea = sliderArea.removeFromLeft (180);
  110. s = createSlider (true);
  111. s->setSliderStyle (Slider::LinearHorizontal);
  112. s->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20);
  113. s->setBounds (horizontalSliderArea.removeFromTop (20));
  114. s = createSlider (false);
  115. s->setSliderStyle (Slider::LinearHorizontal);
  116. s->setTextBoxStyle (Slider::NoTextBox, false, 0, 0);
  117. horizontalSliderArea.removeFromTop (20);
  118. s->setBounds (horizontalSliderArea.removeFromTop (20));
  119. s->setPopupDisplayEnabled (true, false, this);
  120. s->setTextValueSuffix (" nuns required to change a lightbulb");
  121. s = createSlider (false);
  122. s->setSliderStyle (Slider::LinearHorizontal);
  123. s->setTextBoxStyle (Slider::TextEntryBoxPosition::TextBoxAbove, false, 70, 20);
  124. horizontalSliderArea.removeFromTop (20);
  125. s->setBounds (horizontalSliderArea.removeFromTop (50));
  126. s->setPopupDisplayEnabled (true, false, this);
  127. s = createSlider (false);
  128. s->setSliderStyle (Slider::IncDecButtons);
  129. s->setTextBoxStyle (Slider::TextBoxLeft, false, 50, 20);
  130. horizontalSliderArea.removeFromTop (20);
  131. s->setBounds (horizontalSliderArea.removeFromTop (20));
  132. s->setIncDecButtonsMode (Slider::incDecButtonsDraggable_Vertical);
  133. s = createSlider (false);
  134. s->setSliderStyle (Slider::Rotary);
  135. s->setRotaryParameters (MathConstants<float>::pi * 1.2f, MathConstants<float>::pi * 2.8f, false);
  136. s->setTextBoxStyle (Slider::TextBoxRight, false, 70, 20);
  137. horizontalSliderArea.removeFromTop (15);
  138. s->setBounds (horizontalSliderArea.removeFromTop (70));
  139. s->setTextValueSuffix (" mm");
  140. s = createSlider (false);
  141. s->setSliderStyle (Slider::LinearBar);
  142. horizontalSliderArea.removeFromTop (10);
  143. s->setBounds (horizontalSliderArea.removeFromTop (30));
  144. s->setTextValueSuffix (" gallons");
  145. sliderArea.removeFromLeft (20);
  146. auto twoValueSliderArea = sliderArea.removeFromLeft (180);
  147. s = createSlider (false);
  148. s->setSliderStyle (Slider::TwoValueHorizontal);
  149. s->setBounds (twoValueSliderArea.removeFromTop (40));
  150. s = createSlider (false);
  151. s->setSliderStyle (Slider::ThreeValueHorizontal);
  152. s->setPopupDisplayEnabled (true, false, this);
  153. twoValueSliderArea.removeFromTop (10);
  154. s->setBounds (twoValueSliderArea.removeFromTop (40));
  155. s = createSlider (false);
  156. s->setSliderStyle (Slider::TwoValueVertical);
  157. twoValueSliderArea.removeFromLeft (30);
  158. s->setBounds (twoValueSliderArea.removeFromLeft (40));
  159. s = createSlider (false);
  160. s->setSliderStyle (Slider::ThreeValueVertical);
  161. s->setPopupDisplayEnabled (true, false, this);
  162. twoValueSliderArea.removeFromLeft (30);
  163. s->setBounds (twoValueSliderArea.removeFromLeft (40));
  164. s = createSlider (false);
  165. s->setSliderStyle (Slider::LinearBarVertical);
  166. s->setTextBoxStyle (Slider::NoTextBox, false, 0, 0);
  167. sliderArea.removeFromLeft (20);
  168. s->setBounds (sliderArea.removeFromLeft (20));
  169. s->setPopupDisplayEnabled (true, true, this);
  170. s->setTextValueSuffix (" mickles in a muckle");
  171. /* Here, we'll create a Value object, and tell a bunch of our sliders to use it as their
  172. value source. By telling them all to share the same Value, they'll stay in sync with
  173. each other.
  174. We could also optionally keep a copy of this Value elsewhere, and by changing it,
  175. cause all the sliders to automatically update.
  176. */
  177. Value sharedValue;
  178. sharedValue = Random::getSystemRandom().nextDouble() * 100;
  179. for (int i = 0; i < 8; ++i)
  180. sliders.getUnchecked (i)->getValueObject().referTo (sharedValue);
  181. // ..and now we'll do the same for all our min/max slider values..
  182. Value sharedValueMin, sharedValueMax;
  183. sharedValueMin = Random::getSystemRandom().nextDouble() * 40.0;
  184. sharedValueMax = Random::getSystemRandom().nextDouble() * 40.0 + 60.0;
  185. for (int i = 8; i <= 11; ++i)
  186. {
  187. auto* selectedSlider = sliders.getUnchecked (i);
  188. selectedSlider->setTextBoxStyle (Slider::NoTextBox, false, 0, 0);
  189. selectedSlider->getMaxValueObject().referTo (sharedValueMax);
  190. selectedSlider->getMinValueObject().referTo (sharedValueMin);
  191. }
  192. hintLabel.setBounds (layoutArea);
  193. addAndMakeVisible (hintLabel);
  194. }
  195. private:
  196. OwnedArray<Slider> sliders;
  197. Label hintLabel { "hint", "Try right-clicking on a slider for an options menu. \n\n"
  198. "Also, holding down CTRL while dragging will turn on a slider's velocity-sensitive mode" };
  199. Slider* createSlider (bool isSnapping)
  200. {
  201. auto* s = isSnapping ? new SnappingSlider()
  202. : new Slider();
  203. sliders.add (s);
  204. addAndMakeVisible (s);
  205. s->setRange (0.0, 100.0, 0.1);
  206. s->setPopupMenuEnabled (true);
  207. s->setValue (Random::getSystemRandom().nextDouble() * 100.0, dontSendNotification);
  208. return s;
  209. }
  210. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlidersPage)
  211. };
  212. //==============================================================================
  213. struct ButtonsPage final : public Component
  214. {
  215. ButtonsPage (bool isRunningComponentTransformDemo)
  216. {
  217. {
  218. auto* group = addToList (new GroupComponent ("group", "Radio buttons"));
  219. group->setBounds (20, 20, 220, 140);
  220. }
  221. for (int i = 0; i < 4; ++i)
  222. {
  223. auto* tb = addToList (new ToggleButton ("Radio Button #" + String (i + 1)));
  224. tb->setRadioGroupId (1234);
  225. tb->setBounds (45, 46 + i * 22, 180, 22);
  226. tb->setTooltip ("A set of mutually-exclusive radio buttons");
  227. if (i == 0)
  228. tb->setToggleState (true, dontSendNotification);
  229. }
  230. for (int i = 0; i < 4; ++i)
  231. {
  232. DrawablePath normal, over;
  233. Path p;
  234. p.addStar ({}, i + 5, 20.0f, 50.0f, -0.2f);
  235. normal.setPath (p);
  236. normal.setFill (Colours::lightblue);
  237. normal.setStrokeFill (Colours::black);
  238. normal.setStrokeThickness (4.0f);
  239. over.setPath (p);
  240. over.setFill (Colours::blue);
  241. over.setStrokeFill (Colours::black);
  242. over.setStrokeThickness (4.0f);
  243. auto* db = addToList (new DrawableButton (String (i + 5) + " points", DrawableButton::ImageAboveTextLabel));
  244. db->setImages (&normal, &over, nullptr);
  245. db->setClickingTogglesState (true);
  246. db->setRadioGroupId (23456);
  247. int buttonSize = 50;
  248. db->setBounds (25 + i * buttonSize, 180, buttonSize, buttonSize);
  249. if (i == 0)
  250. db->setToggleState (true, dontSendNotification);
  251. }
  252. for (int i = 0; i < 4; ++i)
  253. {
  254. auto* tb = addToList (new TextButton ("Button " + String (i + 1)));
  255. tb->setClickingTogglesState (true);
  256. tb->setRadioGroupId (34567);
  257. tb->setColour (TextButton::textColourOffId, Colours::black);
  258. tb->setColour (TextButton::textColourOnId, Colours::black);
  259. tb->setColour (TextButton::buttonColourId, Colours::white);
  260. tb->setColour (TextButton::buttonOnColourId, Colours::blueviolet.brighter());
  261. tb->setBounds (20 + i * 55, 260, 55, 24);
  262. tb->setConnectedEdges (((i != 0) ? Button::ConnectedOnLeft : 0)
  263. | ((i != 3) ? Button::ConnectedOnRight : 0));
  264. if (i == 0)
  265. tb->setToggleState (true, dontSendNotification);
  266. }
  267. {
  268. auto* colourChangeButton = new ColourChangeButton();
  269. components.add (colourChangeButton);
  270. addAndMakeVisible (colourChangeButton);
  271. colourChangeButton->setTopLeftPosition (20, 320);
  272. }
  273. {
  274. auto* hyperlink = addToList (new HyperlinkButton ("This is a HyperlinkButton",
  275. { "http://www.juce.com" }));
  276. hyperlink->setBounds (260, 20, 200, 24);
  277. }
  278. // create some drawables to use for our drawable buttons...
  279. DrawablePath normal, over;
  280. {
  281. Path p;
  282. p.addStar ({}, 5, 20.0f, 50.0f, 0.2f);
  283. normal.setPath (p);
  284. normal.setFill (getRandomDarkColour());
  285. }
  286. {
  287. Path p;
  288. p.addStar ({}, 9, 25.0f, 50.0f, 0.0f);
  289. over.setPath (p);
  290. over.setFill (getRandomBrightColour());
  291. over.setStrokeFill (getRandomDarkColour());
  292. over.setStrokeThickness (5.0f);
  293. }
  294. DrawableImage down;
  295. down.setImage (getImageFromAssets ("juce_icon.png"));
  296. down.setOverlayColour (Colours::black.withAlpha (0.3f));
  297. auto popupMessageCallback = [this, isRunningComponentTransformDemo]
  298. {
  299. if (auto* focused = Component::getCurrentlyFocusedComponent())
  300. showBubbleMessage (*focused,
  301. "This is a demo of the BubbleMessageComponent, which lets you pop up a message pointing "
  302. "at a component or somewhere on the screen.\n\n"
  303. "The message bubbles will disappear after a timeout period, or when the mouse is clicked.",
  304. this->bubbleMessage,
  305. isRunningComponentTransformDemo);
  306. };
  307. {
  308. // create an image-above-text button from these drawables..
  309. auto db = addToList (new DrawableButton ("Button 1", DrawableButton::ImageAboveTextLabel));
  310. db->setImages (&normal, &over, &down);
  311. db->setBounds (260, 60, 80, 80);
  312. db->setTooltip ("This is a DrawableButton with a label");
  313. db->onClick = popupMessageCallback;
  314. }
  315. {
  316. // create an image-only button from these drawables..
  317. auto db = addToList (new DrawableButton ("Button 2", DrawableButton::ImageFitted));
  318. db->setImages (&normal, &over, &down);
  319. db->setClickingTogglesState (true);
  320. db->setBounds (370, 60, 80, 80);
  321. db->setTooltip ("This is an image-only DrawableButton");
  322. db->onClick = popupMessageCallback;
  323. }
  324. {
  325. // create an image-on-button-shape button from the same drawables..
  326. auto db = addToList (new DrawableButton ("Button 3", DrawableButton::ImageOnButtonBackground));
  327. db->setImages (&normal, nullptr, nullptr);
  328. db->setBounds (260, 160, 110, 25);
  329. db->setTooltip ("This is a DrawableButton on a standard button background");
  330. db->onClick = popupMessageCallback;
  331. }
  332. {
  333. auto db = addToList (new DrawableButton ("Button 4", DrawableButton::ImageOnButtonBackground));
  334. db->setImages (&normal, &over, &down);
  335. db->setClickingTogglesState (true);
  336. db->setColour (DrawableButton::backgroundColourId, Colours::white);
  337. db->setColour (DrawableButton::backgroundOnColourId, Colours::yellow);
  338. db->setBounds (400, 150, 50, 50);
  339. db->setTooltip ("This is a DrawableButton on a standard button background");
  340. db->onClick = popupMessageCallback;
  341. }
  342. {
  343. auto sb = addToList (new ShapeButton ("ShapeButton",
  344. getRandomDarkColour(),
  345. getRandomDarkColour(),
  346. getRandomDarkColour()));
  347. sb->setShape (getJUCELogoPath(), false, true, false);
  348. sb->setBounds (260, 220, 200, 120);
  349. }
  350. {
  351. auto ib = addToList (new ImageButton ("ImageButton"));
  352. auto juceImage = getImageFromAssets ("juce_icon.png");
  353. ib->setImages (true, true, true,
  354. juceImage, 0.7f, Colours::transparentBlack,
  355. juceImage, 1.0f, Colours::transparentBlack,
  356. juceImage, 1.0f, getRandomBrightColour().withAlpha (0.8f),
  357. 0.5f);
  358. ib->setBounds (45, 380, 100, 100);
  359. ib->setTooltip ("ImageButton - showing alpha-channel hit-testing and colour overlay when clicked");
  360. }
  361. }
  362. private:
  363. OwnedArray<Component> components;
  364. std::unique_ptr<BubbleMessageComponent> bubbleMessage;
  365. TooltipWindow tooltipWindow;
  366. // This little function avoids a bit of code-duplication by adding a component to
  367. // our list as well as calling addAndMakeVisible on it..
  368. template <typename ComponentType>
  369. ComponentType* addToList (ComponentType* newComp)
  370. {
  371. components.add (newComp);
  372. addAndMakeVisible (newComp);
  373. return newComp;
  374. }
  375. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonsPage)
  376. };
  377. //==============================================================================
  378. struct MiscPage final : public Component,
  379. private Timer
  380. {
  381. MiscPage()
  382. {
  383. addAndMakeVisible (textEditor1);
  384. textEditor1.setBounds (10, 25, 200, 24);
  385. textEditor1.setText ("Single-line text box");
  386. addAndMakeVisible (textEditor2);
  387. textEditor2.setBounds (10, 55, 200, 24);
  388. textEditor2.setText ("Password");
  389. addAndMakeVisible (comboBox);
  390. comboBox.setBounds (10, 85, 200, 24);
  391. comboBox.setEditableText (true);
  392. comboBox.setJustificationType (Justification::centred);
  393. for (int i = 1; i < 100; ++i)
  394. comboBox.addItem ("combo box item " + String (i), i);
  395. comboBox.setSelectedId (1);
  396. addAndMakeVisible (linearProgressBar);
  397. linearProgressBar.setStyle (ProgressBar::Style::linear);
  398. linearProgressBar.setBounds (10, 115, 200, 24);
  399. addAndMakeVisible (circularProgressBar);
  400. circularProgressBar.setStyle (ProgressBar::Style::circular);
  401. circularProgressBar.setBounds (10, 145, 200, 100);
  402. startTimerHz (10);
  403. }
  404. ~MiscPage() override
  405. {
  406. stopTimer();
  407. }
  408. void lookAndFeelChanged() override
  409. {
  410. textEditor1.applyFontToAllText (textEditor1.getFont());
  411. textEditor2.applyFontToAllText (textEditor2.getFont());
  412. }
  413. void timerCallback() override
  414. {
  415. constexpr auto minValue = -0.2;
  416. constexpr auto maxValue = 1.2;
  417. constexpr auto maxIncrement = 0.05;
  418. if (progress >= maxValue)
  419. progress = minValue;
  420. else
  421. progress += Random::getSystemRandom().nextDouble() * maxIncrement;
  422. if (isPositiveAndNotGreaterThan (progress, 1.0))
  423. {
  424. linearProgressBar.setPercentageDisplay (true);
  425. circularProgressBar.setPercentageDisplay (true);
  426. }
  427. else
  428. {
  429. linearProgressBar.setTextToDisplay ("Linear progress bar");
  430. circularProgressBar.setTextToDisplay ("Circular progress bar");
  431. }
  432. }
  433. TextEditor textEditor1,
  434. textEditor2 { "Password", (juce_wchar) 0x2022 };
  435. ComboBox comboBox { "Combo" };
  436. double progress { 0.0 };
  437. ProgressBar linearProgressBar { progress };
  438. ProgressBar circularProgressBar { progress };
  439. };
  440. //==============================================================================
  441. struct MenuPage final : public Component
  442. {
  443. MenuPage()
  444. {
  445. addAndMakeVisible (shortMenuButton);
  446. shortMenuButton.onClick = [&]
  447. {
  448. PopupMenu menu;
  449. menu.addItem ("Single Item", nullptr);
  450. menu.showMenuAsync (PopupMenu::Options{}.withTargetComponent (shortMenuButton));
  451. };
  452. addAndMakeVisible (longMenuButton);
  453. longMenuButton.onClick = [&]
  454. {
  455. PopupMenu menu;
  456. for (int i = 0; i < 40; ++i)
  457. menu.addItem ("Item " + String (i), nullptr);
  458. menu.showMenuAsync (PopupMenu::Options{}.withTargetComponent (longMenuButton));
  459. };
  460. addAndMakeVisible (nestedMenusButton);
  461. nestedMenusButton.onClick = [&]
  462. {
  463. PopupMenu menu;
  464. for (int i = 0; i < 15; ++i)
  465. {
  466. PopupMenu subMenu;
  467. for (int j = 0; j < 10; ++j)
  468. {
  469. if (j % 2 == 0)
  470. {
  471. PopupMenu subSubMenu;
  472. for (int z = 0; z < 5; ++z)
  473. subSubMenu.addItem ("Sub-sub-item " + String (z), nullptr);
  474. subMenu.addSubMenu ("Sub-item " + String (j), subSubMenu);
  475. }
  476. else
  477. {
  478. subMenu.addItem ("Sub-item " + String (j), nullptr);
  479. }
  480. }
  481. menu.addSubMenu ("Item " + String (i), subMenu);
  482. }
  483. menu.showMenuAsync (PopupMenu::Options{}.withTargetComponent (nestedMenusButton));
  484. };
  485. addAndMakeVisible (multiColumnMenuButton);
  486. multiColumnMenuButton.onClick = [&]
  487. {
  488. PopupMenu menu;
  489. for (int i = 0; i < 200; ++i)
  490. menu.addItem ("Item " + String (i), nullptr);
  491. menu.showMenuAsync (PopupMenu::Options{}.withTargetComponent (multiColumnMenuButton)
  492. .withMinimumNumColumns (2)
  493. .withMaximumNumColumns (4));
  494. };
  495. addAndMakeVisible (customItemButton);
  496. customItemButton.onClick = [&]
  497. {
  498. struct CustomComponent final : public PopupMenu::CustomComponent
  499. {
  500. CustomComponent (int widthIn, int heightIn, Colour backgroundIn)
  501. : PopupMenu::CustomComponent (false),
  502. idealWidth (widthIn),
  503. idealHeight (heightIn),
  504. background (backgroundIn)
  505. {}
  506. void getIdealSize (int& width, int& height) override
  507. {
  508. width = idealWidth;
  509. height = idealHeight;
  510. }
  511. void paint (Graphics& g) override { g.fillAll (background); }
  512. int idealWidth = 0;
  513. int idealHeight = 0;
  514. Colour background;
  515. };
  516. PopupMenu menu;
  517. menu.addCustomItem (-1, std::make_unique<CustomComponent> (100, 20, Colours::darkred));
  518. menu.addCustomItem (-1, std::make_unique<CustomComponent> (20, 100, Colours::darkgreen));
  519. menu.addCustomItem (-1, std::make_unique<CustomComponent> (100, 100, Colours::darkblue));
  520. menu.addCustomItem (-1, std::make_unique<CustomComponent> (100, 50, Colours::darkcyan));
  521. menu.addCustomItem (-1, std::make_unique<CustomComponent> (50, 100, Colours::darkmagenta));
  522. menu.showMenuAsync (PopupMenu::Options{}.withTargetComponent (customItemButton)
  523. .withMinimumNumColumns (5));
  524. };
  525. addAndMakeVisible (fancyThemeButton);
  526. fancyThemeButton.setLookAndFeel (&popupLookAndFeel);
  527. fancyThemeButton.onClick = [&]
  528. {
  529. const auto colour = Colour::fromHSL (randomColourGenerator.nextFloat(), 0.5f, 0.5f, 1.0f);
  530. fancyThemeButton.setColour (TextButton::buttonColourId, colour);
  531. PopupMenu menu;
  532. menu.setLookAndFeel (&popupLookAndFeel);
  533. for (auto length : { 5, 10, 7, 3 })
  534. {
  535. for (int i = 0; i < length; ++i)
  536. menu.addItem ("Item " + String (i), nullptr);
  537. menu.addColumnBreak();
  538. }
  539. menu.showMenuAsync (PopupMenu::Options{}.withTargetComponent (&fancyThemeButton));
  540. };
  541. }
  542. void resized() override
  543. {
  544. const auto makeItem = [] (Component& comp)
  545. {
  546. return FlexItem { comp }.withWidth (200).withHeight (24).withMargin ({ 4 });
  547. };
  548. FlexBox box;
  549. box.flexDirection = FlexBox::Direction::column;
  550. box.items = { makeItem (shortMenuButton),
  551. makeItem (longMenuButton),
  552. makeItem (nestedMenusButton),
  553. makeItem (multiColumnMenuButton),
  554. makeItem (customItemButton),
  555. makeItem (fancyThemeButton) };
  556. box.performLayout (getLocalBounds());
  557. }
  558. struct PopupMenuLookAndFeel : public LookAndFeel_V4
  559. {
  560. void drawPopupMenuColumnSeparatorWithOptions (Graphics& g,
  561. const Rectangle<int>& bounds,
  562. const PopupMenu::Options& opt)
  563. {
  564. if (auto* target = opt.getTargetComponent())
  565. {
  566. const auto baseColour = target->findColour (TextButton::buttonColourId);
  567. g.setColour (baseColour.brighter (0.4f));
  568. const float dashes[] { 5.0f, 5.0f };
  569. const auto centre = bounds.toFloat().getCentre();
  570. g.drawDashedLine ({ centre.withY ((float) bounds.getY()),
  571. centre.withY ((float) bounds.getBottom()) },
  572. dashes,
  573. numElementsInArray (dashes),
  574. 3.0f);
  575. }
  576. }
  577. void drawPopupMenuBackgroundWithOptions (Graphics& g, int, int, const PopupMenu::Options& opt)
  578. {
  579. if (auto* target = opt.getTargetComponent())
  580. {
  581. g.fillAll (target->findColour (TextButton::buttonColourId));
  582. }
  583. }
  584. // Return the amount of space that should be left between popup menu columns.
  585. int getPopupMenuColumnSeparatorWidthWithOptions (const PopupMenu::Options&)
  586. {
  587. return 10;
  588. }
  589. };
  590. Random randomColourGenerator;
  591. PopupMenuLookAndFeel popupLookAndFeel;
  592. TextButton shortMenuButton { "Short" },
  593. longMenuButton { "Long" },
  594. nestedMenusButton { "Nested Sub-Menus" },
  595. multiColumnMenuButton { "Multi Column" },
  596. customItemButton { "Custom Items" },
  597. fancyThemeButton { "Fancy Theme with Column Breaks" };
  598. };
  599. //==============================================================================
  600. class ToolbarDemoComp final : public Component,
  601. private Slider::Listener
  602. {
  603. public:
  604. ToolbarDemoComp()
  605. {
  606. // Create and add the toolbar...
  607. addAndMakeVisible (toolbar);
  608. // And use our item factory to add a set of default icons to it...
  609. toolbar.addDefaultItems (factory);
  610. // Now we'll just create the other sliders and buttons on the demo page, which adjust
  611. // the toolbar's properties...
  612. addAndMakeVisible (infoLabel);
  613. infoLabel.setJustificationType (Justification::topLeft);
  614. infoLabel.setBounds (80, 80, 450, 100);
  615. infoLabel.setInterceptsMouseClicks (false, false);
  616. addAndMakeVisible (depthSlider);
  617. depthSlider.setRange (10.0, 200.0, 1.0);
  618. depthSlider.setValue (50, dontSendNotification);
  619. depthSlider.addListener (this);
  620. depthSlider.setBounds (80, 210, 300, 22);
  621. depthLabel.attachToComponent (&depthSlider, false);
  622. addAndMakeVisible (orientationButton);
  623. orientationButton.onClick = [this] { toolbar.setVertical (! toolbar.isVertical()); resized(); };
  624. orientationButton.changeWidthToFitText (22);
  625. orientationButton.setTopLeftPosition (depthSlider.getX(), depthSlider.getBottom() + 20);
  626. addAndMakeVisible (customiseButton);
  627. customiseButton.onClick = [this] { toolbar.showCustomisationDialog (factory); };
  628. customiseButton.changeWidthToFitText (22);
  629. customiseButton.setTopLeftPosition (orientationButton.getRight() + 20, orientationButton.getY());
  630. }
  631. void resized() override
  632. {
  633. auto toolbarThickness = (int) depthSlider.getValue();
  634. if (toolbar.isVertical())
  635. toolbar.setBounds (getLocalBounds().removeFromLeft (toolbarThickness));
  636. else
  637. toolbar.setBounds (getLocalBounds().removeFromTop (toolbarThickness));
  638. }
  639. void sliderValueChanged (Slider*) override
  640. {
  641. resized();
  642. }
  643. private:
  644. Toolbar toolbar;
  645. Slider depthSlider { Slider::LinearHorizontal, Slider::TextBoxLeft };
  646. Label depthLabel { {}, "Toolbar depth:" },
  647. infoLabel { {}, "As well as showing off toolbars, this demo illustrates how to store "
  648. "a set of SVG files in a Zip file, embed that in your application, and read "
  649. "them back in at runtime.\n\nThe icon images here are taken from the open-source "
  650. "Tango icon project."};
  651. TextButton orientationButton { "Vertical/Horizontal" },
  652. customiseButton { "Customise..." };
  653. //==============================================================================
  654. class DemoToolbarItemFactory final : public ToolbarItemFactory
  655. {
  656. public:
  657. DemoToolbarItemFactory() {}
  658. //==============================================================================
  659. // Each type of item a toolbar can contain must be given a unique ID. These
  660. // are the ones we'll use in this demo.
  661. enum DemoToolbarItemIds
  662. {
  663. doc_new = 1,
  664. doc_open = 2,
  665. doc_save = 3,
  666. doc_saveAs = 4,
  667. edit_copy = 5,
  668. edit_cut = 6,
  669. edit_paste = 7,
  670. juceLogoButton = 8,
  671. customComboBox = 9
  672. };
  673. void getAllToolbarItemIds (Array<int>& ids) override
  674. {
  675. // This returns the complete list of all item IDs that are allowed to
  676. // go in our toolbar. Any items you might want to add must be listed here. The
  677. // order in which they are listed will be used by the toolbar customisation panel.
  678. ids.add (doc_new);
  679. ids.add (doc_open);
  680. ids.add (doc_save);
  681. ids.add (doc_saveAs);
  682. ids.add (edit_copy);
  683. ids.add (edit_cut);
  684. ids.add (edit_paste);
  685. ids.add (juceLogoButton);
  686. ids.add (customComboBox);
  687. // If you're going to use separators, then they must also be added explicitly
  688. // to the list.
  689. ids.add (separatorBarId);
  690. ids.add (spacerId);
  691. ids.add (flexibleSpacerId);
  692. }
  693. void getDefaultItemSet (Array<int>& ids) override
  694. {
  695. // This returns an ordered list of the set of items that make up a
  696. // toolbar's default set. Not all items need to be on this list, and
  697. // items can appear multiple times (e.g. the separators used here).
  698. ids.add (doc_new);
  699. ids.add (doc_open);
  700. ids.add (doc_save);
  701. ids.add (doc_saveAs);
  702. ids.add (spacerId);
  703. ids.add (separatorBarId);
  704. ids.add (edit_copy);
  705. ids.add (edit_cut);
  706. ids.add (edit_paste);
  707. ids.add (separatorBarId);
  708. ids.add (flexibleSpacerId);
  709. ids.add (customComboBox);
  710. ids.add (flexibleSpacerId);
  711. ids.add (separatorBarId);
  712. ids.add (juceLogoButton);
  713. }
  714. ToolbarItemComponent* createItem (int itemId) override
  715. {
  716. switch (itemId)
  717. {
  718. case doc_new: return createButtonFromZipFileSVG (itemId, "new", "document-new.svg");
  719. case doc_open: return createButtonFromZipFileSVG (itemId, "open", "document-open.svg");
  720. case doc_save: return createButtonFromZipFileSVG (itemId, "save", "document-save.svg");
  721. case doc_saveAs: return createButtonFromZipFileSVG (itemId, "save as", "document-save-as.svg");
  722. case edit_copy: return createButtonFromZipFileSVG (itemId, "copy", "edit-copy.svg");
  723. case edit_cut: return createButtonFromZipFileSVG (itemId, "cut", "edit-cut.svg");
  724. case edit_paste: return createButtonFromZipFileSVG (itemId, "paste", "edit-paste.svg");
  725. case juceLogoButton:
  726. {
  727. auto drawable = std::make_unique<DrawableImage>();
  728. drawable->setImage (getImageFromAssets ("juce_icon.png"));
  729. return new ToolbarButton (itemId, "juce!", std::move (drawable), {});
  730. }
  731. case customComboBox: return new CustomToolbarComboBox (itemId);
  732. default: break;
  733. }
  734. return nullptr;
  735. }
  736. private:
  737. StringArray iconNames;
  738. OwnedArray<Drawable> iconsFromZipFile;
  739. // This is a little utility to create a button with one of the SVG images in
  740. // our embedded ZIP file "icons.zip"
  741. ToolbarButton* createButtonFromZipFileSVG (const int itemId, const String& text, const String& filename)
  742. {
  743. if (iconsFromZipFile.size() == 0)
  744. {
  745. // If we've not already done so, load all the images from the zip file..
  746. ZipFile icons (createAssetInputStream ("icons.zip").release(), true);
  747. for (int i = 0; i < icons.getNumEntries(); ++i)
  748. {
  749. std::unique_ptr<InputStream> svgFileStream (icons.createStreamForEntry (i));
  750. if (svgFileStream.get() != nullptr)
  751. {
  752. iconNames.add (icons.getEntry (i)->filename);
  753. iconsFromZipFile.add (Drawable::createFromImageDataStream (*svgFileStream));
  754. }
  755. }
  756. }
  757. auto* image = iconsFromZipFile[iconNames.indexOf (filename)];
  758. return new ToolbarButton (itemId, text, image->createCopy(), {});
  759. }
  760. // Demonstrates how to put a custom component into a toolbar - this one contains
  761. // a ComboBox.
  762. class CustomToolbarComboBox final : public ToolbarItemComponent
  763. {
  764. public:
  765. CustomToolbarComboBox (const int toolbarItemId)
  766. : ToolbarItemComponent (toolbarItemId, "Custom Toolbar Item", false)
  767. {
  768. addAndMakeVisible (comboBox);
  769. for (int i = 1; i < 20; ++i)
  770. comboBox.addItem ("Toolbar ComboBox item " + String (i), i);
  771. comboBox.setSelectedId (1);
  772. comboBox.setEditableText (true);
  773. }
  774. bool getToolbarItemSizes (int /*toolbarDepth*/, bool isVertical,
  775. int& preferredSize, int& minSize, int& maxSize) override
  776. {
  777. if (isVertical)
  778. return false;
  779. preferredSize = 250;
  780. minSize = 80;
  781. maxSize = 300;
  782. return true;
  783. }
  784. void paintButtonArea (Graphics&, int, int, bool, bool) override
  785. {
  786. }
  787. void contentAreaChanged (const Rectangle<int>& newArea) override
  788. {
  789. comboBox.setSize (newArea.getWidth() - 2,
  790. jmin (newArea.getHeight() - 2, 22));
  791. comboBox.setCentrePosition (newArea.getCentreX(), newArea.getCentreY());
  792. }
  793. private:
  794. ComboBox comboBox { "demo toolbar combo box" };
  795. };
  796. };
  797. DemoToolbarItemFactory factory;
  798. };
  799. //==============================================================================
  800. /**
  801. This class shows how to implement a TableListBoxModel to show in a TableListBox.
  802. */
  803. class TableDemoComponent final : public Component,
  804. public TableListBoxModel
  805. {
  806. public:
  807. TableDemoComponent()
  808. {
  809. // Load some data from an embedded XML file..
  810. loadData();
  811. // Create our table component and add it to this component..
  812. addAndMakeVisible (table);
  813. table.setModel (this);
  814. // give it a border
  815. table.setColour (ListBox::outlineColourId, Colours::grey);
  816. table.setOutlineThickness (1);
  817. // Add some columns to the table header, based on the column list in our database..
  818. for (auto* columnXml : columnList->getChildIterator())
  819. {
  820. table.getHeader().addColumn (columnXml->getStringAttribute ("name"),
  821. columnXml->getIntAttribute ("columnId"),
  822. columnXml->getIntAttribute ("width"),
  823. 50, 400,
  824. TableHeaderComponent::defaultFlags);
  825. }
  826. // we could now change some initial settings..
  827. table.getHeader().setSortColumnId (1, true); // sort forwards by the ID column
  828. table.getHeader().setColumnVisible (7, false); // hide the "length" column until the user shows it
  829. // un-comment this line to have a go of stretch-to-fit mode
  830. // table.getHeader().setStretchToFitActive (true);
  831. table.setMultipleSelectionEnabled (true);
  832. }
  833. // This is overloaded from TableListBoxModel, and must return the total number of rows in our table
  834. int getNumRows() override
  835. {
  836. return numRows;
  837. }
  838. // This is overloaded from TableListBoxModel, and should fill in the background of the whole row
  839. void paintRowBackground (Graphics& g, int rowNumber, int /*width*/, int /*height*/, bool rowIsSelected) override
  840. {
  841. auto alternateColour = getLookAndFeel().findColour (ListBox::backgroundColourId)
  842. .interpolatedWith (getLookAndFeel().findColour (ListBox::textColourId), 0.03f);
  843. if (rowIsSelected)
  844. g.fillAll (Colours::lightblue);
  845. else if (rowNumber % 2)
  846. g.fillAll (alternateColour);
  847. }
  848. // This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
  849. // components.
  850. void paintCell (Graphics& g, int rowNumber, int columnId,
  851. int width, int height, bool /*rowIsSelected*/) override
  852. {
  853. g.setColour (getLookAndFeel().findColour (ListBox::textColourId));
  854. g.setFont (font);
  855. if (auto* rowElement = dataList->getChildElement (rowNumber))
  856. {
  857. auto text = rowElement->getStringAttribute (getAttributeNameForColumnId (columnId));
  858. g.drawText (text, 2, 0, width - 4, height, Justification::centredLeft, true);
  859. }
  860. g.setColour (getLookAndFeel().findColour (ListBox::backgroundColourId));
  861. g.fillRect (width - 1, 0, 1, height);
  862. }
  863. // This is overloaded from TableListBoxModel, and tells us that the user has clicked a table header
  864. // to change the sort order.
  865. void sortOrderChanged (int newSortColumnId, bool isForwards) override
  866. {
  867. if (newSortColumnId != 0)
  868. {
  869. DemoDataSorter sorter (getAttributeNameForColumnId (newSortColumnId), isForwards);
  870. dataList->sortChildElements (sorter);
  871. table.updateContent();
  872. }
  873. }
  874. // This is overloaded from TableListBoxModel, and must update any custom components that we're using
  875. Component* refreshComponentForCell (int rowNumber, int columnId, bool /*isRowSelected*/,
  876. Component* existingComponentToUpdate) override
  877. {
  878. if (columnId == 1 || columnId == 7) // The ID and Length columns do not have a custom component
  879. {
  880. jassert (existingComponentToUpdate == nullptr);
  881. return nullptr;
  882. }
  883. if (columnId == 5) // For the ratings column, we return the custom combobox component
  884. {
  885. auto* ratingsBox = static_cast<RatingColumnCustomComponent*> (existingComponentToUpdate);
  886. // If an existing component is being passed-in for updating, we'll re-use it, but
  887. // if not, we'll have to create one.
  888. if (ratingsBox == nullptr)
  889. ratingsBox = new RatingColumnCustomComponent (*this);
  890. ratingsBox->setRowAndColumn (rowNumber, columnId);
  891. return ratingsBox;
  892. }
  893. // The other columns are editable text columns, for which we use the custom Label component
  894. auto* textLabel = static_cast<EditableTextCustomComponent*> (existingComponentToUpdate);
  895. // same as above...
  896. if (textLabel == nullptr)
  897. textLabel = new EditableTextCustomComponent (*this);
  898. textLabel->setRowAndColumn (rowNumber, columnId);
  899. return textLabel;
  900. }
  901. // This is overloaded from TableListBoxModel, and should choose the best width for the specified
  902. // column.
  903. int getColumnAutoSizeWidth (int columnId) override
  904. {
  905. if (columnId == 5)
  906. return 100; // (this is the ratings column, containing a custom combobox component)
  907. int widest = 32;
  908. // find the widest bit of text in this column..
  909. for (int i = getNumRows(); --i >= 0;)
  910. {
  911. if (auto* rowElement = dataList->getChildElement (i))
  912. {
  913. auto text = rowElement->getStringAttribute (getAttributeNameForColumnId (columnId));
  914. widest = jmax (widest, font.getStringWidth (text));
  915. }
  916. }
  917. return widest + 8;
  918. }
  919. // A couple of quick methods to set and get cell values when the user changes them
  920. int getRating (const int rowNumber) const
  921. {
  922. return dataList->getChildElement (rowNumber)->getIntAttribute ("Rating");
  923. }
  924. void setRating (const int rowNumber, const int newRating)
  925. {
  926. dataList->getChildElement (rowNumber)->setAttribute ("Rating", newRating);
  927. }
  928. String getText (const int columnNumber, const int rowNumber) const
  929. {
  930. return dataList->getChildElement (rowNumber)->getStringAttribute (getAttributeNameForColumnId (columnNumber));
  931. }
  932. void setText (const int columnNumber, const int rowNumber, const String& newText)
  933. {
  934. auto columnName = table.getHeader().getColumnName (columnNumber);
  935. dataList->getChildElement (rowNumber)->setAttribute (columnName, newText);
  936. }
  937. //==============================================================================
  938. void resized() override
  939. {
  940. // position our table with a gap around its edge
  941. table.setBoundsInset (BorderSize<int> (8));
  942. }
  943. private:
  944. TableListBox table; // the table component itself
  945. Font font { 14.0f };
  946. std::unique_ptr<XmlElement> demoData; // This is the XML document loaded from the embedded file "demo table data.xml"
  947. XmlElement* columnList = nullptr; // A pointer to the sub-node of demoData that contains the list of columns
  948. XmlElement* dataList = nullptr; // A pointer to the sub-node of demoData that contains the list of data rows
  949. int numRows; // The number of rows of data we've got
  950. //==============================================================================
  951. // This is a custom Label component, which we use for the table's editable text columns.
  952. class EditableTextCustomComponent final : public Label
  953. {
  954. public:
  955. EditableTextCustomComponent (TableDemoComponent& td) : owner (td)
  956. {
  957. // double click to edit the label text; single click handled below
  958. setEditable (false, true, false);
  959. }
  960. void mouseDown (const MouseEvent& event) override
  961. {
  962. // single click on the label should simply select the row
  963. owner.table.selectRowsBasedOnModifierKeys (row, event.mods, false);
  964. Label::mouseDown (event);
  965. }
  966. void textWasEdited() override
  967. {
  968. owner.setText (columnId, row, getText());
  969. }
  970. // Our demo code will call this when we may need to update our contents
  971. void setRowAndColumn (const int newRow, const int newColumn)
  972. {
  973. row = newRow;
  974. columnId = newColumn;
  975. setText (owner.getText (columnId, row), dontSendNotification);
  976. }
  977. void paint (Graphics& g) override
  978. {
  979. auto& lf = getLookAndFeel();
  980. if (! dynamic_cast<LookAndFeel_V4*> (&lf))
  981. lf.setColour (textColourId, Colours::black);
  982. Label::paint (g);
  983. }
  984. private:
  985. TableDemoComponent& owner;
  986. int row, columnId;
  987. Colour textColour;
  988. };
  989. //==============================================================================
  990. // This is a custom component containing a combo box, which we're going to put inside
  991. // our table's "rating" column.
  992. class RatingColumnCustomComponent final : public Component
  993. {
  994. public:
  995. RatingColumnCustomComponent (TableDemoComponent& td) : owner (td)
  996. {
  997. // just put a combo box inside this component
  998. addAndMakeVisible (comboBox);
  999. comboBox.addItem ("fab", 1);
  1000. comboBox.addItem ("groovy", 2);
  1001. comboBox.addItem ("hep", 3);
  1002. comboBox.addItem ("mad for it", 4);
  1003. comboBox.addItem ("neat", 5);
  1004. comboBox.addItem ("swingin", 6);
  1005. comboBox.addItem ("wild", 7);
  1006. comboBox.onChange = [this] { owner.setRating (row, comboBox.getSelectedId()); };
  1007. comboBox.setWantsKeyboardFocus (false);
  1008. }
  1009. void resized() override
  1010. {
  1011. comboBox.setBoundsInset (BorderSize<int> (2));
  1012. }
  1013. // Our demo code will call this when we may need to update our contents
  1014. void setRowAndColumn (int newRow, int newColumn)
  1015. {
  1016. row = newRow;
  1017. columnId = newColumn;
  1018. comboBox.setSelectedId (owner.getRating (row), dontSendNotification);
  1019. }
  1020. private:
  1021. TableDemoComponent& owner;
  1022. ComboBox comboBox;
  1023. int row, columnId;
  1024. };
  1025. //==============================================================================
  1026. // A comparator used to sort our data when the user clicks a column header
  1027. class DemoDataSorter
  1028. {
  1029. public:
  1030. DemoDataSorter (const String& attributeToSortBy, bool forwards)
  1031. : attributeToSort (attributeToSortBy),
  1032. direction (forwards ? 1 : -1)
  1033. {
  1034. }
  1035. int compareElements (XmlElement* first, XmlElement* second) const
  1036. {
  1037. auto result = first->getStringAttribute (attributeToSort)
  1038. .compareNatural (second->getStringAttribute (attributeToSort));
  1039. if (result == 0)
  1040. result = first->getStringAttribute ("ID")
  1041. .compareNatural (second->getStringAttribute ("ID"));
  1042. return direction * result;
  1043. }
  1044. private:
  1045. String attributeToSort;
  1046. int direction;
  1047. };
  1048. //==============================================================================
  1049. // this loads the embedded database XML file into memory
  1050. void loadData()
  1051. {
  1052. demoData = parseXML (loadEntireAssetIntoString ("demo table data.xml"));
  1053. dataList = demoData->getChildByName ("DATA");
  1054. columnList = demoData->getChildByName ("COLUMNS");
  1055. numRows = dataList->getNumChildElements();
  1056. }
  1057. // (a utility method to search our XML for the attribute that matches a column ID)
  1058. String getAttributeNameForColumnId (const int columnId) const
  1059. {
  1060. for (auto* columnXml : columnList->getChildIterator())
  1061. {
  1062. if (columnXml->getIntAttribute ("columnId") == columnId)
  1063. return columnXml->getStringAttribute ("name");
  1064. }
  1065. return {};
  1066. }
  1067. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableDemoComponent)
  1068. };
  1069. //==============================================================================
  1070. class DragAndDropDemo final : public Component,
  1071. public DragAndDropContainer
  1072. {
  1073. public:
  1074. DragAndDropDemo()
  1075. {
  1076. setName ("Drag-and-Drop");
  1077. sourceListBox.setModel (&sourceModel);
  1078. sourceListBox.setMultipleSelectionEnabled (true);
  1079. addAndMakeVisible (sourceListBox);
  1080. addAndMakeVisible (target);
  1081. }
  1082. void resized() override
  1083. {
  1084. auto r = getLocalBounds().reduced (8);
  1085. sourceListBox.setBounds (r.withSize (250, 180));
  1086. target .setBounds (r.removeFromBottom (150).removeFromRight (250));
  1087. }
  1088. private:
  1089. //==============================================================================
  1090. struct SourceItemListboxContents final : public ListBoxModel
  1091. {
  1092. // The following methods implement the necessary virtual functions from ListBoxModel,
  1093. // telling the listbox how many rows there are, painting them, etc.
  1094. int getNumRows() override
  1095. {
  1096. return 30;
  1097. }
  1098. void paintListBoxItem (int rowNumber, Graphics& g,
  1099. int width, int height, bool rowIsSelected) override
  1100. {
  1101. if (rowIsSelected)
  1102. g.fillAll (Colours::lightblue);
  1103. g.setColour (LookAndFeel::getDefaultLookAndFeel().findColour (Label::textColourId));
  1104. g.setFont ((float) height * 0.7f);
  1105. g.drawText ("Draggable Thing #" + String (rowNumber + 1),
  1106. 5, 0, width, height,
  1107. Justification::centredLeft, true);
  1108. }
  1109. var getDragSourceDescription (const SparseSet<int>& selectedRows) override
  1110. {
  1111. // for our drag description, we'll just make a comma-separated list of the selected row
  1112. // numbers - this will be picked up by the drag target and displayed in its box.
  1113. StringArray rows;
  1114. for (int i = 0; i < selectedRows.size(); ++i)
  1115. rows.add (String (selectedRows[i] + 1));
  1116. return rows.joinIntoString (", ");
  1117. }
  1118. };
  1119. //==============================================================================
  1120. // and this is a component that can have things dropped onto it..
  1121. class DragAndDropDemoTarget final : public Component,
  1122. public DragAndDropTarget,
  1123. public FileDragAndDropTarget,
  1124. public TextDragAndDropTarget
  1125. {
  1126. public:
  1127. DragAndDropDemoTarget() {}
  1128. void paint (Graphics& g) override
  1129. {
  1130. g.fillAll (Colours::green.withAlpha (0.2f));
  1131. // draw a red line around the comp if the user's currently dragging something over it..
  1132. if (somethingIsBeingDraggedOver)
  1133. {
  1134. g.setColour (Colours::red);
  1135. g.drawRect (getLocalBounds(), 3);
  1136. }
  1137. g.setColour (getLookAndFeel().findColour (Label::textColourId));
  1138. g.setFont (14.0f);
  1139. g.drawFittedText (message, getLocalBounds().reduced (10, 0), Justification::centred, 4);
  1140. }
  1141. //==============================================================================
  1142. // These methods implement the DragAndDropTarget interface, and allow our component
  1143. // to accept drag-and-drop of objects from other JUCE components..
  1144. bool isInterestedInDragSource (const SourceDetails& /*dragSourceDetails*/) override
  1145. {
  1146. // normally you'd check the sourceDescription value to see if it's the
  1147. // sort of object that you're interested in before returning true, but for
  1148. // the demo, we'll say yes to anything..
  1149. return true;
  1150. }
  1151. void itemDragEnter (const SourceDetails& /*dragSourceDetails*/) override
  1152. {
  1153. somethingIsBeingDraggedOver = true;
  1154. repaint();
  1155. }
  1156. void itemDragMove (const SourceDetails& /*dragSourceDetails*/) override
  1157. {
  1158. }
  1159. void itemDragExit (const SourceDetails& /*dragSourceDetails*/) override
  1160. {
  1161. somethingIsBeingDraggedOver = false;
  1162. repaint();
  1163. }
  1164. void itemDropped (const SourceDetails& dragSourceDetails) override
  1165. {
  1166. message = "Items dropped: " + dragSourceDetails.description.toString();
  1167. somethingIsBeingDraggedOver = false;
  1168. repaint();
  1169. }
  1170. //==============================================================================
  1171. // These methods implement the FileDragAndDropTarget interface, and allow our component
  1172. // to accept drag-and-drop of files..
  1173. bool isInterestedInFileDrag (const StringArray& /*files*/) override
  1174. {
  1175. // normally you'd check these files to see if they're something that you're
  1176. // interested in before returning true, but for the demo, we'll say yes to anything..
  1177. return true;
  1178. }
  1179. void fileDragEnter (const StringArray& /*files*/, int /*x*/, int /*y*/) override
  1180. {
  1181. somethingIsBeingDraggedOver = true;
  1182. repaint();
  1183. }
  1184. void fileDragMove (const StringArray& /*files*/, int /*x*/, int /*y*/) override
  1185. {
  1186. }
  1187. void fileDragExit (const StringArray& /*files*/) override
  1188. {
  1189. somethingIsBeingDraggedOver = false;
  1190. repaint();
  1191. }
  1192. void filesDropped (const StringArray& files, int /*x*/, int /*y*/) override
  1193. {
  1194. message = "Files dropped: " + files.joinIntoString ("\n");
  1195. somethingIsBeingDraggedOver = false;
  1196. repaint();
  1197. }
  1198. //==============================================================================
  1199. // These methods implement the TextDragAndDropTarget interface, and allow our component
  1200. // to accept drag-and-drop of text..
  1201. bool isInterestedInTextDrag (const String& /*text*/) override
  1202. {
  1203. return true;
  1204. }
  1205. void textDragEnter (const String& /*text*/, int /*x*/, int /*y*/) override
  1206. {
  1207. somethingIsBeingDraggedOver = true;
  1208. repaint();
  1209. }
  1210. void textDragMove (const String& /*text*/, int /*x*/, int /*y*/) override
  1211. {
  1212. }
  1213. void textDragExit (const String& /*text*/) override
  1214. {
  1215. somethingIsBeingDraggedOver = false;
  1216. repaint();
  1217. }
  1218. void textDropped (const String& text, int /*x*/, int /*y*/) override
  1219. {
  1220. message = "Text dropped:\n" + text;
  1221. somethingIsBeingDraggedOver = false;
  1222. repaint();
  1223. }
  1224. private:
  1225. String message { "Drag-and-drop some rows from the top-left box onto this component!\n\n"
  1226. "You can also drag-and-drop files and text from other apps"};
  1227. bool somethingIsBeingDraggedOver = false;
  1228. };
  1229. //==============================================================================
  1230. ListBox sourceListBox { "D+D source", nullptr };
  1231. SourceItemListboxContents sourceModel;
  1232. DragAndDropDemoTarget target;
  1233. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragAndDropDemo)
  1234. };
  1235. //==============================================================================
  1236. struct DemoTabbedComponent final : public TabbedComponent
  1237. {
  1238. DemoTabbedComponent (bool isRunningComponenTransformsDemo)
  1239. : TabbedComponent (TabbedButtonBar::TabsAtTop)
  1240. {
  1241. auto colour = findColour (ResizableWindow::backgroundColourId);
  1242. addTab ("Buttons", colour, new ButtonsPage (isRunningComponenTransformsDemo), true);
  1243. addTab ("Sliders", colour, new SlidersPage(), true);
  1244. addTab ("Toolbars", colour, new ToolbarDemoComp(), true);
  1245. addTab ("Misc", colour, new MiscPage(), true);
  1246. addTab ("Menus", colour, new MenuPage(), true);
  1247. addTab ("Tables", colour, new TableDemoComponent(), true);
  1248. addTab ("Drag & Drop", colour, new DragAndDropDemo(), true);
  1249. getTabbedButtonBar().getTabButton (5)->setExtraComponent (new CustomTabButton (isRunningComponenTransformsDemo),
  1250. TabBarButton::afterText);
  1251. }
  1252. // This is a small star button that is put inside one of the tabs. You can
  1253. // use this technique to create things like "close tab" buttons, etc.
  1254. class CustomTabButton final : public Component
  1255. {
  1256. public:
  1257. CustomTabButton (bool isRunningComponenTransformsDemo)
  1258. : runningComponenTransformsDemo (isRunningComponenTransformsDemo)
  1259. {
  1260. setSize (20, 20);
  1261. }
  1262. void paint (Graphics& g) override
  1263. {
  1264. Path star;
  1265. star.addStar ({}, 7, 1.0f, 2.0f);
  1266. g.setColour (Colours::green);
  1267. g.fillPath (star, star.getTransformToScaleToFit (getLocalBounds().reduced (2).toFloat(), true));
  1268. }
  1269. void mouseDown (const MouseEvent&) override
  1270. {
  1271. showBubbleMessage (*this,
  1272. "This is a custom tab component\n"
  1273. "\n"
  1274. "You can use these to implement things like close-buttons "
  1275. "or status displays for your tabs.",
  1276. bubbleMessage,
  1277. runningComponenTransformsDemo);
  1278. }
  1279. private:
  1280. bool runningComponenTransformsDemo;
  1281. std::unique_ptr<BubbleMessageComponent> bubbleMessage;
  1282. };
  1283. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoTabbedComponent)
  1284. };
  1285. //==============================================================================
  1286. struct WidgetsDemo final : public Component
  1287. {
  1288. WidgetsDemo (bool isRunningComponenTransformsDemo = false)
  1289. : tabs (isRunningComponenTransformsDemo)
  1290. {
  1291. setOpaque (true);
  1292. addAndMakeVisible (tabs);
  1293. setSize (700, 500);
  1294. }
  1295. void paint (Graphics& g) override
  1296. {
  1297. g.fillAll (Colours::lightgrey);
  1298. }
  1299. void resized() override
  1300. {
  1301. tabs.setBounds (getLocalBounds().reduced (4));
  1302. }
  1303. DemoTabbedComponent tabs;
  1304. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WidgetsDemo)
  1305. };
  1306. //==============================================================================
  1307. void showBubbleMessage (Component& targetComponent, const String& textToShow,
  1308. std::unique_ptr<BubbleMessageComponent>& bmc,
  1309. bool isRunningComponentTransformDemo)
  1310. {
  1311. bmc.reset (new BubbleMessageComponent());
  1312. if (isRunningComponentTransformDemo)
  1313. {
  1314. targetComponent.findParentComponentOfClass<WidgetsDemo>()->addChildComponent (bmc.get());
  1315. }
  1316. else if (Desktop::canUseSemiTransparentWindows())
  1317. {
  1318. bmc->setAlwaysOnTop (true);
  1319. bmc->addToDesktop (0);
  1320. }
  1321. else
  1322. {
  1323. targetComponent.getTopLevelComponent()->addChildComponent (bmc.get());
  1324. }
  1325. AttributedString text (textToShow);
  1326. text.setJustification (Justification::centred);
  1327. text.setColour (targetComponent.findColour (TextButton::textColourOffId));
  1328. bmc->showAt (&targetComponent, text, 2000, true, false);
  1329. }