Improve Magnifier performance
Current Magnifier implementation unnecessarily consumes resources when there's no Magnifier shown.
When a MagnifierNode is attached to a modifier hierarchy, it receives global position updates even when the magnifier is not showing. Always calculating the `positionInRoot` becomes wasteful. Instead the new solution signals a change to coordinates by keeping it in a Snapshot state wrapper while setting mutation policy to never. The actual `positionInRoot` calculation only happens when necessary.
Similarly, every draw call starts a coroutine which is already wasteful even when magnifier is showing. The new approach uses a channel to send draw signals to a consumer that starts when the node is attached.
Before this CL;
```
158,269 ns 275 allocs Trace BasicTextField2ToggleTextBenchmark.layout[length=32]
4,976 ns 0 allocs Trace BasicTextField2ToggleTextBenchmark.first_draw[length=32]
3,507,578 ns 605 allocs Trace BasicTextField2ToggleTextBenchmark.first_layout[length=32]
6,700 ns 0 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_measure[length=32]
518,848 ns 247 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_draw[length=32]
1,988,724 ns 1221 allocs Trace BasicTextField2ToggleTextBenchmark.first_measure[length=32]
5,346,187 ns 8072 allocs Trace BasicTextField2ToggleTextBenchmark.first_compose[length=32]
3,823 ns 0 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_layout[length=32]
60,488 ns 51 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_recompose[length=32]
```
After this CL;
```
112,220 ns 144 allocs Trace BasicTextField2ToggleTextBenchmark.layout[length=32]
4,801 ns 0 allocs Trace BasicTextField2ToggleTextBenchmark.first_draw[length=32]
2,840,854 ns 475 allocs Trace BasicTextField2ToggleTextBenchmark.first_layout[length=32]
6,585 ns 0 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_measure[length=32]
395,176 ns 97 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_draw[length=32]
1,938,467 ns 1221 allocs Trace BasicTextField2ToggleTextBenchmark.first_measure[length=32]
4,558,038 ns 7919 allocs Trace BasicTextField2ToggleTextBenchmark.first_compose[length=32]
3,805 ns 0 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_layout[length=32]
65,024 ns 51 allocs Trace BasicTextField2ToggleTextBenchmark.toggleText_recompose[length=32]
```
Test: MagnifierTest
Test: gradle :compose:foundation:foundation:cAT
Test: manually tested with BTF1 and BTF2
Test: New benchmark
Change-Id: I546b9dd1bb20e0e71d19b1da513b564f73b93c3a
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 4cb66ac..465fc46 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -1034,7 +1034,14 @@
// Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
// magnifier actually is). See
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
- if ((dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
+ // Also check whether magnifierSize is calculated. A platform magnifier instance is not
+ // created until it's requested for the first time. So the size will only be calculated after we
+ // return a specified offset from this function.
+ // It is very unlikely that this behavior would cause a flicker since magnifier immediately
+ // shows up where the pointer is being dragged. The pointer needs to drag further than the half
+ // of magnifier's width to hide by the following logic.
+ if (magnifierSize != IntSize.Zero &&
+ (dragX - textConstrainedX).absoluteValue > magnifierSize.width / 2) {
return Offset.Unspecified
}