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.

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