Skip to content

Commit f41302c

Browse files
committed
CMP-9264 Expose showLayoutBounds API on ComposePanel
This exposes a showLayoutBounds property on ComposePanel (desktop only) that allows users to turn on or off the showLayoutBounds property on the root layout node owner(s). The change mostly pipes the property up to the ComposePanel, but has no public API impact. The new property is also annotated as experimental. The Skiko RootNodeOwner's showLayoutBounds is now also a state, to cause a full recomposition when the value changes and see it reflected in the UI.
1 parent 4c05dc6 commit f41302c

File tree

8 files changed

+118
-65
lines changed

8 files changed

+118
-65
lines changed

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposePanel.desktop.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
171171

172172
override fun getPreferredSize(): Dimension? = if (isPreferredSizeSet) {
173173
super.getPreferredSize()
174-
} else {
174+
} else {
175175
_composeContainer?.preferredSize ?: Dimension(0, 0)
176176
}
177177

@@ -272,6 +272,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
272272
focusManager.takeFocus(FocusDirection.Next)
273273
}
274274
}
275+
275276
else -> Unit
276277
}
277278
}
@@ -378,4 +379,13 @@ class ComposePanel @ExperimentalComposeUiApi constructor(
378379
fun renderImmediately() {
379380
_composeContainer?.renderImmediately()
380381
}
382+
383+
@ExperimentalComposeUiApi
384+
var showLayoutBounds: Boolean
385+
get() {
386+
return _composeContainer?.showLayoutBounds ?: false
387+
}
388+
set(value) {
389+
_composeContainer?.showLayoutBounds = value
390+
}
381391
}

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/ComposeContainer.desktop.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ internal class ComposeContainer(
175175
val renderApi by mediator::renderApi
176176
val preferredSize by mediator::preferredSize
177177
val semanticsOwners by mediator::semanticsOwners
178+
var showLayoutBounds by mediator::showLayoutBounds
178179

179180
private var isDisposed = false
180181
private var isDetached = true

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.desktop.kt

Lines changed: 68 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,13 @@ internal class ComposeSceneMediator(
257257
platformContext.parentFocusManager.moveFocus(FocusDirection.Previous)
258258
}
259259
}
260+
260261
TRAVERSAL, TRAVERSAL_FORWARD -> {
261262
if (!focusManager.takeFocus(FocusDirection.Next)) {
262263
platformContext.parentFocusManager.moveFocus(FocusDirection.Next)
263264
}
264265
}
266+
265267
else -> Unit
266268
}
267269
}
@@ -318,11 +320,19 @@ internal class ComposeSceneMediator(
318320
}
319321
}
320322

321-
private val scene by lazy { composeSceneFactory(this) }
323+
internal val scene by lazy { composeSceneFactory(this) }
322324
val focusManager get() = scene.focusManager
323325
var compositionLocalContext: CompositionLocalContext?
324326
get() = scene.compositionLocalContext
325-
set(value) { scene.compositionLocalContext = value }
327+
set(value) {
328+
scene.compositionLocalContext = value
329+
}
330+
var showLayoutBounds: Boolean
331+
get() = scene.showLayoutBounds
332+
set(value) {
333+
scene.showLayoutBounds = value
334+
}
335+
326336

327337
/**
328338
* Provides the size of ComposeScene content inside infinity constraints
@@ -493,7 +503,7 @@ internal class ComposeSceneMediator(
493503
/**
494504
* Returns the first heavyweight ancestor of the given component.
495505
*/
496-
private fun Component.heavyWeightAncestorOrNull() : Component? {
506+
private fun Component.heavyWeightAncestorOrNull(): Component? {
497507
var parent = parent
498508
while (parent != null) {
499509
if (!parent.isLightweight) return parent
@@ -671,13 +681,14 @@ internal class ComposeSceneMediator(
671681
scene.layoutDirection = layoutDirection
672682
}
673683

674-
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) = catchExceptions {
675-
interopContainer.postponingExecutingScheduledUpdates {
676-
canvas.withSceneOffset {
677-
scene.render(asComposeCanvas(), nanoTime)
684+
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) =
685+
catchExceptions {
686+
interopContainer.postponingExecutingScheduledUpdates {
687+
canvas.withSceneOffset {
688+
scene.render(asComposeCanvas(), nanoTime)
689+
}
678690
}
679691
}
680-
}
681692

682693
private inline fun Canvas.withSceneOffset(block: Canvas.() -> Unit) {
683694
// Offset of scene relative to [container]
@@ -706,7 +717,8 @@ internal class ComposeSceneMediator(
706717
keyboardModifiersRequireUpdate = true
707718
}
708719

709-
private inner class DesktopViewConfiguration : ViewConfiguration by PlatformContext.DefaultViewConfiguration {
720+
private inner class DesktopViewConfiguration :
721+
ViewConfiguration by PlatformContext.DefaultViewConfiguration {
710722
override val touchSlop: Float get() = with(platformComponent.density) { 18.dp.toPx() }
711723
}
712724

@@ -747,7 +759,8 @@ internal class ComposeSceneMediator(
747759
* A new [SemanticsOwner] is always created above existing ones. So, usage of [LinkedHashMap]
748760
* is required here to keep insertion-order (that equal to [SemanticsOwner]s order).
749761
*/
750-
private val _accessibilityControllers = linkedMapOf<SemanticsOwner, AccessibilityController>()
762+
private val _accessibilityControllers =
763+
linkedMapOf<SemanticsOwner, AccessibilityController>()
751764
val accessibilityControllers get() = _accessibilityControllers.values.reversed()
752765

753766
val semanticsOwners = mutableStateSetOf<SemanticsOwner>()
@@ -797,7 +810,8 @@ internal class ComposeSceneMediator(
797810
override fun convertScreenToLocalPosition(positionOnScreen: Offset): Offset =
798811
windowContext.convertScreenToLocalPosition(container, positionOnScreen)
799812

800-
override val measureDrawLayerBounds: Boolean = this@ComposeSceneMediator.measureDrawLayerBounds
813+
override val measureDrawLayerBounds: Boolean =
814+
this@ComposeSceneMediator.measureDrawLayerBounds
801815
override val viewConfiguration: ViewConfiguration = DesktopViewConfiguration()
802816
override val inputModeManager: InputModeManager = DefaultInputModeManager()
803817
override val textInputService = this@ComposeSceneMediator.textInputService
@@ -810,6 +824,7 @@ internal class ComposeSceneMediator(
810824
contentComponent.cursor =
811825
(pointerIcon as? AwtCursor)?.cursor ?: Cursor(Cursor.DEFAULT_CURSOR)
812826
}
827+
813828
override val parentFocusManager: FocusManager = DesktopFocusManager()
814829
override fun requestFocus(): Boolean {
815830
// Don't check hasFocus(), and don't check the returning result
@@ -893,19 +908,20 @@ private fun ComposeScene.onMouseEvent(
893908
)
894909
}
895910

896-
internal val MouseEvent.composePointerButton: PointerButton? get() {
897-
if (button == MouseEvent.NOBUTTON) return null
898-
return when (button) {
899-
MouseEvent.BUTTON2 -> PointerButton.Tertiary
900-
MouseEvent.BUTTON3 -> PointerButton.Secondary
901-
else -> PointerButton(button - 1)
911+
internal val MouseEvent.composePointerButton: PointerButton?
912+
get() {
913+
if (button == MouseEvent.NOBUTTON) return null
914+
return when (button) {
915+
MouseEvent.BUTTON2 -> PointerButton.Tertiary
916+
MouseEvent.BUTTON3 -> PointerButton.Secondary
917+
else -> PointerButton(button - 1)
918+
}
902919
}
903-
}
904920

905921
private fun ComposeScene.onMouseWheelEvent(
906922
position: Offset,
907923
event: MouseWheelEvent
908-
) : PointerEventResult {
924+
): PointerEventResult {
909925
return sendPointerEvent(
910926
eventType = PointerEventType.Scroll,
911927
position = position,
@@ -923,37 +939,39 @@ private fun ComposeScene.onMouseWheelEvent(
923939
}
924940

925941

926-
private val MouseEvent.buttons get() = PointerButtons(
927-
// We should check [event.button] because of case where [event.modifiersEx] does not provide
928-
// info about the pressed mouse button when using touchpad on MacOS 12 (AWT only).
929-
// When the [Tap to click] feature is activated on Mac OS 12, half of all clicks are not
930-
// handled because [event.modifiersEx] may not provide info about the pressed mouse button.
931-
isPrimaryPressed = ((modifiersEx and MouseEvent.BUTTON1_DOWN_MASK) != 0
932-
|| (id == MouseEvent.MOUSE_PRESSED && button == MouseEvent.BUTTON1))
933-
&& !isMacOsCtrlClick,
934-
isSecondaryPressed = (modifiersEx and MouseEvent.BUTTON3_DOWN_MASK) != 0
935-
|| (id == MouseEvent.MOUSE_PRESSED && button == MouseEvent.BUTTON3)
936-
|| isMacOsCtrlClick,
937-
isTertiaryPressed = (modifiersEx and MouseEvent.BUTTON2_DOWN_MASK) != 0
938-
|| (id == MouseEvent.MOUSE_PRESSED && button == MouseEvent.BUTTON2),
939-
isBackPressed = (modifiersEx and MouseEvent.getMaskForButton(4)) != 0
940-
|| (id == MouseEvent.MOUSE_PRESSED && button == 4),
941-
isForwardPressed = (modifiersEx and MouseEvent.getMaskForButton(5)) != 0
942-
|| (id == MouseEvent.MOUSE_PRESSED && button == 5),
943-
)
944-
945-
private val MouseEvent.keyboardModifiers get() = PointerKeyboardModifiers(
946-
isCtrlPressed = (modifiersEx and InputEvent.CTRL_DOWN_MASK) != 0,
947-
isMetaPressed = (modifiersEx and InputEvent.META_DOWN_MASK) != 0,
948-
isAltPressed = (modifiersEx and InputEvent.ALT_DOWN_MASK) != 0,
949-
isShiftPressed = (modifiersEx and InputEvent.SHIFT_DOWN_MASK) != 0,
950-
isAltGraphPressed = (modifiersEx and InputEvent.ALT_GRAPH_DOWN_MASK) != 0,
951-
isSymPressed = false,
952-
isFunctionPressed = false,
953-
isCapsLockOn = getLockingKeyStateSafe(KeyEvent.VK_CAPS_LOCK),
954-
isScrollLockOn = getLockingKeyStateSafe(KeyEvent.VK_SCROLL_LOCK),
955-
isNumLockOn = getLockingKeyStateSafe(KeyEvent.VK_NUM_LOCK),
956-
)
942+
private val MouseEvent.buttons
943+
get() = PointerButtons(
944+
// We should check [event.button] because of case where [event.modifiersEx] does not provide
945+
// info about the pressed mouse button when using touchpad on MacOS 12 (AWT only).
946+
// When the [Tap to click] feature is activated on Mac OS 12, half of all clicks are not
947+
// handled because [event.modifiersEx] may not provide info about the pressed mouse button.
948+
isPrimaryPressed = ((modifiersEx and MouseEvent.BUTTON1_DOWN_MASK) != 0
949+
|| (id == MouseEvent.MOUSE_PRESSED && button == MouseEvent.BUTTON1))
950+
&& !isMacOsCtrlClick,
951+
isSecondaryPressed = (modifiersEx and MouseEvent.BUTTON3_DOWN_MASK) != 0
952+
|| (id == MouseEvent.MOUSE_PRESSED && button == MouseEvent.BUTTON3)
953+
|| isMacOsCtrlClick,
954+
isTertiaryPressed = (modifiersEx and MouseEvent.BUTTON2_DOWN_MASK) != 0
955+
|| (id == MouseEvent.MOUSE_PRESSED && button == MouseEvent.BUTTON2),
956+
isBackPressed = (modifiersEx and MouseEvent.getMaskForButton(4)) != 0
957+
|| (id == MouseEvent.MOUSE_PRESSED && button == 4),
958+
isForwardPressed = (modifiersEx and MouseEvent.getMaskForButton(5)) != 0
959+
|| (id == MouseEvent.MOUSE_PRESSED && button == 5),
960+
)
961+
962+
private val MouseEvent.keyboardModifiers
963+
get() = PointerKeyboardModifiers(
964+
isCtrlPressed = (modifiersEx and InputEvent.CTRL_DOWN_MASK) != 0,
965+
isMetaPressed = (modifiersEx and InputEvent.META_DOWN_MASK) != 0,
966+
isAltPressed = (modifiersEx and InputEvent.ALT_DOWN_MASK) != 0,
967+
isShiftPressed = (modifiersEx and InputEvent.SHIFT_DOWN_MASK) != 0,
968+
isAltGraphPressed = (modifiersEx and InputEvent.ALT_GRAPH_DOWN_MASK) != 0,
969+
isSymPressed = false,
970+
isFunctionPressed = false,
971+
isCapsLockOn = getLockingKeyStateSafe(KeyEvent.VK_CAPS_LOCK),
972+
isScrollLockOn = getLockingKeyStateSafe(KeyEvent.VK_SCROLL_LOCK),
973+
isNumLockOn = getLockingKeyStateSafe(KeyEvent.VK_NUM_LOCK),
974+
)
957975

958976
private fun Component.subscribeToMouseEvents(mouseAdapter: MouseAdapter) {
959977
addMouseListener(mouseAdapter)

compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/node/RootNodeOwner.skiko.kt

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ package androidx.compose.ui.node
1818

1919
import androidx.collection.MutableIntObjectMap
2020
import androidx.collection.mutableIntObjectMapOf
21-
import androidx.compose.runtime.retain.RetainedValuesStore
2221
import androidx.compose.runtime.collection.mutableVectorOf
2322
import androidx.compose.runtime.getValue
2423
import androidx.compose.runtime.mutableStateOf
2524
import androidx.compose.runtime.retain.ForgetfulRetainedValuesStore
25+
import androidx.compose.runtime.retain.RetainedValuesStore
2626
import androidx.compose.runtime.setValue
2727
import androidx.compose.runtime.snapshotFlow
2828
import androidx.compose.runtime.snapshots.Snapshot
@@ -68,13 +68,13 @@ import androidx.compose.ui.platform.DefaultAccessibilityManager
6868
import androidx.compose.ui.platform.DefaultHapticFeedback
6969
import androidx.compose.ui.platform.DelegatingSoftwareKeyboardController
7070
import androidx.compose.ui.platform.GraphicsLayerOwnerLayer
71+
import androidx.compose.ui.platform.LegacyRenderNodeLayer
7172
import androidx.compose.ui.platform.OwnedLayerManager
7273
import androidx.compose.ui.platform.PlatformClipboardManager
7374
import androidx.compose.ui.platform.PlatformContext
7475
import androidx.compose.ui.platform.PlatformRootForTest
7576
import androidx.compose.ui.platform.PlatformTextInputMethodRequest
7677
import androidx.compose.ui.platform.PlatformTextInputSessionScope
77-
import androidx.compose.ui.platform.LegacyRenderNodeLayer
7878
import androidx.compose.ui.platform.createPlatformClipboard
7979
import androidx.compose.ui.platform.setLightingInfo
8080
import androidx.compose.ui.scene.ComposeScene
@@ -135,7 +135,8 @@ internal class RootNodeOwner(
135135
private val rootSemanticsNode = EmptySemanticsModifier()
136136
private val snapshotObserver = snapshotInvalidationTracker.snapshotObserver()
137137
private val graphicsContext = SkiaGraphicsContext(platformContext.measureDrawLayerBounds)
138-
private val coroutineScope = CoroutineScope(coroutineContext + Job(parent = coroutineContext[Job]))
138+
private val coroutineScope =
139+
CoroutineScope(coroutineContext + Job(parent = coroutineContext[Job]))
139140

140141
private val _owner = OwnerImpl(layoutDirection, coroutineContext)
141142
val owner: Owner get() = _owner
@@ -397,10 +398,10 @@ internal class RootNodeOwner(
397398
override val focusOwner: FocusOwner = FocusOwnerImpl(platformFocusOwner, this)
398399

399400
val rootModifier = if (ComposeUiFlags.areWindowInsetsRulersEnabled) {
400-
RulerProviderModifierElement(platformContext.windowInsets)
401-
} else {
402-
Modifier
403-
}
401+
RulerProviderModifierElement(platformContext.windowInsets)
402+
} else {
403+
Modifier
404+
}
404405
.then(EmptySemanticsElement(rootSemanticsNode))
405406
.focusProperties {
406407
onExit = {
@@ -435,11 +436,14 @@ internal class RootNodeOwner(
435436
override val accessibilityManager = DefaultAccessibilityManager()
436437
override val graphicsContext get() = this@RootNodeOwner.graphicsContext
437438
override val textToolbar get() = platformContext.textToolbar
439+
438440
@Suppress("DEPRECATION")
439441
override val autofillTree = androidx.compose.ui.autofill.AutofillTree()
442+
440443
@Suppress("DEPRECATION")
441444
override val autofill: androidx.compose.ui.autofill.Autofill?
442445
get() = null
446+
443447
// TODO https://youtrack.jetbrains.com/issue/CMP-1572
444448
override val autofillManager: AutofillManager? get() = null
445449
override val density get() = this@RootNodeOwner.density
@@ -449,6 +453,7 @@ internal class RootNodeOwner(
449453
DelegatingSoftwareKeyboardController(textInputService)
450454

451455
private val textInputSessionMutex = SessionMutex<TextInputSession>()
456+
452457
private inner class TextInputSession(
453458
coroutineScope: CoroutineScope,
454459
) : PlatformTextInputSessionScope, CoroutineScope by coroutineScope {
@@ -480,7 +485,7 @@ internal class RootNodeOwner(
480485

481486
override suspend fun textInputSession(
482487
session: suspend PlatformTextInputSessionScope.() -> Nothing
483-
) : Nothing {
488+
): Nothing {
484489
textInputSessionMutex.withSessionCancellingPrevious<Nothing>(
485490
sessionInitializer = ::TextInputSession,
486491
session = session
@@ -498,7 +503,7 @@ internal class RootNodeOwner(
498503
override val fontLoader = androidx.compose.ui.text.platform.FontLoader()
499504
override val fontFamilyResolver = createFontFamilyResolver()
500505
override val layoutDirection get() = _layoutDirection
501-
override var showLayoutBounds = false
506+
override var showLayoutBounds by mutableStateOf(false)
502507
@InternalCoreApi
503508
set
504509

@@ -718,6 +723,7 @@ internal class RootNodeOwner(
718723

719724
private inner class PlatformRootForTestImpl : PlatformRootForTest {
720725
override val density get() = this@RootNodeOwner.density
726+
721727
@Suppress("OVERRIDE_DEPRECATION")
722728
override val textInputService get() = owner.textInputService
723729
override val semanticsOwner get() = owner.semanticsOwner
@@ -934,7 +940,8 @@ internal class RootNodeOwner(
934940
postponed.clear()
935941
}
936942

937-
val isAnyCurrentFrameRateSet = !currentFrameRate.isNaN() || currentFrameRateCategory != 0f
943+
val isAnyCurrentFrameRateSet =
944+
!currentFrameRate.isNaN() || currentFrameRateCategory != 0f
938945
if (ComposeUiFlags.isAdaptiveRefreshRateEnabled && isAnyCurrentFrameRateSet) {
939946
platformContext.voteFrameRate(currentFrameRate, currentFrameRateCategory)
940947
currentFrameRate = Float.NaN
@@ -994,7 +1001,7 @@ private fun MeasureAndLayoutDelegate.updateRootConstraintsWithInfinityCheck(
9941001

9951002
private fun IntSize.toConstraints() = Constraints(maxWidth = width, maxHeight = height)
9961003

997-
private object IdentityPositionCalculator: PositionCalculator {
1004+
private object IdentityPositionCalculator : PositionCalculator {
9981005
override fun screenToLocal(positionOnScreen: Offset): Offset = positionOnScreen
9991006
override fun localToScreen(localPosition: Offset): Offset = localPosition
10001007
}

compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/scene/BaseComposeScene.skiko.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import androidx.compose.runtime.Composable
2121
import androidx.compose.runtime.Composition
2222
import androidx.compose.runtime.CompositionContext
2323
import androidx.compose.runtime.CompositionLocalContext
24-
import androidx.compose.runtime.CompositionLocalProvider
2524
import androidx.compose.runtime.MonotonicFrameClock
2625
import androidx.compose.runtime.getValue
2726
import androidx.compose.runtime.mutableStateOf
@@ -40,8 +39,6 @@ import androidx.compose.ui.input.pointer.PointerType
4039
import androidx.compose.ui.input.rotary.RotaryScrollEvent
4140
import androidx.compose.ui.node.SnapshotInvalidationTracker
4241
import androidx.compose.ui.platform.GlobalSnapshotManager
43-
import androidx.compose.ui.platform.LocalPlatformScreenReader
44-
import androidx.compose.ui.platform.LocalPlatformWindowInsets
4542
import androidx.compose.ui.platform.ProvidePlatformCompositionLocals
4643
import androidx.compose.ui.util.trace
4744
import kotlin.concurrent.Volatile

0 commit comments

Comments
 (0)