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.

1490 lines
61KB

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