community icon indicating copy to clipboard operation
community copied to clipboard

Write an abstraction for `kivy.core.accessibility`, so accessibility tools have a proper API to work with

Open misl6 opened this issue 1 year ago • 3 comments

The new kivy.core.accessibility provider will expose some classes, to better accommodate accessibility tools.

kivy.core.accessibility.AccessibilityRoleType:

An Enum, that defines the role of the linked widget (static text, text box, container ...), so the accessibility tools can know how to act with a specific widget.

As an example:

  • AccessibilityRoleType.STATIC_TEXT
  • AccessibilityRoleType.CONTAINER

kivy.core.accessibility.AccessibilityEventType:

An Enum, which defines the event type that has to be handled by the accessibility provider.

As an example:

  • AccessibilityEventType.NEW
  • AccessibilityEventType.TEXT_CHANGED

kivy.core.accessibility.AccessibilityInterfaceBase:

A base class, that every widget (or set of widgets) can subclass to better accommodate its needs. This serves as an interface between the widget and the accessibility provider (and vice-versa).

The AccessibilityInterfaceBase can bind to widget properties and send "repacked" events to the accessibility provider, by calling the .update(event) method of the AccessibilityBase object.

The AccessibilityBase can reverse-walk the widget tree, to get the position of the widget linked to the AccessibilityInterfaceBase that emitted the event. (when an AccessibilityEventType.NEW is received).

AccessibilityInterfaceBase will also have widget-specialized methods to send "faked" events to the underlying widget. (pressed, released, text edited, ...)


class AccessibilityInterfaceBase:

    def __init__(self, widget: Widget):
        self.widget = widget

kivy.core.accessibility.AccessibilityEvent:

This class will serve as a container for the event information.


class AccessibilityEvent:

    def __init__(self, event_type: AccessibilityEventType, interface: AccessibilityInterface):
        self.event_type = event_type
        self.interface = interface

kivy.core.accessibility.AccessibilityBase:

The base implementation for every accessibility core provider implementation (as it happens for other core providers in the Kivy project).

As accessibility is pretty tied to the whole life-cycle of the app, the accessibility provider will be chosen during the App initialization, and before the Window creation (so the Window can signal to the accessibility provider that is ready).

kivy.core.accessibility.AccessibilityBase methods:

def install(self, window: Window):
     """
     Called only once when `kivy.core.Window` is ready but not yet shown, so the
     accessibility tool can attach flawlessly.
     """

def update(self, event: AccessibilityEvent):
     """
     Called every time a widget (or another entity) needs to let know the accessibility provider
     that something has changed, created or deleted.
     """

misl6 avatar Feb 01 '24 17:02 misl6

Some questions:

  • Would keyboard focus be handled by emitting an EventType?
  • Accessible widgets sometimes need to expose children that aren't necessary part of the widget tree, examples include the dropdown menu of a combo box or each line of text in a multi-line text area. Would we modify existing widgets to create these children, or do we come up with a mechanism to create fake, accessible-only widgets? Kivy's dropdown widget should be fine, I haven't looked into editable text though.
  • I've heard that multi-window apps might soon come to Kivy: what can we do to make it simpler to identify which window a widget is a descendant of?

DataTriny avatar Feb 05 '24 17:02 DataTriny

Would keyboard focus be handled by emitting an EventType?

Yes.

... each line of text in a multi-line text area ...

Since we're rewriting the TextInput widget almost from scratch, can you better explain the kind of interaction needed between accessibility tools and the TextInput widget? I assume you're talking about paragraph separation?

I've heard that multi-window apps might soon come to Kivy: what can we do to make it simpler to identify which window a widget is a descendant of?

We did not added it yet to 3.0.0 schedule, and it will not likely land unless we find a hero. (We need a realistic release date, and 3.0.0 task list already looks pretty big). So, maybe 3.1.0? BTW I guess we can easily get this info by reverse walking the widget tree, in case we find a hero 😀

misl6 avatar Feb 06 '24 17:02 misl6

can you better explain the kind of interaction needed between accessibility tools and the TextInput widget? I assume you're talking about paragraph separation?

I will mostly talk in the context of AccessKit (which derived its data schema from Chromium):

We have a role called InlineTextBox which is a container for a piece of text spanning at most one line. Nodes of this kind obviously hold their text content as value, but they also have to have:

  • a bounding box,
  • the length of each character in bytes,
  • the x coordinate of each character,
  • the width of each character,
  • the length of each word (in characters).

So, paragraphs don't matter here: if a line is too long so that it is split into two, then two nodes will be needed.

Since text decoration and other text formatting attributes apply to an entire node, multiple inline text boxes will be needed if a line contains regular and bold text for instance.

Also worth noting: we will need to receive events when the text selection changes.

To have an idea of the kind of tree updates we have to produce, you can have a look at this test suite.

DataTriny avatar Feb 06 '24 19:02 DataTriny