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.

1706 lines
64KB

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