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.

1483 lines
60KB

  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: AccessibilityDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Simple UI demonstrating accessibility features on supported
  24. platforms.
  25. dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
  26. juce_gui_basics
  27. exporters: xcode_mac, vs2022, androidstudio, xcode_iphone
  28. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  29. type: Component
  30. mainClass: AccessibilityDemo
  31. useLocalCopy: 1
  32. END_JUCE_PIP_METADATA
  33. *******************************************************************************/
  34. #pragma once
  35. #include "../Assets/DemoUtilities.h"
  36. //==============================================================================
  37. /**
  38. A simple holder component with some content, a title and an info tooltip
  39. containing a brief description.
  40. This component sets its accessibility title and help text properties and
  41. also acts as a focus container for its children.
  42. */
  43. class ContentComponent : public Component
  44. {
  45. public:
  46. ContentComponent (const String& title, const String& info, Component& contentToDisplay)
  47. : titleLabel ({}, title),
  48. content (contentToDisplay)
  49. {
  50. addAndMakeVisible (titleLabel);
  51. addAndMakeVisible (infoIcon);
  52. setTitle (title);
  53. setDescription (info);
  54. setFocusContainerType (FocusContainerType::focusContainer);
  55. infoIcon.setTooltip (info);
  56. infoIcon.setHelpText (info);
  57. addAndMakeVisible (content);
  58. }
  59. void paint (Graphics& g) override
  60. {
  61. g.setColour (Colours::black);
  62. g.drawRoundedRectangle (getLocalBounds().reduced (2).toFloat(), 5.0f, 3.0f);
  63. }
  64. void resized() override
  65. {
  66. auto bounds = getLocalBounds().reduced (5);
  67. auto topArea = bounds.removeFromTop (30);
  68. infoIcon.setBounds (topArea.removeFromLeft (30).reduced (5));
  69. titleLabel.setBounds (topArea.reduced (5));
  70. content.setBounds (bounds);
  71. }
  72. private:
  73. //==============================================================================
  74. struct InfoIcon : public Component,
  75. public SettableTooltipClient
  76. {
  77. InfoIcon()
  78. {
  79. constexpr uint8 infoPathData[] =
  80. {
  81. 110,109,0,0,122,67,0,0,0,0,98,79,35,224,66,0,0,0,0,0,0,0,0,79,35,224,66,0,0,0,0,0,0,122,67,98,0,0,
  82. 0,0,44,247,193,67,79,35,224,66,0,0,250,67,0,0,122,67,0,0,250,67,98,44,247,193,67,0,0,250,67,0,0,
  83. 250,67,44,247,193,67,0,0,250,67,0,0,122,67,98,0,0,250,67,79,35,224,66,44,247,193,67,0,0,0,0,0,0,122,
  84. 67,0,0,0,0,99,109,114,79,101,67,79,35,224,66,108,71,88,135,67,79,35,224,66,108,71,88,135,67,132,229,
  85. 28,67,108,116,79,101,67,132,229,28,67,108,116,79,101,67,79,35,224,66,99,109,79,35,149,67,106,132,
  86. 190,67,108,98,185,123,67,106,132,190,67,98,150,123,106,67,106,132,190,67,176,220,97,67,168,17,187,
  87. 67,176,220,97,67,18,150,177,67,108,176,220,97,67,248,52,108,67,98,176,220,97,67,212,8,103,67,238,
  88. 105,94,67,18,150,99,67,204,61,89,67,18,150,99,67,108,98,185,73,67,18,150,99,67,108,98,185,73,67,88,
  89. 238,59,67,108,160,70,120,67,88,238,59,67,98,54,194,132,67,88,238,59,67,169,17,137,67,60,141,68,67,
  90. 169,17,137,67,8,203,85,67,108,169,17,137,67,26,97,166,67,98,169,17,137,67,43,247,168,67,10,203,138,
  91. 67,141,176,170,67,27,97,141,67,141,176,170,67,108,80,35,149,67,141,176,170,67,108,80,35,149,67,106,
  92. 132,190,67,99,101,0,0
  93. };
  94. infoPath.loadPathFromData (infoPathData, numElementsInArray (infoPathData));
  95. setTitle ("Info");
  96. }
  97. void paint (Graphics& g) override
  98. {
  99. auto bounds = getLocalBounds().toFloat().reduced (2);
  100. g.setColour (Colours::white);
  101. g.fillPath (infoPath, RectanglePlacement (RectanglePlacement::centred)
  102. .getTransformToFit (infoPath.getBounds(), bounds));
  103. }
  104. Path infoPath;
  105. };
  106. //==============================================================================
  107. Label titleLabel;
  108. InfoIcon infoIcon;
  109. Component& content;
  110. //==============================================================================
  111. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentComponent)
  112. };
  113. //==============================================================================
  114. /**
  115. The top-level component containing the accessible JUCE widget examples.
  116. Most JUCE UI elements have built-in accessibility support and will be
  117. visible and controllable by accessibility clients. There are a few examples
  118. of some widgets in this demo such as Sliders, Buttons and a TreeView.
  119. */
  120. class JUCEWidgetsComponent : public Component
  121. {
  122. public:
  123. JUCEWidgetsComponent()
  124. {
  125. setTitle ("JUCE Widgets");
  126. setDescription ("A demo of a few of the accessible built-in JUCE widgets.");
  127. setFocusContainerType (FocusContainerType::focusContainer);
  128. addAndMakeVisible (descriptionLabel);
  129. addAndMakeVisible (buttons);
  130. addAndMakeVisible (sliders);
  131. addAndMakeVisible (treeView);
  132. }
  133. void resized() override
  134. {
  135. Grid grid;
  136. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (2)) };
  137. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)) };
  138. grid.items = { GridItem (descriptionLabel).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }),
  139. GridItem (buttons).withMargin ({ 2 }),
  140. GridItem (sliders).withMargin ({ 2 }),
  141. GridItem (treeView).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }) };
  142. grid.performLayout (getLocalBounds());
  143. }
  144. private:
  145. //==============================================================================
  146. class ButtonsComponent : public Component
  147. {
  148. public:
  149. ButtonsComponent()
  150. {
  151. addAndMakeVisible (radioButtons);
  152. textButton.setHasFocusOutline (true);
  153. addAndMakeVisible (textButton);
  154. shapeButton.setShape (getJUCELogoPath(), false, true, false);
  155. shapeButton.onClick = [] { AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon, "Alert", "This is an AlertWindow"); };
  156. shapeButton.setHasFocusOutline (true);
  157. addAndMakeVisible (shapeButton);
  158. }
  159. void resized() override
  160. {
  161. auto bounds = getLocalBounds();
  162. radioButtons.setBounds (bounds.removeFromLeft (bounds.getWidth() / 2).reduced (5));
  163. textButton.setBounds (bounds.removeFromTop (bounds.getHeight() / 2).reduced (5));
  164. shapeButton.setBounds (bounds.reduced (5));
  165. }
  166. private:
  167. //==============================================================================
  168. struct RadioButtonsGroupComponent : public Component
  169. {
  170. RadioButtonsGroupComponent()
  171. {
  172. int index = 1;
  173. for (auto& b : radioButtons)
  174. {
  175. b.setRadioGroupId (1);
  176. b.setButtonText ("Button " + String (index++));
  177. b.setHasFocusOutline (true);
  178. addAndMakeVisible (b);
  179. }
  180. radioButtons[(size_t) Random::getSystemRandom().nextInt (numRadioButtons)].setToggleState (true, dontSendNotification);
  181. setTitle ("Radio Buttons Group");
  182. setFocusContainerType (FocusContainerType::focusContainer);
  183. }
  184. void resized() override
  185. {
  186. auto bounds = getLocalBounds();
  187. const auto height = bounds.getHeight() / numRadioButtons;
  188. for (auto& b : radioButtons)
  189. b.setBounds (bounds.removeFromTop (height).reduced (2));
  190. }
  191. static constexpr int numRadioButtons = 3;
  192. std::array<ToggleButton, numRadioButtons> radioButtons;
  193. };
  194. //==============================================================================
  195. RadioButtonsGroupComponent radioButtons;
  196. TextButton textButton { "Press me!" };
  197. ShapeButton shapeButton { "Pressable JUCE Logo",
  198. Colours::darkorange,
  199. Colours::darkorange.brighter (0.5f),
  200. Colours::darkorange.brighter (0.75f) };
  201. //==============================================================================
  202. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonsComponent)
  203. };
  204. //==============================================================================
  205. class SlidersComponent : public Component
  206. {
  207. public:
  208. SlidersComponent()
  209. {
  210. auto setUpSlider = [this] (Slider& slider, Slider::SliderStyle style,
  211. double start, double end, double interval)
  212. {
  213. slider.setSliderStyle (style);
  214. slider.setRange (start, end, interval);
  215. if (style != Slider::IncDecButtons)
  216. slider.setTextBoxStyle (Slider::NoTextBox, false, 0, 0);
  217. slider.setValue (start + (end - start) * Random::getSystemRandom().nextDouble());
  218. addAndMakeVisible (slider);
  219. };
  220. setUpSlider (horizontalSlider, Slider::LinearHorizontal, 1.0, 100.0, 1.0);
  221. setUpSlider (incDecSlider, Slider::IncDecButtons, 1.0, 10.0, 1.0);
  222. for (auto& rotary : rotarySliders)
  223. setUpSlider (rotary, Slider::Rotary, 1.0, 10.0, 1.0);
  224. }
  225. void resized() override
  226. {
  227. Grid grid;
  228. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (2)) };
  229. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)),
  230. Grid::TrackInfo (Grid::Fr (1)),
  231. Grid::TrackInfo (Grid::Fr (1)) };
  232. grid.items = { GridItem (horizontalSlider).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }),
  233. GridItem (incDecSlider).withMargin ({ 2 }) };
  234. for (auto& rotary : rotarySliders)
  235. grid.items.add (GridItem (rotary).withMargin ({ 2 }));
  236. grid.performLayout (getLocalBounds());
  237. }
  238. private:
  239. Slider horizontalSlider, incDecSlider;
  240. std::array<Slider, 3> rotarySliders;
  241. //==============================================================================
  242. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlidersComponent)
  243. };
  244. //==============================================================================
  245. class TreeViewComponent : public Component
  246. {
  247. public:
  248. TreeViewComponent()
  249. {
  250. tree.setRootItem (&root);
  251. tree.setRootItemVisible (false);
  252. addAndMakeVisible (tree);
  253. }
  254. void resized() override
  255. {
  256. tree.setBounds (getLocalBounds());
  257. }
  258. private:
  259. //==============================================================================
  260. struct RootItem : public TreeViewItem
  261. {
  262. RootItem()
  263. {
  264. struct Item : public TreeViewItem
  265. {
  266. Item (int index, int depth, int numSubItems)
  267. : textToDisplay ("Item " + String (index)
  268. + ". Depth: " + String (depth)
  269. + ". Num sub-items: " + String (numSubItems))
  270. {
  271. for (int i = 0; i < numSubItems; ++i)
  272. addSubItem (new Item (i,
  273. depth + 1,
  274. Random::getSystemRandom().nextInt (jmax (0, 5 - depth))));
  275. }
  276. bool mightContainSubItems() override
  277. {
  278. return getNumSubItems() > 0;
  279. }
  280. void paintItem (Graphics& g, int width, int height) override
  281. {
  282. if (isSelected())
  283. {
  284. g.setColour (Colours::yellow.withAlpha (0.3f));
  285. g.fillRect (0, 0, width, height);
  286. }
  287. g.setColour (Colours::black);
  288. g.drawRect (0, height - 1, width, 1);
  289. g.setColour (Colours::white);
  290. g.drawText (textToDisplay, 0, 0, width, height, Justification::centredLeft);
  291. }
  292. String getAccessibilityName() override { return textToDisplay; }
  293. const String textToDisplay;
  294. };
  295. for (int i = 0; i < 10; ++i)
  296. addSubItem (new Item (i, 0, Random::getSystemRandom().nextInt (10)));
  297. }
  298. bool mightContainSubItems() override
  299. {
  300. return true;
  301. }
  302. };
  303. //==============================================================================
  304. TreeView tree;
  305. RootItem root;
  306. //==============================================================================
  307. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewComponent)
  308. };
  309. //==============================================================================
  310. Label descriptionLabel { {}, "This is a demo of a few of the accessible built-in JUCE widgets.\n\n"
  311. "To navigate this demo with a screen reader, either enable VoiceOver on macOS and iOS, "
  312. "TalkBack on Android, or Narrator on Windows and follow the navigational prompts." };
  313. ButtonsComponent buttonsComponent;
  314. SlidersComponent slidersComponent;
  315. TreeViewComponent treeViewComponent;
  316. ContentComponent buttons { "Buttons",
  317. "Examples of some JUCE buttons.",
  318. buttonsComponent };
  319. ContentComponent sliders { "Sliders",
  320. "Examples of some JUCE sliders.",
  321. slidersComponent };
  322. ContentComponent treeView { "TreeView",
  323. "A JUCE TreeView.",
  324. treeViewComponent };
  325. //==============================================================================
  326. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JUCEWidgetsComponent)
  327. };
  328. struct NameAndRole
  329. {
  330. const char* name;
  331. AccessibilityRole role;
  332. };
  333. constexpr NameAndRole accessibilityRoles[]
  334. {
  335. { "Ignored", AccessibilityRole::ignored },
  336. { "Unspecified", AccessibilityRole::unspecified },
  337. { "Button", AccessibilityRole::button },
  338. { "Toggle", AccessibilityRole::toggleButton },
  339. { "ComboBox", AccessibilityRole::comboBox },
  340. { "Slider", AccessibilityRole::slider },
  341. { "Static Text", AccessibilityRole::staticText },
  342. { "Editable Text", AccessibilityRole::editableText },
  343. { "Image", AccessibilityRole::image },
  344. { "Group", AccessibilityRole::group },
  345. { "Window", AccessibilityRole::window }
  346. };
  347. //==============================================================================
  348. /**
  349. The top-level component containing a customisable accessible widget.
  350. The AccessibleComponent class just draws a JUCE logo and overrides the
  351. Component::createAccessibilityHandler() method to return a custom AccessibilityHandler.
  352. The properties of this handler are set by the various controls in the demo.
  353. */
  354. class CustomWidgetComponent : public Component
  355. {
  356. public:
  357. CustomWidgetComponent()
  358. {
  359. setTitle ("Custom Widget");
  360. setDescription ("A demo of a customisable accessible widget.");
  361. setFocusContainerType (FocusContainerType::focusContainer);
  362. addAndMakeVisible (descriptionLabel);
  363. addAndMakeVisible (infoComponent);
  364. addAndMakeVisible (actionsComponent);
  365. addAndMakeVisible (valueInterfaceComponent);
  366. addAndMakeVisible (stateComponent);
  367. addAndMakeVisible (accessibleComponent);
  368. }
  369. void resized() override
  370. {
  371. Grid grid;
  372. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)),
  373. Grid::TrackInfo (Grid::Fr (2)),
  374. Grid::TrackInfo (Grid::Fr (2)),
  375. Grid::TrackInfo (Grid::Fr (2)) };
  376. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)) };
  377. grid.items = { GridItem (descriptionLabel).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }),
  378. GridItem (infoComponent).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }),
  379. GridItem (actionsComponent).withMargin ({ 2 }),
  380. GridItem (valueInterfaceComponent).withMargin ({ 2 }),
  381. GridItem (stateComponent).withMargin ({ 2 }),
  382. GridItem (accessibleComponent).withMargin ({ 10 }) };
  383. grid.performLayout (getLocalBounds());
  384. }
  385. private:
  386. //==============================================================================
  387. class AccessibleComponent : public Component
  388. {
  389. public:
  390. explicit AccessibleComponent (CustomWidgetComponent& owner)
  391. : customWidgetComponent (owner)
  392. {
  393. }
  394. void paint (Graphics& g) override
  395. {
  396. g.setColour (Colours::darkorange);
  397. g.fillPath (juceLogoPath,
  398. juceLogoPath.getTransformToScaleToFit (getLocalBounds().toFloat(), true));
  399. }
  400. std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
  401. {
  402. /**
  403. The AccessibilityHandler class is the interface between JUCE components
  404. and accessibility clients. This derived class represents the properties
  405. set via the demo UI.
  406. */
  407. struct CustomAccessibilityHandler : public AccessibilityHandler
  408. {
  409. explicit CustomAccessibilityHandler (CustomWidgetComponent& comp)
  410. : AccessibilityHandler (comp.accessibleComponent,
  411. comp.infoComponent.getRole(),
  412. comp.actionsComponent.getActions(),
  413. { comp.valueInterfaceComponent.getValueInterface() }),
  414. customWidgetComponent (comp)
  415. {
  416. }
  417. String getTitle() const override { return customWidgetComponent.infoComponent.getTitle(); }
  418. String getDescription() const override { return customWidgetComponent.infoComponent.getDescription(); }
  419. String getHelp() const override { return customWidgetComponent.infoComponent.getHelp(); }
  420. AccessibleState getCurrentState() const override { return customWidgetComponent.stateComponent.getAccessibleState(); }
  421. CustomWidgetComponent& customWidgetComponent;
  422. };
  423. return std::make_unique<CustomAccessibilityHandler> (customWidgetComponent);
  424. }
  425. private:
  426. CustomWidgetComponent& customWidgetComponent;
  427. Path juceLogoPath { getJUCELogoPath() };
  428. //==============================================================================
  429. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleComponent)
  430. };
  431. //==============================================================================
  432. class InfoComponent : public Component
  433. {
  434. public:
  435. explicit InfoComponent (CustomWidgetComponent& owner)
  436. {
  437. titleEditor.setText ("Custom");
  438. descriptionEditor.setText ("A short description of the custom component.");
  439. helpEditor.setText ("Some help text for the custom component.");
  440. titleEditor.onTextChange = [&owner]
  441. {
  442. if (auto* handler = owner.accessibleComponent.getAccessibilityHandler())
  443. handler->notifyAccessibilityEvent (AccessibilityEvent::titleChanged);
  444. };
  445. for (auto* editor : { &descriptionEditor, &helpEditor })
  446. {
  447. editor->setMultiLine (true);
  448. editor->setReturnKeyStartsNewLine (true);
  449. editor->setJustification (Justification::centredLeft);
  450. }
  451. addAndMakeVisible (titleLabel);
  452. addAndMakeVisible (titleEditor);
  453. addAndMakeVisible (descriptionLabel);
  454. addAndMakeVisible (descriptionEditor);
  455. addAndMakeVisible (helpLabel);
  456. addAndMakeVisible (helpEditor);
  457. setUpAccessibilityRoleSelector (owner);
  458. addAndMakeVisible (roleBox);
  459. addAndMakeVisible (roleLabel);
  460. }
  461. void resized() override
  462. {
  463. Grid grid;
  464. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (3)) };
  465. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)),
  466. Grid::TrackInfo (Grid::Fr (1)),
  467. Grid::TrackInfo (Grid::Fr (1)),
  468. Grid::TrackInfo (Grid::Fr (1)),
  469. Grid::TrackInfo (Grid::Fr (1)),
  470. Grid::TrackInfo (Grid::Fr (1)) };
  471. grid.items = { GridItem (titleLabel).withMargin ({ 2 }),
  472. GridItem (titleEditor).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }),
  473. GridItem (roleLabel).withMargin ({ 2 }),
  474. GridItem (roleBox).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }),
  475. GridItem (descriptionLabel).withMargin ({ 2 }),
  476. GridItem (descriptionEditor).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }),
  477. GridItem (helpLabel).withMargin ({ 2 }),
  478. GridItem (helpEditor).withMargin ({ 2 }).withColumn ({ GridItem::Span (2), {} }) };
  479. grid.performLayout (getLocalBounds());
  480. }
  481. AccessibilityRole getRole() const noexcept { return accessibilityRoles[(size_t) roleBox.getSelectedItemIndex()].role; }
  482. String getTitle() const noexcept { return titleEditor.getText(); }
  483. String getDescription() const noexcept { return descriptionEditor.getText(); }
  484. String getHelp() const noexcept { return helpEditor.getText(); }
  485. private:
  486. void setUpAccessibilityRoleSelector (CustomWidgetComponent& owner)
  487. {
  488. int itemId = 1;
  489. for (const auto& nameAndRole : accessibilityRoles)
  490. roleBox.addItem (nameAndRole.name, itemId++);
  491. roleBox.setSelectedItemIndex (1);
  492. roleBox.onChange = [&owner] { owner.accessibleComponent.invalidateAccessibilityHandler(); };
  493. }
  494. //==============================================================================
  495. Label titleLabel { {}, "Title" }, descriptionLabel { {}, "Description" }, helpLabel { {}, "Help" };
  496. TextEditor titleEditor, descriptionEditor, helpEditor;
  497. Label roleLabel { {}, "Role" };
  498. ComboBox roleBox;
  499. //==============================================================================
  500. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InfoComponent)
  501. };
  502. //==============================================================================
  503. class ActionsComponent : public Component
  504. {
  505. public:
  506. explicit ActionsComponent (CustomWidgetComponent& owner)
  507. : customWidgetComponent (owner)
  508. {
  509. addAndMakeVisible (press);
  510. addAndMakeVisible (toggle);
  511. addAndMakeVisible (focus);
  512. addAndMakeVisible (showMenu);
  513. }
  514. void resized() override
  515. {
  516. Grid grid;
  517. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)) };
  518. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)) };
  519. grid.items = { GridItem (press).withMargin (2), GridItem (toggle).withMargin (2),
  520. GridItem (focus).withMargin (2), GridItem (showMenu).withMargin (2), };
  521. grid.performLayout (getLocalBounds());
  522. }
  523. AccessibilityActions getActions() noexcept
  524. {
  525. AccessibilityActions result;
  526. if (press.isActionEnabled()) result.addAction (AccessibilityActionType::press, [this] { press.onAction(); });
  527. if (toggle.isActionEnabled()) result.addAction (AccessibilityActionType::toggle, [this] { toggle.onAction(); });
  528. if (focus.isActionEnabled()) result.addAction (AccessibilityActionType::focus, [this] { focus.onAction(); });
  529. if (showMenu.isActionEnabled()) result.addAction (AccessibilityActionType::showMenu, [this] { showMenu.onAction(); });
  530. return result;
  531. }
  532. private:
  533. //==============================================================================
  534. class AccessibilityActionComponent : public Component,
  535. private Timer
  536. {
  537. public:
  538. AccessibilityActionComponent (CustomWidgetComponent& owner,
  539. const String& actionName,
  540. bool initialState)
  541. {
  542. enabledToggle.setButtonText (actionName);
  543. enabledToggle.onClick = [&owner] { owner.accessibleComponent.invalidateAccessibilityHandler(); };
  544. enabledToggle.setToggleState (initialState, dontSendNotification);
  545. addAndMakeVisible (enabledToggle);
  546. }
  547. void resized() override
  548. {
  549. auto bounds = getLocalBounds();
  550. flashArea = bounds.removeFromRight (bounds.getHeight()).reduced (5);
  551. bounds.removeFromRight (5);
  552. enabledToggle.setBounds (bounds);
  553. }
  554. void paint (Graphics& g) override
  555. {
  556. g.setColour (flashColour);
  557. g.fillRoundedRectangle (flashArea.toFloat(), 5.0f);
  558. }
  559. void onAction()
  560. {
  561. if (isTimerRunning())
  562. reset();
  563. startTime = Time::getMillisecondCounter();
  564. startTimer (5);
  565. }
  566. bool isActionEnabled() const noexcept { return enabledToggle.getToggleState(); }
  567. private:
  568. void timerCallback() override
  569. {
  570. const auto alpha = [this]
  571. {
  572. const auto progress = static_cast<float> (Time::getMillisecondCounter() - startTime) / (flashTimeMs / 2);
  573. return progress > 1.0f ? 2.0f - progress
  574. : progress;
  575. }();
  576. if (alpha < 0.0f)
  577. {
  578. reset();
  579. return;
  580. }
  581. flashColour = defaultColour.overlaidWith (Colours::yellow.withAlpha (alpha));
  582. repaint();
  583. }
  584. void reset()
  585. {
  586. stopTimer();
  587. flashColour = defaultColour;
  588. repaint();
  589. }
  590. //==============================================================================
  591. static constexpr int flashTimeMs = 500;
  592. ToggleButton enabledToggle;
  593. Rectangle<int> flashArea;
  594. uint32 startTime = 0;
  595. Colour defaultColour = Colours::lightgrey, flashColour = defaultColour;
  596. //==============================================================================
  597. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityActionComponent)
  598. };
  599. //==============================================================================
  600. CustomWidgetComponent& customWidgetComponent;
  601. AccessibilityActionComponent press { customWidgetComponent, "Press", true },
  602. toggle { customWidgetComponent, "Toggle", false },
  603. focus { customWidgetComponent, "Focus", true },
  604. showMenu { customWidgetComponent, "Show menu", false };
  605. //==============================================================================
  606. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActionsComponent)
  607. };
  608. //==============================================================================
  609. class ValueInterfaceComponent : public Component
  610. {
  611. public:
  612. explicit ValueInterfaceComponent (CustomWidgetComponent& owner)
  613. : customWidgetComponent (owner)
  614. {
  615. valueTypeBox.addItemList ({ "Numeric", "Ranged", "Text" }, 1);
  616. valueTypeBox.setSelectedId (1);
  617. addAndMakeVisible (valueTypeBox);
  618. valueTypeBox.onChange = [this]
  619. {
  620. updateValueUI();
  621. customWidgetComponent.accessibleComponent.invalidateAccessibilityHandler();
  622. };
  623. addAndMakeVisible (readOnlyToggle);
  624. numericValueEditor.setInputRestrictions (10, "0123456789.");
  625. addChildComponent (numericValueEditor);
  626. addChildComponent (rangedValueComponent);
  627. addChildComponent (textValueEditor);
  628. updateValueUI();
  629. }
  630. void resized() override
  631. {
  632. Grid grid;
  633. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (2)) };
  634. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (2)), Grid::TrackInfo (Grid::Fr (1)) };
  635. auto& valueEditComponent = [this]() -> Component&
  636. {
  637. if (numericValueEditor.isVisible()) return numericValueEditor;
  638. if (rangedValueComponent.isVisible()) return rangedValueComponent;
  639. if (textValueEditor.isVisible()) return textValueEditor;
  640. jassertfalse;
  641. return numericValueEditor;
  642. }();
  643. grid.items = { GridItem (valueTypeBox).withMargin (2), GridItem (readOnlyToggle).withMargin (2),
  644. GridItem (valueEditComponent).withMargin (2).withColumn ({ GridItem::Span (2), {} }), };
  645. grid.performLayout (getLocalBounds());
  646. }
  647. std::unique_ptr<AccessibilityValueInterface> getValueInterface()
  648. {
  649. struct Numeric : public AccessibilityNumericValueInterface
  650. {
  651. explicit Numeric (ValueInterfaceComponent& o)
  652. : owner (o)
  653. {
  654. }
  655. bool isReadOnly() const override { return owner.readOnlyToggle.getToggleState(); }
  656. double getCurrentValue() const override { return owner.numericValueEditor.getText().getDoubleValue(); }
  657. void setValue (double newValue) override { owner.numericValueEditor.setText (String (newValue)); }
  658. ValueInterfaceComponent& owner;
  659. };
  660. struct Ranged : public AccessibilityRangedNumericValueInterface
  661. {
  662. explicit Ranged (ValueInterfaceComponent& o)
  663. : owner (o)
  664. {
  665. }
  666. bool isReadOnly() const override { return owner.readOnlyToggle.getToggleState(); }
  667. double getCurrentValue() const override { return owner.rangedValueComponent.valueSlider.getValue(); }
  668. void setValue (double newValue) override { owner.rangedValueComponent.valueSlider.setValue (newValue); }
  669. AccessibleValueRange getRange() const override
  670. {
  671. const auto& slider = owner.rangedValueComponent.valueSlider;
  672. return { { slider.getMinimum(), slider.getMaximum() },
  673. slider.getInterval() };
  674. }
  675. ValueInterfaceComponent& owner;
  676. };
  677. struct Text : public AccessibilityTextValueInterface
  678. {
  679. explicit Text (ValueInterfaceComponent& o)
  680. : owner (o)
  681. {
  682. }
  683. bool isReadOnly() const override { return owner.readOnlyToggle.getToggleState(); }
  684. String getCurrentValueAsString() const override { return owner.textValueEditor.getText(); }
  685. void setValueAsString (const String& newValue) override { owner.textValueEditor.setText (newValue); }
  686. ValueInterfaceComponent& owner;
  687. };
  688. const auto valueType = indexToValueType (valueTypeBox.getSelectedId());
  689. if (valueType == ValueType::numeric) return std::make_unique<Numeric> (*this);
  690. if (valueType == ValueType::ranged) return std::make_unique<Ranged> (*this);
  691. if (valueType == ValueType::text) return std::make_unique<Text> (*this);
  692. jassertfalse;
  693. return nullptr;
  694. }
  695. private:
  696. //==============================================================================
  697. struct RangedValueComponent : public Component
  698. {
  699. RangedValueComponent()
  700. {
  701. const auto setUpNumericTextEditor = [this] (TextEditor& ed, double initialValue)
  702. {
  703. ed.setInputRestrictions (10, "0123456789.");
  704. ed.setText (String (initialValue));
  705. ed.onReturnKey = [this] { updateSliderRange(); };
  706. addAndMakeVisible (ed);
  707. };
  708. setUpNumericTextEditor (minValueEditor, 0.0);
  709. setUpNumericTextEditor (maxValueEditor, 10.0);
  710. setUpNumericTextEditor (intervalValueEditor, 0.1);
  711. addAndMakeVisible (minLabel);
  712. addAndMakeVisible (maxLabel);
  713. addAndMakeVisible (intervalLabel);
  714. valueSlider.setSliderStyle (Slider::SliderStyle::LinearHorizontal);
  715. addAndMakeVisible (valueSlider);
  716. updateSliderRange();
  717. }
  718. void resized() override
  719. {
  720. Grid grid;
  721. grid.templateRows = { Grid::TrackInfo (Grid::Fr (2)), Grid::TrackInfo (Grid::Fr (3)), Grid::TrackInfo (Grid::Fr (3)) };
  722. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)) };
  723. grid.items = { GridItem (minLabel).withMargin (2), GridItem (maxLabel).withMargin (2), GridItem (intervalLabel).withMargin (2),
  724. GridItem (minValueEditor).withMargin (2), GridItem (maxValueEditor).withMargin (2), GridItem (intervalValueEditor).withMargin (2),
  725. GridItem (valueSlider).withMargin (2).withColumn ({ GridItem::Span (3), {} }) };
  726. grid.performLayout (getLocalBounds());
  727. }
  728. void updateSliderRange()
  729. {
  730. auto minValue = minValueEditor.getText().getDoubleValue();
  731. auto maxValue = maxValueEditor.getText().getDoubleValue();
  732. const auto intervalValue = jmax (intervalValueEditor.getText().getDoubleValue(), 0.0001);
  733. if (maxValue <= minValue)
  734. {
  735. maxValue = minValue + intervalValue;
  736. maxValueEditor.setText (String (maxValue));
  737. }
  738. else if (minValue >= maxValue)
  739. {
  740. minValue = maxValue - intervalValue;
  741. minValueEditor.setText (String (minValue));
  742. }
  743. valueSlider.setRange (minValue, maxValue, intervalValue);
  744. }
  745. Label minLabel { {}, "Min" }, maxLabel { {}, "Max" }, intervalLabel { {}, "Interval" };
  746. TextEditor minValueEditor, maxValueEditor, intervalValueEditor;
  747. Slider valueSlider;
  748. };
  749. //==============================================================================
  750. enum class ValueType { numeric, ranged, text };
  751. static ValueType indexToValueType (int index) noexcept
  752. {
  753. if (index == 1) return ValueType::numeric;
  754. if (index == 2) return ValueType::ranged;
  755. if (index == 3) return ValueType::text;
  756. jassertfalse;
  757. return ValueType::numeric;
  758. }
  759. void updateValueUI()
  760. {
  761. const auto valueType = indexToValueType (valueTypeBox.getSelectedId());
  762. numericValueEditor.setVisible (valueType == ValueType::numeric);
  763. textValueEditor.setVisible (valueType == ValueType::text);
  764. rangedValueComponent.setVisible (valueType == ValueType::ranged);
  765. resized();
  766. }
  767. //==============================================================================
  768. CustomWidgetComponent& customWidgetComponent;
  769. ComboBox valueTypeBox;
  770. ToggleButton readOnlyToggle { "Read-Only" };
  771. TextEditor numericValueEditor, textValueEditor;
  772. RangedValueComponent rangedValueComponent;
  773. //==============================================================================
  774. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueInterfaceComponent)
  775. };
  776. //==============================================================================
  777. class StateComponent : public Component
  778. {
  779. public:
  780. StateComponent()
  781. {
  782. for (auto& property : properties)
  783. addAndMakeVisible (property.button);
  784. }
  785. void resized() override
  786. {
  787. Grid grid;
  788. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)),
  789. Grid::TrackInfo (Grid::Fr (1)),
  790. Grid::TrackInfo (Grid::Fr (1)),
  791. Grid::TrackInfo (Grid::Fr (1)),
  792. Grid::TrackInfo (Grid::Fr (1)),
  793. Grid::TrackInfo (Grid::Fr (1)) };
  794. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)) };
  795. for (auto& property : properties)
  796. grid.items.add (GridItem (property.button));
  797. grid.performLayout (getLocalBounds());
  798. }
  799. AccessibleState getAccessibleState() const
  800. {
  801. AccessibleState result;
  802. for (auto& property : properties)
  803. if (property.button.getToggleState())
  804. result = property.setState (std::move (result));
  805. return result;
  806. }
  807. private:
  808. struct Property
  809. {
  810. Property (const String& name,
  811. bool initialState,
  812. AccessibleState (AccessibleState::* fn)() const)
  813. : button (name),
  814. setStateFn (fn)
  815. {
  816. button.setToggleState (initialState, dontSendNotification);
  817. }
  818. AccessibleState setState (AccessibleState s) const noexcept { return (s.*setStateFn)(); }
  819. ToggleButton button;
  820. AccessibleState (AccessibleState::* setStateFn)() const;
  821. };
  822. std::array<Property, 12> properties
  823. { {
  824. { "Checkable", false, &AccessibleState::withCheckable },
  825. { "Checked", false, &AccessibleState::withChecked },
  826. { "Collapsed", false, &AccessibleState::withCollapsed },
  827. { "Expandable", false, &AccessibleState::withExpandable },
  828. { "Expanded", false, &AccessibleState::withExpanded },
  829. { "Focusable", true, &AccessibleState::withFocusable },
  830. { "Focused", false, &AccessibleState::withFocused },
  831. { "Ignored", false, &AccessibleState::withIgnored },
  832. { "Selectable", false, &AccessibleState::withSelectable },
  833. { "Multi-Selectable", false, &AccessibleState::withMultiSelectable },
  834. { "Selected", false, &AccessibleState::withSelected },
  835. { "Accessible Offscreen", false, &AccessibleState::withAccessibleOffscreen }
  836. } };
  837. //==============================================================================
  838. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StateComponent)
  839. };
  840. //==============================================================================
  841. Label descriptionLabel { {}, "This is a demo of a custom accessible widget.\n\n"
  842. "The JUCE logo component at the bottom of the page will use the settable properties when queried by "
  843. "an accessibility client." };
  844. InfoComponent infoComponent { *this };
  845. ActionsComponent actionsComponent { *this };
  846. ValueInterfaceComponent valueInterfaceComponent { *this };
  847. StateComponent stateComponent;
  848. ContentComponent info { "Info",
  849. "Set the title, role, description and help text properties of the component.",
  850. infoComponent };
  851. ContentComponent actions { "Actions",
  852. "Specify the accessibility actions that the component can perform. When invoked the indicator will flash.",
  853. actionsComponent };
  854. ContentComponent valueInterface { "Value",
  855. "Sets the value that this component represents. This can be numeric, ranged or textual and can optionally be read-only.",
  856. valueInterfaceComponent };
  857. ContentComponent state { "State",
  858. "Modify the AccessibleState properties of the component.",
  859. stateComponent };
  860. AccessibleComponent accessibleComponent { *this };
  861. //==============================================================================
  862. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomWidgetComponent)
  863. };
  864. //==============================================================================
  865. /**
  866. The top-level component containing an example of custom child component navigation.
  867. */
  868. class CustomNavigationComponent : public Component
  869. {
  870. public:
  871. CustomNavigationComponent()
  872. {
  873. setTitle ("Custom Navigation");
  874. setDescription ("A demo of custom component navigation.");
  875. setFocusContainerType (FocusContainerType::focusContainer);
  876. addAndMakeVisible (descriptionLabel);
  877. addAndMakeVisible (navigableComponents);
  878. }
  879. void resized() override
  880. {
  881. Grid grid;
  882. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)),
  883. Grid::TrackInfo (Grid::Fr (2)) };
  884. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)) };
  885. grid.items = { GridItem (descriptionLabel).withMargin (2),
  886. GridItem (navigableComponents).withMargin (5) };
  887. grid.performLayout (getLocalBounds());
  888. }
  889. private:
  890. //==============================================================================
  891. class NavigableComponentsHolder : public Component
  892. {
  893. public:
  894. NavigableComponentsHolder()
  895. {
  896. setTitle ("Navigable Components");
  897. setDescription ("A container of some navigable components.");
  898. setFocusContainerType (FocusContainerType::focusContainer);
  899. constexpr int numChildren = 12;
  900. for (int i = 1; i <= numChildren; ++i)
  901. {
  902. children.push_back (std::make_unique<NavigableComponent> (i, numChildren, *this));
  903. addAndMakeVisible (*children.back());
  904. }
  905. }
  906. void resized() override
  907. {
  908. Grid grid;
  909. grid.templateRows = { Grid::TrackInfo (Grid::Fr (1)),
  910. Grid::TrackInfo (Grid::Fr (1)),
  911. Grid::TrackInfo (Grid::Fr (1)),
  912. Grid::TrackInfo (Grid::Fr (1)) };
  913. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)), Grid::TrackInfo (Grid::Fr (1)) };
  914. for (auto& child : children)
  915. grid.items.add (GridItem (*child).withMargin (5));
  916. grid.performLayout (getLocalBounds());
  917. }
  918. std::unique_ptr<ComponentTraverser> createFocusTraverser() override
  919. {
  920. struct CustomTraverser : public FocusTraverser
  921. {
  922. explicit CustomTraverser (NavigableComponentsHolder& owner)
  923. : navigableComponentsHolder (owner) {}
  924. Component* getDefaultComponent (Component*) override
  925. {
  926. for (auto& child : navigableComponentsHolder.children)
  927. if (child->defaultToggle.getToggleState() && child->focusableToggle.getToggleState())
  928. return child.get();
  929. return nullptr;
  930. }
  931. Component* getNextComponent (Component* current) override
  932. {
  933. const auto& comps = navigableComponentsHolder.children;
  934. const auto iter = std::find_if (comps.cbegin(), comps.cend(),
  935. [current] (const std::unique_ptr<NavigableComponent>& c) { return c.get() == current; });
  936. if (iter != comps.cend() && iter != std::prev (comps.cend()))
  937. return std::next (iter)->get();
  938. return nullptr;
  939. }
  940. Component* getPreviousComponent (Component* current) override
  941. {
  942. const auto& comps = navigableComponentsHolder.children;
  943. const auto iter = std::find_if (comps.cbegin(), comps.cend(),
  944. [current] (const std::unique_ptr<NavigableComponent>& c) { return c.get() == current; });
  945. if (iter != comps.cend() && iter != comps.cbegin())
  946. return std::prev (iter)->get();
  947. return nullptr;
  948. }
  949. std::vector<Component*> getAllComponents (Component*) override
  950. {
  951. std::vector<Component*> comps;
  952. for (auto& child : navigableComponentsHolder.children)
  953. if (child->focusableToggle.getToggleState())
  954. comps.push_back (child.get());
  955. return comps;
  956. }
  957. NavigableComponentsHolder& navigableComponentsHolder;
  958. };
  959. return std::make_unique<CustomTraverser> (*this);
  960. }
  961. private:
  962. struct NavigableComponent : public Component
  963. {
  964. NavigableComponent (int index, int total, NavigableComponentsHolder& owner)
  965. {
  966. const auto textColour = Colours::black.withAlpha (0.8f);
  967. titleLabel.setColour (Label::textColourId, textColour);
  968. orderLabel.setColour (Label::textColourId, textColour);
  969. const auto setToggleButtonColours = [textColour] (ToggleButton& b)
  970. {
  971. b.setColour (ToggleButton::textColourId, textColour);
  972. b.setColour (ToggleButton::tickDisabledColourId, textColour);
  973. b.setColour (ToggleButton::tickColourId, textColour);
  974. };
  975. setToggleButtonColours (focusableToggle);
  976. setToggleButtonColours (defaultToggle);
  977. const auto title = "Component " + String (index);
  978. setTitle (title);
  979. titleLabel.setText (title, dontSendNotification);
  980. focusableToggle.setToggleState (true, dontSendNotification);
  981. defaultToggle.setToggleState (index == 1, dontSendNotification);
  982. for (int i = 1; i <= total; ++i)
  983. orderBox.addItem (String (i), i);
  984. orderBox.setSelectedId (index);
  985. orderBox.onChange = [this, &owner] { owner.orderChanged (*this); };
  986. focusableToggle.onClick = [this] { repaint(); };
  987. defaultToggle.onClick = [this, &owner]
  988. {
  989. if (! defaultToggle.getToggleState())
  990. defaultToggle.setToggleState (true, dontSendNotification);
  991. else
  992. owner.defaultChanged (*this);
  993. };
  994. addAndMakeVisible (titleLabel);
  995. addAndMakeVisible (focusableToggle);
  996. addAndMakeVisible (defaultToggle);
  997. addAndMakeVisible (orderLabel);
  998. addAndMakeVisible (orderBox);
  999. setFocusContainerType (FocusContainerType::focusContainer);
  1000. }
  1001. void paint (Graphics& g) override
  1002. {
  1003. g.fillAll (backgroundColour.withAlpha (focusableToggle.getToggleState() ? 1.0f : 0.5f));
  1004. }
  1005. void resized() override
  1006. {
  1007. Grid grid;
  1008. grid.templateRows = { Grid::TrackInfo (Grid::Fr (2)),
  1009. Grid::TrackInfo (Grid::Fr (3)),
  1010. Grid::TrackInfo (Grid::Fr (3)) };
  1011. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (1)),
  1012. Grid::TrackInfo (Grid::Fr (1)),
  1013. Grid::TrackInfo (Grid::Fr (1)),
  1014. Grid::TrackInfo (Grid::Fr (1)) };
  1015. grid.items = { GridItem (titleLabel).withMargin (2).withColumn ({ GridItem::Span (4), {} }),
  1016. GridItem (focusableToggle).withMargin (2).withColumn ({ GridItem::Span (2), {} }),
  1017. GridItem (defaultToggle).withMargin (2).withColumn ({ GridItem::Span (2), {} }),
  1018. GridItem (orderLabel).withMargin (2),
  1019. GridItem (orderBox).withMargin (2).withColumn ({ GridItem::Span (3), {} }) };
  1020. grid.performLayout (getLocalBounds());
  1021. }
  1022. Colour backgroundColour = getRandomBrightColour();
  1023. Label titleLabel;
  1024. ToggleButton focusableToggle { "Focusable" }, defaultToggle { "Default" };
  1025. Label orderLabel { {}, "Order" };
  1026. ComboBox orderBox;
  1027. };
  1028. void orderChanged (const NavigableComponent& changedChild)
  1029. {
  1030. const auto addressMatches = [&] (const std::unique_ptr<NavigableComponent>& child)
  1031. {
  1032. return child.get() == &changedChild;
  1033. };
  1034. const auto iter = std::find_if (children.begin(), children.end(), addressMatches);
  1035. if (iter != children.end())
  1036. std::swap (*iter, *std::next (children.begin(), changedChild.orderBox.getSelectedItemIndex()));
  1037. int order = 1;
  1038. for (auto& child : children)
  1039. child->orderBox.setSelectedId (order++);
  1040. if (auto* handler = getAccessibilityHandler())
  1041. handler->notifyAccessibilityEvent (AccessibilityEvent::structureChanged);
  1042. }
  1043. void defaultChanged (const NavigableComponent& newDefault)
  1044. {
  1045. for (auto& child : children)
  1046. if (child->defaultToggle.getToggleState() && child.get() != &newDefault)
  1047. child->defaultToggle.setToggleState (false, dontSendNotification);
  1048. }
  1049. std::vector<std::unique_ptr<NavigableComponent>> children;
  1050. //==============================================================================
  1051. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NavigableComponentsHolder)
  1052. };
  1053. //==============================================================================
  1054. Label descriptionLabel { {}, "This is a demo of how to control the navigation order of components when navigating with an accessibility client.\n\n"
  1055. "You can set the order of navigation, whether components are focusable and set a default component which will "
  1056. "receive the focus first." };
  1057. NavigableComponentsHolder navigableComponents;
  1058. //==============================================================================
  1059. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomNavigationComponent)
  1060. };
  1061. //==============================================================================
  1062. /**
  1063. The top-level component containing an example of how to post system announcements.
  1064. The AccessibilityHandler::postAnnouncement() method will post some text to the native
  1065. screen reader application to be read out along with a priority determining how
  1066. it should be read out (whether it should interrupt other announcements, etc.).
  1067. */
  1068. class AnnouncementsComponent : public Component
  1069. {
  1070. public:
  1071. AnnouncementsComponent()
  1072. {
  1073. addAndMakeVisible (descriptionLabel);
  1074. textEntryBox.setMultiLine (true);
  1075. textEntryBox.setReturnKeyStartsNewLine (true);
  1076. textEntryBox.setText ("Announcement text.");
  1077. addAndMakeVisible (textEntryBox);
  1078. priorityComboBox.addItemList ({ "Priority - Low", "Priority - Medium", "Priority - High" }, 1);
  1079. priorityComboBox.setSelectedId (2);
  1080. addAndMakeVisible (priorityComboBox);
  1081. announceButton.onClick = [this]
  1082. {
  1083. auto priority = [this]
  1084. {
  1085. switch (priorityComboBox.getSelectedId())
  1086. {
  1087. case 1: return AccessibilityHandler::AnnouncementPriority::low;
  1088. case 2: return AccessibilityHandler::AnnouncementPriority::medium;
  1089. case 3: return AccessibilityHandler::AnnouncementPriority::high;
  1090. }
  1091. jassertfalse;
  1092. return AccessibilityHandler::AnnouncementPriority::medium;
  1093. }();
  1094. AccessibilityHandler::postAnnouncement (textEntryBox.getText(), priority);
  1095. };
  1096. addAndMakeVisible (announceButton);
  1097. setTitle ("Announcements");
  1098. setHelpText ("Type some text into the box and click the announce button to have it read out.");
  1099. setFocusContainerType (FocusContainerType::focusContainer);
  1100. }
  1101. void resized() override
  1102. {
  1103. Grid grid;
  1104. grid.templateRows = { Grid::TrackInfo (Grid::Fr (3)),
  1105. Grid::TrackInfo (Grid::Fr (1)),
  1106. Grid::TrackInfo (Grid::Fr (1)),
  1107. Grid::TrackInfo (Grid::Fr (1)),
  1108. Grid::TrackInfo (Grid::Fr (1)),
  1109. Grid::TrackInfo (Grid::Fr (1)) };
  1110. grid.templateColumns = { Grid::TrackInfo (Grid::Fr (3)),
  1111. Grid::TrackInfo (Grid::Fr (2)) };
  1112. grid.items = { GridItem (descriptionLabel).withMargin (2).withColumn ({ GridItem::Span (2), {} }),
  1113. GridItem (textEntryBox).withMargin (2).withArea ({ 2 }, { 1 }, { 5 }, { 2 }),
  1114. GridItem (priorityComboBox).withMargin (2).withArea ({ 5 }, { 1 }, { 6 }, { 2 }),
  1115. GridItem (announceButton).withMargin (2).withArea ({ 4 }, { 2 }, { 5 }, { 3 }) };
  1116. grid.performLayout (getLocalBounds());
  1117. }
  1118. private:
  1119. Label descriptionLabel { {}, "This is a demo of posting system announcements that will be read out by an accessibility client.\n\n"
  1120. "You can enter some text to be read out in the text box below, set a priority for the message and then "
  1121. "post it using the \"Announce\" button." };
  1122. TextEditor textEntryBox;
  1123. ComboBox priorityComboBox;
  1124. TextButton announceButton { "Announce" };
  1125. //==============================================================================
  1126. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnnouncementsComponent)
  1127. };
  1128. //==============================================================================
  1129. /**
  1130. The main demo content component.
  1131. This just contains a TabbedComponent with a tab for each of the top-level demos.
  1132. */
  1133. class AccessibilityDemo : public Component
  1134. {
  1135. public:
  1136. AccessibilityDemo()
  1137. {
  1138. setTitle ("Accessibility Demo");
  1139. setDescription ("A demo of JUCE's accessibility features.");
  1140. setFocusContainerType (FocusContainerType::focusContainer);
  1141. tabs.setTitle ("Demo tabs");
  1142. const auto tabColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker (0.1f);
  1143. tabs.addTab ("JUCE Widgets", tabColour, &juceWidgetsComponent, false);
  1144. tabs.addTab ("Custom Widget", tabColour, &customWidgetComponent, false);
  1145. tabs.addTab ("Custom Navigation", tabColour, &customNavigationComponent, false);
  1146. tabs.addTab ("Announcements", tabColour, &announcementsComponent, false);
  1147. addAndMakeVisible (tabs);
  1148. setSize (800, 600);
  1149. }
  1150. void paint (Graphics& g) override
  1151. {
  1152. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  1153. }
  1154. void resized() override
  1155. {
  1156. tabs.setBounds (getLocalBounds());
  1157. }
  1158. private:
  1159. TooltipWindow tooltipWindow { nullptr, 100 };
  1160. TabbedComponent tabs { TabbedButtonBar::Orientation::TabsAtTop };
  1161. JUCEWidgetsComponent juceWidgetsComponent;
  1162. CustomWidgetComponent customWidgetComponent;
  1163. CustomNavigationComponent customNavigationComponent;
  1164. AnnouncementsComponent announcementsComponent;
  1165. //==============================================================================
  1166. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityDemo)
  1167. };