Skip to content

Commit

Permalink
Merge branch 'jb-main' into andrei.salavei/fix-nodes-merging
Browse files Browse the repository at this point in the history
# Conflicts:
#	compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/Accessibility.uikit.kt
  • Loading branch information
ASalavei committed Dec 20, 2024
2 parents 3ae7ab2 + 0c234a4 commit 495e931
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ import androidx.compose.ui.platform.accessibility.canBeAccessibilityElement
import androidx.compose.ui.platform.accessibility.isRTL
import androidx.compose.ui.platform.accessibility.isScreenReaderFocusable
import androidx.compose.ui.platform.accessibility.scrollIfPossible
import androidx.compose.ui.platform.accessibility.scrollToIfPossible
import androidx.compose.ui.platform.accessibility.scrollToCenterRectIfNeeded
import androidx.compose.ui.platform.accessibility.unclippedBoundsInWindow
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsOwner
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.uikit.density
import androidx.compose.ui.uikit.toDpRect
import androidx.compose.ui.uikit.utils.CMPAccessibilityElement
import androidx.compose.ui.unit.asCGRect
import androidx.compose.ui.unit.toDpRect
import androidx.compose.ui.unit.toRect
import androidx.compose.ui.viewinterop.InteropWrappingView
import androidx.compose.ui.viewinterop.NativeAccessibilityViewSemanticsKey
import kotlin.coroutines.CoroutineContext
Expand All @@ -44,7 +50,6 @@ import kotlin.time.measureTime
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExportObjCClass
import kotlinx.cinterop.readValue
import kotlinx.cinterop.useContents
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -63,7 +68,6 @@ import platform.CoreGraphics.CGRectGetMidY
import platform.CoreGraphics.CGRectGetMinX
import platform.CoreGraphics.CGRectGetMinY
import platform.CoreGraphics.CGRectIsEmpty
import platform.CoreGraphics.CGRectZero
import platform.Foundation.NSNotFound
import platform.UIKit.NSStringFromCGRect
import platform.UIKit.UIAccessibilityCustomAction
Expand All @@ -75,8 +79,8 @@ import platform.UIKit.UIAccessibilityScreenChangedNotification
import platform.UIKit.UIAccessibilityScrollDirection
import platform.UIKit.UIAccessibilityTraitNone
import platform.UIKit.UIAccessibilityTraits
import platform.UIKit.UIEdgeInsetsInsetRect
import platform.UIKit.UIView
import platform.UIKit.UIWindow
import platform.UIKit.accessibilityElementAtIndex
import platform.UIKit.accessibilityElementCount
import platform.UIKit.accessibilityElements
Expand Down Expand Up @@ -142,7 +146,7 @@ private sealed interface AccessibilityNode {
private val semanticsNode: SemanticsNode,
private val mediator: AccessibilityMediator,
private val ignoreSemanticChildren: Boolean = false
): AccessibilityNode {
) : AccessibilityNode {
private val cachedConfig = semanticsNode.copyWithMergingEnabled().config

override val key: AccessibilityElementKey
Expand Down Expand Up @@ -225,9 +229,10 @@ private sealed interface AccessibilityNode {
}

override fun accessibilityScrollToVisible(): Boolean {
semanticsNode.scrollToIfPossible()

return true
return semanticsNode.parent?.scrollToCenterRectIfNeeded(
rect = semanticsNode.unclippedBoundsInWindow,
safeAreaRectInWindow = mediator.safeAreaRectInWindow
) ?: false
}

override fun accessibilityScroll(direction: UIAccessibilityScrollDirection): Boolean {
Expand Down Expand Up @@ -604,11 +609,6 @@ internal class AccessibilityMediator(
val view: UIView,
val owner: SemanticsOwner,
coroutineContext: CoroutineContext,
/**
* A function that converts the given [Rect] from the semantics tree coordinate space (window container for layers)
* to the [CGRect] in coordinate space of the app window.
*/
val convertToAppWindowCGRect: (Rect, UIWindow) -> CValue<CGRect>,
val performEscape: () -> Boolean
): NSObject() {

Expand Down Expand Up @@ -667,6 +667,14 @@ internal class AccessibilityMediator(
}
}

val safeAreaRectInWindow: Rect get() {
val rectInWindow = view.convertRect(
rect = UIEdgeInsetsInsetRect(view.bounds, view.safeAreaInsets),
toView = null
)
return rectInWindow.toDpRect().toRect(view.density)
}

init {
accessibilityDebugLogger?.log("AccessibilityMediator for $view created")

Expand Down Expand Up @@ -717,9 +725,7 @@ internal class AccessibilityMediator(
val hasPendingInvalidations: Boolean get() = !invalidationChannel.isEmpty

fun convertToAppWindowCGRect(rect: Rect): CValue<CGRect> {
val window = view.window ?: return CGRectZero.readValue()

return convertToAppWindowCGRect(rect, window)
return rect.toDpRect(view.density).asCGRect()
}

fun notifyScrollCompleted(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,58 +150,58 @@ internal data class AccessibilityScrollEventResult(

/**
* Try to perform a scroll on any ancestor of this element if the element is not fully visible.
* @param rect to place in the center of scrollable area
* @param safeAreaRectInWindow safe area rect to reduce focusable borders
* @return true if the scroll was successful, otherwise returns false
*/
internal fun SemanticsNode.scrollToIfPossible() {
val scrollableAncestor = scrollableByAncestor ?: return
internal fun SemanticsNode.scrollToCenterRectIfNeeded(
rect: Rect,
safeAreaRectInWindow: Rect
): Boolean {
val scrollableAncestor = scrollableByAncestor ?: return false
val scrollableAncestorRect = scrollableAncestor.boundsInWindow

val unclippedRect = unclippedBoundsInWindow
val scrollableViewportRect = scrollableAncestorRect.intersect(safeAreaRectInWindow)

fun Float.invertIfNeeded() = if (isRTL) -this else this
// TODO: consider safe areas?
if (unclippedRect.top < scrollableAncestorRect.top) {

val dy = if (rect.top < scrollableViewportRect.top) {
// The element is above the screen, scroll up
parent?.scrollByIfPossible(
0f,
unclippedRect.top - scrollableAncestorRect.top -
(scrollableAncestor.size.height - unclippedRect.size.height) / 2
)
} else if (unclippedRect.bottom > scrollableAncestorRect.bottom) {
rect.top - scrollableViewportRect.top -
(scrollableAncestor.size.height - rect.size.height) / 2
} else if (rect.bottom > scrollableViewportRect.bottom) {
// The element is below the screen, scroll down
parent?.scrollByIfPossible(
0f,
unclippedRect.bottom - scrollableAncestorRect.bottom +
(scrollableAncestor.size.height - unclippedRect.size.height) / 2
)
} else if (unclippedRect.left < scrollableAncestorRect.left) {
rect.bottom - scrollableViewportRect.bottom +
(scrollableAncestor.size.height - rect.size.height) / 2
} else {
0f
}

val dx = if (rect.left < scrollableViewportRect.left) {
// The element is to the left of the screen, scroll left
parent?.scrollByIfPossible(
(unclippedRect.left - scrollableAncestorRect.left -
(scrollableAncestor.size.width - unclippedRect.size.width) / 2).invertIfNeeded(),
0f
)
} else if (unclippedRect.right > scrollableAncestorRect.right) {
(rect.left - scrollableViewportRect.left -
(scrollableAncestor.size.width - rect.size.width) / 2).invertIfNeeded()
} else if (rect.right > scrollableViewportRect.right) {
// The element is to the right of the screen, scroll right
parent?.scrollByIfPossible(
(unclippedRect.right - scrollableAncestorRect.right +
(scrollableAncestor.size.width - unclippedRect.size.width) / 2).invertIfNeeded(),
0f
)
(rect.right - scrollableViewportRect.right +
(scrollableAncestor.size.width - rect.size.width) / 2).invertIfNeeded()
} else {
0f
}
return scrollByIfPossible(dx, dy)
}

private fun SemanticsNode.scrollByIfPossible(dx: Float, dy: Float) {
private fun SemanticsNode.scrollByIfPossible(dx: Float, dy: Float): Boolean {
// if it has scrollBy action, invoke it, otherwise try to scroll the parent
val action = config.getOrNull(SemanticsActions.ScrollBy)?.action

if (action != null) {
return if (action != null) {
action(dx, dy)
} else {
parent?.scrollByIfPossible(dx, dy)
parent?.scrollByIfPossible(dx, dy) ?: false
}
}

private val SemanticsNode.unclippedBoundsInWindow: Rect
internal val SemanticsNode.unclippedBoundsInWindow: Rect
get() = Rect(positionInWindow, size.toSize())

internal val SemanticsNode.isRTL: Boolean
Expand Down Expand Up @@ -239,7 +239,7 @@ private val SemanticsNode.isHiddenFromAccessibility: Boolean
*/
private val SemanticsNode.scrollableByAncestor: SemanticsNode?
get() {
var current = parent
var current: SemanticsNode? = this

while (current != null) {
if (current.config.getOrNull(SemanticsActions.ScrollBy) != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,11 @@ import androidx.compose.ui.uikit.embedSubview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.asCGRect
import androidx.compose.ui.unit.asDpOffset
import androidx.compose.ui.unit.asDpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import androidx.compose.ui.unit.roundToIntSize
import androidx.compose.ui.unit.toDpRect
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toPlatformInsets
import androidx.compose.ui.unit.toSize
Expand All @@ -94,15 +92,13 @@ import kotlinx.cinterop.useContents
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.suspendCancellableCoroutine
import platform.CoreGraphics.CGPoint
import platform.CoreGraphics.CGRect
import platform.QuartzCore.CACurrentMediaTime
import platform.QuartzCore.CATransaction
import platform.UIKit.UIEvent
import platform.UIKit.UIPress
import platform.UIKit.UITouch
import platform.UIKit.UITouchPhase
import platform.UIKit.UIView
import platform.UIKit.UIWindow

/**
* iOS specific-implementation of [PlatformContext.SemanticsOwnerListener] used to track changes in [SemanticsOwner].
Expand All @@ -114,7 +110,6 @@ import platform.UIKit.UIWindow
private class SemanticsOwnerListenerImpl(
private val rootView: UIView,
private val coroutineContext: CoroutineContext,
private val convertToAppWindowCGRect: (Rect, UIWindow) -> CValue<CGRect>,
private val performEscape: () -> Boolean
) : PlatformContext.SemanticsOwnerListener {

Expand All @@ -132,7 +127,6 @@ private class SemanticsOwnerListenerImpl(
rootView,
semanticsOwner,
coroutineContext,
convertToAppWindowCGRect,
performEscape
).also {
it.isEnabled = isEnabled
Expand Down Expand Up @@ -278,11 +272,6 @@ internal class ComposeSceneMediator(
SemanticsOwnerListenerImpl(
rootView = view,
coroutineContext = coroutineContext,
convertToAppWindowCGRect = { rect, window ->
windowContext.convertWindowRect(rect, window)
.toDpRect(Density(window.screen.scale.toFloat()))
.asCGRect()
},
performEscape = {
val down = onKeyboardEvent(KeyEvent(Key.Escape, KeyEventType.KeyDown))
val up = onKeyboardEvent(KeyEvent(Key.Escape, KeyEventType.KeyUp))
Expand Down

0 comments on commit 495e931

Please sign in to comment.