Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,10 @@ private sealed interface AccessibilityElementFocusMode {
data class KeepFocus(val key: AccessibilityElementKey) : AccessibilityElementFocusMode {
override val targetElementKey: AccessibilityElementKey = key
}

data class Focus(val key: AccessibilityElementKey) : AccessibilityElementFocusMode {
override val targetElementKey: AccessibilityElementKey = key
}
}

/**
Expand All @@ -746,6 +750,7 @@ internal class AccessibilityMediator(
private set

var keyboardFocusedElementKey: AccessibilityElementKey? = null
private var forceFocusedElementKey: AccessibilityElementKey? = null

/**
* A set of node ids that had their bounds invalidated after the last sync.
Expand Down Expand Up @@ -987,7 +992,9 @@ internal class AccessibilityMediator(
* Inserts new elements to [accessibilityElementsMap], updates the old ones, and removes the elements
* that are not present in the tree anymore.
*/
private fun traverseSemanticsTree(rootNode: SemanticsNode): AccessibilityElement {
private fun traverseSemanticsTree(
rootNode: SemanticsNode
): Pair<AccessibilityElement, AccessibilityElementKey?> {
val presentIds = mutableSetOf<AccessibilityElementKey>()

val nodes = owner.getAllUncoveredSemanticsNodesToIntObjectMap(rootNode.id)
Expand All @@ -998,6 +1005,7 @@ internal class AccessibilityMediator(
refocusKeyboardElementIfNeeded()
}
}
var focusedKey: AccessibilityElementKey? = null

// 1. Flatten all children except nodes inside traversal groups to:
// - have the same traversal order as on Android
Expand Down Expand Up @@ -1038,6 +1046,9 @@ internal class AccessibilityMediator(
presentIds.add(node.semanticsKey)

val frame = nodes[node.id]?.adjustedBounds?.toRect() ?: node.unclippedBoundsInWindow
if (node.unmergedConfig.getOrNull(SemanticsProperties.Focused) == true) {
focusedKey = node.semanticsKey
}

fun makeSemanticsNode() = createOrUpdateAccessibilityElement(
node = AccessibilityNode.Semantics(
Expand Down Expand Up @@ -1097,7 +1108,7 @@ internal class AccessibilityMediator(
isPresent
}

return rootAccessibilityElement
return rootAccessibilityElement to focusedKey
}

/**
Expand All @@ -1110,12 +1121,20 @@ internal class AccessibilityMediator(
"Root view must not be an accessibility element"
}

root.element = traverseSemanticsTree(rootSemanticsNode)
val (element, focusedElementKey) = traverseSemanticsTree(rootSemanticsNode)
root.element = element

accessibilityDebugLogger?.let {
debugTraverse(it, view)
}

if (forceFocusedElementKey != focusedElementKey) {
forceFocusedElementKey = focusedElementKey
focusedElementKey?.let {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there is no focused elemet (focusedElementKey == null) don't we want to set the focuseMode to None here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we should keep the old value. The reason is that Compose Ficus and Accessibility focus are work in parallel.
forceFocusedElementKey represent focused element of the Compose, but focuseMode represent the focus of Accessibility (at least it tries).
The logic is the following: when Compose focuses some node, we have to move Accessibility focus to this place. If compose releases focus, we don't need to do anything and keep focus as it was before.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, thanks for the explanation!

focusMode = AccessibilityElementFocusMode.Focus(it)
}
}

return updateFocusedElement()
}

Expand Down Expand Up @@ -1144,6 +1163,17 @@ internal class AccessibilityMediator(
NodesSyncResult(null, isScreenChange = false)
}
}

is AccessibilityElementFocusMode.Focus -> {
val element = accessibilityElementsMap[mode.key]
if (element != null && !CGRectIsEmpty(element.accessibilityFrame())) {
focusMode = AccessibilityElementFocusMode.KeepFocus(mode.key)
NodesSyncResult(element, isScreenChange = false)
} else {
focusMode = AccessibilityElementFocusMode.None
NodesSyncResult(null, isScreenChange = false)
}
}
}
}

Expand Down
Loading