Keep screen reader inside a component? (FocusContainer)

I have certain components that act as KeyboardFocusContainers, so that the tab key moves focus around inside the components children in a loop. This is done using setFocusContainerType(FocusContainerType::keyboardFocusContainer).

Is there a way to do this so that a screenreader (i.e. Mac VoiceOver) does the same thing?

Basically, I have some components that act as “floating components” like modal dialogs (without being modal), and disable access to the mainComponent behind them, but with the VoiceOver screenreader you can move out of the floating component into the supposedly disabled background components and then operate controls. With the tab keys, you cannot.

Currently, VoiceOver will exit the component after going through all of the children, into the parent or another component, which is not what I want.

I tried setting my component to be a plain focusContainer, but that doesn’t seem to make any difference - and then it is no longer a focusContainer for the tab key.

There’s also FocusContainerType::focusContainer which says…

        /** The component will act as a top-level component within which focus is passed around.

            The default traverser implementation returned by createFocusTraverser() will use this
            flag to find the first parent component (of the currently focused one) that wants to
            be a focus container.

            This is currently used when determining the hierarchy of accessible UI elements presented
            to screen reader clients on supported platforms. See the AccessibilityHandler class for
            more information.
        */

Does this help at all?

I mentioned that I tried using that (maybe it wasn’t clear) in the last sentence - but it didn’t seem to change anything, and then my tab keys don’t stay inside the component either. :frowning:

:man_facepalming: sorry completely missed that! I’ll try and take a look to see if this is a bug.

If you have a simple example to help demonstrate the issue that would be really helpful.

Thanks.

I’ll see if i can put something together perhaps. I’m not suggesting it’s a bug; it sorta makes sense to me that it works that way; I was just wondering how to deal with it. How to limit it to stay inside a certain component.

Honestly, I find the whole Accessibility thing to be so complicated that I’m wondering if it is better/easier to turn off Accessibility for the entire app and just not deal with it for the tiny percentage of users that will actually use it. As “unkind” as that may be…

I would certainly need to refresh my memory with it. If you’re planning on attending ADC there’s a good talk scheduled to help with this kind of thing (Audio Developer Conference: Building an accessible JUCE app).

I don’t know if I should make a new post for this, but I was trying to use the AccessibilityDemo to work on a solution for this, and the CustomNavigation tab shows how to create a FocusTraverser:

        std::unique_ptr<ComponentTraverser> createFocusTraverser() override
        {
            struct CustomTraverser  : public FocusTraverser
            {
                explicit CustomTraverser (NavigableComponentsHolder& owner)
                    : navigableComponentsHolder (owner)  {}

                Component* getDefaultComponent (Component*) override
                {
                    for (auto& child : navigableComponentsHolder.children)
                        if (child->defaultToggle.getToggleState() && child->focusableToggle.getToggleState())
                            return child.get();

                    return nullptr;
                }

                Component* getNextComponent (Component* current) override
                {
                    const auto& comps = navigableComponentsHolder.children;

                    const auto iter = std::find_if (comps.cbegin(), comps.cend(),
                                                    [current] (const std::unique_ptr<NavigableComponent>& c) { return c.get() == current; });

                    if (iter != comps.cend() && iter != std::prev (comps.cend()))
                        return std::next (iter)->get();

                    return nullptr;
                }

                Component* getPreviousComponent (Component* current) override
                {
                    const auto& comps = navigableComponentsHolder.children;

                    const auto iter = std::find_if (comps.cbegin(), comps.cend(),
                                                    [current] (const std::unique_ptr<NavigableComponent>& c) { return c.get() == current; });

                    if (iter != comps.cend() && iter != comps.cbegin())
                        return std::prev (iter)->get();

                    return nullptr;
                }

                std::vector<Component*> getAllComponents (Component*) override
                {
                    std::vector<Component*> comps;

                    for (auto& child : navigableComponentsHolder.children)
                        if (child->focusableToggle.getToggleState())
                            comps.push_back (child.get());

                    return comps;
                }

                NavigableComponentsHolder& navigableComponentsHolder;
            };

            return std::make_unique<CustomTraverser> (*this);
        }

What I cannot understand is that after building this, and placing debug points in all 4 of these override functions, getPreviousComponent and getNextComponent are NEVER called, even when moving VoiceOver from once component to the next, out of components and into others - I could never ever make it hit. I would love an explanation for that! :slight_smile: