JUCE Accessibility on `develop`

Ah yes, I see what the issue is. There is a fix for this on develop here:

1 Like

Thanks, I’ve pushed a fix for this to develop here:

If I disable accessibility with setAccessible(false) on a parent component that contains ComboBoxes, the PopupMenus of those combo boxes still appear as accessible.

  1. would it be possible to add an option for PopupMenus to be created as not accessible?
  2. would it be possible to use that option to create PopupMenus with accessibility disabled for ComboBoxes that have it disabled?

That seems to work. Thanks

I’m getting some odd behaviour with setting parent components to be non-accessible…

I have a settings panel with a tabbed component within it. I’ve set the tabbed component itself to be non-accessible which leaves the tab buttons, and the content within the tab to remain accessible.

Firstly, this goes against the documentation which says that the child components will also become non-accessible when the parent is set to be non-accessible.

Secondly, when I use Accessibility Inspector and target the area that contains the tabbed component, the inspector highlights the entire window, rather than the next accessible component in the hierarchy (the settings panel in this case) which is what I would have expected. To the inspector, it looks as though there’s large holes in my settings panel.

Maybe this is the expected behaviour and I just need to become more familiar with accessibility clients, but it certainly seems odd to me. The documentation for setAccessible seems to be wrong either way.

No that doesn’t sound like the desired behaviour. Are you using the latest develop? I’ve just tried the following example and it seems to be working as expected - I’m able to navigate the window, content and ComboBox but the entire TabbedComponent (as well as the buttons and tab content components) are inaccessible:

class MainComponent  : public juce::Component
{
public:
    //==============================================================================
    MainComponent()
    {
        combo.addItemList ({ "Item 1", "Item 2", "Item 3" }, 1);
        combo.setSelectedItemIndex (0);
        addAndMakeVisible (combo);
        
        tabComp.addTab ("Tab 1", juce::Colours::transparentBlack, new InnerComp (juce::Colours::hotpink), true);
        tabComp.addTab ("Tab 2", juce::Colours::transparentBlack, new InnerComp (juce::Colours::cyan), true);
        tabComp.addTab ("Tab 3", juce::Colours::transparentBlack, new InnerComp (juce::Colours::grey), true);
        tabComp.addTab ("Tab 4", juce::Colours::transparentBlack, new InnerComp (juce::Colours::yellow), true);
        tabComp.addTab ("Tab 5", juce::Colours::transparentBlack, new InnerComp (juce::Colours::orange), true);
        addAndMakeVisible (tabComp);
        
        tabComp.setAccessible (false);
        
        setSize (600, 400);
    }

    ~MainComponent() override
    {
    }

    //==============================================================================
    void paint (juce::Graphics& g) override
    {
        // (Our component is opaque, so we must completely fill the background with a solid colour)
        g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
    }

    void resized() override
    {
        auto bounds = getLocalBounds().reduced (20);
        
        combo.setBounds (bounds.removeFromTop (50).reduced (40, 10));
        tabComp.setBounds (bounds);
    }


private:
    //==============================================================================
    // Your private member variables go here...
    juce::ComboBox combo;
    juce::TabbedComponent tabComp { juce::TabbedButtonBar::Orientation::TabsAtTop };
    
    struct InnerComp  : public juce::Component
    {
        InnerComp (juce::Colour c)  : bg (c)
        {
            label.setJustificationType (juce::Justification::centred);

            addAndMakeVisible (label);
            addAndMakeVisible (button);
        }
        
        void paint (juce::Graphics& g) override
        {
            g.fillAll (bg);
        }
        
        void resized() override
        {
            auto b = getLocalBounds().reduced (20);
            
            label.setBounds (b.removeFromTop (50));
            b.removeFromTop (20);
            button.setBounds (b.removeFromTop (35));
        }
        
        juce::Colour bg;
        
        juce::Label label { {}, "Hello!" };
        juce::TextButton button { "Button" };
    };
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Are you able to put together a simple test example which reproduces your issue?

I’m on the latest develop (9db2647971beb24bb5449458090f8c9446fa43bd).

That example doesn’t seem to work as I’d expect either. When I target the blank space to the right of the tab buttons, Accessibility Inspector highlights the window, not the MainComponent:

(Screenshot hides the mouse, but it’s to the right of the buttons there).

If I target the space around the tabbed component, the inspector correctly highlights the main component, not the window.

This is on macOS 11.5.2.

Ah gotcha, I think I see what’s happening here. Will push a fix shortly.

1 Like

I’ve added a fix for this to the develop branch here:

I’ve pushed that fix to develop now:

@ImJimmi

1 Like

Awesome, that’s working well now. Thanks!

Having some problems with iOS accessibility on sliders. Nothing I do (changing VoiceOver level of detail, adding names, help, tooltips) to the slider, causes the slider value or slider info to be read out. I’ve also checked it out in the Accessibility inspector and that reports that the slider has neither a label or identifier. On the bright side, the slider value an actually be changed with double-tap and drag. Any thoughts?

Sorry for asking, but I don’t have any experience with VoiceOver or Narrator whatsoever:
are there any good instructions / videos somewhere on how I can actually test this new accessibility feature as a user? I mean: when I start VoiceOver I just get a Window with all kinds of options, but I have not the slightest idea of what I should do to get the tool to read out my plugin components etc…
Now that I switched to 6.1.1 I’d like to see how it works on my plugin so I can decide if it’s a good idea to announce it in the release notes for the next release :wink:

Where I started was with the Apple guide here and particularly the commands section. It’s a good intro, but be sure to read it through so that you can pick up on some of the quirks. Also you should be aware of the settings for levels of detail - these can sometimes influence which text is read. Tabbing through the components gives you a good idea if you need to do something with the default traversal. And also, it helps enormously if you can find a visually impaired beta tester.

1 Like

Thanks for the pointers!
I was able to do a quick test already on macOS and quickly noticed a few things that don’t work well with my plugin. Mostly traversal order weirdness, no way to change my min/max sliders (which are custom components) although the text edit controls linked to them do work, labels don’t read out (might be a setting), and one button that can’t be accessed by VoiceOver apparently, …
I think it’s all probably because of the way I’m doing things in the GUI, so it might require some work to get it to work properly.

If your slider is totally custom them you’re going to have to do the work an write an AccessibilityHandler, but if it’s subclassed from juce::Slider it should work. If you have a slider without a text box associated you’ll need to call setWantsKeyboardFocus(true).