1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Accessibility: Added VoiceOver (macOS) and Narrator (Windows) accessibility screen reader support to juce_gui_basics

This commit is contained in:
ed 2021-05-10 09:38:00 +01:00
parent 1df59f7469
commit ec990202b1
133 changed files with 10158 additions and 1297 deletions

View file

@ -26,105 +26,248 @@
namespace juce
{
namespace KeyboardFocusHelpers
//==============================================================================
namespace KeyboardFocusTraverserHelpers
{
static int getOrder (const Component* c)
static bool isKeyboardFocusable (const Component* comp, const Component* container)
{
auto order = c->getExplicitFocusOrder();
return order > 0 ? order : (std::numeric_limits<int>::max() / 2);
return comp->getWantsKeyboardFocus() && container->isParentOf (comp);
}
static void findAllFocusableComponents (Component* parent, Array<Component*>& comps)
static Component* traverse (Component* current, Component* container,
FocusHelpers::NavigationDirection direction)
{
if (parent->getNumChildComponents() != 0)
if (auto* comp = FocusHelpers::navigateFocus (current, container, direction,
&Component::isKeyboardFocusContainer))
{
Array<Component*> localComps;
if (isKeyboardFocusable (comp, container))
return comp;
for (auto* c : parent->getChildren())
if (c->isVisible() && c->isEnabled())
localComps.add (c);
// This will sort so that they are ordered in terms of left-to-right
// and then top-to-bottom.
std::stable_sort (localComps.begin(), localComps.end(),
[] (const Component* a, const Component* b)
{
auto explicitOrder1 = getOrder (a);
auto explicitOrder2 = getOrder (b);
if (explicitOrder1 != explicitOrder2)
return explicitOrder1 < explicitOrder2;
if (a->getY() != b->getY())
return a->getY() < b->getY();
return a->getX() < b->getX();
});
for (auto* c : localComps)
{
if (c->getWantsKeyboardFocus())
comps.add (c);
if (! c->isFocusContainer())
findAllFocusableComponents (c, comps);
}
}
}
static Component* findFocusContainer (Component* c)
{
c = c->getParentComponent();
if (c != nullptr)
while (c->getParentComponent() != nullptr && ! c->isFocusContainer())
c = c->getParentComponent();
return c;
}
static Component* getIncrementedComponent (Component* current, int delta)
{
if (auto* focusContainer = findFocusContainer (current))
{
Array<Component*> comps;
KeyboardFocusHelpers::findAllFocusableComponents (focusContainer, comps);
if (! comps.isEmpty())
{
auto index = comps.indexOf (current);
return comps [(index + comps.size() + delta) % comps.size()];
}
return traverse (comp, container, direction);
}
return nullptr;
}
}
//==============================================================================
KeyboardFocusTraverser::KeyboardFocusTraverser() {}
KeyboardFocusTraverser::~KeyboardFocusTraverser() {}
Component* KeyboardFocusTraverser::getNextComponent (Component* current)
{
jassert (current != nullptr);
return KeyboardFocusHelpers::getIncrementedComponent (current, 1);
return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(),
FocusHelpers::NavigationDirection::forwards);
}
Component* KeyboardFocusTraverser::getPreviousComponent (Component* current)
{
jassert (current != nullptr);
return KeyboardFocusHelpers::getIncrementedComponent (current, -1);
return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(),
FocusHelpers::NavigationDirection::backwards);
}
Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentComponent)
{
Array<Component*> comps;
for (auto* comp : getAllComponents (parentComponent))
if (KeyboardFocusTraverserHelpers::isKeyboardFocusable (comp, parentComponent))
return comp;
if (parentComponent != nullptr)
KeyboardFocusHelpers::findAllFocusableComponents (parentComponent, comps);
return comps.getFirst();
return nullptr;
}
std::vector<Component*> KeyboardFocusTraverser::getAllComponents (Component* parentComponent)
{
std::vector<Component*> components;
FocusHelpers::findAllComponents (parentComponent,
components,
&Component::isKeyboardFocusContainer);
auto removePredicate = [parentComponent] (const Component* comp)
{
return ! KeyboardFocusTraverserHelpers::isKeyboardFocusable (comp, parentComponent);
};
components.erase (std::remove_if (std::begin (components), std::end (components), std::move (removePredicate)),
std::end (components));
return components;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct KeyboardFocusTraverserTests : public UnitTest
{
KeyboardFocusTraverserTests()
: UnitTest ("KeyboardFocusTraverser", UnitTestCategories::gui)
{}
void runTest() override
{
ScopedJuceInitialiser_GUI libraryInitialiser;
beginTest ("No child wants keyboard focus");
{
TestComponent parent;
expect (traverser.getDefaultComponent (&parent) == nullptr);
expect (traverser.getAllComponents (&parent).empty());
}
beginTest ("Single child wants keyboard focus");
{
TestComponent parent;
parent.children[5].setWantsKeyboardFocus (true);
auto* defaultComponent = traverser.getDefaultComponent (&parent);
expect (defaultComponent == &parent.children[5]);
expect (defaultComponent->getWantsKeyboardFocus());
expect (traverser.getNextComponent (defaultComponent) == nullptr);
expect (traverser.getPreviousComponent (defaultComponent) == nullptr);
expect (traverser.getAllComponents (&parent).size() == 1);
}
beginTest ("Multiple children want keyboard focus");
{
TestComponent parent;
Component* focusChildren[]
{
&parent.children[1],
&parent.children[9],
&parent.children[3],
&parent.children[5],
&parent.children[8],
&parent.children[0]
};
for (auto* focusChild : focusChildren)
focusChild->setWantsKeyboardFocus (true);
auto allComponents = traverser.getAllComponents (&parent);
for (auto* focusChild : focusChildren)
expect (std::find (allComponents.cbegin(), allComponents.cend(), focusChild) != allComponents.cend());
auto* componentToTest = traverser.getDefaultComponent (&parent);
for (;;)
{
expect (componentToTest->getWantsKeyboardFocus());
expect (std::find (std::begin (focusChildren), std::end (focusChildren), componentToTest) != std::end (focusChildren));
componentToTest = traverser.getNextComponent (componentToTest);
if (componentToTest == nullptr)
break;
}
int focusOrder = 1;
for (auto* focusChild : focusChildren)
focusChild->setExplicitFocusOrder (focusOrder++);
componentToTest = traverser.getDefaultComponent (&parent);
for (auto* focusChild : focusChildren)
{
expect (componentToTest == focusChild);
expect (componentToTest->getWantsKeyboardFocus());
componentToTest = traverser.getNextComponent (componentToTest);
}
}
beginTest ("Single nested child wants keyboard focus");
{
TestComponent parent;
Component grandparent;
grandparent.addAndMakeVisible (parent);
auto& focusChild = parent.children[5];
focusChild.setWantsKeyboardFocus (true);
expect (traverser.getDefaultComponent (&grandparent) == &focusChild);
expect (traverser.getDefaultComponent (&parent) == &focusChild);
expect (traverser.getNextComponent (&focusChild) == nullptr);
expect (traverser.getPreviousComponent (&focusChild) == nullptr);
expect (traverser.getAllComponents (&parent).size() == 1);
}
beginTest ("Multiple nested children want keyboard focus");
{
TestComponent parent;
Component grandparent;
grandparent.addAndMakeVisible (parent);
Component* focusChildren[]
{
&parent.children[1],
&parent.children[4],
&parent.children[5]
};
for (auto* focusChild : focusChildren)
focusChild->setWantsKeyboardFocus (true);
auto allComponents = traverser.getAllComponents (&parent);
expect (std::equal (allComponents.cbegin(), allComponents.cend(), focusChildren,
[] (const Component* c1, const Component* c2) { return c1 == c2; }));
const auto front = *focusChildren;
const auto back = *std::prev (std::end (focusChildren));
expect (traverser.getDefaultComponent (&grandparent) == front);
expect (traverser.getDefaultComponent (&parent) == front);
expect (traverser.getNextComponent (front) == *std::next (std::begin (focusChildren)));
expect (traverser.getPreviousComponent (back) == *std::prev (std::end (focusChildren), 2));
std::array<Component, 3> otherParents;
for (auto& p : otherParents)
{
grandparent.addAndMakeVisible (p);
p.setWantsKeyboardFocus (true);
}
expect (traverser.getDefaultComponent (&grandparent) == front);
expect (traverser.getDefaultComponent (&parent) == front);
expect (traverser.getNextComponent (back) == &otherParents.front());
expect (traverser.getNextComponent (&otherParents.back()) == nullptr);
expect (traverser.getAllComponents (&grandparent).size() == numElementsInArray (focusChildren) + otherParents.size());
expect (traverser.getAllComponents (&parent).size() == (size_t) numElementsInArray (focusChildren));
for (auto* focusChild : focusChildren)
focusChild->setWantsKeyboardFocus (false);
expect (traverser.getDefaultComponent (&grandparent) == &otherParents.front());
expect (traverser.getDefaultComponent (&parent) == nullptr);
expect (traverser.getAllComponents (&grandparent).size() == otherParents.size());
expect (traverser.getAllComponents (&parent).empty());
}
}
private:
struct TestComponent : public Component
{
TestComponent()
{
for (auto& child : children)
addAndMakeVisible (child);
}
std::array<Component, 10> children;
};
KeyboardFocusTraverser traverser;
};
static KeyboardFocusTraverserTests keyboardFocusTraverserTests;
#endif
} // namespace juce

View file

@ -28,63 +28,60 @@ namespace juce
//==============================================================================
/**
Controls the order in which focus moves between components.
Controls the order in which keyboard focus moves between components.
The default algorithm used by this class to work out the order of traversal
is as follows:
- if two components both have an explicit focus order specified, then the
one with the lowest number comes first (see the Component::setExplicitFocusOrder()
method).
- any component with an explicit focus order greater than 0 comes before ones
that don't have an order specified.
- any unspecified components are traversed in a left-to-right, then top-to-bottom
order.
The default behaviour of this class uses a FocusTraverser object internally to
determine the default/next/previous component until it finds one which wants
keyboard focus, as set by the Component::setWantsKeyboardFocus() method.
If you need traversal in a more customised way, you can create a subclass
of KeyboardFocusTraverser that uses your own algorithm, and use
Component::createFocusTraverser() to create it.
If you need keyboard focus traversal in a more customised way, you can create
a subclass of ComponentTraverser that uses your own algorithm, and use
Component::createKeyboardFocusTraverser() to create it.
@see Component::setExplicitFocusOrder, Component::createFocusTraverser
@see FocusTraverser, ComponentTraverser, Component::createKeyboardFocusTraverser
@tags{GUI}
*/
class JUCE_API KeyboardFocusTraverser
class JUCE_API KeyboardFocusTraverser : public ComponentTraverser
{
public:
KeyboardFocusTraverser();
/** Destructor. */
virtual ~KeyboardFocusTraverser();
~KeyboardFocusTraverser() override = default;
/** Returns the component that should be given focus after the specified one
when moving "forwards".
/** Returns the component that should receive keyboard focus by default within the
given parent component.
The default implementation will return the next component which is to the
right of or below this one.
This may return nullptr if there's no suitable candidate.
The default implementation will return the foremost focusable component (as
determined by FocusTraverser) that also wants keyboard focus, or nullptr if
there is no suitable component.
*/
virtual Component* getNextComponent (Component* current);
Component* getDefaultComponent (Component* parentComponent) override;
/** Returns the component that should be given focus after the specified one
when moving "backwards".
/** Returns the component that should be given keyboard focus after the specified
one when moving "forwards".
The default implementation will return the next component which is to the
left of or above this one.
This may return nullptr if there's no suitable candidate.
The default implementation will return the next focusable component (as
determined by FocusTraverser) that also wants keyboard focus, or nullptr if
there is no suitable component.
*/
virtual Component* getPreviousComponent (Component* current);
Component* getNextComponent (Component* current) override;
/** Returns the component that should receive focus be default within the given
parent component.
/** Returns the component that should be given keyboard focus after the specified
one when moving "backwards".
The default implementation will just return the foremost child component that
wants focus.
This may return nullptr if there's no suitable candidate.
The default implementation will return the previous focusable component (as
determined by FocusTraverser) that also wants keyboard focus, or nullptr if
there is no suitable component.
*/
virtual Component* getDefaultComponent (Component* parentComponent);
Component* getPreviousComponent (Component* current) override;
/** Returns all of the components that can receive keyboard focus within the given
parent component in traversal order.
The default implementation will return all focusable child components (as
determined by FocusTraverser) that also wants keyboard focus.
*/
std::vector<Component*> getAllComponents (Component* parentComponent) override;
};
} // namespace juce