Merge "Update custom slider thumb + label samples so that the text inside the label will scale based on the display / text size of the device." into androidx-main
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt
index eeab41c..2d55801 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapter.kt
@@ -54,6 +54,12 @@
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
] == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
+ /**
+ * Maps [CaptureConfig] to [Request].
+ *
+ * @throws IllegalStateException When CaptureConfig does not have any surface or a CaptureConfig
+ * surface is not recognized in [UseCaseGraphConfig.surfaceToStreamMap]
+ */
@OptIn(ExperimentalGetImage::class)
fun mapToRequest(
captureConfig: CaptureConfig,
@@ -68,7 +74,7 @@
val streamIdList = surfaces.map {
checkNotNull(useCaseGraphConfig.surfaceToStreamMap[it]) {
- "Attempted to issue a capture with an unrecognized surface."
+ "Attempted to issue a capture with an unrecognized surface: $it"
}
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
index 19d4488..2f747c1 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
@@ -404,46 +404,66 @@
}
debug { "CapturePipeline#submitRequestInternal; Submitting $configs with CameraPipe" }
val deferredList = mutableListOf<CompletableDeferred<Void?>>()
- val requests = configs.map {
+ val requests = configs.mapNotNull {
val completeSignal = CompletableDeferred<Void?>().also { deferredList.add(it) }
- configAdapter.mapToRequest(
- it, requestTemplate, sessionConfigOptions,
- listOf(object : Request.Listener {
- override fun onAborted(request: Request) {
- completeSignal.completeExceptionally(
- ImageCaptureException(
- ERROR_CAMERA_CLOSED,
- "Capture request is cancelled because camera is closed",
- null
+ try {
+ configAdapter.mapToRequest(
+ it, requestTemplate, sessionConfigOptions,
+ listOf(object : Request.Listener {
+ override fun onAborted(request: Request) {
+ completeSignal.completeExceptionally(
+ ImageCaptureException(
+ ERROR_CAMERA_CLOSED,
+ "Capture request is cancelled because camera is closed",
+ null
+ )
)
- )
- }
+ }
- override fun onTotalCaptureResult(
- requestMetadata: RequestMetadata,
- frameNumber: FrameNumber,
- totalCaptureResult: FrameInfo,
- ) {
- completeSignal.complete(null)
- }
+ override fun onTotalCaptureResult(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ totalCaptureResult: FrameInfo,
+ ) {
+ completeSignal.complete(null)
+ }
- @SuppressLint("ClassVerificationFailure")
- override fun onFailed(
- requestMetadata: RequestMetadata,
- frameNumber: FrameNumber,
- requestFailure: RequestFailure
- ) {
- completeSignal.completeExceptionally(
- ImageCaptureException(
- ERROR_CAPTURE_FAILED,
- "Capture request failed with reason " +
- requestFailure.reason,
- null
+ @SuppressLint("ClassVerificationFailure")
+ override fun onFailed(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ requestFailure: RequestFailure
+ ) {
+ completeSignal.completeExceptionally(
+ ImageCaptureException(
+ ERROR_CAPTURE_FAILED,
+ "Capture request failed with reason " +
+ requestFailure.reason,
+ null
+ )
)
- )
- }
- })
- )
+ }
+ })
+ )
+ } catch (e: IllegalStateException) {
+ info(e) {
+ "CapturePipeline#submitRequestInternal: configAdapter.mapToRequest failed!"
+ }
+ completeSignal.completeExceptionally(
+ ImageCaptureException(
+ ERROR_CAPTURE_FAILED,
+ "Capture request failed with reason " + e.message,
+ e
+ )
+ )
+ null
+ }
+ }
+
+ if (requests.isEmpty()) {
+ // requests can be empty due to configAdapter.mapToRequest throwing exception, all the
+ // deferred instances in the list should already be completed exceptionally.
+ return deferredList
}
threads.sequentialScope.launch {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 8703b53..162e92b 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration.SESSION_HIGH_SPEED
import android.hardware.camera2.params.SessionConfiguration.SESSION_REGULAR
import android.media.MediaCodec
@@ -54,6 +55,7 @@
import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
import androidx.camera.core.DynamicRange
+import androidx.camera.core.MirrorMode
import androidx.camera.core.UseCase
import androidx.camera.core.impl.CameraControlInternal
import androidx.camera.core.impl.CameraInfoInternal
@@ -702,6 +704,7 @@
val deferrableSurface = outputConfig.surface
val physicalCameraId =
physicalCameraIdForAllStreams ?: outputConfig.physicalCameraId
+ val mirrorMode = outputConfig.mirrorMode
val outputStreamConfig = OutputStream.Config.create(
size = deferrableSurface.prescribedSize,
format = StreamFormat(deferrableSurface.prescribedStreamFormat),
@@ -710,6 +713,15 @@
} else {
CameraId.fromCamera2Id(physicalCameraId)
},
+ // No need to map MIRROR_MODE_ON_FRONT_ONLY to MIRROR_MODE_AUTO
+ // since its default value in framework
+ mirrorMode = when (mirrorMode) {
+ MirrorMode.MIRROR_MODE_OFF -> OutputStream.MirrorMode(
+ OutputConfiguration.MIRROR_MODE_NONE)
+ MirrorMode.MIRROR_MODE_ON -> OutputStream.MirrorMode(
+ OutputConfiguration.MIRROR_MODE_H)
+ else -> null
+ },
streamUseCase = getStreamUseCase(
deferrableSurface,
sessionConfigAdapter.surfaceToStreamUseCaseMap
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
index c0d2b91..548f95db 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Streams.kt
@@ -497,12 +497,10 @@
val maxImages: Int
val format: StreamFormat
- // TODO: b/330594328 - Remove `format` and make `streamFormat` required.
class Config(
val stream: CameraStream.Config,
val maxImages: Int,
- var format: Int? = null,
- var streamFormat: StreamFormat? = null
+ var streamFormat: StreamFormat
)
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
index d2b595e..dab85d0 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
@@ -154,11 +154,10 @@
}
}
inputs = graphConfig.input?.map {
- val streamFormat = it.streamFormat ?: StreamFormat(checkNotNull(it.format))
InputStreamImpl(
nextInputId(),
it.maxImages,
- streamFormat,
+ it.streamFormat,
)
} ?: emptyList()
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 5be543a..b0e3164 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -23,6 +23,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.DynamicRangeProfiles;
+import android.hardware.camera2.params.OutputConfiguration;
import android.os.Build;
import android.view.Surface;
@@ -44,6 +45,7 @@
import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.Logger;
+import androidx.camera.core.MirrorMode;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.CaptureConfig;
import androidx.camera.core.impl.DeferrableSurface;
@@ -391,6 +393,14 @@
outputConfig.getPhysicalCameraId());
}
+ // No need to map MIRROR_MODE_ON_FRONT_ONLY to MIRROR_MODE_AUTO
+ // since its default value in framework
+ if (outputConfig.getMirrorMode() == MirrorMode.MIRROR_MODE_OFF) {
+ outputConfiguration.setMirrorMode(OutputConfiguration.MIRROR_MODE_NONE);
+ } else if (outputConfig.getMirrorMode() == MirrorMode.MIRROR_MODE_ON) {
+ outputConfiguration.setMirrorMode(OutputConfiguration.MIRROR_MODE_H);
+ }
+
if (!outputConfig.getSharedSurfaces().isEmpty()) {
outputConfiguration.enableSurfaceSharing();
for (DeferrableSurface sharedDeferSurface : outputConfig.getSharedSurfaces()) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java
index 9503797..a445d3f 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompat.java
@@ -200,6 +200,24 @@
}
/**
+ * Returns mirror mode of {@link OutputConfiguration}.
+ * @return {@link OutputConfiguration#getMirrorMode()}
+ * @see OutputConfiguration#getMirrorMode()
+ */
+ public int getMirrorMode() {
+ return mImpl.getMirrorMode();
+ }
+
+ /**
+ * Sets mirror mode of {@link OutputConfiguration}.
+ * @param mirrorMode mirror mode to set for {@link OutputConfiguration}.
+ * @see OutputConfiguration#setMirrorMode(int)
+ */
+ public void setMirrorMode(int mirrorMode) {
+ mImpl.setMirrorMode(mirrorMode);
+ }
+
+ /**
* Retrieve the physical camera ID set by {@link #setPhysicalCameraId(String)}.
*
*/
@@ -488,6 +506,10 @@
interface OutputConfigurationCompatImpl {
void enableSurfaceSharing();
+ int getMirrorMode();
+
+ void setMirrorMode(int mirrorMode);
+
@Nullable
String getPhysicalCameraId();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java
index a2d905b..bc8829f 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatApi33Impl.java
@@ -48,6 +48,16 @@
}
@Override
+ public int getMirrorMode() {
+ return ((OutputConfiguration) getOutputConfiguration()).getMirrorMode();
+ }
+
+ @Override
+ public void setMirrorMode(int mirrorMode) {
+ ((OutputConfiguration) getOutputConfiguration()).setMirrorMode(mirrorMode);
+ }
+
+ @Override
public long getDynamicRangeProfile() {
return ((OutputConfiguration) getOutputConfiguration()).getDynamicRangeProfile();
}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java
index ef70b78..07ee1df 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatBaseImpl.java
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint;
import android.graphics.ImageFormat;
import android.hardware.camera2.params.DynamicRangeProfiles;
+import android.hardware.camera2.params.OutputConfiguration;
import android.os.Build;
import android.util.Size;
import android.view.Surface;
@@ -82,6 +83,16 @@
return ((OutputConfigurationParamsApi21) mObject).mPhysicalCameraId;
}
+ @Override
+ public void setMirrorMode(int mirrorMode) {
+ //No-op
+ }
+
+ @Override
+ public int getMirrorMode() {
+ return OutputConfiguration.MIRROR_MODE_AUTO;
+ }
+
/**
* Set stream use case for this OutputConfiguration.
*/
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java
index 7b2ad2a..4dca498 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/OutputConfigurationCompatTest.java
@@ -16,6 +16,9 @@
package androidx.camera.camera2.internal.compat.params;
+import static android.hardware.camera2.params.OutputConfiguration.MIRROR_MODE_H;
+import static android.hardware.camera2.params.OutputConfiguration.MIRROR_MODE_NONE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -240,6 +243,22 @@
}
@Test
+ @Config(minSdk = 33)
+ public void canSetMirrorMode() {
+ OutputConfiguration outputConfig = mock(OutputConfiguration.class);
+
+ OutputConfigurationCompat outputConfigCompat = OutputConfigurationCompat.wrap(outputConfig);
+
+ outputConfigCompat.setMirrorMode(MIRROR_MODE_NONE);
+
+ verify(outputConfig, times(1)).setMirrorMode(MIRROR_MODE_NONE);
+
+ outputConfigCompat.setMirrorMode(MIRROR_MODE_H);
+
+ verify(outputConfig, times(1)).setMirrorMode(MIRROR_MODE_H);
+ }
+
+ @Test
public void canSetDynamicRangeProfile() {
OutputConfigurationCompat outputConfigCompat =
new OutputConfigurationCompat(mock(Surface.class));
diff --git a/camera/camera-core/api/1.4.0-beta01.txt b/camera/camera-core/api/1.4.0-beta01.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/1.4.0-beta01.txt
+++ b/camera/camera-core/api/1.4.0-beta01.txt
@@ -212,6 +212,9 @@
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+ }
+
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
}
@@ -507,6 +510,7 @@
ctor public Preview.Builder();
method public androidx.camera.core.Preview build();
method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -212,6 +212,9 @@
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+ }
+
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
}
@@ -507,6 +510,7 @@
ctor public Preview.Builder();
method public androidx.camera.core.Preview build();
method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/api/restricted_1.4.0-beta01.txt b/camera/camera-core/api/restricted_1.4.0-beta01.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/restricted_1.4.0-beta01.txt
+++ b/camera/camera-core/api/restricted_1.4.0-beta01.txt
@@ -212,6 +212,9 @@
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+ }
+
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
}
@@ -507,6 +510,7 @@
ctor public Preview.Builder();
method public androidx.camera.core.Preview build();
method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index c84401f..058ffa8 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -212,6 +212,9 @@
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
}
+ @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalMirrorMode {
+ }
+
@SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
}
@@ -507,6 +510,7 @@
ctor public Preview.Builder();
method public androidx.camera.core.Preview build();
method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+ method @SuppressCompatibility @androidx.camera.core.ExperimentalMirrorMode public androidx.camera.core.Preview.Builder setMirrorMode(int);
method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt
index f288885..e8a9867 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/UseCaseTest.kt
@@ -25,6 +25,7 @@
import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
import androidx.camera.core.MirrorMode.MIRROR_MODE_ON
import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
+import androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED
import androidx.camera.core.UseCase.snapToSurfaceRotation
import androidx.camera.core.concurrent.CameraCoordinator
import androidx.camera.core.impl.Config
@@ -265,7 +266,7 @@
@Test
fun defaultMirrorModeIsOff() {
val fakeUseCase = createFakeUseCase()
- assertThat(fakeUseCase.mirrorModeInternal).isEqualTo(MIRROR_MODE_OFF)
+ assertThat(fakeUseCase.mirrorModeInternal).isEqualTo(MIRROR_MODE_UNSPECIFIED)
}
@Test
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalMirrorMode.java b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalMirrorMode.java
new file mode 100644
index 0000000..5e841b7
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ExperimentalMirrorMode.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.RequiresOptIn;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Denotes that the annotated API is designed to be experimental for {@link MirrorMode}
+ */
+@Retention(CLASS)
+@RequiresOptIn
+public @interface ExperimentalMirrorMode {
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 798ba62..2cf9086 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -423,7 +423,10 @@
sessionConfigBuilder.setExpectedFrameRateRange(streamSpec.getExpectedFrameRateRange());
- sessionConfigBuilder.addSurface(mDeferrableSurface, streamSpec.getDynamicRange(), null);
+ sessionConfigBuilder.addSurface(mDeferrableSurface,
+ streamSpec.getDynamicRange(),
+ null,
+ MirrorMode.MIRROR_MODE_UNSPECIFIED);
sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
clearPipeline();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java b/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
index 49af138..cb2e328 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
@@ -42,12 +42,16 @@
*/
public static final int MIRROR_MODE_ON_FRONT_ONLY = 2;
+ /** The mirror mode is not specified by the user **/
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static final int MIRROR_MODE_UNSPECIFIED = -1;
+
private MirrorMode() {
}
/**
*/
- @IntDef({MIRROR_MODE_OFF, MIRROR_MODE_ON, MIRROR_MODE_ON_FRONT_ONLY})
+ @IntDef({MIRROR_MODE_OFF, MIRROR_MODE_ON, MIRROR_MODE_ON_FRONT_ONLY, MIRROR_MODE_UNSPECIFIED})
@Retention(RetentionPolicy.SOURCE)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public @interface Mirror {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index 2163ecb..04f330d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -18,6 +18,7 @@
import static androidx.camera.core.CameraEffect.PREVIEW;
import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED;
import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE;
import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_FORMAT;
@@ -54,6 +55,7 @@
import android.graphics.SurfaceTexture;
import android.media.ImageReader;
import android.media.MediaCodec;
+import android.os.Build;
import android.util.Pair;
import android.util.Range;
import android.util.Size;
@@ -332,7 +334,8 @@
if (mSurfaceProvider != null) {
sessionConfigBuilder.addSurface(mSessionDeferrableSurface,
streamSpec.getDynamicRange(),
- getPhysicalCameraId());
+ getPhysicalCameraId(),
+ getMirrorModeInternal());
}
sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
@@ -846,7 +849,11 @@
setCaptureType(UseCaseConfigFactory.CaptureType.PREVIEW);
setTargetClass(Preview.class);
- mutableConfig.insertOption(OPTION_MIRROR_MODE, Defaults.DEFAULT_MIRROR_MODE);
+
+ if (mutableConfig.retrieveOption(
+ OPTION_MIRROR_MODE, MIRROR_MODE_UNSPECIFIED) == MIRROR_MODE_UNSPECIFIED) {
+ mutableConfig.insertOption(OPTION_MIRROR_MODE, Defaults.DEFAULT_MIRROR_MODE);
+ }
}
/**
@@ -1024,13 +1031,30 @@
}
/**
- * setMirrorMode is not supported on Preview.
+ * Sets the mirror mode.
+ *
+ * <p>Valid values include: {@link MirrorMode#MIRROR_MODE_OFF},
+ * {@link MirrorMode#MIRROR_MODE_ON} and {@link MirrorMode#MIRROR_MODE_ON_FRONT_ONLY}.
+ * If not set, it defaults to {@link MirrorMode#MIRROR_MODE_ON_FRONT_ONLY}.
+ *
+ * <p>For API 33 and above, it will change the mirroring behavior for Preview use case.
+ * It is calling
+ * {@link android.hardware.camera2.params.OutputConfiguration#setMirrorMode(int)}.
+ *
+ * <p> For API 32 and below, it will be no-op.
+ *
+ * @param mirrorMode The mirror mode of the intended target.
+ * @return The current Builder.
+ * @see android.hardware.camera2.params.OutputConfiguration#setMirrorMode(int)
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @ExperimentalMirrorMode
@NonNull
@Override
public Builder setMirrorMode(@MirrorMode.Mirror int mirrorMode) {
- throw new UnsupportedOperationException("setMirrorMode is not supported.");
+ if (Build.VERSION.SDK_INT >= 33) {
+ getMutableConfig().insertOption(OPTION_MIRROR_MODE, mirrorMode);
+ }
+ return this;
}
/**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
index eb703aa..5918040 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
@@ -19,6 +19,7 @@
import static androidx.camera.core.MirrorMode.MIRROR_MODE_OFF;
import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON;
import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED;
import static androidx.camera.core.impl.ImageOutputConfig.OPTION_MAX_RESOLUTION;
import static androidx.camera.core.impl.ImageOutputConfig.OPTION_RESOLUTION_SELECTOR;
import static androidx.camera.core.impl.ImageOutputConfig.OPTION_TARGET_ASPECT_RATIO;
@@ -442,7 +443,7 @@
@RestrictTo(Scope.LIBRARY_GROUP)
@MirrorMode.Mirror
protected int getMirrorModeInternal() {
- return ((ImageOutputConfig) mCurrentConfig).getMirrorMode(MIRROR_MODE_OFF);
+ return ((ImageOutputConfig) mCurrentConfig).getMirrorMode(MIRROR_MODE_UNSPECIFIED);
}
/**
@@ -453,6 +454,7 @@
public boolean isMirroringRequired(@NonNull CameraInternal camera) {
int mirrorMode = getMirrorModeInternal();
switch (mirrorMode) {
+ case MIRROR_MODE_UNSPECIFIED:
case MIRROR_MODE_OFF:
return false;
case MIRROR_MODE_ON:
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
index 414ddb0..6ea58a0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
@@ -30,6 +30,7 @@
import androidx.annotation.RequiresApi;
import androidx.camera.core.DynamicRange;
import androidx.camera.core.Logger;
+import androidx.camera.core.MirrorMode;
import androidx.camera.core.impl.stabilization.StabilizationMode;
import androidx.camera.core.internal.compat.workaround.SurfaceSorter;
@@ -109,6 +110,14 @@
public abstract String getPhysicalCameraId();
/**
+ * Returns the mirror mode.
+ *
+ * @return {@link MirrorMode}
+ */
+ @MirrorMode.Mirror
+ public abstract int getMirrorMode();
+
+ /**
* Returns the surface group ID. Default value is {@link #SURFACE_GROUP_ID_NONE} meaning
* it doesn't belong to any surface group. A surface group ID is used to identify which
* surface group this output surface belongs to. Output streams with the same
@@ -137,6 +146,7 @@
.setSurface(surface)
.setSharedSurfaces(Collections.emptyList())
.setPhysicalCameraId(null)
+ .setMirrorMode(MirrorMode.MIRROR_MODE_UNSPECIFIED)
.setSurfaceGroupId(SURFACE_GROUP_ID_NONE)
.setDynamicRange(DynamicRange.SDR);
}
@@ -168,6 +178,14 @@
public abstract Builder setPhysicalCameraId(@Nullable String cameraId);
/**
+ * Sets the mirror mode. It specifies mirroring mode for
+ * {@link android.hardware.camera2.params.OutputConfiguration}.
+ * @see android.hardware.camera2.params.OutputConfiguration#setMirrorMode(int)
+ */
+ @NonNull
+ public abstract Builder setMirrorMode(@MirrorMode.Mirror int mirrorMode);
+
+ /**
* Sets the surface group ID. A surface group ID is used to identify which surface group
* this output surface belongs to. Output streams with the same non-negative group ID
* won't receive the camera output simultaneously therefore it could be used to reduce
@@ -643,11 +661,13 @@
* Add a surface to the set that the session repeatedly writes data to.
*
* <p>The dynamic range of this surface will default to {@link DynamicRange#SDR}. To
- * manually set the dynamic range, use {@link #addSurface(DeferrableSurface, DynamicRange)}.
+ * manually set the dynamic range, use
+ * {@link #addSurface(DeferrableSurface, DynamicRange, String, int)}.
*/
@NonNull
public Builder addSurface(@NonNull DeferrableSurface surface) {
- return addSurface(surface, DynamicRange.SDR, null);
+ return addSurface(surface, DynamicRange.SDR, null,
+ MirrorMode.MIRROR_MODE_UNSPECIFIED);
}
/**
@@ -657,10 +677,12 @@
@NonNull
public Builder addSurface(@NonNull DeferrableSurface surface,
@NonNull DynamicRange dynamicRange,
- @Nullable String physicalCameraId) {
+ @Nullable String physicalCameraId,
+ @MirrorMode.Mirror int mirrorMode) {
OutputConfig outputConfig = OutputConfig.builder(surface)
.setPhysicalCameraId(physicalCameraId)
.setDynamicRange(dynamicRange)
+ .setMirrorMode(mirrorMode)
.build();
mOutputConfigs.add(outputConfig);
mCaptureConfigBuilder.addSurface(surface);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
index d690404..b98833a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
@@ -45,6 +45,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.CameraEffect;
import androidx.camera.core.ImageCapture;
+import androidx.camera.core.MirrorMode;
import androidx.camera.core.UseCase;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
@@ -277,7 +278,10 @@
propagateChildrenCamera2Interop(streamSpec.getResolution(), builder);
- builder.addSurface(mCameraEdge.getDeferrableSurface(), streamSpec.getDynamicRange(), null);
+ builder.addSurface(mCameraEdge.getDeferrableSurface(),
+ streamSpec.getDynamicRange(),
+ null,
+ MirrorMode.MIRROR_MODE_UNSPECIFIED);
builder.addRepeatingCameraCaptureCallback(
mVirtualCameraAdapter.getParentMetadataCallback());
if (streamSpec.getImplementationOptions() != null) {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
index 0469f62..380be3b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -16,8 +16,8 @@
package androidx.camera.core;
-import static androidx.camera.core.MirrorMode.MIRROR_MODE_OFF;
import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED;
import static com.google.common.truth.Truth.assertThat;
@@ -172,7 +172,7 @@
@Test
public void defaultMirrorModeIsOff() {
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
- assertThat(imageAnalysis.getMirrorModeInternal()).isEqualTo(MIRROR_MODE_OFF);
+ assertThat(imageAnalysis.getMirrorModeInternal()).isEqualTo(MIRROR_MODE_UNSPECIFIED);
}
@Test(expected = UnsupportedOperationException.class)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
index bc1b60a..affae0b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -33,8 +33,8 @@
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG
import androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR
-import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
+import androidx.camera.core.MirrorMode.MIRROR_MODE_UNSPECIFIED
import androidx.camera.core.impl.CameraFactory
import androidx.camera.core.impl.CaptureConfig
import androidx.camera.core.impl.ImageCaptureConfig
@@ -211,7 +211,7 @@
@Test
fun defaultMirrorModeIsOff() {
val imageCapture = ImageCapture.Builder().build()
- assertThat(imageCapture.mirrorModeInternal).isEqualTo(MIRROR_MODE_OFF)
+ assertThat(imageCapture.mirrorModeInternal).isEqualTo(MIRROR_MODE_UNSPECIFIED)
}
@Test(expected = UnsupportedOperationException::class)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 7ab1e17..f4e6f6b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -34,6 +34,8 @@
import androidx.camera.core.CameraEffect.PREVIEW
import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
+import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
+import androidx.camera.core.MirrorMode.MIRROR_MODE_ON
import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
import androidx.camera.core.Preview.SurfaceProvider
import androidx.camera.core.SurfaceRequest.TransformationInfo
@@ -279,9 +281,34 @@
assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_ON_FRONT_ONLY)
}
- @Test(expected = UnsupportedOperationException::class)
- fun setMirrorMode_throwException() {
- Preview.Builder().setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
+ @Config(minSdk = 33)
+ @Test
+ fun setMirrorMode_OnFrontOnly() {
+ val preview = createPreview()
+ assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_ON_FRONT_ONLY)
+
+ val sessionConfig = preview.sessionConfig
+ assertThat(sessionConfig.outputConfigs[0].mirrorMode).isEqualTo(MIRROR_MODE_ON_FRONT_ONLY)
+ }
+
+ @Config(minSdk = 33)
+ @Test
+ fun setMirrorMode_On() {
+ val preview = createPreview(mirrorMode = MIRROR_MODE_ON)
+ assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_ON)
+
+ val sessionConfig = preview.sessionConfig
+ assertThat(sessionConfig.outputConfigs[0].mirrorMode).isEqualTo(MIRROR_MODE_ON)
+ }
+
+ @Config(minSdk = 33)
+ @Test
+ fun setMirrorMode_Off() {
+ val preview = createPreview(mirrorMode = MIRROR_MODE_OFF)
+ assertThat(preview.mirrorModeInternal).isEqualTo(MIRROR_MODE_OFF)
+
+ val sessionConfig = preview.sessionConfig
+ assertThat(sessionConfig.outputConfigs[0].mirrorMode).isEqualTo(MIRROR_MODE_OFF)
}
@Test
@@ -833,9 +860,11 @@
camera: FakeCamera = backCamera,
targetRotation: Int = ROTATION_90,
surfaceProvider: SurfaceProvider = SurfaceProvider {
- }
+ },
+ mirrorMode: Int = MirrorMode.MIRROR_MODE_UNSPECIFIED
): Preview {
previewToDetach = Preview.Builder()
+ .setMirrorMode(mirrorMode)
.setTargetRotation(targetRotation)
.build()
previewToDetach.effect = effect
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 95971f2..dcdc96a 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -356,7 +356,11 @@
*/
@MirrorMode.Mirror
public int getMirrorMode() {
- return getMirrorModeInternal();
+ int mirrorMode = getMirrorModeInternal();
+ if (mirrorMode == MirrorMode.MIRROR_MODE_UNSPECIFIED) {
+ return MirrorMode.MIRROR_MODE_OFF;
+ }
+ return mirrorMode;
}
@SuppressWarnings("unchecked")
@@ -903,7 +907,10 @@
DynamicRange dynamicRange = streamSpec.getDynamicRange();
if (!isStreamError && mDeferrableSurface != null) {
if (isStreamActive) {
- sessionConfigBuilder.addSurface(mDeferrableSurface, dynamicRange, null);
+ sessionConfigBuilder.addSurface(mDeferrableSurface,
+ dynamicRange,
+ null,
+ MirrorMode.MIRROR_MODE_UNSPECIFIED);
} else {
sessionConfigBuilder.addNonRepeatingSurface(mDeferrableSurface, dynamicRange);
}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
index e64e886..5421e2f 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
@@ -46,8 +46,10 @@
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ConcurrentCamera;
import androidx.camera.core.ConcurrentCamera.SingleCameraConfig;
+import androidx.camera.core.ExperimentalMirrorMode;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.MirrorMode;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration;
@@ -274,7 +276,7 @@
}
@SuppressLint("NullAnnotationGroup")
- @OptIn(markerClass = {ExperimentalCamera2Interop.class,
+ @OptIn(markerClass = {ExperimentalCamera2Interop.class, ExperimentalMirrorMode.class,
androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop.class})
private void bindToLifecycleForConcurrentCamera(
@NonNull ProcessCameraProvider cameraProvider,
@@ -329,6 +331,7 @@
.build(),
lifecycleOwner);
Preview previewBack = new Preview.Builder()
+ .setMirrorMode(MirrorMode.MIRROR_MODE_OFF)
.build();
previewBack.setSurfaceProvider(backPreviewView.getSurfaceProvider());
SingleCameraConfig secondary = new SingleCameraConfig(
diff --git a/compose/material3/material3/api/1.3.0-beta01.txt b/compose/material3/material3/api/1.3.0-beta01.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/1.3.0-beta01.txt
+++ b/compose/material3/material3/api/1.3.0-beta01.txt
@@ -2012,8 +2012,6 @@
}
public final class TooltipKt {
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
}
+ public final class Tooltip_androidKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -2012,8 +2012,6 @@
}
public final class TooltipKt {
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
}
+ public final class Tooltip_androidKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/api/restricted_1.3.0-beta01.txt b/compose/material3/material3/api/restricted_1.3.0-beta01.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/restricted_1.3.0-beta01.txt
+++ b/compose/material3/material3/api/restricted_1.3.0-beta01.txt
@@ -2012,8 +2012,6 @@
}
public final class TooltipKt {
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
}
+ public final class Tooltip_androidKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index f92b5a9..40db5a9 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -2012,8 +2012,6 @@
}
public final class TooltipKt {
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipScope,kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
@@ -2035,6 +2033,11 @@
property public abstract androidx.compose.animation.core.MutableTransitionState<java.lang.Boolean> transition;
}
+ public final class Tooltip_androidKt {
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(androidx.compose.material3.TooltipScope, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional long caretSize, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function0<kotlin.Unit> text);
+ }
+
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
ctor public TopAppBarColors(long containerColor, long scrolledContainerColor, long navigationIconContentColor, long titleContentColor, long actionIconContentColor);
method public androidx.compose.material3.TopAppBarColors copy(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
index b42e087..8777756 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Tooltip.android.kt
@@ -16,12 +16,320 @@
package androidx.compose.material3
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.RichTooltipTokens
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.CacheDrawScope
+import androidx.compose.ui.draw.DrawResult
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.isSpecified
+/**
+ * Plain tooltip that provides a descriptive message.
+ *
+ * Usually used with [TooltipBox].
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param contentColor [Color] that will be applied to the tooltip's content.
+ * @param containerColor [Color] that will be applied to the tooltip's container.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param content the composable that will be used to populate the tooltip's content.
+ */
@Composable
-internal actual fun windowContainerWidthInPx(): Int = with(LocalDensity.current) {
- LocalConfiguration.current.screenWidthDp.dp.roundToPx()
+@ExperimentalMaterial3Api
+actual fun TooltipScope.PlainTooltip(
+ modifier: Modifier,
+ caretSize: DpSize,
+ shape: Shape,
+ contentColor: Color,
+ containerColor: Color,
+ tonalElevation: Dp,
+ shadowElevation: Dp,
+ content: @Composable () -> Unit
+) {
+ val drawCaretModifier =
+ if (caretSize.isSpecified) {
+ val density = LocalDensity.current
+ val configuration = LocalConfiguration.current
+ Modifier.drawCaret { anchorLayoutCoordinates ->
+ drawCaretWithPath(
+ CaretType.Plain,
+ density,
+ configuration,
+ containerColor,
+ caretSize,
+ anchorLayoutCoordinates
+ )
+ }.then(modifier)
+ } else modifier
+ Surface(
+ modifier = drawCaretModifier,
+ shape = shape,
+ color = containerColor,
+ tonalElevation = tonalElevation,
+ shadowElevation = shadowElevation
+ ) {
+ Box(modifier = Modifier
+ .sizeIn(
+ minWidth = TooltipMinWidth,
+ maxWidth = PlainTooltipMaxWidth,
+ minHeight = TooltipMinHeight
+ )
+ .padding(PlainTooltipContentPadding)
+ ) {
+ val textStyle =
+ PlainTooltipTokens.SupportingTextFont.value
+
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ LocalTextStyle provides textStyle,
+ content = content
+ )
+ }
+ }
+}
+
+/**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message.
+ *
+ * Usually used with [TooltipBox]
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param text the composable that will be used to populate the rich tooltip's text.
+ */
+@Composable
+@ExperimentalMaterial3Api
+actual fun TooltipScope.RichTooltip(
+ modifier: Modifier,
+ title: (@Composable () -> Unit)?,
+ action: (@Composable () -> Unit)?,
+ caretSize: DpSize,
+ shape: Shape,
+ colors: RichTooltipColors,
+ tonalElevation: Dp,
+ shadowElevation: Dp,
+ text: @Composable () -> Unit
+) {
+ val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
+ val elevatedColor =
+ MaterialTheme.colorScheme.applyTonalElevation(
+ colors.containerColor,
+ absoluteElevation
+ )
+ val drawCaretModifier =
+ if (caretSize.isSpecified) {
+ val density = LocalDensity.current
+ val configuration = LocalConfiguration.current
+ Modifier.drawCaret { anchorLayoutCoordinates ->
+ drawCaretWithPath(
+ CaretType.Rich,
+ density,
+ configuration,
+ elevatedColor,
+ caretSize,
+ anchorLayoutCoordinates
+ )
+ }.then(modifier)
+ } else modifier
+ Surface(
+ modifier = drawCaretModifier
+ .sizeIn(
+ minWidth = TooltipMinWidth,
+ maxWidth = RichTooltipMaxWidth,
+ minHeight = TooltipMinHeight
+ ),
+ shape = shape,
+ color = colors.containerColor,
+ tonalElevation = tonalElevation,
+ shadowElevation = shadowElevation
+ ) {
+ val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
+ val subheadTextStyle = RichTooltipTokens.SubheadFont.value
+ val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
+
+ Column(
+ modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
+ ) {
+ title?.let {
+ Box(
+ modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides colors.titleContentColor,
+ LocalTextStyle provides subheadTextStyle,
+ content = it
+ )
+ }
+ }
+ Box(
+ modifier = Modifier.textVerticalPadding(
+ title != null,
+ action != null
+ )
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides colors.contentColor,
+ LocalTextStyle provides supportingTextStyle,
+ content = text
+ )
+ }
+ action?.let {
+ Box(
+ modifier = Modifier
+ .requiredHeightIn(min = ActionLabelMinHeight)
+ .padding(bottom = ActionLabelBottomPadding)
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides colors.actionContentColor,
+ LocalTextStyle provides actionLabelTextStyle,
+ content = it
+ )
+ }
+ }
+ }
+ }
+}
+
+@ExperimentalMaterial3Api
+private fun CacheDrawScope.drawCaretWithPath(
+ caretType: CaretType,
+ density: Density,
+ configuration: Configuration,
+ containerColor: Color,
+ caretSize: DpSize,
+ anchorLayoutCoordinates: LayoutCoordinates?
+): DrawResult {
+ val path = Path()
+
+ if (anchorLayoutCoordinates != null) {
+ val caretHeightPx: Int
+ val caretWidthPx: Int
+ val screenWidthPx: Int
+ val tooltipAnchorSpacing: Int
+ with(density) {
+ caretHeightPx = caretSize.height.roundToPx()
+ caretWidthPx = caretSize.width.roundToPx()
+ screenWidthPx = configuration.screenWidthDp.dp.roundToPx()
+ tooltipAnchorSpacing = SpacingBetweenTooltipAndAnchor.roundToPx()
+ }
+ val anchorBounds = anchorLayoutCoordinates.boundsInWindow()
+ val anchorLeft = anchorBounds.left
+ val anchorRight = anchorBounds.right
+ val anchorTop = anchorBounds.top
+ val anchorMid = (anchorRight + anchorLeft) / 2
+ val anchorWidth = anchorRight - anchorLeft
+ val tooltipWidth = this.size.width
+ val tooltipHeight = this.size.height
+ val isCaretTop = anchorTop - tooltipHeight - tooltipAnchorSpacing < 0
+ val caretY = if (isCaretTop) { 0f } else { tooltipHeight }
+
+ val position: Offset
+ if (caretType == CaretType.Plain) {
+ position =
+ if (anchorMid + tooltipWidth / 2 > screenWidthPx) {
+ // Caret needs to be near the right
+ val anchorMidFromRightScreenEdge =
+ screenWidthPx - anchorMid
+ val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+ Offset(caretX, caretY)
+ } else {
+ // Caret needs to be near the left
+ val tooltipLeft =
+ anchorLeft - (this.size.width / 2 - anchorWidth / 2)
+ val caretX = anchorMid - maxOf(tooltipLeft, 0f)
+ Offset(caretX, caretY)
+ }
+ } else {
+ // Default the caret to the left
+ var preferredPosition = Offset(anchorMid - anchorLeft, caretY)
+ if (anchorLeft + tooltipWidth > screenWidthPx) {
+ // Need to move the caret to the right
+ preferredPosition = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
+ if (anchorRight - tooltipWidth < 0) {
+ // Need to center the caret
+ // Caret might need to be offset depending on where
+ // the tooltip is placed relative to the anchor
+ if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
+ preferredPosition = Offset(anchorMid, caretY)
+ } else if (anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= screenWidthPx) {
+ val anchorMidFromRightScreenEdge =
+ screenWidthPx - anchorMid
+ val caretX = tooltipWidth - anchorMidFromRightScreenEdge
+ preferredPosition = Offset(caretX, caretY)
+ } else {
+ preferredPosition = Offset(tooltipWidth / 2, caretY)
+ }
+ }
+ }
+ position = preferredPosition
+ }
+
+ if (isCaretTop) {
+ path.apply {
+ moveTo(x = position.x, y = position.y)
+ lineTo(x = position.x + caretWidthPx / 2, y = position.y)
+ lineTo(x = position.x, y = position.y - caretHeightPx)
+ lineTo(x = position.x - caretWidthPx / 2, y = position.y)
+ close()
+ }
+ } else {
+ path.apply {
+ moveTo(x = position.x, y = position.y)
+ lineTo(x = position.x + caretWidthPx / 2, y = position.y)
+ lineTo(x = position.x, y = position.y + caretHeightPx.toFloat())
+ lineTo(x = position.x - caretWidthPx / 2, y = position.y)
+ close()
+ }
+ }
+ }
+
+ return onDrawWithContent {
+ if (anchorLayoutCoordinates != null) {
+ drawContent()
+ drawPath(
+ path = path,
+ color = containerColor
+ )
+ }
+ }
+}
+
+@ExperimentalMaterial3Api
+private enum class CaretType {
+ Plain, Rich
}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 18f443c..a673371 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -26,42 +26,35 @@
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.MutatorMutex
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.paddingFromBaseline
-import androidx.compose.foundation.layout.requiredHeightIn
-import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.internal.BasicTooltipBox
import androidx.compose.material3.internal.BasicTooltipDefaults
import androidx.compose.material3.tokens.ElevationTokens
import androidx.compose.material3.tokens.PlainTooltipTokens
import androidx.compose.material3.tokens.RichTooltipTokens
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.CacheDrawScope
import androidx.compose.ui.draw.DrawResult
import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
@@ -69,7 +62,6 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.window.PopupPositionProvider
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -208,7 +200,7 @@
*/
@Composable
@ExperimentalMaterial3Api
-fun TooltipScope.PlainTooltip(
+expect fun TooltipScope.PlainTooltip(
modifier: Modifier = Modifier,
caretSize: DpSize = DpSize.Unspecified,
shape: Shape = TooltipDefaults.plainTooltipContainerShape,
@@ -217,48 +209,7 @@
tonalElevation: Dp = 0.dp,
shadowElevation: Dp = 0.dp,
content: @Composable () -> Unit
-) {
- val drawCaretModifier =
- if (caretSize.isSpecified) {
- val density = LocalDensity.current
- val windowContainerWidthInPx = windowContainerWidthInPx()
- Modifier.drawCaret { anchorLayoutCoordinates ->
- drawCaretWithPath(
- CaretType.Plain,
- density,
- windowContainerWidthInPx,
- containerColor,
- caretSize,
- anchorLayoutCoordinates
- )
- }.then(modifier)
- } else modifier
- Surface(
- modifier = drawCaretModifier,
- shape = shape,
- color = containerColor,
- tonalElevation = tonalElevation,
- shadowElevation = shadowElevation
- ) {
- Box(modifier = Modifier
- .sizeIn(
- minWidth = TooltipMinWidth,
- maxWidth = PlainTooltipMaxWidth,
- minHeight = TooltipMinHeight
- )
- .padding(PlainTooltipContentPadding)
- ) {
- val textStyle =
- PlainTooltipTokens.SupportingTextFont.value
-
- CompositionLocalProvider(
- LocalContentColor provides contentColor,
- LocalTextStyle provides textStyle,
- content = content
- )
- }
- }
-}
+)
/**
* Rich text tooltip that allows the user to pass in a title, text, and action.
@@ -280,7 +231,7 @@
*/
@Composable
@ExperimentalMaterial3Api
-fun TooltipScope.RichTooltip(
+expect fun TooltipScope.RichTooltip(
modifier: Modifier = Modifier,
title: (@Composable () -> Unit)? = null,
action: (@Composable () -> Unit)? = null,
@@ -290,86 +241,7 @@
tonalElevation: Dp = ElevationTokens.Level0,
shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
text: @Composable () -> Unit
-) {
- val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
- val elevatedColor =
- MaterialTheme.colorScheme.applyTonalElevation(
- colors.containerColor,
- absoluteElevation
- )
- val drawCaretModifier =
- if (caretSize.isSpecified) {
- val density = LocalDensity.current
- val windowContainerWidthInPx = windowContainerWidthInPx()
- Modifier.drawCaret { anchorLayoutCoordinates ->
- drawCaretWithPath(
- CaretType.Rich,
- density,
- windowContainerWidthInPx,
- elevatedColor,
- caretSize,
- anchorLayoutCoordinates
- )
- }.then(modifier)
- } else modifier
- Surface(
- modifier = drawCaretModifier
- .sizeIn(
- minWidth = TooltipMinWidth,
- maxWidth = RichTooltipMaxWidth,
- minHeight = TooltipMinHeight
- ),
- shape = shape,
- color = colors.containerColor,
- tonalElevation = tonalElevation,
- shadowElevation = shadowElevation
- ) {
- val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
- val subheadTextStyle = RichTooltipTokens.SubheadFont.value
- val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
-
- Column(
- modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
- ) {
- title?.let {
- Box(
- modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
- ) {
- CompositionLocalProvider(
- LocalContentColor provides colors.titleContentColor,
- LocalTextStyle provides subheadTextStyle,
- content = it
- )
- }
- }
- Box(
- modifier = Modifier.textVerticalPadding(
- title != null,
- action != null
- )
- ) {
- CompositionLocalProvider(
- LocalContentColor provides colors.contentColor,
- LocalTextStyle provides supportingTextStyle,
- content = text
- )
- }
- action?.let {
- Box(
- modifier = Modifier
- .requiredHeightIn(min = ActionLabelMinHeight)
- .padding(bottom = ActionLabelBottomPadding)
- ) {
- CompositionLocalProvider(
- LocalContentColor provides colors.actionContentColor,
- LocalTextStyle provides actionLabelTextStyle,
- content = it
- )
- }
- }
- }
- }
-}
+)
/**
* Tooltip defaults that contain default values for both [PlainTooltip] and [RichTooltip]
@@ -638,7 +510,7 @@
override val isVisible: Boolean
get() = transition.currentState || transition.targetState
- /**
+ /**
* continuation used to clean up
*/
private var job: (CancellableContinuation<Unit>)? = null
@@ -812,118 +684,6 @@
)
}
-@ExperimentalMaterial3Api
-private fun CacheDrawScope.drawCaretWithPath(
- caretType: CaretType,
- density: Density,
- windowContainerWidthInPx: Int,
- containerColor: Color,
- caretSize: DpSize,
- anchorLayoutCoordinates: LayoutCoordinates?
-): DrawResult {
- val path = Path()
-
- if (anchorLayoutCoordinates != null) {
- val caretHeightPx: Int
- val caretWidthPx: Int
- val tooltipAnchorSpacing: Int
- with(density) {
- caretHeightPx = caretSize.height.roundToPx()
- caretWidthPx = caretSize.width.roundToPx()
- tooltipAnchorSpacing = SpacingBetweenTooltipAndAnchor.roundToPx()
- }
- val anchorBounds = anchorLayoutCoordinates.boundsInWindow()
- val anchorLeft = anchorBounds.left
- val anchorRight = anchorBounds.right
- val anchorTop = anchorBounds.top
- val anchorMid = (anchorRight + anchorLeft) / 2
- val anchorWidth = anchorRight - anchorLeft
- val tooltipWidth = this.size.width
- val tooltipHeight = this.size.height
- val isCaretTop = anchorTop - tooltipHeight - tooltipAnchorSpacing < 0
- val caretY = if (isCaretTop) { 0f } else { tooltipHeight }
-
- val position: Offset
- if (caretType == CaretType.Plain) {
- position =
- if (anchorMid + tooltipWidth / 2 > windowContainerWidthInPx) {
- // Caret needs to be near the right
- val anchorMidFromRightScreenEdge =
- windowContainerWidthInPx - anchorMid
- val caretX = tooltipWidth - anchorMidFromRightScreenEdge
- Offset(caretX, caretY)
- } else {
- // Caret needs to be near the left
- val tooltipLeft =
- anchorLeft - (this.size.width / 2 - anchorWidth / 2)
- val caretX = anchorMid - maxOf(tooltipLeft, 0f)
- Offset(caretX, caretY)
- }
- } else {
- // Default the caret to the left
- var preferredPosition = Offset(anchorMid - anchorLeft, caretY)
- if (anchorLeft + tooltipWidth > windowContainerWidthInPx) {
- // Need to move the caret to the right
- preferredPosition = Offset(anchorMid - (anchorRight - tooltipWidth), caretY)
- if (anchorRight - tooltipWidth < 0) {
- // Need to center the caret
- // Caret might need to be offset depending on where
- // the tooltip is placed relative to the anchor
- if (anchorLeft - tooltipWidth / 2 + anchorWidth / 2 <= 0) {
- preferredPosition = Offset(anchorMid, caretY)
- } else if (
- anchorRight + tooltipWidth / 2 - anchorWidth / 2 >= windowContainerWidthInPx
- ) {
- val anchorMidFromRightScreenEdge =
- windowContainerWidthInPx - anchorMid
- val caretX = tooltipWidth - anchorMidFromRightScreenEdge
- preferredPosition = Offset(caretX, caretY)
- } else {
- preferredPosition = Offset(tooltipWidth / 2, caretY)
- }
- }
- }
- position = preferredPosition
- }
-
- if (isCaretTop) {
- path.apply {
- moveTo(x = position.x, y = position.y)
- lineTo(x = position.x + caretWidthPx / 2, y = position.y)
- lineTo(x = position.x, y = position.y - caretHeightPx)
- lineTo(x = position.x - caretWidthPx / 2, y = position.y)
- close()
- }
- } else {
- path.apply {
- moveTo(x = position.x, y = position.y)
- lineTo(x = position.x + caretWidthPx / 2, y = position.y)
- lineTo(x = position.x, y = position.y + caretHeightPx.toFloat())
- lineTo(x = position.x - caretWidthPx / 2, y = position.y)
- close()
- }
- }
- }
-
- return onDrawWithContent {
- if (anchorLayoutCoordinates != null) {
- drawContent()
- drawPath(
- path = path,
- color = containerColor
- )
- }
- }
-}
-
-@ExperimentalMaterial3Api
-private enum class CaretType {
- Plain, Rich
-}
-
-@Composable
-internal expect fun windowContainerWidthInPx(): Int
-
internal val SpacingBetweenTooltipAndAnchor = 4.dp
internal val TooltipMinHeight = 24.dp
internal val TooltipMinWidth = 40.dp
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
index c3cbf05..71f431b 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/Tooltip.desktop.kt
@@ -16,12 +16,158 @@
package androidx.compose.material3
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.RichTooltipTokens
import androidx.compose.runtime.Composable
-import androidx.compose.ui.LocalComposeScene
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+/**
+ * Plain tooltip that provides a descriptive message.
+ *
+ * Usually used with [TooltipBox].
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param contentColor [Color] that will be applied to the tooltip's content.
+ * @param containerColor [Color] that will be applied to the tooltip's container.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param content the composable that will be used to populate the tooltip's content.
+ */
@Composable
-internal actual fun windowContainerWidthInPx(): Int {
- // TODO: Upstream a proper way to get this from JetBrains fork
- // LocalWindowInfo.current.containerSize.width
- return LocalComposeScene.current.constraints.maxWidth
+@ExperimentalMaterial3Api
+actual fun TooltipScope.PlainTooltip(
+ modifier: Modifier,
+ caretSize: DpSize,
+ shape: Shape,
+ contentColor: Color,
+ containerColor: Color,
+ tonalElevation: Dp,
+ shadowElevation: Dp,
+ content: @Composable () -> Unit
+) {
+ Surface(
+ modifier = modifier,
+ shape = shape,
+ color = containerColor,
+ tonalElevation = tonalElevation,
+ shadowElevation = shadowElevation
+ ) {
+ Box(modifier = Modifier
+ .sizeIn(
+ minWidth = TooltipMinWidth,
+ maxWidth = PlainTooltipMaxWidth,
+ minHeight = TooltipMinHeight
+ )
+ .padding(PlainTooltipContentPadding)
+ ) {
+ val textStyle = PlainTooltipTokens.SupportingTextFont.value
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ LocalTextStyle provides textStyle,
+ content = content
+ )
+ }
+ }
+}
+
+/**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message.
+ *
+ * Usually used with [TooltipBox]
+ *
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param caretSize [DpSize] for the caret of the tooltip, if a default
+ * caret is desired with a specific dimension. Please see [TooltipDefaults.caretSize] to
+ * see the default dimensions. Pass in Dp.Unspecified for this parameter if no caret is desired.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param tonalElevation the tonal elevation of the tooltip.
+ * @param shadowElevation the shadow elevation of the tooltip.
+ * @param text the composable that will be used to populate the rich tooltip's text.
+ */
+@Composable
+@ExperimentalMaterial3Api
+actual fun TooltipScope.RichTooltip(
+ modifier: Modifier,
+ title: (@Composable () -> Unit)?,
+ action: (@Composable () -> Unit)?,
+ caretSize: DpSize,
+ shape: Shape,
+ colors: RichTooltipColors,
+ tonalElevation: Dp,
+ shadowElevation: Dp,
+ text: @Composable () -> Unit
+) {
+ Surface(
+ modifier = modifier
+ .sizeIn(
+ minWidth = TooltipMinWidth,
+ maxWidth = RichTooltipMaxWidth,
+ minHeight = TooltipMinHeight
+ ),
+ shape = shape,
+ color = colors.containerColor,
+ tonalElevation = tonalElevation,
+ shadowElevation = shadowElevation
+ ) {
+ val actionLabelTextStyle = RichTooltipTokens.ActionLabelTextFont.value
+ val subheadTextStyle = RichTooltipTokens.SubheadFont.value
+ val supportingTextStyle = RichTooltipTokens.SupportingTextFont.value
+
+ Column(
+ modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
+ ) {
+ title?.let {
+ Box(
+ modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides colors.titleContentColor,
+ LocalTextStyle provides subheadTextStyle,
+ content = it
+ )
+ }
+ }
+ Box(
+ modifier = Modifier.textVerticalPadding(title != null, action != null)
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides colors.contentColor,
+ LocalTextStyle provides supportingTextStyle,
+ content = text
+ )
+ }
+ action?.let {
+ Box(
+ modifier = Modifier
+ .requiredHeightIn(min = ActionLabelMinHeight)
+ .padding(bottom = ActionLabelBottomPadding)
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides colors.actionContentColor,
+ LocalTextStyle provides actionLabelTextStyle,
+ content = it
+ )
+ }
+ }
+ }
+ }
}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index ca6348c..ae3ba98 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -38,7 +38,6 @@
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.layer.setOutline
import androidx.compose.ui.graphics.nativeCanvas
-import androidx.compose.ui.internal.throwIllegalStateException
import androidx.compose.ui.layout.GraphicLayerInfo
import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.unit.Density
@@ -50,7 +49,7 @@
import androidx.compose.ui.unit.toSize
internal class GraphicsLayerOwnerLayer(
- private val graphicsLayer: GraphicsLayer,
+ private var graphicsLayer: GraphicsLayer,
// when we have a context it means the object is created by us and we need to release it
private val context: GraphicsContext?,
private val ownerView: AndroidComposeView,
@@ -303,7 +302,10 @@
invalidateParentLayer = null
isDestroyed = true
isDirty = false
- context?.releaseGraphicsLayer(graphicsLayer)
+ if (context != null) {
+ context.releaseGraphicsLayer(graphicsLayer)
+ ownerView.recycle(this)
+ }
}
override fun mapOffset(point: Offset, inverse: Boolean): Offset {
@@ -331,7 +333,25 @@
drawBlock: (canvas: Canvas, parentLayer: GraphicsLayer?) -> Unit,
invalidateParentLayer: () -> Unit
) {
- throwIllegalStateException("reuseLayer is not supported yet")
+ val context = requireNotNull(context) {
+ "currently reuse is only supported when we manage the layer lifecycle"
+ }
+ require(graphicsLayer.isReleased) { "layer should have been released before reuse" }
+
+ // recreate a layer
+ graphicsLayer = context.createGraphicsLayer()
+ isDestroyed = false
+
+ // apply new params
+ this.drawBlock = drawBlock
+ this.invalidateParentLayer = invalidateParentLayer
+
+ // reset mutable variables to their initial values
+ transformOrigin = TransformOrigin.Center
+ drawnWithEnabledZ = false
+ size = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
+ outline = null
+ mutatedFields = 0
}
override fun transform(matrix: Matrix) {
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
index d510fac..2f16c79 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt
@@ -56,9 +56,7 @@
import kotlinx.coroutines.launch
import org.jetbrains.skia.Canvas
-// TODO: This val should not be public!
-// Upstream current state of [ComposeScene] from JetBrains fork
-val LocalComposeScene = staticCompositionLocalOf<ComposeScene> {
+internal val LocalComposeScene = staticCompositionLocalOf<ComposeScene> {
error("CompositionLocal LocalComposeScene not provided")
}
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index c0ebd31..d6775c43 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -153,11 +153,11 @@
docs("androidx.customview:customview:1.2.0-alpha02")
// TODO(b/294531403): Turn on apiSince for customview-poolingcontainer when it releases as alpha
docsWithoutApiSince("androidx.customview:customview-poolingcontainer:1.0.0-rc01")
- docs("androidx.datastore:datastore:1.1.1")
- docs("androidx.datastore:datastore-core:1.1.1")
- docs("androidx.datastore:datastore-core-okio:1.1.1")
- docs("androidx.datastore:datastore-preferences:1.1.1")
- docs("androidx.datastore:datastore-preferences-core:1.1.1")
+ kmpDocs("androidx.datastore:datastore:1.1.1")
+ kmpDocs("androidx.datastore:datastore-core:1.1.1")
+ kmpDocs("androidx.datastore:datastore-core-okio:1.1.1")
+ kmpDocs("androidx.datastore:datastore-preferences:1.1.1")
+ kmpDocs("androidx.datastore:datastore-preferences-core:1.1.1")
docs("androidx.datastore:datastore-preferences-rxjava2:1.1.1")
docs("androidx.datastore:datastore-preferences-rxjava3:1.1.1")
docs("androidx.datastore:datastore-rxjava2:1.1.1")